Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84666a9433 | ||
|
|
9bb93847c7 | ||
|
|
46fb512690 | ||
|
|
8ffd3e514e | ||
|
|
7d8318f275 | ||
|
|
9688f0ae62 | ||
|
|
79afb020db | ||
|
|
45bc39a47a | ||
|
|
d35ea27ea5 | ||
|
|
490d134507 | ||
|
|
41eeaf3883 | ||
|
|
11e9e07f20 | ||
|
|
9f34f37c27 | ||
|
|
9d8257d2eb | ||
|
|
185cfe4323 | ||
|
|
78fb4f767a | ||
|
|
05da6bbcc8 | ||
|
|
0a55e6ef83 | ||
|
|
1846407e93 | ||
|
|
52679ea1bc | ||
|
|
d48e63b5e3 | ||
|
|
7a2daaa351 | ||
|
|
08f086daf3 | ||
|
|
293aa3e0ed | ||
|
|
faeb84da72 | ||
|
|
c30c9beb09 | ||
|
|
77f8c64d37 | ||
|
|
00a2c5a087 | ||
|
|
71bb400e46 | ||
|
|
2ccabb5695 | ||
|
|
0420da44b2 | ||
|
|
96dbe755b3 | ||
|
|
970598ea31 | ||
|
|
a8b44278e6 | ||
|
|
fc312fbb31 | ||
|
|
6bbacc15ba | ||
|
|
d85a876250 | ||
|
|
587c6db1ac | ||
|
|
887fbfc397 | ||
|
|
8ce05c03ec | ||
|
|
cd5a156e6e | ||
|
|
62a45befac | ||
|
|
d89c6add98 | ||
|
|
ec29903e4c | ||
|
|
2c8296a9bc | ||
|
|
71c13f0546 | ||
|
|
69be77d859 | ||
|
|
0e9d390a1c | ||
|
|
1468eb0d8e | ||
|
|
fd1bfe55f7 | ||
|
|
5e6cf9619b | ||
|
|
67cbf31e8b | ||
|
|
817d73c0e5 | ||
|
|
8abd9b44d3 | ||
|
|
be6c67758a | ||
|
|
3d7682e292 | ||
|
|
77bd987979 | ||
|
|
3559fc883b | ||
|
|
f296c7c221 | ||
|
|
21a31660d2 | ||
|
|
db0e14ceab | ||
|
|
81b8b01a01 | ||
|
|
2f4c9ab66d | ||
|
|
d766854af7 | ||
|
|
2355bb998d | ||
|
|
e8b63da1e2 | ||
|
|
013ac898ba | ||
|
|
5c7cb6bed5 | ||
|
|
3aefae016f | ||
|
|
e245f5d069 | ||
|
|
96f2c8e698 | ||
|
|
d855bab19d | ||
|
|
cde889a7c3 | ||
|
|
045504c880 | ||
|
|
6166bac17a | ||
|
|
165a1e9f4c | ||
|
|
c215d0291b | ||
|
|
d926f7c490 | ||
|
|
2db770b672 | ||
|
|
2a46197322 | ||
|
|
15fd3fc97c | ||
|
|
00f778e863 | ||
|
|
dc1e8b6dc3 | ||
|
|
753054974b | ||
|
|
eacc236ccb | ||
|
|
345369e005 | ||
|
|
18c5d31fb2 | ||
|
|
61d2d4355a | ||
|
|
ab023f3f6d | ||
|
|
b073aa4d23 | ||
|
|
8247514ae2 | ||
|
|
00f55639d3 | ||
|
|
68d754d946 | ||
|
|
dc5a48bdfd | ||
|
|
05083bb6ab | ||
|
|
a393b21642 | ||
|
|
a7c8dcc58d | ||
|
|
3514249197 | ||
|
|
7f2598d0d6 |
@@ -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,10 +78,11 @@ 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
```bash
|
||||
./scripts/check_profile.sh # everything CI runs
|
||||
./scripts/check_profile.sh --vendor "<Vendor>" # fast loop
|
||||
./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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,14 @@ jobs:
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.artifact }}
|
||||
# run_unit_tests.sh installs the plugin tests' numpy with the uv the build stages
|
||||
# beside them; the Windows arm64 build bundles none, so put one on PATH there.
|
||||
- name: Install uv
|
||||
if: runner.os == 'Windows' && runner.arch == 'ARM64'
|
||||
uses: astral-sh/setup-uv@v10.2.0
|
||||
with:
|
||||
version: "0.11.21" # ORCA_UV_VERSION in CMakeLists.txt
|
||||
enable-cache: false
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
|
||||
|
||||
@@ -52,4 +52,6 @@ internal_docs/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.opc
|
||||
/.test/
|
||||
docs/superpowers/
|
||||
ctest_results.xml
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
# Texture Displacement - Technical Notes
|
||||
|
||||
Branch: `feature/texture_displacement`. Reference for the feature as it stands: what it does, how the
|
||||
algorithms work, and where the code lives.
|
||||
|
||||
## What it does
|
||||
|
||||
A paint-style gizmo (`GLGizmoTextureDisplacement`) that lets you:
|
||||
- Paint one or more "layers" onto a model's surface, each a height-map texture with its own
|
||||
depth/tiling/rotation/offset/invert/tile-mode/projection-mode/blend-mode.
|
||||
- Pick a texture from a shipped library (`resources/textures/displacement/`) or import your own
|
||||
(saved into `<data_dir>/textures/displacement/`, kept separate so app updates can't clobber it).
|
||||
- Combine overlapping layers with image-editor-style blend modes (Add/Subtract/Multiply/Divide).
|
||||
- Preview the true displaced result live, before baking (background job, not on the UI thread).
|
||||
- Preview via a fast GPU shader instead (no real geometry movement) for a lighter-weight alternative.
|
||||
- Bake into real mesh geometry on demand, restricted to the painted area only.
|
||||
- Remesh and subdivide so a low-poly model has enough vertices to show fine detail.
|
||||
- Unwrap a painted patch with a real CGAL LSCM parameterization and view it in a dedicated,
|
||||
dockable 2D "UV Editor" pane.
|
||||
|
||||
## Standard vs Pro mode
|
||||
|
||||
A two-position slider in the panel header, right of the Dock/Undock button.
|
||||
|
||||
**Pro** shows every mesh-preparation control; Remesh, Subdivide and Bake are run separately by the user,
|
||||
in whatever order they like.
|
||||
|
||||
**Standard** hides all of it and folds one fixed recipe into the Bake button, because a height map only
|
||||
ever *moves vertices that already exist* - painting onto an imported 12-triangle box and pressing Bake
|
||||
would otherwise do nothing visible. Standard's Bake is:
|
||||
|
||||
1. `plan_remesh()` + `replace_mesh_keep_all_paint()` - isotropic remesh to 1 mm, sharp edges above 40
|
||||
degrees protected. Gives the subdivider an even starting density whatever the input looked like.
|
||||
2. `plan_adaptive_subdivision()` + `apply_adaptive_subdivision()` - feature-adaptive refinement, max
|
||||
edge 20 mm, detail 0.02 mm, min edge 0.02 mm.
|
||||
3. `bake()` - the ordinary background displacement job.
|
||||
|
||||
Both preparation stages are *planned* before the undo snapshot and *applied* after it, so a stage with
|
||||
nothing to do is skipped without leaving an empty undo step. The standalone Pro buttons share the same
|
||||
plan/apply split.
|
||||
|
||||
**All three stages sit under one undo step.** `Plater::take_snapshot()` records the state *before* the
|
||||
change, so a single snapshot taken at the top of `bake_standard()` means one Undo returns the mesh to
|
||||
exactly what was imported. `TextureDisplacementBakeInput::take_snapshot` lets the caller say who owns
|
||||
the undo step - true for the Pro-mode button, false for the pipeline, whose background job commits long
|
||||
after that snapshot's scope has closed.
|
||||
|
||||
The presets live in one place (`STD_*` constants) and `apply_standard_mode_presets()` pins the hidden
|
||||
controls to them every frame while Standard is active, so the live preview cannot disagree with what
|
||||
Bake will do. Switching to Standard also closes the subdivision preview, whose controls have just gone.
|
||||
|
||||
One control survives into Standard: **"Added triangles (k)"**, the subdivision budget. It is deliberately
|
||||
*not* pinned - pinning would fight the user's own slider every frame - because unlike the rest of the
|
||||
recipe its right value depends on the part rather than on the method (a big model, or a fine texture,
|
||||
simply needs more triangles). Default 1500. The widget is one lambda shared by both layouts.
|
||||
|
||||
Standard remeshes *after* painting, so the remesh has to preserve paint: `ModelVolume::restore_painting()`
|
||||
only remaps the four standard channels, so `replace_mesh_keep_all_paint()` additionally runs
|
||||
`TriangleSelector::remap_painting()` over the eight texture-displacement masks. The Pro Remesh button
|
||||
goes through the same helper. If the remap comes back empty the pipeline stops with a message rather
|
||||
than baking a flat mesh.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data model (per `ModelVolume`)
|
||||
|
||||
Each of up to `TEXTURE_DISPLACEMENT_MAX_LAYERS` (8) layers gets its **own independent
|
||||
`FacetsAnnotation`** paint mask - the same `TriangleSelector`/`FacetsAnnotation` machinery every other
|
||||
paint gizmo (FdmSupports, Seam, MMU, FuzzySkin) already uses, just one full instance per layer slot
|
||||
instead of one per volume. This is what makes layered/blended painting work for free: the same triangle
|
||||
can be `ENFORCER` in layer 2's mask and layer 5's mask simultaneously, and at bake/preview time each
|
||||
layer displaces the surface left by the previous one (image-editor-layer semantics).
|
||||
|
||||
Whole-stack settings (border handling, post-process smoothing) live beside the layers in
|
||||
`texture_displacement_options` (`TextureDisplacementOptions`), since they belong to no single layer.
|
||||
|
||||
### Bake algorithm (`libslic3r/TextureDisplacement.cpp`)
|
||||
|
||||
`build_texture_displacement(base_mesh, layers, facets_data, options)` is **accumulate-then-displace,
|
||||
and topology-preserving**: the returned mesh has exactly the input's vertices and triangles, in the
|
||||
same order - only the positions of displaced vertices differ.
|
||||
|
||||
1. `its_compactify_vertices()` on a copy of the input. In practice a no-op (it only drops
|
||||
*unreferenced* vertices, and preserves the order and indices of the rest). It is there to
|
||||
guarantee the index alignment step 3 depends on.
|
||||
2. Area-weighted vertex normals of the **undisplaced** mesh, computed once. Every layer both projects
|
||||
and displaces along these, so a vertex covered by several layers moves along one single well-defined
|
||||
direction. Where the paint does *not* cover every triangle around a vertex, the normal is recomputed
|
||||
from the painted triangles alone (the union over all layers, so it stays one direction per vertex):
|
||||
on the rim of a fully painted top face the whole-mesh normal is the 45-degree bisector it shares with
|
||||
the side wall, and displacing along that flares the rim outwards instead of raising it. Interior
|
||||
vertices are unaffected - all their triangles are painted, so the two normals coincide. Paint
|
||||
coverage per original triangle comes straight off `TriangleSplittingData::triangles_to_split`.
|
||||
3. For each layer in slot order: deserialize its stored paint mask into a `TriangleSelector` against
|
||||
the **base mesh** (never against a previous layer's output), then
|
||||
`selector.get_facets_strict(ENFORCER)` → the painted patch. Two facts are exploited:
|
||||
- `get_facets_strict()` returns the mesh's **entire** referenced vertex array regardless of which
|
||||
state was asked for - only `.indices` is filtered by state. So `get_facets_strict(ENFORCER)`
|
||||
and `get_facets_strict(NONE)` share identical vertex indexing, which is what lets boundary
|
||||
detection be a plain index check instead of a position-hash lookup.
|
||||
- The selector's vertex array *starts with* the mesh's own vertices (extra ones created where a
|
||||
brush stroke split a triangle are appended after them), and `get_facets_strict()` emits the
|
||||
referenced ones in order. Combined with step 1, **selector vertex index `i` is our vertex `i`**.
|
||||
Split vertices live past the end of our array and are simply skipped - they sit on the paint
|
||||
boundary anyway (splitting only happens at partial coverage).
|
||||
4. A vertex used by at least one **unpainted** triangle is a border vertex. Whether it moves is
|
||||
`TextureDisplacementOptions::displace_border`, and it does by default. Nothing can tear: the bake is
|
||||
topology-preserving, so a border vertex is *one* vertex shared by both regions and moving it simply
|
||||
tilts the unpainted triangles that use it. Pinning it instead clamps the outermost ring of relief to
|
||||
zero, which on a fully painted face collapses the pattern into a ring of steep ramps at the edge; it
|
||||
is kept as an option for when the relief must not spill past the paint at all. Either way the border
|
||||
drives the `edge_smoothing` falloff.
|
||||
5. Per interior vertex: sample the height texture (`sample_layer_height()`, see Projection methods)
|
||||
and fold `height * depth_mm * (invert ? -1 : 1)` into that vertex's running total via the layer's
|
||||
`TextureBlendMode` (see Blend modes). A `visited` set makes each layer fold in exactly **once**
|
||||
per vertex, no matter how many of the patch's triangles share it - otherwise a Multiply/Subtract
|
||||
layer would apply two or three times over depending on local triangle fan-out.
|
||||
6. Move each touched vertex along its (step 2) normal by its accumulated total.
|
||||
7. Optionally (`TextureDisplacementOptions::smooth_*`) relax the result - see Post-process smoothing.
|
||||
|
||||
### Post-process smoothing
|
||||
|
||||
`smooth_mesh_vertices(mesh, movable, strength, iterations)` - Laplacian relaxation, run after all layers
|
||||
have been folded in, restricted to the vertices flagged in `movable`. Each pass moves a movable vertex a
|
||||
`strength` fraction of the way to the average of its one-ring, read from a **snapshot** of the previous
|
||||
pass so the result does not depend on vertex order (a Gauss-Seidel sweep would smooth several times as
|
||||
hard at the end of the array as at the start). Neighbours come from a CSR-style adjacency built once per
|
||||
call. Topology-preserving, like the bake.
|
||||
|
||||
Its job is to round off the hard steps a bitmap height map leaves behind - a different knob from
|
||||
`TextureDisplacementLayer::smoothing`, which blurs the *height map* before it is ever sampled.
|
||||
|
||||
Two ways in, sharing one set of settings on the volume:
|
||||
- The **"Smooth result"** checkbox + "Smoothing (%)" / "Passes" ride along with Preview and Bake.
|
||||
`movable` is exactly the set of vertices the displacement moved, so the untouched part of the model
|
||||
keeps its exact geometry and the ring just outside the displaced set anchors the relaxation (the
|
||||
relief cannot creep outward).
|
||||
- **"Smooth baked mesh now"** (`GLGizmoTextureDisplacement::smooth_model()`) applies the same settings to
|
||||
the volume's *committed* geometry, for relief that is already baked in. `movable` there is the painted
|
||||
triangles' vertices. Because smoothing never touches the triangle list, this is the one geometry
|
||||
operation in the gizmo that keeps **every** paint channel verbatim - it saves and restores the eight
|
||||
texture-displacement masks around `set_mesh()` rather than remapping or dropping them.
|
||||
|
||||
**"Ignore outer ring"** (`smooth_skip_border`, on by default) drops the patch's own outermost ring of
|
||||
vertices from `movable`. That ring's neighbours *outside* the paint never move, so relaxing it drags the
|
||||
rim of the relief down toward the flat surface and the pattern comes out half-melted where it meets the
|
||||
edge. Held out, the border keeps the full depth the texture asked for and only the interior relaxes.
|
||||
Turning it off softens the outer edge deliberately (a blunter version of the per-layer edge-smoothing
|
||||
falloff). This is the *smoothing* rim, independent of whether that rim is displaced at all
|
||||
(`displace_border`, step 4 above); both default to keeping the border sharp.
|
||||
|
||||
### Blend modes
|
||||
|
||||
`TextureBlendMode` {Add, Subtract, Multiply, Divide}, per layer, applied per vertex against the
|
||||
total accumulated by the layers **below** it (lower slots). The quantity blended is a signed
|
||||
displacement in **mm**, not a pixel value.
|
||||
|
||||
Add/Subtract are self-explanatory. Multiply/Divide are *scaling* operations and so need a unit
|
||||
convention: they treat the layer's own value as a **factor relative to 1 mm**. That makes `depth_mm`
|
||||
a gain, and - the property that makes a Multiply layer usable as a mask - a layer with depth 1 mm
|
||||
sampling a white (1.0) texel multiplies by exactly 1, i.e. leaves the layers below unchanged.
|
||||
Divide floors its divisor's magnitude at 0.05: a black texel samples to *exactly* zero, so the divisor
|
||||
really does hit zero in ordinary use, and an unbounded `1/0` would fling vertices thousands of mm away
|
||||
and poison the mesh's bounding box (and every plate/print-volume check downstream). The floor doubles as
|
||||
a cap on how far Divide can amplify the relief beneath it: at most 20×.
|
||||
|
||||
The **lowest painted layer ignores its blend mode**: it has nothing beneath it, and Multiply/Divide
|
||||
against an implicit zero base would annihilate (or blow up) it. Enforced in
|
||||
`build_texture_displacement()` (the first layer to reach a given vertex always folds in additively) and
|
||||
surfaced in the UI, which labels that layer "Base layer" instead of offering a control that does nothing.
|
||||
|
||||
### Projection methods
|
||||
|
||||
Five choices per layer (`TextureProjectionMethod`), all funneling through `apply_uv_transform()`
|
||||
(scale by `1/tiling_scale`, rotate by `rotation_deg`, add `offset`). They are dispatched by
|
||||
`sample_layer_height()`, which returns a **height**, not a UV - because Triplanar takes three
|
||||
texture samples per vertex and so has no single UV that represents it.
|
||||
|
||||
- **Triplanar** (default) - samples the texture on all three world planes (`(y,z)`, `(x,z)`, `(x,y)`)
|
||||
and blends the three by the vertex's own normal raised to `TRIPLANAR_BLEND_SHARPNESS` (4). Hard-picking
|
||||
the single axis most aligned with the normal instead is discontinuous wherever that dominant axis
|
||||
flips: on a +X face the planar coordinate is `(y, z)`, on a −Y face it is `(x, z)`, so at the shared
|
||||
edge `u` jumps. A weighted blend is continuous across the transition by construction, since the weight
|
||||
of the axis being left behind falls smoothly to zero. This removes the hard *seam*; some cross-fade
|
||||
blurring in the band right at a 90° edge is inherent to triplanar mapping. A genuinely seam-free wrap
|
||||
around a box needs a real unwrap - that is what the LSCM mode is for.
|
||||
- **Cylindrical** - wraps around an axis through the patch centroid, axis auto-picked as the world
|
||||
axis *least* aligned with the average normal (perpendicular to the outward radial normal, as a
|
||||
cylinder's own axis would be). `u = angle * local_radius` (arc length in mm), `v = distance along
|
||||
axis`. An approximation, not an exact fit for arbitrary geometry, and the axis/centre are not
|
||||
user-overridable.
|
||||
- **Spherical** - longitude/latitude around the centroid, scaled by local radius. Same caveat.
|
||||
- **LSCM** - real UV unwrap via `MeshBoolean::cgal::parameterize_lscm()` (CGAL's
|
||||
`Surface_mesh_parameterization` package, LSCM algorithm). Computed **once per patch** (not
|
||||
per-vertex like the others - it's a single global least-squares solve), then each vertex looks up
|
||||
its precomputed UV. Requires the patch to be a single topological disk (one connected component,
|
||||
one boundary loop) - `compute_lscm_uvs()` returns empty and the layer falls back to Triplanar if not
|
||||
(e.g. multiple disconnected painted islands, or a fully closed patch). CGAL's parameterizer needs a
|
||||
mesh with no isolated/unreferenced vertices, but `get_facets_strict()` returns the *whole* mesh's
|
||||
vertex array - so `compact_patch_with_map()` builds a clean sub-mesh plus an index map back to the
|
||||
original vertex numbering, purely local to this file.
|
||||
- **ViewProjected** ("From view") - a flat projection along a fixed direction captured from the 3D
|
||||
camera, like a slide projector. `capture_view_projection()` takes the camera's right/up axes,
|
||||
transforms them into the volume's *local* frame (so the projection rides along if the part is later
|
||||
moved), and stores them as `TextureDisplacementLayer::view_project_right/up` (unit vectors, so the
|
||||
projected coordinate stays in mm and `tiling_scale` keeps meaning mm). `sample_layer_height()`
|
||||
projects `Vec2f(dot(pos, right), dot(pos, up))`. Single-valued per point, so - like LSCM but unlike
|
||||
blended Triplanar - the fast preview and UV-check overlay precompute it per vertex
|
||||
(`compute_layer_vertex_uvs()`) and drive the shader's `use_vertex_uv` path. Faces angled away from
|
||||
the projector smear; that is inherent to view projection.
|
||||
|
||||
Two companions to this mode:
|
||||
- **Projection frame overlay** (`TextureProjectorFrame`, see below) - a semi-transparent window
|
||||
dragged over the 3D view whose border becomes the projection's edge. Applying it stores an exact
|
||||
**projective** map in `view_project_matrix`, which supersedes the affine `right`/`up` axes above
|
||||
for that layer (`view_project_projective`).
|
||||
- **"Project only on visible"** (`select_visible_faces()`) - repaints the layer with exactly the
|
||||
facets the camera can see, so the projected area matches the viewpoint the projector was captured
|
||||
from. Two tests: a facing test (normal vs. view direction, per triangle - under perspective the
|
||||
view direction varies across the model, so it is taken from the eye to each centroid), then
|
||||
`MeshRaycaster::get_unobscured_idxs()` on the survivors to drop facets hidden behind other
|
||||
geometry, so a concave part's far inner wall is correctly excluded. One ray query per front-facing
|
||||
facet, hence click-driven (on the checkbox and on each "Capture current view"), never per frame.
|
||||
It **replaces** the layer's paint rather than adding to it - "project onto what I can see" would
|
||||
otherwise accumulate every angle the user had ever looked from.
|
||||
|
||||
### Manual seams and island cutting
|
||||
|
||||
`TextureDisplacementLayer::lscm_seam_edges` - undirected mesh-vertex-index edge pairs the unwrap is
|
||||
forced to cut along, on top of the dihedral-angle seams. `segment_into_charts()` takes a set of these
|
||||
(translated from mesh → compacted-patch numbering inside `compute_patch_unwrap()`) and refuses to
|
||||
union two triangles across a marked edge whatever their angle. Both the unwrap cache key and the
|
||||
gizmo's `UVEditorState` include the seam list, so marking a seam (which leaves the paint mask
|
||||
untouched) still forces a re-solve. Like the paint masks, seams are mesh-index-space and so dropped on
|
||||
any topology change.
|
||||
|
||||
Two ways to write to it:
|
||||
- **Mark seam (manual)** - a "Mark seams" click mode (`m_seam_edit_mode`) that suppresses painting. A
|
||||
click raycasts the volume (`m_c->raycaster()->raycasters()[idx]->unproject_on_mesh()`, `idx` = the
|
||||
volume's slot among model-part volumes), finds the facet's edge nearest the hit point, and toggles it.
|
||||
Marked edges render as a red overlay (`render_seam_overlay()`), pulled toward the camera so they read
|
||||
on top. This is the Blender mark-seam workflow.
|
||||
- **Cut island (auto)** - `cut_island()` takes the selected chart's triangles (back-mapped from the
|
||||
unwrap via `source_vertex`), finds their 3D bounding box, and marks every edge that straddles the
|
||||
mid-plane perpendicular to the longest axis. The re-unwrap then splits the chart across its narrow
|
||||
waist. Exposed as the UV pane's **Cut** button.
|
||||
|
||||
### UV-check overlays (checker / distortion)
|
||||
|
||||
`resources/shaders/{110,140}/texture_displacement_uvcheck.{vs,fs}`, one shader with a `mode` uniform,
|
||||
drawn over the painted patch (`rebuild_uvcheck_mesh()`/`render_uvcheck_mesh()`, P3N3T2: `normal.x` =
|
||||
distortion, `tex_coord` = uv), pulled forward with a polygon offset. **Checker** samples a procedural
|
||||
checkerboard at the layer's uv (per-vertex for LSCM/ViewProjected, in-shader triplanar otherwise) -
|
||||
squares that stay square mean low distortion. **Distortion** colours each triangle blue→green→red by
|
||||
`log2(uv_area / surface_area)` centred on the patch's *median* stretch (so a globally-scaled unwrap
|
||||
reads as uniformly ideal and only relative stretch shows), averaged to vertices. A separate **Show mesh
|
||||
wireframe** toggle draws the whole volume's triangle edges, rebuilt only when the vertex count changes
|
||||
(not per stroke).
|
||||
|
||||
### Tiling
|
||||
|
||||
`DecodedHeightTexture::sample(uv, tile_enabled, tile_method)`. Two tile methods when enabled
|
||||
(Repeat, MirroredRepeat). **When `tile_enabled` is false, sampling outside `[0,1)` returns `0`
|
||||
directly** rather than clamping the *coordinate* into range, which would smear the border row/column of
|
||||
pixels outward to infinity in every direction (streaky lines radiating out from the painted patch).
|
||||
|
||||
### Subdivision — two modes
|
||||
|
||||
**Uniform (`subdivide_mesh_uniform()`)** — whole-mesh, 1-to-4 split. Recursive edge-midpoint split with
|
||||
a shared per-pass midpoint cache (keyed by sorted vertex-index pair) so triangles sharing an edge get
|
||||
the *same* new vertex - capped at `max_iterations` (default 6). Whole-mesh so it never leaves a
|
||||
T-junction, at the cost of densifying everywhere. Wired as a "Subdivide steps" slider (**0–5**, 0 =
|
||||
no subdivision), Apply snaps back to 0. Drops texture-displacement paint (no remap) via the standard
|
||||
`save_painting()`/`set_mesh()`/`restore_painting()` dance; the other four channels are remapped.
|
||||
|
||||
**Adaptive (`subdivide_mesh_adaptive()`)** — refine **only the painted area**, by **Rivara longest-edge
|
||||
bisection**, which is *conformal by construction*. Only **terminal** edges are ever bisected - an edge
|
||||
that is the longest edge of *every* triangle sharing it - which splits both those triangles along one
|
||||
shared midpoint at once, so a hanging node is never created. The edge to split for a triangle that wants
|
||||
refining is found by **longest-edge propagation (LEPP)**: walk to the longest edge of ever-longer-edged
|
||||
neighbours until a terminal one is reached, and bisect that. Edge length strictly increases along the
|
||||
path (ties broken by mesh-vertex key, which both sides of an edge compute identically), so the walk
|
||||
cannot cycle, and Rivara's result is that repeating it refines the original triangle in a bounded number
|
||||
of bisections. The transition triangles it pulls in just outside the painted patch are the graded band
|
||||
that makes the size change conformal.
|
||||
|
||||
The win: a small decal on a big model no longer quadruples the *whole* model's triangle count.
|
||||
|
||||
**Run to completion, worst-first, against a triangle budget.** The refinement loop is not a fixed number
|
||||
of sweeps: it holds every triangle that is over its criteria in a max-heap keyed by *how many times over*
|
||||
it is, pops the worst, walks its LEPP, bisects, and re-scores. Edge adjacency (`nb[e]`, the triangle
|
||||
across each edge) is built **once** and maintained incrementally through each bisection, so the cost
|
||||
scales with the refined region rather than with the whole model. `max_triangles` is the only bound;
|
||||
stopping on it leaves a perfectly valid, still-conformal mesh that spent its budget on the largest
|
||||
errors. A fixed sweep count instead spends itself grading the *coarse surroundings* - whose edges are
|
||||
the longest, so they win every terminal-edge contest - and never reaches the painted patch.
|
||||
|
||||
**It carries the paint forward**, which is what makes it usable (uniform subdivide drops paint). Because
|
||||
the refinement is *driven by* the paint, the remap is trivial: `subdivide_mesh_adaptive()` fills an
|
||||
`out_source[new_tri] = input_tri` map (children inherit their parent), and the gizmo rebuilds each
|
||||
layer's mask on the new mesh - a new triangle is painted iff its source was fully painted in that
|
||||
layer. `collect_paint_region()` derives both:
|
||||
- the union refine-region: **exactly** the original triangles the brush touched, read straight off
|
||||
`TriangleSplittingData::triangles_to_split` (`serialize()` records an entry per original triangle that
|
||||
is either split - i.e. partially painted, the patch boundary - or carries a non-default state). No
|
||||
dilation: marking every triangle that shares a *vertex* with the patch drags in a whole fan of huge
|
||||
unpainted neighbours and refines *those* down to the resolution floor, since the height field the
|
||||
detail test samples is not restricted to the painted area. The conformal closure already grades the
|
||||
size change outward on its own.
|
||||
- the per-layer fully-painted-triangle sets (a `get_facets_strict(ENFORCER)` sub-triangle with all three
|
||||
*original* vertex indices == a whole, fully-painted original triangle; a partial stroke's sub-triangles
|
||||
always carry a split vertex).
|
||||
|
||||
The other four channels ride the normal `restore_painting()` remap.
|
||||
|
||||
Both modes share the gizmo's Preview/Apply/Done flow; the **"Only painted area (adaptive)"** checkbox
|
||||
picks the mode, and the adaptive preview follows the paint live (`rebuild_preview()` refreshes the
|
||||
wireframe while the subdivide preview is open in adaptive mode). The panel shows the previewed triangle
|
||||
count.
|
||||
|
||||
**Feature-adaptive (follow texture detail).** A sub-mode of adaptive (the **"Follow texture detail"**
|
||||
checkbox) that puts triangles where the *displaced surface actually bends*, not evenly. A flat region or
|
||||
a linear **ramp** needs no extra vertices (linear interpolation is exact for a ramp); what needs them is
|
||||
**curvature** - the *second* derivative, not the gradient. So the extra predicate is a **chord-error**
|
||||
test: sample the combined displacement at the triangle's three edge midpoints *and its centroid*
|
||||
(sampling the interior is what catches a hill sitting inside a triangle, the blind spot of an edge-only
|
||||
test) and take the largest departure from the flat triangle's barycentric interpolation. Refine while
|
||||
that exceeds `chord_tolerance_mm` ("Detail (mm)"). Zero chord error on a ramp ⇒ untouched; high on a
|
||||
hill/ridge/noise ⇒ refined until captured. Same conformal machinery, so still crack-free. The
|
||||
per-triangle error is cached and recomputed only for the children of a split.
|
||||
|
||||
Four knobs bracket it, and all four matter:
|
||||
- **"Max edge (mm)"** (`target_edge_length_mm`) is a **baseline that applies in feature mode too**.
|
||||
Without it the chord test aliases: a big triangle over a fine pattern can sample four points that all
|
||||
land at similar heights, report no error, and stall before refinement ever starts. The baseline
|
||||
guarantees a sampling density fine enough for the curvature test to see the texture at all.
|
||||
- **"Detail (mm)"** is the chord tolerance above.
|
||||
- **"Min edge (mm)"** is a hard floor under both, and is what guarantees termination across a sharp
|
||||
texture *step*, where the error never falls however fine the mesh gets.
|
||||
- **"Added triangles (k)"** is the budget, passed as `max_triangles` (the model's own triangle count plus
|
||||
the slider, so the control still means something on an already-dense model).
|
||||
|
||||
The height field is `make_combined_displacement_sampler()` - it mirrors `build_texture_displacement()`'s
|
||||
per-layer setup (decode, patch centroid, cylinder axis, blend order, "lowest layer folds additively")
|
||||
but evaluated per point. Two deliberate simplifications, both erring toward *more* detail (safe -
|
||||
over-refinement is never a crack): every sampleable layer is sampled at every point (no per-point paint
|
||||
test), and edge-smoothing falloff is ignored. The first is *why* the refine region must not be dilated -
|
||||
outside the paint the sampler still reports full relief. **LSCM layers are skipped** (no per-point UV); a
|
||||
purely LSCM stack yields a null sampler and the code falls back to the length baseline alone. Per-vertex
|
||||
heights are sampled lazily, so a small patch on a huge model never pays for the rest of it.
|
||||
|
||||
### Fast preview (GPU-only, no CPU meshing)
|
||||
|
||||
`resources/shaders/{110,140}/texture_displacement_shaded.{vs,fs}`, registered as
|
||||
`"texture_displacement_shaded"`. Shades the *displaced* surface without moving geometry - active-layer
|
||||
only, selected from the View row, and the default when the gizmo opens (`m_use_shaded_preview = true`).
|
||||
Vertex format is `GLModel::Geometry::EVertexLayout::P3N3T2`: `normal.x` carries the per-vertex paint
|
||||
weight (0/1), `normal.y` flags the UV island currently being dragged, and `tex_coord` carries a
|
||||
precomputed texture UV, so it can use `GLModel` normally instead of a hand-rolled VBO/VAO manager.
|
||||
|
||||
The mesh is **flat** (vertices not shared between triangles): every corner of a painted triangle gets
|
||||
weight 1, every corner of an unpainted one weight 0. A coarse mesh needs that - one painted face of a raw
|
||||
cube has no strictly-interior vertex, so per-vertex weighting would either bleed onto the neighbours or
|
||||
vanish outright. Duplicating vertices costs no shading quality here because the shader takes its surface
|
||||
normal from screen-space derivatives of position, not from a per-vertex normal.
|
||||
|
||||
**Both preview meshes work in the patch's vertex space, not the mesh's.** Those agree only until a
|
||||
*brush* stroke splits a triangle: `get_facets_strict()` then appends the split vertices, so the patch
|
||||
array is longer. `rebuild_shaded_preview_mesh()` and `rebuild_uvcheck_mesh()` therefore index
|
||||
`patch.vertices` throughout. The weight buffer is rebuilt at the same cadence as the true-displacement
|
||||
preview (stroke-end/slider-release) but from the **live** `TriangleSelector` state, not the flushed model
|
||||
facets, so it does not lag by a full model round-trip.
|
||||
|
||||
The perturbed normal is the analytic one for a height field `H = ±depth_mm · h(uv)` displaced along
|
||||
`N` over any orthonormal surface tangent pair `T`/`B`:
|
||||
|
||||
N' = normalize(N − (dH/da)·T − (dH/db)·B), a = dot(p,T), b = dot(p,B)
|
||||
|
||||
The two slopes have to be genuine **mm-per-mm** derivatives for the preview's apparent depth to match
|
||||
the bake's.
|
||||
|
||||
**Two projection paths (`use_vertex_uv` uniform):**
|
||||
- **Triplanar (`use_vertex_uv = 0`)** - `uv` and the `T`/`B` axes are both derived in-shader from
|
||||
the dominant normal component, mirroring `project_planar()`/`apply_uv_transform()`, and the slope is
|
||||
formed analytically. `T`/`B` are the projection's axis-aligned pair, exact only when the face is
|
||||
axis-aligned; the shader drops the along-normal component to keep the gradient in the surface. Here
|
||||
one `uv` unit is exactly `tiling_scale` mm, so the `1/tiling_scale` gradient factor is right.
|
||||
- **Precomputed UV (`use_vertex_uv = 1`, used for LSCM and ViewProjected)** - `uv` comes per-vertex from
|
||||
the CPU (`compute_layer_vertex_uvs()`, so island placement + tiling/rotation/offset are already folded
|
||||
in), and the perturbed normal is built with **Mikkelsen's method** ("Bump Mapping Unparametrized
|
||||
Surfaces on the GPU"): the surface gradient taken directly from the screen-space derivatives of the
|
||||
*sampled height* and position. **This makes no uv→mm scale assumption**, which is essential, because an
|
||||
LSCM map is **conformal, not isometric**: it is globally area-scaled but the *local* mm-per-uv varies
|
||||
across the chart, so a single global `1/tiling_scale` factor gets the apparent depth wrong. `dFdx(h)`
|
||||
captures the true on-screen rate of change however the chart is stretched. This path is also what makes
|
||||
the fast preview follow the UV editor: move an island and its uv - hence its shading - moves with it
|
||||
(the mesh rebuilds on drag-end, `on_island_edited(finished)` → `rebuild_preview()` →
|
||||
`rebuild_shaded_preview_mesh()`). The branch is uniform and the paint weight gates by multiply, so the
|
||||
texture derivatives stay well defined. A triangle straddling a seam has a discontinuous uv → the
|
||||
`det≈0` guard skips it (a localised preview-only artifact, never in the bake).
|
||||
|
||||
**Parallax (triplanar path).** Perturbing the shading normal alone welds the pattern to the base surface:
|
||||
it does not slide as the camera orbits, and does not get deeper as `depth_mm` grows. The triplanar path
|
||||
therefore shades at the point the *displaced* surface would show at this pixel, found by **ray marching**
|
||||
(parallax occlusion mapping). A point at ray parameter `s`, i.e. `P + V·s` (`P` the base point, `V` the
|
||||
unit direction to the eye), sits at height `s·dot(V,n)` above the undisplaced surface. The displaced
|
||||
surface lives in a shell between the extreme values of `amp·(h − midlevel)` - taken from both ends of
|
||||
`h ∈ [0,1]`, so it holds for an inverted layer and a raised midlevel too, where the surface sits *below*
|
||||
the undisplaced one. The march starts at the top of that shell, where the ray is outside the surface by
|
||||
construction, and steps inward until the ray height drops below the sampled height. That crossing *is*
|
||||
the visible point.
|
||||
|
||||
Solving `Q = P + V·(H(Q)/dot(V,n))` by fixed-point iteration instead is geometrically exact but the
|
||||
divisor goes to zero edge-on; the sample then lands a large fraction of a tile away and the iteration
|
||||
oscillates, which reads as a second, flat copy of the pattern ghosted over the real one. Clamping the
|
||||
step to one tile does not help - a tile-sized shift lands on the neighbouring tile, the same pattern
|
||||
again. Offset limiting (stepping along the tangential part of `V`) is stable but understates parallax
|
||||
enough that the relief still flattens as soon as the camera tilts. Marching has neither problem.
|
||||
|
||||
The hit is interpolated between the last two samples, which keeps `PARALLAX_STEPS` (24) affordable, and
|
||||
the whole march is skipped when sweeping the shell would move the sample point less than half a texel -
|
||||
the head-on case, so the common view pays almost nothing. The 140 variant samples with
|
||||
`textureLod(…, 0.0)` inside the loop, since implicit derivatives are undefined in non-uniform control
|
||||
flow. Two uniforms exist for this: `midlevel` (parallax needs the real height, not just its derivative)
|
||||
and `eye_model_pos` (the camera in the volume's local frame).
|
||||
|
||||
Parallax cannot change the model's silhouette or cast shadows; the View row's Normal mode is one click
|
||||
away for that. The LSCM path stays plain Mikkelsen normal perturbation - it has no closed-form uv, so there is no cheap
|
||||
way to re-project a marched position. One further approximation: the GPU sampler's wrap mode stands in
|
||||
for `tile_enabled`/`tile_method`, so with tiling *off* the GPU repeats where the CPU returns 0 outside
|
||||
`[0,1)`.
|
||||
|
||||
### On-canvas "Adjust Texture" gizmo
|
||||
|
||||
A per-active-layer toggle ("Adjust placement") that disables painting and shows a flat pan panel (free
|
||||
2D drag on both axes) plus two arrows along the patch's own U/V axes (constrained single-axis drag).
|
||||
Anchored to the painted patch's centroid/average-normal (`compute_layer_paint_anchor()`). Hit-testing is
|
||||
screen-space distance/point-to-segment, not real 3D ray intersection against the handle geometry - simple
|
||||
and good enough at this handle size.
|
||||
|
||||
### Projection frame overlay (ViewProjected)
|
||||
|
||||
`src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` - a semi-transparent, resizable `wxFrame` the user
|
||||
drags **over the 3D view**, like a slide projector's gate. Whatever the model shows through it is what
|
||||
the texture is projected onto, and the window's border becomes the hard edge of the displacement.
|
||||
Press **Apply projection frame** and the gizmo reads the window's rectangle and commits it.
|
||||
|
||||
The window is deliberately **dumb**: it owns no placement state and reports nothing continuously. Its
|
||||
position and size *are* the placement, read on demand at Apply - which is also when the expensive
|
||||
visible-facet raycast runs. So dragging it is free and nothing recomputes until asked.
|
||||
|
||||
Plain 2D (`wxPaintDC`), not a `wxGLCanvas`: a second GL canvas would have to share the app's one real
|
||||
`wxGLContext`. It only ever draws a bitmap and a border.
|
||||
|
||||
**The projective mapping (`apply_projection_frame()`)**. The frame defines a **screen-space** rectangle,
|
||||
but the bake samples from a **local-space** position, so the two have to be reconciled.
|
||||
`view_project_right/up` can only express an *affine* projection - exact under an orthographic camera, but
|
||||
wrong under perspective, where the near end of a part projects larger than the far end and no pair of
|
||||
axes reproduces that. So the layer instead stores a full projective map (`view_project_matrix`, row-major
|
||||
3×4, `uv = (row0·p̃/row2·p̃, row1·p̃/row2·p̃)`), built like this:
|
||||
|
||||
- `K = projection · view · (instance · volume)`, i.e. local → clip, the same product the renderer uses.
|
||||
Note `Camera::get_projection_matrix()` is typed `Transform3d` (nominally affine) but its perspective
|
||||
form explicitly writes a `(0, 0, −1, 0)` bottom row into the underlying 4×4, so `clip.w = −z_eye` is
|
||||
genuinely carried. The build therefore multiplies **`.matrix()` products** (plain `Matrix4d`), never
|
||||
`Transform3d` products, which would not compose that row correctly.
|
||||
- Window coordinates follow `igl::project`'s convention (as `CameraUtils::project` does), with y
|
||||
measured downward. Writing `uv = (win − rect_origin) / rect_size` makes u and v affine in
|
||||
`ndc = clip.xyz / clip.w`; multiplying through by `clip.w` leaves a plain linear combination of `K`'s
|
||||
rows, which is exactly the 3×4 matrix - the perspective divide survives intact.
|
||||
- `w > 0` is checked rather than divided blindly. A point behind the projector has `w < 0` and divides
|
||||
to a plausible-looking but **mirrored** uv - the classic way a projected decal reappears on the back
|
||||
of a model. `project_uv_projective()` returns false there and the caller treats it as no height.
|
||||
|
||||
The map already includes placement, so `apply_uv_transform()` is **not** applied on top of it - the
|
||||
window's own position and size are the placement, and the tiling/rotation/offset sliders would shove
|
||||
the result off the frame the user just aligned. A "Clear" button drops back to the affine path where
|
||||
those controls mean something again.
|
||||
|
||||
Apply also sets `tile_enabled = false`, so `DecodedHeightTexture::sample()` returns 0 outside `[0,1)`
|
||||
and the border is a hard edge rather than the first seam of an endless repeat, and repaints the layer
|
||||
via `select_visible_faces(&matrix)` - the frame's uv square clips the selection, which both matches the
|
||||
paint to the border and keeps the ray queries proportional to the framed area instead of the model.
|
||||
|
||||
Owned by the gizmo and **destroyed** (not just hidden) in `on_shutdown()`. Closing it only hides it, so
|
||||
reopening keeps it where it was left.
|
||||
|
||||
### UV Editor pane
|
||||
|
||||
`UVEditorCanvas` (`src/slic3r/GUI/UVEditorCanvas.hpp/.cpp`) - a standalone `wxGLCanvas` rendering the
|
||||
flattened LSCM islands (per-island wireframe + outline + fill) over the height texture (background
|
||||
quad tiled across the whole unwrap), with mouse pan/zoom. It is wrapped in a **`UVEditorPanel`**
|
||||
(same file) that adds a button row (Frame / Snap / Avg scale / Cut / Join / Unjoin) and a status line
|
||||
along the bottom naming the current gesture and the shortcuts in play. The *panel* is what is
|
||||
registered as a `wxAuiPaneInfo` pane on `Plater`'s `m_aui_mgr`; `Plater::show_uv_editor(bool)`
|
||||
shows/hides it (deferred via `CallAfter`, since the gizmo calls it mid-3D-frame), and
|
||||
`get_uv_editor_canvas()` returns the inner canvas the gizmo talks to.
|
||||
|
||||
Deliberately **shares the app's one real `wxGLContext`** (`wxGetApp().init_glcontext(*this)`, the
|
||||
same call `View3D`/`Preview`/`AssembleView` make) rather than creating an independent context like
|
||||
`SkipPartCanvas` does elsewhere in this codebase - this is what lets it reuse the already-registered
|
||||
`"flat"`/`"flat_texture"` shaders and `GLModel` as-is, instead of needing its own shader
|
||||
compilation/VBO management.
|
||||
|
||||
**Geometry is uploaded once, in the unwrap's own (raw, mm) coordinates**, one `GLModel` set per island;
|
||||
each island is then drawn through its own 2x3 affine (`island_transform_matrix()` composed with the
|
||||
layer's tiling/rotation/offset) passed as the `flat` shader's `view_model_matrix`. A drag updates one
|
||||
matrix per island and touches no vertex buffer - `on_island_edited(!finished)` calls only
|
||||
`set_island_transforms()`, and the full `set_islands()` rebuild happens solely when the unwrap itself
|
||||
changes (`unwrap_changed` in `update_uv_editor()`).
|
||||
|
||||
**Gestures** (canvas-owned, reported to the gizmo as incremental deltas via `IslandEditFn`): left-drag
|
||||
= move, right-drag or **R** = rotate (hold **Shift** to snap to 15° steps - quantised on the
|
||||
*cumulative* rotation, not each delta, so it doesn't judder, and accumulated incrementally so it
|
||||
survives crossing ±180°), **S** = scale (R/S modal, click/Enter to confirm, Esc to cancel), wheel =
|
||||
zoom about the cursor, middle-drag = pan, **Home**/**F** = frame all. Scale writes
|
||||
`TextureIsland::scale`; "Avg scale" (`average_island_scales()`) sets every island to the mean, so
|
||||
one island scaled by hand can be matched back to its neighbours' texel density. **Snap** (canvas-owned
|
||||
`m_snap_enabled`, toggled from the toolbar) sticks a dragged island's nearest boundary vertex onto a
|
||||
neighbouring island's at drag-*end* only - a magnet that re-applies mid-drag is very hard to pull out
|
||||
of. Toolbar commands the canvas can't service itself (Avg scale, Cut, Join, Unjoin) are forwarded to the
|
||||
gizmo via `CommandFn`; view-only ones (Frame, Snap) it handles directly.
|
||||
|
||||
## File map
|
||||
|
||||
**libslic3r (core, no GUI dependency):**
|
||||
- `src/libslic3r/TextureDisplacement.hpp/.cpp` - data model, bake algorithm, projection methods,
|
||||
tiling, subdivision (uniform + adaptive longest-edge bisection), post-process smoothing
|
||||
(`smooth_mesh_vertices()`), and `TextureDisplacementOptions` (the whole-stack settings). See doc
|
||||
comments throughout, they're kept accurate and up to date.
|
||||
- `src/libslic3r/MeshBoolean.hpp/.cpp` - `parameterize_lscm()` and `remesh_isotropic()` in the `cgal`
|
||||
sub-namespace, reusing the existing `CGALMesh`/`_EpicMesh`/conversion-helper infrastructure already
|
||||
there for mesh boolean ops. CGAL includes: `Polygon_mesh_processing/border.h`,
|
||||
`Polygon_mesh_processing/connected_components.h`, `Surface_mesh_parameterization/{Error_code,
|
||||
LSCM_parameterizer_3, parameterize}.h`. No new dependency - CGAL 5.6.3 is already vendored and the
|
||||
`Surface_mesh_parameterization` package headers were already present.
|
||||
- `src/libslic3r/Model.hpp/.cpp` - the 8 named `FacetsAnnotation` fields + accessor,
|
||||
`texture_displacement_layers`, `texture_displacement_options`, and all the mirrored touch points
|
||||
(see Data model above).
|
||||
|
||||
**GUI:**
|
||||
- `src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp/.cpp` - the gizmo and its whole panel.
|
||||
- `src/slic3r/GUI/TextureLibrary.hpp/.cpp` - scans the shipped + user texture folders, imports an
|
||||
arbitrary image into the user folder (converting it to the 8-bit grayscale PNG libslic3r decodes),
|
||||
and loads a library file's bytes for a layer. The image→grayscale-PNG conversion lives here, on the
|
||||
GUI side, because libslic3r has no image toolkit; both the import path and the "pick a shipped
|
||||
texture" path go through the same one function.
|
||||
- `resources/textures/displacement/*.png` - the 10 shipped height maps (Bricks, Grid, Hexagons,
|
||||
Knurl, Noise, Quilt, Studs, Waves, Weave, Wood Grain). All 512×512 8-bit grayscale and **seamless**
|
||||
(each is periodic over the full image in both axes, so tiling shows no seam). Generated
|
||||
procedurally; the whole `resources/` tree is installed recursively by CMake, so a new folder under
|
||||
it ships with no build-system change.
|
||||
- `src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp/.cpp` - background bake commit.
|
||||
- `src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp/.cpp` - background preview compute
|
||||
(mirrors the bake job's shape but commits nothing to the Model).
|
||||
- `src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` - the semi-transparent projection-frame overlay for
|
||||
ViewProjected layers (plain 2D `wxPaintDC`, no GL context - see its section above).
|
||||
- `src/slic3r/GUI/UVEditorCanvas.hpp/.cpp` - the 2D UV unwrap viewer widget.
|
||||
- `src/slic3r/GUI/Plater.hpp/.cpp` - `uv_editor_canvas` member, AUI pane registration,
|
||||
`get_uv_editor_canvas()`/`show_uv_editor()`.
|
||||
- `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_shaded"`.
|
||||
- `resources/shaders/{110,140}/texture_displacement_shaded.{vs,fs}` - the fast-preview shader.
|
||||
- `src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp` - `PainterGizmoType::TEXTURE_DISPLACEMENT`.
|
||||
- `src/slic3r/GUI/Gizmos/GLGizmosManager.hpp/.cpp` - `EType::TextureDisplacement` registration.
|
||||
|
||||
## Tests
|
||||
|
||||
`tests/libslic3r/test_texture_displacement.cpp`. Covers `decode_height_texture` round-trip, empty-layer
|
||||
no-op, full-cube uniform displacement, a second layer over the same area contributing, all four blend
|
||||
modes (table-driven), the lowest layer ignoring its blend mode, border displace/pin, post-process
|
||||
smoothing and its mask guarantees, and adaptive subdivision: conformality (`every_edge_used_twice` on a
|
||||
partially-refined cube - an exact crack detector for a closed mesh), the target edge length actually
|
||||
being reached, the triangle budget capping the result without opening a crack, curvature-driven
|
||||
refinement (a Gaussian hill refines at its centre, a linear ramp adds nothing), and the max-edge
|
||||
baseline.
|
||||
|
||||
`BUILD_TESTS` is `OFF` in the checked-in build cache; flip it on to run them:
|
||||
|
||||
cmake -S . -B build -DBUILD_TESTS=ON
|
||||
cmake --build build --config Release --target libslic3r_tests -- -m
|
||||
./build/tests/libslic3r/Release/libslic3r_tests.exe "[TextureDisplacement]" --order rand
|
||||
@@ -0,0 +1,326 @@
|
||||
# Texture Displacement — Feature & Controls Guide
|
||||
|
||||
Texture Displacement is a paint-style gizmo that stamps height-map textures onto a model's surface
|
||||
and turns them into real relief — engraved or embossed detail — either as a live preview or baked
|
||||
into actual mesh geometry. You paint where the texture applies, stack multiple textures as blended
|
||||
layers, choose how each is projected onto the surface, and (for the unwrap projection) lay the result
|
||||
out by hand in a dedicated 2D **UV Editor** pane.
|
||||
|
||||
This document describes every feature and control. For the internal architecture and algorithms, see
|
||||
`TEXTURE_DISPLACEMENT.md`.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Quick start](#quick-start)
|
||||
2. [Entering the tool](#entering-the-tool)
|
||||
3. [Selection modes](#selection-modes)
|
||||
4. [View modes](#view-modes)
|
||||
5. [Auto update](#auto-update)
|
||||
6. [Texture layers](#texture-layers)
|
||||
7. [Per-layer settings](#per-layer-settings)
|
||||
8. [Projection methods](#projection-methods)
|
||||
9. [The UV Editor](#the-uv-editor)
|
||||
10. [Seams](#seams)
|
||||
11. [Adjust placement (on-model)](#adjust-placement-on-model)
|
||||
12. [Preparing the mesh: Subdivide & Remesh](#preparing-the-mesh-subdivide--remesh)
|
||||
13. [Baking & resetting](#baking--resetting)
|
||||
14. [Controls reference](#controls-reference)
|
||||
15. [Tips & limitations](#tips--limitations)
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Select an object and open the **Texture displacement** gizmo from the left toolbar.
|
||||
2. A texture layer is added automatically. Pick a texture from the layer's picker, or import your own.
|
||||
3. **Paint** the area you want the texture to affect (or press **Select whole model**).
|
||||
4. The relief appears live on the model. Tune **Depth**, **Tile size**, **Rotation**, etc.
|
||||
5. If the model is low-poly, use **Subdivide** or **Remesh** so there are enough vertices for detail.
|
||||
6. Press **Bake** to convert the preview into real geometry, or leave it as a live preview.
|
||||
|
||||
> The tool only ever affects the **painted** area. Everything you don't paint keeps its original
|
||||
> surface, and bake blends the relief seamlessly into it.
|
||||
|
||||
---
|
||||
|
||||
## Entering the tool
|
||||
|
||||
The gizmo lives on the left gizmo toolbar (icon: `toolbar_texture_displacement.svg`). Its settings
|
||||
panel opens beside the toolbar. You can **Dock panel / Undock panel** (top of the panel) to pin it or
|
||||
float it freely over the 3D view, and **Close** at the bottom exits the gizmo.
|
||||
|
||||
When you first open the tool on a never-textured object it starts with **one texture layer already
|
||||
added**, so you can paint straight away.
|
||||
|
||||
---
|
||||
|
||||
## Selection modes
|
||||
|
||||
Choose *how* you paint. All three write into the **active layer's** mask.
|
||||
|
||||
| Mode | What it does |
|
||||
|------|--------------|
|
||||
| **Brush** | Free-hand painting with a round brush. Shows a **Brush size** slider and a **Circle / Sphere** choice (circle = surface disc, sphere = 3D ball that also paints around curves). |
|
||||
| **Face** | Click a single triangle to paint it. |
|
||||
| **Connected area** | Click to flood-fill a region; the **Angle threshold** slider limits how far the fill spreads across changes in surface angle. |
|
||||
|
||||
- **Select whole model** — marks the entire model as painted for the active layer, instead of
|
||||
brushing it by hand.
|
||||
|
||||
---
|
||||
|
||||
## View modes
|
||||
|
||||
A row of icon buttons labelled **View** controls how the painted area is shown. The first four are a
|
||||
radio group; **Wireframe** is an independent toggle. Hover any icon for its tooltip.
|
||||
|
||||
| View | Meaning |
|
||||
|------|---------|
|
||||
| **Normal** | The true displaced geometry — exactly what **Bake** produces. Rebuilt in the background. |
|
||||
| **Fast** | A GPU shaded approximation of the *active layer only*. No real geometry movement — quick to update, not exact. Best while tuning or dragging islands. |
|
||||
| **Checker** | A test grid painted over the unwrap so you can see stretching (squares stay square where the map isn't distorted). |
|
||||
| **Distortion** | A blue→green→red heatmap of how much each area is compressed or stretched in UV space. Needs the **Unwrap (LSCM)** projection. |
|
||||
| **Wireframe** | Overlays the mesh edges (white). Independent of the view above; in **Normal** view it sits on the displaced surface. |
|
||||
|
||||
---
|
||||
|
||||
## Auto update
|
||||
|
||||
**Auto update** (on by default) rebuilds the true displaced geometry as soon as *anything* changes —
|
||||
painting, swapping textures, moving sliders. Turn it off on very heavy models to only rebuild when you
|
||||
release a slider (painting still updates on stroke end).
|
||||
|
||||
---
|
||||
|
||||
## Texture layers
|
||||
|
||||
You can stack up to **8** texture layers. Each has its own independent paint mask, its own texture,
|
||||
and its own parameters, and they combine in slot order like layers in an image editor.
|
||||
|
||||
- **Add a layer** — the **+ icon** to the right of the *Texture layers* heading (reuses the tool icon
|
||||
for now).
|
||||
- **Remove** — the button on each layer's header row.
|
||||
- **Active layer** — click a layer's header (or anywhere in its block) to make it active. The active
|
||||
layer is the one you paint into and the one whose block is tinted. Only one layer is active at a time.
|
||||
- **Erase all** — clears the active layer's paint.
|
||||
|
||||
Each layer shows a texture **picker** (large preview + name). Open it to choose from the shipped
|
||||
library or import your own image (any png/jpg/bmp; it's converted to an 8-bit grayscale height map and
|
||||
copied into your user texture folder so app updates can't overwrite it).
|
||||
|
||||
---
|
||||
|
||||
## Per-layer settings
|
||||
|
||||
| Control | Range / options | What it does |
|
||||
|---------|-----------------|--------------|
|
||||
| **Depth (mm)** | 0.01–10 (log) | Maximum displacement along the surface normal. |
|
||||
| **Tile size (mm)** | 0.2–200 (log) | Physical size of one texture tile on the surface. |
|
||||
| **Rotation** | 0–360° | Rotates the texture on the surface. |
|
||||
| **Midlevel** | 0–10 | The grey level that means "don't move". At 0 the texture only pushes outward; raise it and darker texels cut *inward* (one map both embosses and engraves). 0.5 makes mid-grey neutral. |
|
||||
| **Smoothing** | 0–1 | Blurs the height texture before it displaces — rounds hard edges and removes speckle without needing a softer source image. |
|
||||
| **Edge smoothing** | checkbox + **Edge amount** 0–1 | Fades the relief to flat toward the *edge of the painted area*, so it blends into the surrounding surface. A small amount softens only a thin band at the very edge; the maximum flattens the whole painted face. |
|
||||
| **Invert** | checkbox | Flips the height map (peaks become valleys). |
|
||||
| **Blend** | Add / Subtract / Multiply / Divide | How this layer combines with the layers **below** it where they overlap. Add/Subtract pile relief on or carve it away; Multiply/Divide scale the relief underneath (a mask). The lowest painted layer is the **Base** and always behaves additively. |
|
||||
| **Tile** | checkbox + **Repeat / Mirrored repeat** | When off, the texture is placed once (a decal) instead of repeating. Mirrored repeat flips every other tile to hide seams. |
|
||||
| **Projection** | see below | How the texture is mapped onto the painted surface. |
|
||||
|
||||
> **Midlevel warning:** cutting inward can fold the surface through itself in sharp concave corners or
|
||||
> thin walls. Keep Depth small relative to the feature you're cutting into; the panel warns when a deep
|
||||
> inward setting is risky.
|
||||
|
||||
---
|
||||
|
||||
## Projection methods
|
||||
|
||||
How the 2D texture is wrapped onto the 3D painted area.
|
||||
|
||||
| Method | Best for | Notes |
|
||||
|--------|----------|-------|
|
||||
| **Triplanar (blended)** | Patches wrapping around edges | Projects from all three axes at once and blends, so there's no seam across a sharp edge. |
|
||||
| **Cylindrical** | Round, tube-like selections | Wraps the texture around the patch's own centre/axis. |
|
||||
| **Spherical** | Ball-like selections | Longitude/latitude wrap around the patch centre. |
|
||||
| **Unwrap (LSCM)** | Flat, controlled layout | A real conformal unwrap. Cuts the area into pieces at sharp edges (see **Seam angle**), flattens each, and lets you lay them out by hand in the **UV Editor**. Unlocks Checker/Distortion, seams, and island editing. |
|
||||
| **From view** | Decals / slide-projector look | Projects straight onto the surface from the current camera direction. Use **Capture current view** to re-lay it from wherever you're looking. |
|
||||
|
||||
### LSCM-only controls
|
||||
|
||||
These appear when a layer uses **Unwrap (LSCM)**:
|
||||
|
||||
- **Seam angle** (5–90°) — edges sharper than this are cut so each piece lies flat. Lower cuts more
|
||||
(less stretching, more seams); raise to keep more in one piece. A box's 90° corners are cut by
|
||||
default. *Ignored once you've marked any seam by hand* (your seams then define the pieces).
|
||||
- **Connect islands** (on by default) — lays the unwrap out as a **connected net**: pieces that share
|
||||
an edge are unfolded next to each other (a cube becomes a joined net instead of six loose squares).
|
||||
They stay separate islands, so you can still move any of them by hand. Turn off for the classic
|
||||
packed-grid layout.
|
||||
- **Open UV editor** — shows the flattened unwrap in a side pane (see below). Opens *only* when you
|
||||
turn this on — it never pops up on its own.
|
||||
- **Mark seams** / **Path** / **Clear seams** — see [Seams](#seams).
|
||||
- An **Unwrap: N islands, F faces, V verts** read-out tells you what the unwrap actually produced.
|
||||
|
||||
---
|
||||
|
||||
## The UV Editor
|
||||
|
||||
A dockable 2D pane (enable **Open UV editor** on an LSCM layer) showing the flattened unwrap over the
|
||||
height texture. Islands are the flattened pieces; you can rearrange them freely — nothing re-packs them
|
||||
behind your back. Moving an island updates the model **live** (in Fast view it tracks the cursor
|
||||
smoothly, via a shader uniform — no rebuild until you release).
|
||||
|
||||
### Navigation
|
||||
|
||||
| Action | Control |
|
||||
|--------|---------|
|
||||
| Pan | Middle-drag |
|
||||
| Zoom | Mouse wheel (zooms about the cursor) |
|
||||
| Frame everything | **Home** or **F**, or the **Frame** toolbar button |
|
||||
|
||||
### Editing an island
|
||||
|
||||
| Action | Control |
|
||||
|--------|---------|
|
||||
| Select | Left-click an island |
|
||||
| Move | Left-drag |
|
||||
| Rotate | Right-drag, or press **R** then move the mouse (click/Enter to confirm, Esc to cancel) |
|
||||
| Rotate snapped | Hold **Shift** while rotating — snaps to **global** 15° marks (0/15/30…). A protractor dial with tick marks and the current angle is shown. |
|
||||
| Scale | Press **S** then move the mouse (click/Enter to confirm, Esc to cancel) |
|
||||
| Undo / Redo | **Ctrl+Z** / **Ctrl+Shift+Z** or **Ctrl+Y** |
|
||||
|
||||
The **selected** island gets a bold light-green outline and a brighter wireframe; unselected islands
|
||||
are a translucent light-green wash. The texture underneath repeats exactly as it will when baked.
|
||||
A **status line** along the bottom always names the current gesture and the shortcuts in play.
|
||||
|
||||
### Toolbar
|
||||
|
||||
| Button | Action |
|
||||
|--------|--------|
|
||||
| **Frame** | Frame all islands (same as Home). |
|
||||
| **Snap** | Toggle magnetic snapping — a dragged island sticks its boundary to a neighbour's when they come close. |
|
||||
| **Avg scale** | Give every island the same texel density (Blender's "Average Islands Scale"). |
|
||||
| **Cut** | Split the selected island across its long axis (useful for very long islands). |
|
||||
| **Join** | Unfold the selected island onto its nearest neighbour along their shared edge — keeps both as separate islands with their own borders. |
|
||||
| **Unjoin** | Send the selected island back to its own packed position. |
|
||||
|
||||
> **Checker / Distortion in the UV editor:** selecting those View modes also colours the UV pane — a
|
||||
> checker background, or a per-island distortion heatmap — so you can judge stretch in 2D as well as
|
||||
> on the model.
|
||||
|
||||
---
|
||||
|
||||
## Seams
|
||||
|
||||
Seams are edges the unwrap is forced to cut along, on top of whatever the Seam angle cuts — the
|
||||
Blender "mark seam" workflow. They let you control exactly where the unwrap splits.
|
||||
|
||||
Enable **Mark seams** on an active LSCM layer, then:
|
||||
|
||||
- **Click an edge** on the model to mark it (it turns **red**); click a red edge again to unmark it.
|
||||
The edge under the cursor is highlighted **yellow** so you can see what a click will toggle.
|
||||
- **Path mode** (the **Path** checkbox) — for dense meshes where clicking each edge is tedious: click a
|
||||
start point, then an end point, and the whole **shortest path** between them is seamed at once. It
|
||||
chains (each click extends from the last point); the start vertex is shown in **green**.
|
||||
- **Ctrl+drag** rotates/pans the camera while in seam mode.
|
||||
- **Clear seams** removes them all.
|
||||
|
||||
Once any seam is marked, the automatic Seam-angle cutting is disabled so *your* seams define the
|
||||
islands — pieces you leave un-seamed merge together.
|
||||
|
||||
---
|
||||
|
||||
## Adjust placement (on-model)
|
||||
|
||||
**Adjust placement** (on an active layer) lets you position the texture by dragging a handle on the
|
||||
model instead of nudging the Rotation/offset numbers. The handle is a flat panel in the patch's
|
||||
tangent plane (drag anywhere on it to move freely) plus U/V arrows for single-axis nudges. It's
|
||||
anchored to the painted patch, so paint something first.
|
||||
|
||||
---
|
||||
|
||||
## Preparing the mesh: Subdivide & Remesh
|
||||
|
||||
Displacement can only move vertices that exist, so a coarse model needs more of them first.
|
||||
|
||||
### Subdivide
|
||||
|
||||
Splits every triangle into four, **1–5 times** (each step roughly quadruples the triangle count).
|
||||
|
||||
- **Subdivide steps** (1–5) — how many times to split.
|
||||
- **Preview subdivision** — shows the result as a **cyan wireframe** without changing the model.
|
||||
- **Apply** — commits the subdivision to the geometry.
|
||||
- **Done** — ends the preview and leaves the model as it is.
|
||||
|
||||
### Remesh
|
||||
|
||||
Rebuilds the whole model with triangles close to a target edge length — evens out a mesh with wildly
|
||||
varying triangle sizes (CGAL isotropic remeshing).
|
||||
|
||||
- **Target edge (mm)** — desired triangle edge length (seeded to the model's current average).
|
||||
- **Remesh** — splits the big triangles and merges the small ones to that size.
|
||||
|
||||
> Both Subdivide-Apply and Remesh **replace the geometry** and clear any *not-yet-baked* paint on it
|
||||
> (already-baked relief is kept). If you had the mesh **Wireframe** on before, it stays on afterward.
|
||||
|
||||
---
|
||||
|
||||
## Baking & resetting
|
||||
|
||||
- **Bake** — converts the current preview into real, permanent mesh geometry, restricted to the
|
||||
painted area. Runs in the background; the button shows *Baking…* while it works.
|
||||
- **Erase all** — clears the active layer's paint.
|
||||
|
||||
Baking is the exact same algorithm as the **Normal** preview, so what you see is what you get.
|
||||
|
||||
---
|
||||
|
||||
## Controls reference
|
||||
|
||||
### Mouse — 3D view (while painting)
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Left-drag | Paint the active layer |
|
||||
| Ctrl + drag | Rotate / pan the camera (works in seam mode too) |
|
||||
| Wheel | Zoom |
|
||||
|
||||
### Mouse & keys — UV Editor
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Left-click | Select island |
|
||||
| Left-drag | Move island |
|
||||
| Right-drag | Rotate island |
|
||||
| **R** / **S** | Modal rotate / scale (mouse drives it, click or Enter confirms, Esc cancels) |
|
||||
| **Shift** (while rotating) | Snap to global 15° marks |
|
||||
| Middle-drag | Pan |
|
||||
| Wheel | Zoom about cursor |
|
||||
| **Home** / **F** | Frame all islands |
|
||||
| **Ctrl+Z** / **Ctrl+Shift+Z** / **Ctrl+Y** | Undo / redo |
|
||||
|
||||
### Seam mode
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Click edge | Mark / unmark a seam (yellow = hover, red = marked) |
|
||||
| Click (Path mode) | Set start, then seam the shortest path to the next click |
|
||||
| Ctrl + drag | Rotate / pan camera |
|
||||
|
||||
---
|
||||
|
||||
## Tips & limitations
|
||||
|
||||
- **Paint first, then bake.** The preview is free to explore; only Bake changes the real mesh.
|
||||
- **Not enough detail?** Subdivide or Remesh before painting fine textures.
|
||||
- **Inward cuts** (high Midlevel + big Depth) can self-intersect on thin walls or sharp concave
|
||||
corners — keep Depth modest there.
|
||||
- **Fast vs Normal:** Fast preview only shades the relief and shows only the active layer; use it for quick
|
||||
tuning and smooth UV dragging, but trust **Normal**/**Bake** for the exact result.
|
||||
- **Topology changes drop unbaked paint.** Subdivide-Apply, Remesh, and Simplify replace the mesh, and
|
||||
texture-displacement paint isn't remapped across that change (already-baked relief is unaffected).
|
||||
- **Island placements** are tied to the current unwrap. Re-painting or changing the Seam angle can
|
||||
re-segment the charts and renumber them, so a re-unwrap re-lays the connected net and discards
|
||||
hand placements made before it.
|
||||
- **Connect islands** is on by default; turn it off (per layer) for the classic packed-grid layout, or
|
||||
if an unfold looks wrong on an unusual mesh.
|
||||
@@ -40,6 +40,12 @@ The pattern is phased on fixed positions, so it lines up across layers and
|
||||
across the regions of one layer. Rounding the corners with
|
||||
`sparse_infill_smooth_factor` happens before `multiline_fill()`.
|
||||
|
||||
Each region builds only the rows over its bounding box in that frame, and
|
||||
outlines only the centerlines within `d1 / 2` of it, the ones whose outlines
|
||||
reach it. Every row is monotone along its direction, so each outline is started
|
||||
on the cap at the first end of its centerline, outside the region, and clipping
|
||||
to the region cuts it only where it crosses the boundary.
|
||||
|
||||
## Cubic
|
||||
|
||||
Single-line Cubic draws the three families at the same spacing `h` and shifts
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="7.5" cy="7.5" r="7" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="4" x2="7.5" y2="11" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="4" y1="7.5" x2="11" y2="7.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="7.5" cy="7.5" r="7" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="4" y1="7.5" x2="11" y2="7.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="5.5" y="5.5" width="4" height="4" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><path d="M7.5 0.5v4M7.5 10.5v4M0.5 7.5h4M10.5 7.5h4M5.5 2.5l2-2 2 2M5.5 12.5l2 2 2-2M2.5 5.5l-2 2 2 2M12.5 5.5l2 2-2 2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_group.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="25.28125"
|
||||
inkscape:cy="18.359375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><g
|
||||
id="g31"
|
||||
transform="translate(-1.2926181,-1.328125)"><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30"
|
||||
inkscape:path-effect="#path-effect31"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z" /><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30-4"
|
||||
inkscape:path-effect="#path-effect31-9"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z"
|
||||
transform="translate(11.058594)" /><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30-4-3"
|
||||
inkscape:path-effect="#path-effect31-9-1"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z"
|
||||
transform="translate(0,11.15625)" /></g></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="12.5 0.5 15.5 3.5 10 9 7 6" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M6.5 7C4.7 6.8 3.2 8 2.9 9.9c-.2 1.3-.8 2.2-2.4 3 2.8 1.5 6.6 1 7.5-1.6.5-1.5.3-3.1-1.5-4.3Z" style="fill:#009688"/></svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="0.5" width="7" height="7" style="fill:#009688"/><rect x="7.5" y="7.5" width="7" height="7" style="fill:#009688"/><rect x="0.5" y="0.5" width="14" height="14" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="0.5" x2="7.5" y2="14.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="0.5" y1="7.5" x2="14.5" y2="7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 582 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M7.5 7.5 L14.5 7.5 L11 13.5 Z" style="fill:#009688"/><path d="M7.5 7.5 L11 13.5 L4 13.5 Z" style="fill:#009688"/><path d="M7.5 7.5 L4 13.5 L0.5 7.5 Z" style="fill:#009688"/><polygon points="14.5 7.5 11 13.5 4 13.5 0.5 7.5 4 1.5 11 1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="14.5" y2="7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="11" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="4" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="0.5" y2="7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="4" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="7.5" x2="11" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="2.5" y1="2.5" x2="12.5" y2="12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="12.5" y1="2.5" x2="2.5" y2="12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 332 B |
@@ -0,0 +1,359 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_cut.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="15.375"
|
||||
inkscape:cy="16.140625"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
d="m 16.082031,8.7625 v 22.475 a 0.965625,0.965625 45 0 0 0.965625,0.965625 h 5.85 A 0.965625,0.965625 135 0 0 23.863281,31.2375 V 8.7625 A 0.965625,0.965625 45 0 0 22.897656,7.796875 h -5.85 A 0.965625,0.965625 135 0 0 16.082031,8.7625 Z m 1.325781,-0.032031 h 5.184376 a 0.3257813,0.3257813 45 0 1 0.325781,0.3257813 V 30.94375 a 0.3257813,0.3257813 135 0 1 -0.325781,0.325781 H 17.407812 A 0.3257813,0.3257813 45 0 1 17.082031,30.94375 V 9.0562501 a 0.3257813,0.3257813 135 0 1 0.325781,-0.3257813 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path33"
|
||||
inkscape:path-effect="#path-effect34"
|
||||
inkscape:original-d="m 16.082031,7.796875 v 24.40625 h 7.78125 V 7.796875 Z m 1,0.9335938 h 5.835938 V 31.269531 h -5.835938 z" /><path
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect32"
|
||||
width="1.0000267"
|
||||
height="22.719467"
|
||||
x="27.527292"
|
||||
y="-12.430544"
|
||||
transform="rotate(47.337307)"
|
||||
inkscape:path-effect="#path-effect33"
|
||||
sodipodi:type="rect"
|
||||
d="m 28.027306,-12.430544 c 0.277007,0 0.500013,0.223006 0.500013,0.500013 V 9.7889099 c 0,0.2770071 -0.223006,0.5000131 -0.500013,0.5000131 -0.277008,0 -0.500014,-0.223006 -0.500014,-0.5000131 V -11.930531 c 0,-0.277007 0.223006,-0.500013 0.500014,-0.500013 z"
|
||||
ry="0.50001335" /></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="0.5" width="14" height="14" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="0.5" y1="7.5" x2="14.5" y2="7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="2.5" y1="0.5" x2="2.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="5" y1="0.5" x2="5" y2="14.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="8.5" y1="0.5" x2="8.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 688 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="5.5" cy="3.5" r="1.25" style="fill:#949494"/><circle cx="9.5" cy="3.5" r="1.25" style="fill:#949494"/><circle cx="5.5" cy="7.5" r="1.25" style="fill:#949494"/><circle cx="9.5" cy="7.5" r="1.25" style="fill:#949494"/><circle cx="5.5" cy="11.5" r="1.25" style="fill:#949494"/><circle cx="9.5" cy="11.5" r="1.25" style="fill:#949494"/></svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="0.5 3 6 5.5 11.5 3 6 0.5 0.5 3" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="0.5 3 0.5 9.5 6 12 8 11.1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="11.5 3 11.5 8" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="6" y1="5.5" x2="6" y2="12" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="10" y1="10" x2="15" y2="15" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="15" y1="10" x2="10" y2="15" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="0.5 13.5 7.5 1.5 14.5 13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="4 7.5 11 7.5 7.5 13.5" style="fill:#009688;stroke:#009688;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 311 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="10.5" width="14" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="9 0.5 4.5 5.5 7.5 5.5 6 9 10.5 4 7.5 4" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1,242 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_frame.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="11.6875"
|
||||
inkscape:cy="6.28125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><g
|
||||
id="g5-0"
|
||||
transform="translate(6.4998844,6.0000382)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2"
|
||||
style="fill:#4e2ca6;fill-opacity:1;fill-rule:nonzero" /></g><g
|
||||
id="g5-0-8"
|
||||
transform="matrix(-1,0,0,1,33.44613,6.0000382)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><g
|
||||
id="g5-0-8-2"
|
||||
transform="rotate(180,16.723064,16.999981)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3-0"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5-1"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><g
|
||||
id="g5-0-8-2-4"
|
||||
transform="matrix(1,0,0,-1,6.4998854,33.999963)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3-0-0"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5-1-6"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><path
|
||||
id="path13"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.407813 A 0.490625,0.490625 45 0 0 11.463281,17.5 h 0.0094 a 0.4999428,0.4999428 135 0 0 0.499943,-0.499943 V 15.601562 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.255468 a 0.4984375,0.4984375 135 0 0 0.498438,-0.498437 v -0.0018 A 0.4997516,0.4997516 45 0 0 17.472904,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect15" /><path
|
||||
id="path16"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect17"
|
||||
transform="matrix(-1,0,0,1,39.939926,2.52e-5)" /><path
|
||||
id="path17"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect19"
|
||||
transform="rotate(180,19.972885,20.000096)" /><path
|
||||
id="path19"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect21"
|
||||
transform="matrix(1,0,0,-1,0.0060898,39.99995)" /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,387 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_join.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="18.3125"
|
||||
inkscape:cy="22.328125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
id="rect35"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(0.294922,2.953125)" /><path
|
||||
id="rect35-3"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35-7"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(9.345703,2.953125)" /></svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="5.5" y="5.5" width="4" height="4" style="fill:#009688"/><rect x="5.5" y="1.5" width="4" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="1.5" y="5.5" width="4" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="5.5" y="5.5" width="4" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="9.5" y="5.5" width="4" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="5.5" y="9.5" width="4" height="4" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 755 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="2.5" cy="7.5" r="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="4.3" y1="6.5" x2="11.5" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="4.3" y1="8.5" x2="11.5" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="11.5" y="1.5" width="3" height="12" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="7.5" y1="1.5" x2="7.5" y2="12" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="3.5 8.5 7.5 12.5 11.5 8.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 330 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="7.5" y1="3" x2="7.5" y2="13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="3.5 6.5 7.5 2.5 11.5 6.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polyline points="0.5 7.5 0.5 14.5 14.5 14.5 14.5 7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M0.5 7.5c1.2-3 2.3-3 3.5 0s2.3 3 3.5 0 2.3-3 3.5 0 2.3 3 3.5 0" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 372 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="0.5 3.5 7.5 6.5 7.5 14.5 0.5 11.5 0.5 3.5" style="fill:#009688;fill-opacity:0.55"/><polygon points="7.5 6.5 14.5 3.5 14.5 11.5 7.5 14.5 7.5 6.5" style="fill:#009688;fill-opacity:0.3"/><polygon points="0.5 3.5 7.5 6.5 14.5 3.5 7.5 0.5 0.5 3.5" style="fill:#009688"/><polygon points="0.5 3.5 7.5 6.5 14.5 3.5 7.5 0.5 0.5 3.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="0.5 3.5 0.5 11.5 7.5 14.5 14.5 11.5 14.5 3.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="7.5" y1="6.5" x2="7.5" y2="14.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 769 B |
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_snap.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="14.363106"
|
||||
inkscape:cy="12.827359"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
d="M 20.422985,9.2501548 V 29.968905 H 28.78236 A 1.015625,1.015625 135 0 0 29.797985,28.95328 V 9.2501548 A 1.015625,1.015625 45 0 0 28.78236,8.2345298 h -7.34375 a 1.015625,1.015625 135 0 0 -1.015625,1.015625 z m 1.015625,-0.015625 h 7.34375 V 28.968905 h -7.34375 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path23"
|
||||
inkscape:path-effect="#path-effect24"
|
||||
inkscape:original-d="M 20.422985,8.2345298 V 29.968905 h 9.375 V 8.2345298 Z m 1.015625,1 h 7.34375 V 28.968905 h -7.34375 z" /><path
|
||||
d="m 11.047985,11.324808 v 19.616307 a 1.059034,1.059034 45 0 0 1.059034,1.059034 h 7.256932 a 1.059034,1.059034 135 0 0 1.059034,-1.059034 V 10.265774 h -8.315966 a 1.059034,1.059034 135 0 0 -1.059034,1.059034 z m 1.015625,-0.05903 h 7.34375 v 19.734375 h -7.34375 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path23-8"
|
||||
inkscape:path-effect="#path-effect25"
|
||||
inkscape:original-d="m 11.047985,10.265774 v 21.734375 h 9.375 V 10.265774 Z m 1.015625,1 h 7.34375 v 19.734375 h -7.34375 z" /><path
|
||||
id="use25"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="M 21.439453 8.234375 A 1.015625 1.015625 0 0 0 20.423828 9.25 L 20.423828 10.265625 L 12.107422 10.265625 A 1.059034 1.059034 0 0 0 11.048828 11.324219 L 11.048828 30.941406 A 1.059034 1.059034 0 0 0 12.107422 32 L 19.363281 32 A 1.059034 1.059034 0 0 0 20.423828 30.941406 L 20.423828 29.96875 L 28.783203 29.96875 A 1.015625 1.015625 0 0 0 29.798828 28.953125 L 29.798828 9.25 A 1.015625 1.015625 0 0 0 28.783203 8.234375 L 21.439453 8.234375 z M 21.439453 9.234375 L 28.783203 9.234375 L 28.783203 28.96875 L 21.439453 28.96875 L 21.439453 9.234375 z M 12.064453 11.265625 L 19.408203 11.265625 L 19.408203 31 L 12.064453 31 L 12.064453 11.265625 z " /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="0.5 14.5 1.5 14.5 5.5 7.5 1.5 0.5 0.5 0.5 0.5 14.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="7" width="1" height="3" style="fill:#009688"/><rect x="7" y="4" width="1" height="1" style="fill:#009688"/><rect x="7" y="6" width="1" height="3" style="fill:#009688"/><rect x="7" y="10" width="1" height="1" style="fill:#009688"/><rect x="7" y="12" width="1" height="3" style="fill:#009688"/><polygon points="9.5 14.5 10.5 14.5 14.5 7.5 10.5 0.5 9.5 0.5 9.5 14.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 686 B |
@@ -0,0 +1,387 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_unjoin.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="18.3125"
|
||||
inkscape:cy="22.328125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
id="rect35"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(-1.142612,2.9530855)" /><path
|
||||
id="rect35-3"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35-7"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(10.783203,2.9530855)" /></svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="1" y="5.5" width="4" height="4" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><rect x="6" y="5.5" width="4" height="4" fill="none" stroke="#009688" stroke-linecap="round" stroke-linejoin="round"/><rect x="11" y="5.5" width="4" height="4" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 12.5h12" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1 1.6"/></svg>
|
||||
|
After Width: | Height: | Size: 568 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="1.5" y="1.5" width="13" height="13" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><path d="M1.5 10c2-3 3.5-3 5 0s3 3 5 0c1-1.5 2-1.5 3-.5" fill="none" stroke="#009688" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M4 6.5h6a3.5 3.5 0 0 1 0 7H6" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><polyline points="6.5 4 4 6.5 6.5 9" fill="none" stroke="#009688" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="2.5" y="2.5" width="11" height="11" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><line x1="1" y1="15" x2="15" y2="1" fill="none" stroke="#009688" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M1.5 5V1.5H5M11 1.5h3.5V5M14.5 11v3.5H11M5 14.5H1.5V11" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><rect x="5.5" y="5.5" width="5" height="5" fill="none" stroke="#009688" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="1.5" y="4.5" width="6" height="7" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><rect x="7.5" y="4.5" width="7" height="7" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><line x1="7.5" y1="4.5" x2="7.5" y2="11.5" stroke="#009688" stroke-width="1.8"/></svg>
|
||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polyline points="2.5 12.5 6 8 9.5 9.5 13 3.5" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><circle cx="2.5" cy="12.5" r="1.7" fill="#009688"/><circle cx="13" cy="3.5" r="1.7" fill="#009688"/></svg>
|
||||
|
After Width: | Height: | Size: 314 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="1.5 13.5 7.5 1.5 13.5 13.5" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><line x1="7.5" y1="1.5" x2="7.5" y2="13.5" stroke="#009688" stroke-width="1.6" stroke-linecap="round" stroke-dasharray="2 1.7"/></svg>
|
||||
|
After Width: | Height: | Size: 340 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="1.5 13.5 3.5 2.5 13.5 1.5 12.5 12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="3.5" y1="2.5" x2="12.5" y2="12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="3.5" y1="2.5" x2="13.5" y2="1.5" style="fill:none;stroke:#009688;stroke-width:2;stroke-linecap:round"/></svg>
|
||||
|
After Width: | Height: | Size: 455 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="0.5 1.5 6.5 0.5 7.5 6.5 1.5 7.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="9.5 1.5 14.5 2.5 11.5 6.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="1.5 9.5 5.5 9 6 14.5 0.5 13.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="8.5 8.5 14.5 7.5 13.5 14.5 7.5 13.5" style="fill:#009688;stroke:#009688;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 576 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points="1.5 13.5 3.5 2.5 13.5 1.5 12.5 12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="3.5" y1="2.5" x2="12.5" y2="12.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><circle cx="1.5" cy="13.5" r="1" style="fill:#949494"/><circle cx="12.5" cy="12.5" r="1" style="fill:#949494"/><circle cx="3.5" cy="2.5" r="1" style="fill:#949494"/><circle cx="13.5" cy="1.5" r="1.8" style="fill:#009688"/></svg>
|
||||
|
After Width: | Height: | Size: 564 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3 2v5.5a5 5 0 0 0 10 0V2" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.4 2v5.5a2.6 2.6 0 0 0 5.2 0V2" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 2h2.4M10.6 2H13" stroke="#009688" stroke-width="2" stroke-linecap="round"/></svg>
|
||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="4.5" width="5.5" height="7" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><rect x="10" y="4.5" width="5.5" height="7" fill="none" stroke="#949494" stroke-linecap="round" stroke-linejoin="round"/><line x1="8" y1="3" x2="8" y2="13" stroke="#009688" stroke-dasharray="1.6 1.6"/></svg>
|
||||
|
After Width: | Height: | Size: 413 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="0.5" y="0.5" width="14" height="14" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M0.5 7.5h14M7.5 0.5v14M0.5 0.5l14 14M14.5 0.5l-14 14" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_big_brush.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17" />
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="-13.081475"
|
||||
inkscape:cy="98.994949"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="M 30.109375 6.9648438 A 1.8710937 1.8710937 0 0 0 28.705078 7.5996094 A 1.8710937 1.8710937 0 0 0 28.591797 7.7421875 L 18.103516 20.615234 A 6.1070809 6.1070809 0 0 0 17.226562 20.552734 A 6.1070809 6.1070809 0 0 0 11.513672 24.503906 A 6.1070809 6.1070809 0 0 0 11.121094 26.658203 A 6.1070809 6.1070809 0 0 0 11.121094 26.757812 A 4.4931102 4.4931102 0 0 1 11.15625 27.3125 A 4.4931102 4.4931102 0 0 1 7.7890625 31.662109 A 4.4931102 4.4931102 0 0 1 7.5839844 31.708984 A 0.55609465 0.55242634 0 0 0 7.2558594 32.212891 A 0.55609465 0.55242634 0 0 0 7.8105469 32.765625 A 0.55609465 0.55242634 0 0 0 7.8125 32.765625 L 7.9570312 32.765625 L 17.224609 32.765625 L 17.226562 32.765625 A 6.1070809 6.1070809 0 0 0 23.333984 26.658203 A 6.1070809 6.1070809 0 0 0 22.792969 24.144531 L 31.666016 9.875 A 1.8710937 1.8710937 0 0 1 31.638672 9.9101562 A 1.8710937 1.8710937 0 0 0 31.699219 9.8242188 A 1.8710937 1.8710937 0 0 0 31.980469 8.8359375 A 1.8710937 1.8710937 0 0 0 31.724609 7.890625 A 1.8710937 1.8710937 0 0 0 30.480469 7.0019531 A 1.8710937 1.8710937 0 0 0 30.109375 6.9648438 z M 28.525391 7.8476562 A 1.8710937 1.8710937 0 0 0 28.472656 7.9335938 A 1.8710937 1.8710937 0 0 1 28.478516 7.9199219 A 1.8710937 1.8710937 0 0 1 28.525391 7.8476562 z M 30.109375 7.9570312 A 0.87817276 0.87817276 0 0 1 30.988281 8.8359375 A 0.87817276 0.87817276 0 0 1 30.90625 9.2050781 A 0.87817276 0.87817276 0 0 1 30.791016 9.390625 L 29.974609 10.703125 L 22.230469 23.15625 A 6.1070809 6.1070809 0 0 0 19.183594 20.873047 L 28.388672 9.5703125 L 29.359375 8.3808594 A 0.87817276 0.87817276 0 0 1 29.511719 8.1933594 A 0.87817276 0.87817276 0 0 1 30.109375 7.9570312 z M 28.457031 7.9628906 A 1.8710937 1.8710937 0 0 0 28.373047 8.1425781 A 1.8710937 1.8710937 0 0 1 28.457031 7.9628906 z M 28.300781 8.359375 A 1.8710937 1.8710937 0 0 0 28.263672 8.5507812 A 1.8710937 1.8710937 0 0 1 28.298828 8.3613281 A 1.8710937 1.8710937 0 0 1 28.300781 8.359375 z M 17.226562 21.65625 A 5.0022264 5.0022264 0 0 1 22.230469 26.658203 A 5.0022264 5.0022264 0 0 1 17.226562 31.662109 L 13.724609 31.662109 L 10.199219 31.662109 A 5.6057596 5.6057596 0 0 0 11.822266 29.501953 A 5.6057596 5.6057596 0 0 0 12.267578 27.3125 A 5.0022264 5.0022264 0 0 1 12.224609 26.658203 A 5.0022264 5.0022264 0 0 1 12.224609 26.621094 A 5.0022264 5.0022264 0 0 1 17.226562 21.65625 z " />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_face.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="23.113553"
|
||||
inkscape:cy="18.318485"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers;fill-opacity:1"
|
||||
d="M 19.58431,7.9401469 19.519531,8.0820312 9.1699219,30.736328 a 0.61329334,0.61329334 42.827026 0 0 0.828125,0.767578 l 0,0 20.7832031,-7.320312 0.0019,-6.61e-4 a 0.7874119,0.7874119 107.77086 0 0 0.382891,-1.194652 L 20.714844,8.0449219 20.578195,7.8494958 A 0.57476577,0.57476577 174.78854 0 0 19.58431,7.9401469 Z M 20.232422,9.1015625 30.1875,23.332031 10.576172,30.240234 Z"
|
||||
inkscape:path-effect="#path-effect48"
|
||||
inkscape:original-d="M 20.001953,7.0253906 19.519531,8.0820312 9.1699219,30.736328 8.5917969,32 9.9980469,31.503906 30.78125,24.183594 31.761719,23.837891 31.166016,22.988281 20.714844,8.0449219 Z M 20.232422,9.1015625 30.1875,23.332031 10.576172,30.240234 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_texture_displacement.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17" />
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="27.578125"
|
||||
inkscape:cy="27.671875"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<g
|
||||
id="g17"
|
||||
transform="translate(0.69176086,0.249987)">
|
||||
<path
|
||||
d="m 27.659236,35.176022 h -4.399987 c -0.508996,0 -0.844996,0 -0.954999,-0.394015 a 0.59299998,0.59299998 0 0 1 0.36,-0.669997 2.3,2.3 0 0 0 1.2,-2.188989 3.253,3.253 0 1 1 3.800012,3.205984 z M 24.12125,34.175997 h 2.988 a 2.253,2.253 0 1 0 -2.252999,-2.252976 3.445,3.445 0 0 1 -0.735001,2.252976 z"
|
||||
style="fill:#009688;stroke-width:0.999999"
|
||||
id="path1-6" />
|
||||
<path
|
||||
d="m 30.281245,30.538996 -0.846009,-0.534993 4.364031,-6.899981 A 0.222,0.222 0 0 0 33.439456,22.84588 l -5.4,6.644032 -0.776013,-0.63103 5.400001,-6.643994 a 1.222,1.222 0 0 1 1.981984,1.422992 z"
|
||||
style="fill:#009688;stroke-width:0.999999"
|
||||
id="path2-0" />
|
||||
<path
|
||||
d="m 33.281245,35.250026 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 a 0.49999998,0.49999998 0 0 0 0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.281251,35.250026 H 5.28125 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 5.750019 A 1.5,1.5 0 0 1 5.28125,4.25 h 27.999995 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 5.750019 A 0.49999998,0.49999998 0 0 0 33.281245,5.250025 H 5.28125 A 0.49999998,0.49999998 0 0 0 4.781253,5.750019 V 33.750007 A 0.49999998,0.49999998 0 0 0 5.28125,34.25 h 14.000001 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-1"
|
||||
width="39.379108"
|
||||
height="0.99609375"
|
||||
x="-19.909107"
|
||||
y="27.094706"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-45)" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2"
|
||||
width="27.566595"
|
||||
height="0.99609375"
|
||||
x="7.9031763"
|
||||
y="-0.27849278"
|
||||
ry="0.49487707"
|
||||
transform="rotate(45)" />
|
||||
<g
|
||||
id="g7"
|
||||
transform="matrix(3.7795276,0,0,3.7795276,-350.69182,-427.25001)">
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2"
|
||||
width="2.8453603"
|
||||
height="0.2635498"
|
||||
x="150.08304"
|
||||
y="13.289762"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2"
|
||||
width="1.7498204"
|
||||
height="0.2635498"
|
||||
x="12.763216"
|
||||
y="-149.44461"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2"
|
||||
width="0.82876241"
|
||||
height="0.27337581"
|
||||
x="13.654455"
|
||||
y="-148.49495"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6"
|
||||
width="2.5927844"
|
||||
height="0.25224927"
|
||||
x="150.38367"
|
||||
y="12.286384"
|
||||
ry="0.12612464"
|
||||
transform="rotate(45)"
|
||||
rx="0.12612464" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1"
|
||||
width="1.9647908"
|
||||
height="0.2635498"
|
||||
x="151.01166"
|
||||
y="11.21043"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-8"
|
||||
width="10.754118"
|
||||
height="0.99609375"
|
||||
x="-10.211728"
|
||||
y="-31.661213"
|
||||
ry="0.49804688"
|
||||
transform="rotate(135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-9"
|
||||
width="6.6134944"
|
||||
height="0.99609375"
|
||||
x="-33.794979"
|
||||
y="12.679931"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2-3"
|
||||
width="3.1323304"
|
||||
height="1.0332314"
|
||||
x="-30.282875"
|
||||
y="16.213961"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<g
|
||||
id="g7-1"
|
||||
transform="matrix(0,-3.7795276,3.7795276,0,-427.55829,389.70233)">
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-9"
|
||||
width="2.8453603"
|
||||
height="0.2635498"
|
||||
x="150.08304"
|
||||
y="13.289762"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-8"
|
||||
width="1.7498204"
|
||||
height="0.2635498"
|
||||
x="12.763216"
|
||||
y="-149.44461"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2-6"
|
||||
width="0.82876241"
|
||||
height="0.27337581"
|
||||
x="13.654455"
|
||||
y="-148.49495"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-5"
|
||||
width="2.5927844"
|
||||
height="0.25224927"
|
||||
x="150.38367"
|
||||
y="12.286384"
|
||||
ry="0.12612464"
|
||||
transform="rotate(45)"
|
||||
rx="0.12612464" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1-0"
|
||||
width="1.9647908"
|
||||
height="0.2635498"
|
||||
x="151.01166"
|
||||
y="11.21043"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-9-4"
|
||||
width="9.0822706"
|
||||
height="0.99609375"
|
||||
x="-36.379971"
|
||||
y="-4.5927153"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-5-5"
|
||||
width="9.7995005"
|
||||
height="0.95338309"
|
||||
x="-36.915619"
|
||||
y="-8.385005"
|
||||
ry="0.47669154"
|
||||
transform="rotate(-135)"
|
||||
rx="0.47669154" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1-0-0"
|
||||
width="7.425981"
|
||||
height="0.99609375"
|
||||
x="-34.54211"
|
||||
y="-12.451604"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -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,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",
|
||||
|
||||