Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258bbee999 | ||
|
|
46b3871f8d | ||
|
|
84768da6a1 | ||
|
|
bb0d8ee1c4 | ||
|
|
f32591ed64 | ||
|
|
db9917e722 | ||
|
|
cb752d38a3 | ||
|
|
5bce937d07 | ||
|
|
de3af0b7de | ||
|
|
5a427f2eb6 | ||
|
|
008ee792f6 | ||
|
|
1591423f91 | ||
|
|
7131dd30e7 | ||
|
|
84feeaa587 | ||
|
|
5c8a287b5e | ||
|
|
a0a4a33c72 | ||
|
|
02b0d2b614 | ||
|
|
0dd4b98604 | ||
|
|
a338f6ca7d | ||
|
|
e821665e4b | ||
|
|
b77b5afd49 | ||
|
|
f546551b3f | ||
|
|
ab42ddeef2 | ||
|
|
39c77325ce | ||
|
|
af29e4d553 | ||
|
|
df392508b7 | ||
|
|
11493a2236 | ||
|
|
df4c8cd531 | ||
|
|
bf5a378479 | ||
|
|
01499e536e | ||
|
|
f33a9bf999 | ||
|
|
869e450981 | ||
|
|
b920116fbd | ||
|
|
237586675f | ||
|
|
6d35ff6b9a | ||
|
|
075b03975f | ||
|
|
a379ec4a67 | ||
|
|
6700d4c5df | ||
|
|
a28c9c3a82 | ||
|
|
3dd547c8be | ||
|
|
f0e9cdc470 | ||
|
|
58a71331d0 | ||
|
|
f127b758ed | ||
|
|
1494de66de | ||
|
|
3ca182a98e | ||
|
|
5e19fc0e6c | ||
|
|
3f4709aaa7 | ||
|
|
c63bd5d474 | ||
|
|
6263502697 | ||
|
|
1a9201690d | ||
|
|
c6a338f35f | ||
|
|
4f2d2ae11a | ||
|
|
c57b5dc6c5 | ||
|
|
7149728219 | ||
|
|
f49d49b5a6 | ||
|
|
08f2037cbd | ||
|
|
1abd6649c7 | ||
|
|
d8cae3268a | ||
|
|
7d4ec3494c | ||
|
|
cccf53850e | ||
|
|
5095733323 | ||
|
|
0cec10748e | ||
|
|
c55f9c2251 | ||
|
|
3f78959a0c | ||
|
|
d2675f15d1 | ||
|
|
5371c85362 | ||
|
|
30e48183e3 | ||
|
|
a243941b72 | ||
|
|
6a1a5d51e9 | ||
|
|
e625845b34 | ||
|
|
01b340dee1 | ||
|
|
4c2a63e11e | ||
|
|
56b559d36a | ||
|
|
ed1f3e52c9 | ||
|
|
f8cf34c55b | ||
|
|
d007134001 | ||
|
|
00d11a7ba0 | ||
|
|
70f518d87a | ||
|
|
f4bde2da0a | ||
|
|
d5a11eb076 | ||
|
|
461275d9bb | ||
|
|
95d3e8eafc | ||
|
|
52eeb0f228 | ||
|
|
8145b0792f | ||
|
|
2ccd73759f | ||
|
|
cab7a7d75f | ||
|
|
bd125d32da | ||
|
|
546b9c621d | ||
|
|
8ecfee6a21 | ||
|
|
fbf4652e12 | ||
|
|
be9ff50130 | ||
|
|
dee1f1715f | ||
|
|
fcb1f62bd9 | ||
|
|
bea412fddf | ||
|
|
391c53a8da | ||
|
|
a13089d357 | ||
|
|
fb6c93963d | ||
|
|
7688f7a16c |
@@ -0,0 +1,220 @@
|
||||
---
|
||||
name: orca-translations
|
||||
description: Use when updating, filling, reviewing or fixing OrcaSlicer translations in localization/i18n — running scripts/run_gettext --full, translating empty or new msgids in the .po catalogs, AI translation passes and their "# AI Translated" / "# Needs human review" comments, plural forms, msgctxt homonyms, and keeping catalogs consistent with the localization glossary (OrcaSlicer_WIKI localization_glossary.tsv), including adding new glossary terms. Also use for msgfmt or run_gettext failures, placeholder or line-break mismatches, and reviewing a translation PR against the glossary.
|
||||
---
|
||||
|
||||
# OrcaSlicer translations
|
||||
|
||||
The UI strings live in gettext catalogs: `localization/i18n/OrcaSlicer.pot` is the template, and
|
||||
`localization/i18n/<lang>/OrcaSlicer_<lang>.po` holds each language. The `en` catalog is empty on
|
||||
purpose, so skip it. Recurring terms are fixed by the
|
||||
[localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md),
|
||||
whose source is `developer_reference/localization_glossary.tsv` in the **OrcaSlicer_WIKI** repository.
|
||||
A translation pass therefore touches two repositories: the catalogs here, and the glossary there.
|
||||
|
||||
All catalog editing goes through [`scripts/po_tool.py`](scripts/po_tool.py) (standard library only).
|
||||
It lists empty entries with the glossary terms they contain and validates each translation. It writes
|
||||
only the entries it changes, in the layout `msgmerge --no-wrap` produces, and adds the AI comments.
|
||||
Run commands from the repository root, with `python3` (`python` or `py -3` on Windows):
|
||||
|
||||
```bash
|
||||
T=.claude/skills/orca-translations/scripts/po_tool.py
|
||||
python3 $T --help
|
||||
```
|
||||
|
||||
Read [translation-rules.md](references/translation-rules.md) before translating. It holds the
|
||||
mechanical rules, how to pick words, style, and when to flag an entry for review. To find hard-coded
|
||||
English, strings that need a plural form or a context, or files missing from the extraction list, read
|
||||
[source-audit.md](references/source-audit.md); that work changes C++ and comes before a translation pass.
|
||||
|
||||
## Workflow: fill every empty translation
|
||||
|
||||
Unless the user asks for something narrower, a translation pass means the steps below, in order.
|
||||
|
||||
### 1. Find the glossary
|
||||
|
||||
The tool looks for an `OrcaSlicer_WIKI` clone in `--wiki`, then `$ORCA_WIKI_DIR`, then next to this
|
||||
repository.
|
||||
- **A clone is found:** `git -C <wiki> fetch` and make sure it is on an up-to-date `main`. The tool
|
||||
notes when it reads another branch, or a clone that is behind.
|
||||
- **No clone:** the tool reads the published glossary read-only. That is enough to translate, but not
|
||||
to add terms (see step 3).
|
||||
|
||||
### 2. Run the full gettext
|
||||
|
||||
This comes first, always. It regenerates the template from the sources and merges it into every
|
||||
catalog:
|
||||
|
||||
```bash
|
||||
./scripts/run_gettext.sh --full # Linux/macOS: needs gettext and python3 on PATH
|
||||
scripts\run_gettext.bat --full # Windows (PowerShell: & .\scripts\run_gettext.bat --full)
|
||||
```
|
||||
|
||||
It must exit 0. New msgids arrive empty, and removed ones become obsolete `#~` entries. A msgid that
|
||||
comes back gets its old translation revived from `#~`, which is one reason never to purge obsolete
|
||||
entries. It also compiles the `.mo` files (gitignored). The bundled Windows gettext may re-wrap a long
|
||||
`#:` reference line. That churn is harmless.
|
||||
|
||||
Then take stock: `python3 $T status`.
|
||||
|
||||
### 3. Settle new glossary terms before translating anything
|
||||
|
||||
Terms are decided once, for every language, so all the catalogs agree.
|
||||
|
||||
1. `python3 $T candidates` lists recurring phrases in the empty entries that the glossary lacks. Read it
|
||||
together with the new msgids themselves.
|
||||
2. Pick the real terms: feature and mode names, labels that tooltips repeat, and anything translators
|
||||
would otherwise render in several ways.
|
||||
3. Brand names and acronyms belong in the glossary page's hand-written *Kept in English* table instead.
|
||||
4. Skip ordinary words.
|
||||
5. For each term, choose a rendering per language: check `lookup` for existing usage, and follow each
|
||||
language's conventions. Then record it:
|
||||
|
||||
```bash
|
||||
python3 $T glossary-set --term "Belt printer" \
|
||||
--description "One sentence saying what it is, and what it is not." \
|
||||
--set de=... --set es=... --set pt_BR=...
|
||||
```
|
||||
|
||||
The command adds or updates the row, keeps the TSV's BOM and line endings, and regenerates the page.
|
||||
- **An established cell:** it refuses to change it without `--replace`. Changing an established term
|
||||
also means fixing every catalog entry that uses it, so do that only on request.
|
||||
- **An empty cell:** fill it the same way when a pending string establishes the term for that
|
||||
language.
|
||||
|
||||
**No clone, and a new term is needed: stop.** Tell the user the glossary must be updated with these
|
||||
terms, and list each one with its description and proposed renderings (`glossary-set` prints them too).
|
||||
Ask them to clone `https://github.com/OrcaSlicer/OrcaSlicer_WIKI` next to this repository, or to give
|
||||
its path. Translate nothing that uses those terms until they answer.
|
||||
|
||||
### 4. Translate, language by language, in batches
|
||||
|
||||
For each language:
|
||||
|
||||
1. Read its glossary column and notes once: `python3 $T glossary --lang <L>`.
|
||||
2. Loop over the empty entries:
|
||||
|
||||
```bash
|
||||
python3 $T pending --lang <L> --limit 60 --out <scratch>/<L>.json
|
||||
# write <scratch>/<L>-tr.json: [{"id": "...", "msgstr": "..."}, ...], "msgstr_plural" for plurals,
|
||||
# plus "review": "<what to check>" where unsure
|
||||
python3 $T apply --lang <L> --input <scratch>/<L>-tr.json
|
||||
```
|
||||
|
||||
- **Batch files go in a scratch directory, not the repository.**
|
||||
- **Each entry's `id` identifies it,** so the translation file need not repeat the English. `apply`
|
||||
also accepts `msgid` with `msgctxt`, or the pending file itself with its `msgstr` fields filled.
|
||||
- **Translate every entry in the batch.** A blank `msgstr` is skipped, not cleared.
|
||||
- **Next batch:** applied entries leave the list, so run `pending` again at offset 0. Use `--offset`
|
||||
only to step past entries you deliberately leave blank.
|
||||
- **What each entry carries:**
|
||||
- `glossary`: the terms it contains, with the language's rendering.
|
||||
- `note`: the developer's `TRN` comment.
|
||||
- `flags`: `c-format` or `boost-format`.
|
||||
- `description`: present when a sense has to be chosen.
|
||||
3. Read every apply message:
|
||||
- **REJECTED:** a hard rule broke. Fix the translation and apply the corrected items again.
|
||||
- **WARNING:** something worth a second look, such as a glossary rendering not found, ending
|
||||
punctuation, or snake_case. A glossary warning on an inflected form you checked is fine.
|
||||
Otherwise correct the translation (`apply --overwrite` replaces your own entry), or add a
|
||||
`review` reason.
|
||||
|
||||
`apply` adds `# AI Translated` to every entry it writes, and `# Needs human review: <reason>` where an
|
||||
item has `review`. On an `--overwrite`, `"review": ""` removes an earlier note. Never add either to a translation a person wrote. `--human` is for applying their
|
||||
text.
|
||||
|
||||
**The work is large:** every new string, times every language. When subagents are available, give each
|
||||
one a language or a few. Each agent only touches its own catalogs, so they can run
|
||||
in parallel. Settle step 3 first. Give each agent the language list, this skill's path, the rules file,
|
||||
and these instructions:
|
||||
- Fill every empty entry.
|
||||
- Don't add glossary rows.
|
||||
- Report what it could not translate, the review notes it added, and any term it thinks the glossary
|
||||
still lacks.
|
||||
|
||||
Add the terms they report yourself, then have the affected languages revisit the entries.
|
||||
|
||||
### 5. Verify
|
||||
|
||||
```bash
|
||||
python3 $T check --lang <L> --warnings # every AI-translated entry, plus msgfmt --check-format
|
||||
```
|
||||
|
||||
- **Run the full gettext again.** It must exit 0 and leave the catalogs as the pass left them.
|
||||
- **Run `python generate_glossary.py --check` in the wiki clone.**
|
||||
- **Errors on entries you didn't write predate the pass.** Report them rather than fixing them in the
|
||||
same change.
|
||||
|
||||
### 6. Report
|
||||
|
||||
Report:
|
||||
- the entries filled, per language;
|
||||
- the review notes added;
|
||||
- anything left empty, and why;
|
||||
- the glossary rows added or filled.
|
||||
|
||||
The catalogs (and `OrcaSlicer.pot`) and the glossary (`localization_glossary.tsv` and `.md`) are
|
||||
separate changes in separate repositories. The wiki change should land with, or before, the catalog
|
||||
change. Commit or open PRs only when asked.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Run the full gettext first.** Translate against the regenerated template, never a stale one.
|
||||
2. **The glossary is binding.** Use the language's rendering for each term, in the right sense. When a
|
||||
rendering looks wrong, use it and flag the entry. Then raise the change with the user, rather than
|
||||
deviating in one string.
|
||||
3. **New recurring terms go into the glossary before the translations that use them.** Without a wiki
|
||||
clone, stop and tell the user (step 3).
|
||||
4. **Fill, don't rewrite.** A fill pass writes only empty and fuzzy entries.
|
||||
- Existing translations, human or AI, change only on request (`--overwrite`).
|
||||
- A glossary violation in a human translation is reported, not changed.
|
||||
5. **Machine output is labelled.** Every AI translation gets `# AI Translated`; uncertain ones also get
|
||||
`# Needs human review: <reason>`. Never use `fuzzy`.
|
||||
6. **Never edit `msgid`, `msgctxt`, the header, or obsolete `#~` entries,** and never fix the English in
|
||||
a translation. Report source problems (see the rules file).
|
||||
7. **Write catalogs only through `po_tool.py` or gettext.** PO libraries such as polib reflow unrelated
|
||||
entries, and hand edits skip the validation.
|
||||
|
||||
## Changing an established term
|
||||
|
||||
A glossary cell is usually copied from the catalog's own label. When the label is wrong, the glossary
|
||||
inherits the mistake, so fix both in the same change:
|
||||
|
||||
1. **Check the catalog's usage first.** `lookup --lang <L> "<term>"` searches the English, and with
|
||||
`--msgstr` it searches the translations.
|
||||
- When most entries already use the correct form, move the cell to it.
|
||||
- When the wrong form is used consistently throughout, leave both as they are and ask a
|
||||
translator.
|
||||
2. **Update the cell:** `glossary-set --term "<Term>" --replace --set <L>=<value>`.
|
||||
3. **Rewrite the entries that still use the old form,** only where the term has that meaning.
|
||||
- Write `[{"id": "<id from lookup>", "msgstr": "<full corrected text>"}]` and run
|
||||
`apply --overwrite`.
|
||||
- The rewritten entries are marked `# AI Translated`.
|
||||
|
||||
## Reviewing a translation change
|
||||
|
||||
Review it for these:
|
||||
- glossary compliance (`check --scope all --warnings` on the language, then read the hits);
|
||||
- placeholders and line breaks (`check` errors);
|
||||
- `# AI Translated` kept on text a person rewrote, or added to text a person wrote;
|
||||
- purged obsolete entries;
|
||||
- reflowed unrelated entries;
|
||||
- plural forms short of `nplurals`.
|
||||
|
||||
When a reviewer disputes a term, compare it with the glossary first. A machine translation that follows
|
||||
the glossary is correct even if the term itself is debatable. Then the fix is a glossary change, made
|
||||
with the translators, followed by the catalogs.
|
||||
|
||||
## Source of truth
|
||||
|
||||
When this skill and the checkouts disagree, the checkouts win:
|
||||
- `scripts/run_gettext.sh` / `.bat` for extraction and merging;
|
||||
- `localization/i18n/list.txt` for the scanned sources;
|
||||
- the wiki's `localization_guide.md` and `localization_glossary.md` / `.tsv` for the rules and terms;
|
||||
- `AGENTS.md` (Localization & translations) for the repository's conventions.
|
||||
|
||||
## Editing this skill
|
||||
|
||||
State rules and procedures, not the state of the catalogs: no counts, lists of untranslated strings or
|
||||
per-language defects. Language-specific terminology belongs in the glossary's *Language specifics*
|
||||
section, not here.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Auditing the source for translatable text
|
||||
|
||||
Use this when asked to find hard-coded English, strings that need a context, or strings that need plural
|
||||
forms. It changes C++ and the `.pot`, so it runs before a translation pass and is followed by one.
|
||||
|
||||
## How a string reaches translators
|
||||
|
||||
- `xgettext` reads only the files named in `localization/i18n/list.txt`, and only the macros
|
||||
`L`, `_L`, `_u8L`, `L_CONTEXT`, `_L_CONTEXT`, `_u8L_CONTEXT` and `_L_PLURAL`.
|
||||
- A file that marks strings but is missing from `list.txt` is silently never extracted. Find such
|
||||
files by comparing `grep -l` for the macros with the list.
|
||||
- `_()`, `_utf8()` and `wxGetTranslation()` do not extract. Calls that rely on them need an `L()`
|
||||
or `_L()` somewhere xgettext sees.
|
||||
- **GUI code:** `_L` gives a `wxString`, `_u8L` a UTF-8 `std::string` (use it for ImGui). `L` only
|
||||
marks the string; the display site must translate it.
|
||||
- **libslic3r:** the macro is whatever the file defines. Some files `#define L(s)
|
||||
Slic3r::I18N::translate(s)`, so `L()` translates at runtime there. Others, such as `PrintConfig.cpp`,
|
||||
define it as a no-op marker, and the GUI translates the text later. Read the top of the file before
|
||||
judging a `format(L(...))` call. libslic3r has no plural or context lookup at runtime.
|
||||
- The libslic3r translate callback is installed in `GUI_App::on_init_inner`. Text produced before it
|
||||
is installed stays in English.
|
||||
|
||||
## What to look for
|
||||
|
||||
1. **Visible English with no macro:** labels, tooltips, menu items, dialog text, notifications and
|
||||
ImGui text. `scripts/find_untranslated.py OUT_DIR` lists candidates; most are noise, so trace each
|
||||
one to the place it is shown.
|
||||
2. **Sentences built from translated pieces:** `_L("A") + name + _L(" is B")`, or a translated word
|
||||
spliced into another sentence. Translators cannot reorder or inflect these. Replace each with one
|
||||
msgid with placeholders. A spliced fragment with a leading or trailing space is the usual sign.
|
||||
3. **Counts next to words that must agree:** `(s)`, "%d objects", a verb that follows a number. Use
|
||||
`_L_PLURAL(singular, plural, n)`. If the singular and plural are identical on purpose, translators
|
||||
still inflect each form.
|
||||
4. **One English word with several meanings:** a short msgid used in files with different meanings
|
||||
(a verb in one place, a noun in another, a mode name, a CAD term). Generate locations with
|
||||
`xgettext` *without* `--no-location` and list short msgids that occur in two or more files.
|
||||
5. **Code that compares translated text with English,** or decides behaviour from a message text.
|
||||
Translation breaks it. Compare ids or flags instead.
|
||||
6. **Tests that quote message text:** `grep` the tests for a message before rewording it.
|
||||
|
||||
## What not to translate
|
||||
|
||||
Keys, config values and identifiers; log and debug output; assertion and check messages; developer-only
|
||||
UI behind debug switches; undo/redo snapshot names (the history is never shown); text returned to a
|
||||
plugin's code; HTML, JS and desktop-file content in string literals; brand, printer-model and format
|
||||
names.
|
||||
|
||||
## Adding a context
|
||||
|
||||
- Keep the most common sense without a context, so its existing translations stay valid. Give the
|
||||
other senses a short, readable context.
|
||||
- A context cannot be carried through code that translates generically: enum labels that
|
||||
`Field.cpp` translates with `_()`, tables translated by a loop, and anything in libslic3r. Leave that
|
||||
sense on the plain entry and give the contexts to the other uses.
|
||||
|
||||
## After the edits
|
||||
|
||||
1. Build and run the tests (`libslic3r_tests` in particular).
|
||||
2. Run `run_gettext --full`. Reworded strings become new empty entries and the old ones become
|
||||
obsolete, which keeps their translations as a memory. Strings that gain a plural form leave a fuzzy
|
||||
entry.
|
||||
3. Run the translation pass from SKILL.md. `po_tool.py pending` suggests the closest earlier translation
|
||||
for each entry.
|
||||
4. Fix English typos *before* the translation pass; fixing one afterwards orphans every translation.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Translation rules
|
||||
|
||||
The rules a translation must follow, condensed from the wiki's
|
||||
[Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_guide.md),
|
||||
the [glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md)
|
||||
and the localization section of `AGENTS.md`. `po_tool.py apply` rejects the mechanical violations it
|
||||
can detect; everything else is on the translator.
|
||||
|
||||
## Mechanical: enforced by `apply`
|
||||
|
||||
A rejected entry is not written. Fix it and apply again.
|
||||
|
||||
- **Placeholders stay exactly as written:** `%s`, `%d`, `%.1f`, `%1$d`, `%1%`, `%%`. Never `%1 %`, never
|
||||
`%` for `%%`. Plain printf placeholders keep their order; only positional ones (`%1$s`, `%1%`) may move.
|
||||
A broken placeholder crashes the app.
|
||||
- **Line breaks:** the same number of `\n`, with the same leading and trailing ones. Don't add breaks to fit
|
||||
text, least of all in parameter names.
|
||||
- **Leading and trailing spaces** are kept. A leading space usually means the string is appended to another.
|
||||
- **HTML tags** are kept, and the text between them is translated: `Click <a>here</a> to install it.`
|
||||
- **`℃`** (U+2103) stays one character, written exactly as in the source: `%d℃`, never `°C`, `° C` or
|
||||
`%d ℃`.
|
||||
- **Tokens stay verbatim:** `{placeholders}`, `[macros]`, G-code commands (`M141`, `G28`). Setting keys
|
||||
in snake_case (`use_relative_e_distances`) are flagged as warnings, because a few snake_case msgids
|
||||
are really labels (`top_front`). Keep a key unless the string uses it as a display name.
|
||||
- **Plural entries** need every form from the catalog's `Plural-Forms` header (`nplurals`: 1 for
|
||||
ja/ko/zh/th/vi, 3 for ru/cs/pl/lt, 4 for uk). Each form is really inflected for its quantity. Repeating
|
||||
one sentence is a bug in Slavic and Baltic languages, but correct in Turkish and Hungarian.
|
||||
|
||||
## Mechanical: not detectable, still required
|
||||
|
||||
- Edit only `msgstr`. Never change `msgid`, `msgctxt`, the header or another entry's text.
|
||||
- **Ending punctuation follows the source:** no added period, and no dropped one. `apply` warns.
|
||||
- Units follow the SI convention of the language: `s`, not `sec`. A parameter description holds no
|
||||
unit, because the field shows it.
|
||||
- **A button or short label holds one translation:** no alternatives in parentheses. They widen the
|
||||
control.
|
||||
- Don't use `fuzzy` for uncertainty. Fuzzy entries are hidden, and users then see English.
|
||||
|
||||
## Choosing the words
|
||||
|
||||
- **The glossary is binding.** Use the language's rendering for every glossary term the `pending`
|
||||
output lists, inflected as the sentence needs.
|
||||
- **Rows that share a base term are different senses:** `Seam` (the print seam) versus `Seam (UV)`
|
||||
(the unwrap cut), and `Instance` versus `Instance (application)`. Pick by the description.
|
||||
- **A qualified row may be a false match.** `Feature (Design)` does not apply to "features of the
|
||||
app".
|
||||
- **A cell marked ⚠ (or holding two renderings) is unsettled.** Use the form the catalog already
|
||||
uses for that sense (`lookup`), and flag the entry for review.
|
||||
- **Kept in English:** brand and product names (OrcaSlicer, Bambu Lab, Orca Cloud, AMS, Micro-Lidar),
|
||||
acronyms (FDM, LAN, HTTPS), file formats, material codes (PLA, PETG-CF), G-code, macros and variables
|
||||
stay as written. In "Adaptive Pressure Advance", only "Pressure Advance" stays English. See the
|
||||
glossary page's *Kept in English* table.
|
||||
- **Language specifics** on the glossary page (`po_tool.py glossary --lang L` prints them) override
|
||||
general habits for that language.
|
||||
- **Consistency beyond the glossary:** before translating a label, or a tooltip that names a setting,
|
||||
run `lookup` for the setting name. Quote the label's existing translation exactly, so the tooltip and
|
||||
the field match. Reuse obsolete (`#~`) entries as a translation memory.
|
||||
- **`msgctxt` separates homonyms.** `Back` in *Camera View* is the rear view, and `Back` in
|
||||
*Navigation* is the go-back button. Translate each context on its own.
|
||||
- **Overloaded English: translate the meaning.**
|
||||
- *Flow ratio* is a multiplier, *Flow Rate* is throughput, and *Flow Dynamics* is pressure
|
||||
compensation. These are three different terms.
|
||||
- *Extruder* can mean the toolhead, the feeder motor ("High extruder current on filament swap") or
|
||||
the nozzle ("all extruders must have the same diameter").
|
||||
- When a short label is ambiguous, grep `src/` for the msgid to see the widget it labels.
|
||||
- **Boilerplate stays uniform.** `Failed to connect to …`, `Are you sure you want to …?` and
|
||||
`If expressed as a %, it will be computed over …` each get one template in the language, even where
|
||||
the English varies.
|
||||
|
||||
## Style
|
||||
|
||||
- **Localize, don't translate word-for-word.** Know what the string controls before translating it.
|
||||
- **Simplify developer phrasing** when the meaning survives. *"This option determines whether
|
||||
OrcaSlicer should apply the retract reduction on infill"* reads better as *"Disable retractions while
|
||||
printing infill."* Introduce nuances in following sentences, not in one packed clause.
|
||||
- **Don't expand.** A machine translation adds no facts the English doesn't state. Expanding
|
||||
descriptions is for human translators, who can confirm the extra content.
|
||||
- **Write for both audiences:** professionals and people with a printer at home. Keep the established
|
||||
terminology, used as plainly as the string allows.
|
||||
|
||||
## When to add a review note
|
||||
|
||||
Add `"review": "<what to check>"` to an item when the translation is probable but unconfirmed. It
|
||||
becomes `# Needs human review: …`, and the entry still ships. Typical reasons:
|
||||
|
||||
- The English is ambiguous out of context: "Tile" as a verb or a noun.
|
||||
- A short label's role only shows in the app.
|
||||
- Two established terms compete, or the glossary cell is unsettled.
|
||||
- The string's grammar depends on a value substituted at runtime: `%zu %s` counts with no plural entry.
|
||||
|
||||
Say exactly what to look at and where: *"Row name in the Design tab tool-offer menu; check it reads
|
||||
right for the whole row."*
|
||||
|
||||
## Problems that belong in the source, not the translation
|
||||
|
||||
Report these; don't work around them in a catalog.
|
||||
|
||||
- **The English is wrong.** Never "fix" it in the translation only.
|
||||
- **One msgid needs two translations.** The source needs `_L_CONTEXT` / `_u8L_CONTEXT` (a code change).
|
||||
- **A literal `%` in a string xgettext flagged `possible-c-format`** fails `msgfmt`. The fix is a
|
||||
`// xgettext:no-c-format, no-boost-format` comment above the string in the source, not `%%` in the
|
||||
translation.
|
||||
@@ -0,0 +1,113 @@
|
||||
"""List C++ string literals that look like user-facing English but are not marked for translation.
|
||||
|
||||
Usage: find_untranslated.py OUT_DIR (one hits file per source area; each line is file:line: [call] "text" | code)
|
||||
Works on whole files, so literals continuing a translation macro call from an earlier line are
|
||||
recognised. Skips comments, log/assert/check calls, includes, printer model lists and markup."""
|
||||
import re, sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[4]
|
||||
OUT = Path(sys.argv[1]); OUT.mkdir(parents=True, exist_ok=True)
|
||||
MACROS = {"_L", "_u8L", "L", "_L_CONTEXT", "_u8L_CONTEXT", "L_CONTEXT", "_L_PLURAL", "_devL", "_omitL",
|
||||
"_CHB", "_utf8", "_", "translate", "translate_utf8", "wxGetTranslation", "T"}
|
||||
NOISE_CALLS = {"BOOST_LOG_TRIVIAL", "wxCHECK", "wxCHECK_MSG", "wxCHECK_RET", "wxASSERT", "wxASSERT_MSG", "wxFAIL_MSG",
|
||||
"assert", "static_assert", "printf", "fprintf", "sprintf", "snprintf", "wxLogDebug", "wxLogTrace",
|
||||
"wxLogMessage", "wxLogError", "wxLogWarning", "OutputDebugStringA", "OutputDebugStringW", "DEBUG_LOG",
|
||||
"glsafe", "GLShaderProgram", "find", "count", "contains", "starts_with", "ends_with", "get_value",
|
||||
"has", "opt_bool", "opt_int", "opt_float", "opt_string", "option", "get", "set_key_value", "at",
|
||||
"boost_log", "LOGD", "LOGE", "LOGI", "LOGW", "SPDLOG", "CLI_LOG"}
|
||||
LIT = re.compile(r'"((?:[^"\\\n]|\\.)*)"')
|
||||
WORD = re.compile(r"[A-Za-z]{2,}")
|
||||
|
||||
def strip_comments(text):
|
||||
# Replace comments with spaces, keeping offsets and string literals intact.
|
||||
out, i, n = [], 0, len(text)
|
||||
while i < n:
|
||||
c = text[i]
|
||||
if c == '"' or c == "'":
|
||||
j = i + 1
|
||||
while j < n and text[j] != c and text[j] != "\n":
|
||||
j += 2 if text[j] == "\\" else 1
|
||||
out.append(text[i:j + 1]); i = j + 1
|
||||
elif text.startswith("//", i):
|
||||
j = text.find("\n", i); j = n if j < 0 else j
|
||||
out.append(" " * (j - i)); i = j
|
||||
elif text.startswith("/*", i):
|
||||
j = text.find("*/", i + 2); j = n if j < 0 else j + 2
|
||||
out.append(re.sub(r"[^\n]", " ", text[i:j])); i = j
|
||||
else:
|
||||
out.append(c); i += 1
|
||||
return "".join(out)
|
||||
|
||||
def enclosing_call(text, pos):
|
||||
depth = 0
|
||||
i = pos - 1
|
||||
while i >= 0:
|
||||
c = text[i]
|
||||
if c == '"': # skip over a literal backwards
|
||||
i -= 1
|
||||
while i >= 0 and not (text[i] == '"' and text[i - 1] != "\\"):
|
||||
i -= 1
|
||||
elif c in ")]}":
|
||||
depth += 1
|
||||
elif c in "([{":
|
||||
if depth == 0:
|
||||
if c != "(":
|
||||
return None
|
||||
m = re.search(r"([A-Za-z_][\w:]*)\s*$", text[max(0, i - 80):i])
|
||||
return m.group(1).split("::")[-1] if m else None
|
||||
depth -= 1
|
||||
elif c == ";" and depth == 0:
|
||||
return None
|
||||
i -= 1
|
||||
return None
|
||||
|
||||
def looks_user_facing(s):
|
||||
if len(WORD.findall(s)) < 2 and not re.fullmatch(r"[A-Z][a-z]{2,}[.!?:]?", s):
|
||||
return False
|
||||
if " " not in s and re.fullmatch(r"[\w./\\:#%-]+", s):
|
||||
return False
|
||||
if re.search(r"https?://|<\w+[^>]*>|\.(png|svg|json|ini|3mf|stl|gcode|bbl|js|html|css|txt|xml|zip)\b|=\s*\w+;|^\s*[-\w]+\s*:\s*[\w#]", s):
|
||||
return False
|
||||
if re.fullmatch(r"[a-z0-9_ ]+", s) and "_" in s:
|
||||
return False
|
||||
return bool(re.search(r"[A-Za-z]{2,} [a-z]{2,}|^[A-Z][a-z]{2,}", s))
|
||||
|
||||
groups = {}
|
||||
for path in sorted((REPO / "src").rglob("*.[ch]pp")):
|
||||
rel = path.relative_to(REPO).as_posix()
|
||||
if "/dev-utils/" in rel or "/libvgcode/" in rel or rel.startswith("src/libnest2d") or "/tests/" in rel:
|
||||
continue
|
||||
raw = path.read_text(encoding="utf-8", errors="replace")
|
||||
text = strip_comments(raw)
|
||||
line_starts = [0] + [m.end() for m in re.finditer("\n", text)]
|
||||
lines = raw.splitlines()
|
||||
hits = []
|
||||
for m in LIT.finditer(text):
|
||||
s = m.group(1)
|
||||
if not looks_user_facing(s):
|
||||
continue
|
||||
call = enclosing_call(text, m.start())
|
||||
if call in MACROS or call in NOISE_CALLS:
|
||||
continue
|
||||
ln = next(k for k in range(len(line_starts) - 1, -1, -1) if line_starts[k] <= m.start()) + 1
|
||||
src = lines[ln - 1].strip()
|
||||
if re.search(r"BOOST_LOG|#\s*include|<<\s*\"|\"\s*<<|std::cerr|std::cout", src):
|
||||
continue
|
||||
# printer model / vendor tables
|
||||
if len(LIT.findall(src)) >= 4 and not re.search(r"_L|_u8L", src):
|
||||
continue
|
||||
hits.append(f"{rel}:{ln}: [{call or '-'}] \"{s[:150]}\" | {src[:170]}")
|
||||
if hits:
|
||||
parts = rel.split("/")
|
||||
if parts[1] == "slic3r":
|
||||
key = f"GUI/{parts[3] if len(parts) > 4 else 'root'}" if parts[2] == "GUI" else f"slic3r/{parts[2]}"
|
||||
else:
|
||||
key = parts[1].replace(".cpp", "")
|
||||
groups.setdefault(key, []).extend(hits)
|
||||
total = 0
|
||||
for key, hits in sorted(groups.items()):
|
||||
(OUT / (key.replace("/", "_") + ".txt")).write_text("\n".join(hits) + "\n", encoding="utf-8")
|
||||
total += len(hits)
|
||||
print(f"{len(hits):5} {key}")
|
||||
print("total", total)
|
||||
@@ -0,0 +1,870 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Translation helper for the OrcaSlicer gettext catalogs and the wiki glossary.
|
||||
|
||||
Standard library only. Edits a catalog in place, rewriting just the entries it
|
||||
changes in the layout `msgmerge --no-wrap` writes, so every other byte stays put.
|
||||
|
||||
status [--lang L ...] per-catalog counts
|
||||
pending --lang L [--offset N --limit N] [--out FILE] [--no-include-fuzzy] (adds similar earlier translations)
|
||||
apply --lang L --input FILE [--overwrite] [--dry-run] adds "# AI Translated"
|
||||
check [--lang L ...] [--scope ai|all] [--warnings]
|
||||
lookup --lang L PATTERN [--msgstr] existing translations with ids, obsolete ones included
|
||||
glossary --lang L [TERM] the language's glossary column and notes
|
||||
candidates [--min-count N] [--limit N] recurring untranslated phrases not in the glossary
|
||||
glossary-set --term T [--description D] --set L=VALUE ... [--replace]
|
||||
|
||||
The glossary is read from an OrcaSlicer_WIKI clone (--wiki, $ORCA_WIKI_DIR, or a
|
||||
sibling of this repository). Without one it is downloaded read-only, and
|
||||
glossary-set refuses to run.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unicodedata
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[4]
|
||||
I18N = REPO / "localization" / "i18n"
|
||||
SKIP_LANGS = {"en"} # the English catalog is intentionally empty
|
||||
GLOSSARY_TSV = Path("developer_reference") / "localization_glossary.tsv"
|
||||
GLOSSARY_MD = Path("developer_reference") / "localization_glossary.md"
|
||||
MISSING = {"", "—"}
|
||||
RAW_URL = "https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer_WIKI/main/"
|
||||
AI_TAG = "AI Translated"
|
||||
REVIEW_TAG = "Needs human review:"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PO parsing and writing
|
||||
|
||||
_UNESCAPE = {"n": "\n", "t": "\t", "r": "\r", "a": "\a", "b": "\b", "f": "\f", "v": "\v", '"': '"', "\\": "\\"}
|
||||
_ESCAPE = {v: "\\" + k for k, v in _UNESCAPE.items()}
|
||||
|
||||
|
||||
def unescape(s):
|
||||
return re.sub(r'\\(.)', lambda m: _UNESCAPE.get(m.group(1), m.group(0)), s)
|
||||
|
||||
|
||||
def escape(s):
|
||||
return "".join(_ESCAPE.get(c, c) for c in s)
|
||||
|
||||
|
||||
def format_field(keyword, value, prefix=""):
|
||||
"""Lines for `keyword "value"` as msgmerge --no-wrap writes them."""
|
||||
parts = value.split("\n")
|
||||
segments = [p + "\n" for p in parts[:-1]] + ([parts[-1]] if parts[-1] else [])
|
||||
if len(segments) <= 1:
|
||||
return [f'{prefix}{keyword} "{escape(value)}"']
|
||||
return [f'{prefix}{keyword} ""'] + [f'{prefix}"{escape(s)}"' for s in segments]
|
||||
|
||||
|
||||
class Entry:
|
||||
"""One blank-line-delimited block of a PO file."""
|
||||
|
||||
def __init__(self, lines, first_line):
|
||||
self.lines = lines
|
||||
self.first_line = first_line # 0-based index in the file
|
||||
self.obsolete = all(l.startswith("#~") for l in lines if not l.startswith("#") or l.startswith("#~"))
|
||||
self.tcomments, self.xcomments, self.flags = [], [], []
|
||||
self.fields = {} # keyword -> [value, start, end)
|
||||
self.order = []
|
||||
current = None
|
||||
for i, raw in enumerate(lines):
|
||||
line = raw
|
||||
if self.obsolete and line.startswith("#~"):
|
||||
line = line[2:].lstrip(" ")
|
||||
if line.startswith("|"):
|
||||
current = None
|
||||
continue
|
||||
elif line.startswith("#"):
|
||||
current = None
|
||||
if line.startswith("#."):
|
||||
self.xcomments.append(line[2:].strip())
|
||||
elif line.startswith("#,"):
|
||||
self.flags += [f.strip() for f in line[2:].split(",") if f.strip()]
|
||||
elif line == "#" or line.startswith("# "):
|
||||
self.tcomments.append(line[2:])
|
||||
continue
|
||||
m = re.match(r'^(msgctxt|msgid_plural|msgid|msgstr(?:\[\d+\])?)\s+"(.*)"\s*$', line)
|
||||
if m:
|
||||
current = m.group(1)
|
||||
self.fields[current] = [unescape(m.group(2)), i, i + 1]
|
||||
self.order.append(current)
|
||||
elif line.startswith('"') and current:
|
||||
self.fields[current][0] += unescape(line.strip()[1:-1])
|
||||
self.fields[current][2] = i + 1
|
||||
|
||||
def get(self, key, default=None):
|
||||
f = self.fields.get(key)
|
||||
return f[0] if f else default
|
||||
|
||||
@property
|
||||
def msgctxt(self):
|
||||
return self.get("msgctxt")
|
||||
|
||||
@property
|
||||
def msgid(self):
|
||||
return self.get("msgid", "")
|
||||
|
||||
@property
|
||||
def msgid_plural(self):
|
||||
return self.get("msgid_plural")
|
||||
|
||||
@property
|
||||
def is_header(self):
|
||||
return self.msgid == "" and "msgid" in self.fields and not self.msgctxt
|
||||
|
||||
def msgstrs(self):
|
||||
if self.msgid_plural is None:
|
||||
return [self.get("msgstr", "")]
|
||||
idx = sorted(int(k[7:-1]) for k in self.fields if k.startswith("msgstr["))
|
||||
return [self.get(f"msgstr[{i}]", "") for i in range(max(idx) + 1)] if idx else []
|
||||
|
||||
@property
|
||||
def fuzzy(self):
|
||||
return "fuzzy" in self.flags
|
||||
|
||||
@property
|
||||
def ai(self):
|
||||
return any(c.strip() == AI_TAG for c in self.tcomments)
|
||||
|
||||
@property
|
||||
def review(self):
|
||||
return [c for c in self.tcomments if c.startswith(REVIEW_TAG)]
|
||||
|
||||
def key(self):
|
||||
return (self.msgctxt, self.msgid)
|
||||
|
||||
def id(self):
|
||||
"""Short stable handle, so a translation file need not repeat the English."""
|
||||
return hashlib.sha1(f"{self.msgctxt or ''}\x04{self.msgid}".encode("utf-8")).hexdigest()[:10]
|
||||
|
||||
def rewrite(self, msgstrs, ai=True, review=None):
|
||||
"""Replace the translation; add the AI and review comments; clear fuzzy."""
|
||||
lines = list(self.lines)
|
||||
# translations
|
||||
keys = [k for k in self.order if k.startswith("msgstr")]
|
||||
start = min(self.fields[k][1] for k in keys)
|
||||
end = max(self.fields[k][2] for k in keys)
|
||||
if self.msgid_plural is None:
|
||||
new = format_field("msgstr", msgstrs[0])
|
||||
else:
|
||||
new = [l for i, s in enumerate(msgstrs) for l in format_field(f"msgstr[{i}]", s)]
|
||||
lines[start:end] = new
|
||||
# flags and previous-msgid lines of a fuzzy entry
|
||||
out = []
|
||||
for line in lines:
|
||||
if line.startswith("#,"):
|
||||
flags = [f.strip() for f in line[2:].split(",") if f.strip() and f.strip() != "fuzzy"]
|
||||
if flags:
|
||||
out.append("#, " + ", ".join(flags))
|
||||
continue
|
||||
if line.startswith("#|") and self.fuzzy:
|
||||
continue
|
||||
out.append(line)
|
||||
lines = out
|
||||
# translator comments go first, before #. #: #,
|
||||
pos = 0
|
||||
while pos < len(lines) and (lines[pos] == "#" or lines[pos].startswith("# ")):
|
||||
pos += 1
|
||||
add = []
|
||||
if review is not None: # "" clears an earlier note, a reason replaces it
|
||||
lines = [l for l in lines if not l.startswith("# " + REVIEW_TAG)]
|
||||
pos = 0
|
||||
while pos < len(lines) and (lines[pos] == "#" or lines[pos].startswith("# ")):
|
||||
pos += 1
|
||||
if ai and not any(l == "# " + AI_TAG for l in lines[:pos]):
|
||||
add.append("# " + AI_TAG)
|
||||
if review:
|
||||
add.append(f"# {REVIEW_TAG} {' '.join(review.split())}")
|
||||
lines[pos:pos] = add
|
||||
return lines
|
||||
|
||||
|
||||
class Catalog:
|
||||
def __init__(self, path):
|
||||
self.path = Path(path)
|
||||
data = self.path.read_bytes()
|
||||
self.bom = data.startswith(b"\xef\xbb\xbf")
|
||||
text = data.decode("utf-8-sig")
|
||||
self.newline = "\r\n" if "\r\n" in text else "\n"
|
||||
self.trailing = text.endswith(("\n", "\r"))
|
||||
self.lines = text.splitlines()
|
||||
self.entries = []
|
||||
block, first = [], 0
|
||||
for i, line in enumerate(self.lines + [""]):
|
||||
if line.strip() == "":
|
||||
if block:
|
||||
self.entries.append(Entry(block, first))
|
||||
block = []
|
||||
else:
|
||||
if not block:
|
||||
first = i
|
||||
block.append(line)
|
||||
self.header = next((e for e in self.entries if e.is_header), None)
|
||||
m = re.search(r"nplurals\s*=\s*(\d+)", self.header.msgstrs()[0] if self.header else "")
|
||||
self.nplurals = int(m.group(1)) if m else 2
|
||||
self.changes = {} # entry index -> new lines
|
||||
|
||||
def active(self):
|
||||
return [e for e in self.entries if not e.obsolete and not e.is_header]
|
||||
|
||||
def index(self):
|
||||
return {e.key(): i for i, e in enumerate(self.entries) if not e.obsolete and not e.is_header}
|
||||
|
||||
def save(self):
|
||||
lines = list(self.lines)
|
||||
for i in sorted(self.changes, reverse=True):
|
||||
e = self.entries[i]
|
||||
lines[e.first_line:e.first_line + len(e.lines)] = self.changes[i]
|
||||
text = self.newline.join(lines) + (self.newline if self.trailing else "")
|
||||
self.path.write_bytes((b"\xef\xbb\xbf" if self.bom else b"") + text.encode("utf-8"))
|
||||
|
||||
|
||||
def needs_translation(e, nplurals, include_fuzzy=False):
|
||||
strs = e.msgstrs()
|
||||
if e.msgid_plural is not None and len(strs) < nplurals:
|
||||
return True
|
||||
return any(s == "" for s in strs) or (include_fuzzy and e.fuzzy)
|
||||
|
||||
|
||||
def catalog_path(lang):
|
||||
p = I18N / lang / f"OrcaSlicer_{lang}.po"
|
||||
if not p.exists():
|
||||
sys.exit(f"no catalog for '{lang}': {p}")
|
||||
return p
|
||||
|
||||
|
||||
def all_langs():
|
||||
return sorted(d.name for d in I18N.iterdir() if (d / f"OrcaSlicer_{d.name}.po").exists() and d.name not in SKIP_LANGS)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Glossary
|
||||
|
||||
def find_wiki(arg):
|
||||
for cand in [arg, os.environ.get("ORCA_WIKI_DIR"), REPO.parent / "OrcaSlicer_WIKI", REPO.parent / "OrcaSlicer.wiki"]:
|
||||
if cand and (Path(cand) / GLOSSARY_TSV).exists():
|
||||
return Path(cand)
|
||||
return None
|
||||
|
||||
|
||||
def _download(rel):
|
||||
cache = Path(tempfile.gettempdir()) / ("orca_glossary_" + rel.name)
|
||||
if not cache.exists() or time.time() - cache.stat().st_mtime > 3600:
|
||||
with urllib.request.urlopen(RAW_URL + rel.as_posix(), timeout=30) as r:
|
||||
cache.write_bytes(r.read())
|
||||
return cache
|
||||
|
||||
|
||||
class Glossary:
|
||||
def __init__(self, wiki_arg=None, quiet=False):
|
||||
self.wiki = find_wiki(wiki_arg)
|
||||
if self.wiki:
|
||||
self.tsv, self.md = self.wiki / GLOSSARY_TSV, self.wiki / GLOSSARY_MD
|
||||
if not quiet:
|
||||
self.report_clone_state()
|
||||
else:
|
||||
if not quiet:
|
||||
print("note: no OrcaSlicer_WIKI clone found; using the published glossary read-only", file=sys.stderr)
|
||||
self.tsv, self.md = _download(GLOSSARY_TSV), _download(GLOSSARY_MD)
|
||||
self.load()
|
||||
|
||||
def report_clone_state(self):
|
||||
git = lambda *args: subprocess.run(["git", "-C", str(self.wiki), *args], capture_output=True, text=True).stdout.strip()
|
||||
try:
|
||||
branch = git("rev-parse", "--abbrev-ref", "HEAD")
|
||||
behind = git("rev-list", "--count", "HEAD..origin/main")
|
||||
except OSError:
|
||||
return
|
||||
if branch and branch != "main":
|
||||
print(f"note: glossary read from wiki branch '{branch}', not main", file=sys.stderr)
|
||||
if behind and behind != "0":
|
||||
print(f"note: the wiki clone is {behind} commits behind origin/main (as of its last fetch)", file=sys.stderr)
|
||||
|
||||
def load(self):
|
||||
rows = [l.split("\t") for l in self.tsv.read_text(encoding="utf-8-sig").splitlines() if l.strip()]
|
||||
self.header = rows[0]
|
||||
# "—" marks a language with no established rendering yet
|
||||
self.rows = [[c if c.strip() not in MISSING else "" for c in r] + [""] * (len(rows[0]) - len(r)) for r in rows[1:]]
|
||||
self.matchers = []
|
||||
for row in self.rows:
|
||||
base = re.sub(r"\s*\([^)]*\)\s*$", "", row[0]).strip()
|
||||
alts = [a.strip() for a in base.split(" / ") if a.strip()]
|
||||
pats = [re.compile(r"(?<![\w-])" + r"[\s-]+".join(map(re.escape, a.split())) + r"(?:s|es)?(?![\w-])", re.I) for a in alts]
|
||||
self.matchers.append((row, pats))
|
||||
|
||||
def column(self, lang):
|
||||
return self.header.index(lang) if lang in self.header else None
|
||||
|
||||
def matches(self, text):
|
||||
"""Glossary rows found in text, without terms only seen inside a longer match."""
|
||||
spans = []
|
||||
for row, pats in self.matchers:
|
||||
for p in pats:
|
||||
spans += [(m.start(), m.end(), row) for m in p.finditer(text)]
|
||||
keep = []
|
||||
for row in {id(r): r for _, _, r in spans}.values():
|
||||
mine = [(s, e) for s, e, r in spans if r is row]
|
||||
if any(not any(s2 <= s and e <= e2 and (e2 - s2) > (e - s) for s2, e2, r2 in spans if r2 is not row) for s, e in mine):
|
||||
keep.append(row)
|
||||
return keep
|
||||
|
||||
def hints(self, text, lang):
|
||||
col = self.column(lang)
|
||||
return [{"term": r[0], "translation": (r[col] if col is not None else "") or None, "description": r[1]} for r in self.matches(text)]
|
||||
|
||||
def language_notes(self, lang):
|
||||
text = self.md.read_text(encoding="utf-8-sig")
|
||||
m = re.search(r"^### [^\n]*\(" + re.escape(lang) + r"\)\s*$(.*?)(?=^##|\Z)", text, re.M | re.S)
|
||||
return m.group(0).strip() if m else None
|
||||
|
||||
|
||||
def _norm(s):
|
||||
s = unicodedata.normalize("NFD", s.casefold())
|
||||
return "".join(c for c in s if unicodedata.category(c) != "Mn")
|
||||
|
||||
|
||||
def glossary_ok(msgstr, translation):
|
||||
"""Loose check that a glossary rendering appears in msgstr, tolerating inflection."""
|
||||
target = _norm(msgstr)
|
||||
for alt in re.split(r"\s*/\s*", re.sub(r"\([^)]*\)|⚠", "", translation)):
|
||||
words = [w for w in re.findall(r"\w+", _norm(alt)) if len(w) > 2] or re.findall(r"\w+", _norm(alt))
|
||||
if not words:
|
||||
return True
|
||||
if all(w in target or (len(w) > 4 and w[:max(4, len(w) * 2 // 3)] in target) for w in words):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Validation
|
||||
|
||||
C_FMT = re.compile(r"%(?:(\d+)\$)?[-+ #0']*(?:\d+|\*)?(?:\.(?:\d+|\*))?(?:hh|h|ll|l|L|q|j|z|Z|t)?[diouxXeEfFgGaAcspn]")
|
||||
BOOST_FMT = re.compile(r"%\d+%")
|
||||
TAG = re.compile(r"</?[A-Za-z][^<>]*>")
|
||||
SNAKE = re.compile(r"[a-z][a-z0-9]*(?:_[a-z0-9]+)+")
|
||||
IDENT = re.compile(r"\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b|\b[GM]\d{1,4}\b|\{[A-Za-z_][\w.\[\]]*\}|\[[a-z_]+\]")
|
||||
END = re.compile(r"[.:?!。:?!]$")
|
||||
|
||||
|
||||
def _placeholders(s, flags):
|
||||
s = s.replace("%%", "")
|
||||
boost = BOOST_FMT.findall(s)
|
||||
s2 = BOOST_FMT.sub("", s) if "boost-format" in flags or boost else s
|
||||
c = [m.group(0) for m in C_FMT.finditer(s2)] if "no-c-format" not in flags else []
|
||||
return boost, c
|
||||
|
||||
|
||||
def validate(e, msgstrs, nplurals, glossary=None, lang=None):
|
||||
"""Return (errors, warnings) for a proposed translation of entry e."""
|
||||
errors, warnings = [], []
|
||||
flags = set(e.flags)
|
||||
if e.msgid_plural is not None and len(msgstrs) != nplurals:
|
||||
errors.append(f"plural entry needs {nplurals} forms, got {len(msgstrs)}")
|
||||
for i, s in enumerate(msgstrs):
|
||||
src = e.msgid if (e.msgid_plural is None or (i == 0 and nplurals > 1)) else e.msgid_plural
|
||||
tag = f"[{i}] " if e.msgid_plural is not None else ""
|
||||
if not s:
|
||||
errors.append(f"{tag}empty translation")
|
||||
continue
|
||||
b1, c1 = _placeholders(src, flags)
|
||||
b2, c2 = _placeholders(s, flags)
|
||||
positional = any("$" in p for p in c1)
|
||||
c_bad = sorted(c1) != sorted(c2) if positional else c1 != c2
|
||||
bad = []
|
||||
if sorted(b1) != sorted(b2):
|
||||
bad.append(f"boost placeholders {b1} -> {b2}")
|
||||
if c_bad:
|
||||
bad.append(f"printf placeholders {c1} -> {c2}" + ("" if positional else " (order must match)"))
|
||||
if bad:
|
||||
strict = flags & {"c-format", "boost-format"} and not (e.msgid_plural is not None and i == 0 and nplurals > 1 and not c2 and not b2)
|
||||
(errors if strict else warnings).extend(tag + b for b in bad)
|
||||
if s.count("%%") != src.count("%%") and "c-format" in flags:
|
||||
errors.append(f"{tag}'%%' count differs")
|
||||
if src.count("\n") != s.count("\n"):
|
||||
errors.append(f"{tag}line breaks: {src.count(chr(10))} in source, {s.count(chr(10))} in translation")
|
||||
else:
|
||||
lead = lambda t: len(t) - len(t.lstrip("\n"))
|
||||
trail = lambda t: len(t) - len(t.rstrip("\n"))
|
||||
if lead(src) != lead(s) or trail(src) != trail(s):
|
||||
errors.append(f"{tag}leading/trailing line breaks differ")
|
||||
ls = lambda t: re.match(r"[ \t]*", t.strip("\n")).group(0)
|
||||
ts = lambda t: re.search(r"[ \t]*$", t.strip("\n")).group(0)
|
||||
if ls(src) != ls(s) or ts(src) != ts(s):
|
||||
errors.append(f"{tag}leading/trailing spaces differ")
|
||||
if sorted(TAG.findall(src)) != sorted(TAG.findall(s)):
|
||||
errors.append(f"{tag}HTML tags {TAG.findall(src)} -> {TAG.findall(s)}")
|
||||
if src.count("℃") != s.count("℃"):
|
||||
errors.append(f"{tag}'℃' count differs (keep U+2103, not °C)")
|
||||
missing = [t for t in set(IDENT.findall(src)) if t not in s]
|
||||
tokens = [t for t in missing if not SNAKE.fullmatch(t)]
|
||||
if tokens:
|
||||
errors.append(f"{tag}tokens must stay verbatim: {tokens}")
|
||||
if len(tokens) < len(missing): # snake_case is usually a setting key, sometimes a label
|
||||
warnings.append(f"{tag}snake_case kept? {[t for t in missing if SNAKE.fullmatch(t)]}")
|
||||
e1, e2 = END.search(src.rstrip()), END.search(s.rstrip())
|
||||
if bool(e1) != bool(e2) and lang != "th": # Thai has no sentence-final period
|
||||
warnings.append(f"{tag}ending punctuation: source {'ends with ' + repr(e1.group(0)) if e1 else 'has none'}, translation {'ends with ' + repr(e2.group(0)) if e2 else 'has none'}")
|
||||
if s == src and len(src.split()) >= 3:
|
||||
warnings.append(f"{tag}identical to the English")
|
||||
if glossary and lang:
|
||||
# Rows sharing a base term ("Seam", "Seam (UV)") are competing senses; one is enough.
|
||||
# A match on qualified rows only ("Feature (Design)") may be another sense entirely.
|
||||
for base, senses in group_senses(glossary.hints(src, lang)).items():
|
||||
if all(h["term"] != base for h in senses) or not strong_match(src, base):
|
||||
continue
|
||||
if not any(glossary_ok(s, h["translation"]) for h in senses):
|
||||
wanted = " or ".join(f"'{h['translation']}' ({h['term']})" for h in senses)
|
||||
warnings.append(f"{tag}glossary: expected {wanted}")
|
||||
return errors, warnings
|
||||
|
||||
|
||||
def strong_match(src, base):
|
||||
"""Whether a term is surely meant as the glossary term, not as everyday English
|
||||
("each part of a frame"): a multi-word term, a short label, or capitalised mid-sentence."""
|
||||
if any(" " in alt for alt in base.split(" / ")) or len(src.split()) <= 5:
|
||||
return True
|
||||
for alt in base.split(" / "):
|
||||
for m in re.finditer(r"(?<![\w-])" + re.escape(alt) + r"(?:s|es)?(?![\w-])", src, re.I):
|
||||
if m.group(0)[0].isupper() and not re.search(r"(^|[.!?:]\s+|\n)$", src[:m.start()]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def base_term(term):
|
||||
return re.sub(r"\s*\([^)]*\)\s*$", "", term).strip()
|
||||
|
||||
|
||||
def group_senses(hints):
|
||||
groups = {}
|
||||
for h in hints:
|
||||
if h["translation"]:
|
||||
groups.setdefault(base_term(h["term"]), []).append(h)
|
||||
return groups
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commands
|
||||
|
||||
def out_json(obj, path):
|
||||
text = json.dumps(obj, ensure_ascii=False, indent=1)
|
||||
if path:
|
||||
Path(path).write_text(text + "\n", encoding="utf-8")
|
||||
print(f"wrote {path}")
|
||||
else:
|
||||
sys.stdout.write(text + "\n")
|
||||
|
||||
|
||||
def cmd_status(a):
|
||||
print(f"{'lang':7} {'active':>7} {'empty':>6} {'plural':>6} {'fuzzy':>6} {'AI':>6} {'review':>6}")
|
||||
for lang in a.lang or all_langs():
|
||||
c = Catalog(catalog_path(lang))
|
||||
act = c.active()
|
||||
empty = sum(1 for e in act if needs_translation(e, c.nplurals) and e.msgid_plural is None)
|
||||
plural = sum(1 for e in act if needs_translation(e, c.nplurals) and e.msgid_plural is not None)
|
||||
print(f"{lang:7} {len(act):7} {empty:6} {plural:6} {sum(e.fuzzy for e in act):6} {sum(e.ai for e in act):6} {sum(bool(e.review) for e in act):6}")
|
||||
|
||||
|
||||
def similar_entries(c, e, pool, cutoff=0.72):
|
||||
"""The closest earlier translation of a similar English text: obsolete entries and translated
|
||||
active ones. A reworded or split string usually still has one, and reusing it keeps the wording."""
|
||||
import difflib
|
||||
sm = difflib.SequenceMatcher(autojunk=False)
|
||||
sm.set_seq2(e.msgid)
|
||||
best = []
|
||||
for other, msgstr, obsolete in pool:
|
||||
sm.set_seq1(other)
|
||||
if sm.real_quick_ratio() < cutoff or sm.quick_ratio() < cutoff:
|
||||
continue
|
||||
r = sm.ratio()
|
||||
if r >= cutoff and other != e.msgid:
|
||||
best.append((r, other, msgstr, obsolete))
|
||||
best.sort(key=lambda t: -t[0])
|
||||
return [{"msgid": o, "msgstr": s, "obsolete": ob, "similarity": round(r, 2)} for r, o, s, ob in best[:2]]
|
||||
|
||||
|
||||
def cmd_pending(a):
|
||||
c = Catalog(catalog_path(a.lang))
|
||||
g = Glossary(a.wiki)
|
||||
todo = [e for e in c.active() if needs_translation(e, c.nplurals, a.include_fuzzy)]
|
||||
batch = todo[a.offset:a.offset + a.limit] if a.limit else todo[a.offset:]
|
||||
pool = [(x.msgid, x.msgstrs()[0], x.obsolete) for x in c.entries
|
||||
if not x.is_header and x.msgstrs() and x.msgstrs()[0] and not x.fuzzy and 8 < len(x.msgid) < 600]
|
||||
items = []
|
||||
for e in batch:
|
||||
item = {"id": e.id(), "msgctxt": e.msgctxt, "msgid": e.msgid}
|
||||
if e.msgid_plural is not None:
|
||||
item["msgid_plural"] = e.msgid_plural
|
||||
item["msgstr_plural"] = (e.msgstrs() + [""] * c.nplurals)[:c.nplurals]
|
||||
else:
|
||||
item["msgstr"] = e.msgstrs()[0]
|
||||
if e.xcomments:
|
||||
item["note"] = " ".join(e.xcomments)
|
||||
flags = [f for f in e.flags if f != "fuzzy"]
|
||||
if flags:
|
||||
item["flags"] = flags
|
||||
if e.fuzzy:
|
||||
item["fuzzy"] = True
|
||||
if e.msgstrs()[0]:
|
||||
item["previous_translation"] = e.msgstrs()[0]
|
||||
if len(e.msgid) > 8:
|
||||
sim = similar_entries(c, e, pool)
|
||||
if sim:
|
||||
item["similar"] = sim
|
||||
hints = g.hints(e.msgid + "\n" + (e.msgid_plural or ""), a.lang)
|
||||
if hints:
|
||||
# Descriptions only where the sense must be chosen or no rendering exists yet.
|
||||
bases = [base_term(h["term"]) for h in hints]
|
||||
item["glossary"] = [{k: v for k, v in h.items() if k != "description" or not h["translation"] or bases.count(b) > 1 or b != h["term"]}
|
||||
for h, b in zip(hints, bases)]
|
||||
items.append({k: v for k, v in item.items() if v is not None})
|
||||
out_json({"lang": a.lang, "nplurals": c.nplurals, "plural_forms": re.search(r"Plural-Forms: ([^\n]*)", c.header.msgstrs()[0]).group(1) if c.header else None,
|
||||
"total_pending": len(todo), "offset": a.offset, "count": len(items), "entries": items}, a.out)
|
||||
|
||||
|
||||
def cmd_apply(a):
|
||||
c = Catalog(catalog_path(a.lang))
|
||||
g = None if a.no_glossary else Glossary(a.wiki, quiet=True)
|
||||
data = json.loads(Path(a.input).read_text(encoding="utf-8"))
|
||||
items = data["entries"] if isinstance(data, dict) else data
|
||||
idx = c.index()
|
||||
by_id = {c.entries[i].id(): i for i in idx.values()}
|
||||
applied, skipped, rejected, warned = 0, 0, [], []
|
||||
for it in items:
|
||||
if "msgid" in it:
|
||||
key = (it.get("msgctxt"), it["msgid"])
|
||||
i = idx.get(key)
|
||||
label = (f"[{key[0]}] " if key[0] else "") + key[1][:70].replace("\n", "\\n")
|
||||
else:
|
||||
i = by_id.get(it.get("id"))
|
||||
label = f"id {it.get('id')}" if i is None else c.entries[i].msgid[:70].replace("\n", "\\n")
|
||||
if i is None:
|
||||
rejected.append((label, ["no such entry in the catalog (give its id, or msgid and msgctxt)"]))
|
||||
continue
|
||||
e = c.entries[i]
|
||||
if e.msgid_plural is not None and "msgstr_plural" not in it:
|
||||
rejected.append((label, [f"plural entry: give msgstr_plural with {c.nplurals} forms"]))
|
||||
continue
|
||||
strs = it["msgstr_plural"] if e.msgid_plural is not None else [it.get("msgstr", "")]
|
||||
if not strs or all(not s for s in strs):
|
||||
skipped += 1
|
||||
continue
|
||||
if not a.overwrite and not needs_translation(e, c.nplurals, include_fuzzy=True):
|
||||
rejected.append((label, ["already translated (pass --overwrite to replace)"]))
|
||||
continue
|
||||
errors, warnings = validate(e, strs, c.nplurals, g, a.lang)
|
||||
if errors:
|
||||
rejected.append((label, errors))
|
||||
continue
|
||||
if warnings:
|
||||
warned.append((label, warnings))
|
||||
c.changes[i] = e.rewrite(strs, ai=not a.human, review=it.get("review"))
|
||||
applied += 1
|
||||
if not a.dry_run and c.changes:
|
||||
c.save()
|
||||
for label, msgs in rejected:
|
||||
print(f"REJECTED {label}\n " + "\n ".join(msgs))
|
||||
for label, msgs in warned:
|
||||
print(f"WARNING {label}\n " + "\n ".join(msgs))
|
||||
print(f"{'would apply' if a.dry_run else 'applied'} {applied}, rejected {len(rejected)}, warnings {len(warned)}, blank skipped {skipped}")
|
||||
return 1 if rejected else 0
|
||||
|
||||
|
||||
def find_msgfmt():
|
||||
exe = REPO / "tools" / "msgfmt.exe"
|
||||
if os.name == "nt" and exe.exists():
|
||||
return str(exe)
|
||||
from shutil import which
|
||||
return which("msgfmt")
|
||||
|
||||
|
||||
def cmd_check(a):
|
||||
g = None if a.no_glossary else Glossary(a.wiki, quiet=True)
|
||||
msgfmt = find_msgfmt()
|
||||
status = 0
|
||||
for lang in a.lang or all_langs():
|
||||
c = Catalog(catalog_path(lang))
|
||||
errs = warns = 0
|
||||
for e in c.active():
|
||||
if a.scope == "ai" and not e.ai:
|
||||
continue
|
||||
strs = e.msgstrs()
|
||||
if all(s == "" for s in strs) or e.fuzzy:
|
||||
continue
|
||||
errors, warnings = validate(e, strs, c.nplurals, g, lang)
|
||||
if errors or (warnings and a.warnings):
|
||||
label = (f"[{e.msgctxt}] " if e.msgctxt else "") + e.msgid[:70].replace("\n", "\\n")
|
||||
print(f"{lang} line {e.first_line + 1}: {label}")
|
||||
for m in errors:
|
||||
print(f" ERROR {m}")
|
||||
for m in warnings if a.warnings else []:
|
||||
print(f" warn {m}")
|
||||
errs += bool(errors)
|
||||
warns += bool(warnings)
|
||||
if msgfmt:
|
||||
fd, mo = tempfile.mkstemp(suffix=".mo") # Windows msgfmt cannot write to "nul"
|
||||
os.close(fd)
|
||||
r = subprocess.run([msgfmt, "--check-format", "-o", mo, str(c.path)], capture_output=True, text=True)
|
||||
os.unlink(mo)
|
||||
if r.returncode:
|
||||
print(f"{lang}: msgfmt --check-format failed\n{r.stderr.strip()}")
|
||||
status = 1
|
||||
print(f"{lang}: {errs} entries with errors, {warns} with warnings (scope: {a.scope})")
|
||||
status |= bool(errs)
|
||||
if not msgfmt:
|
||||
print("note: msgfmt not found; skipped --check-format", file=sys.stderr)
|
||||
return status
|
||||
|
||||
|
||||
def cmd_lookup(a):
|
||||
c = Catalog(catalog_path(a.lang))
|
||||
rx = re.compile(a.pattern, re.I)
|
||||
n = 0
|
||||
for e in c.entries:
|
||||
strs = [s for s in e.msgstrs() if s]
|
||||
if e.is_header or not strs:
|
||||
continue
|
||||
texts = strs if a.msgstr else [e.msgid, e.msgid_plural or ""]
|
||||
if not any(rx.search(t) for t in texts):
|
||||
continue
|
||||
mark = "obsolete " if e.obsolete else f"{e.id()} " + ("AI " if e.ai else "")
|
||||
ctx = f"[{e.msgctxt}] " if e.msgctxt else ""
|
||||
print(f"{mark}{ctx}{json.dumps(e.msgid, ensure_ascii=False)}\n -> {json.dumps(strs[0] if len(strs) == 1 else strs, ensure_ascii=False)}")
|
||||
n += 1
|
||||
if n >= a.limit:
|
||||
print("... (limit reached)")
|
||||
break
|
||||
|
||||
|
||||
def cmd_glossary(a):
|
||||
g = Glossary(a.wiki)
|
||||
col = g.column(a.lang)
|
||||
if col is None:
|
||||
print(f"'{a.lang}' has no glossary column yet; add one with glossary-set --set {a.lang}=...")
|
||||
rx = re.compile(a.term, re.I) if a.term else None
|
||||
for r in g.rows:
|
||||
if rx and not rx.search(r[0]):
|
||||
continue
|
||||
val = r[col] if col is not None and r[col] else "—"
|
||||
print(f"{r[0]} => {val}\n {r[1]}")
|
||||
notes = g.language_notes(a.lang)
|
||||
if notes and not a.term:
|
||||
print("\n" + notes)
|
||||
|
||||
|
||||
STOP = set("""a an and are as at be been but by can could do does for from has have how if in into is it its
|
||||
may more most must no not of on once one only or other out over same set should so some such than that the
|
||||
their them then there these this those to too two up use used uses using very was when where whether which
|
||||
while will with without would you your yet all any each every per both also between before after first last
|
||||
off new now get gets let lets just already instead still even back down here make makes made via""".split())
|
||||
|
||||
|
||||
def _singular(key):
|
||||
return " ".join(re.sub(r"(?<=[a-z]{3})(es|s)$", "", w) for w in key.split())
|
||||
|
||||
|
||||
def kept_in_english(g):
|
||||
"""Names from the glossary page's "Kept in English" table."""
|
||||
text = g.md.read_text(encoding="utf-8-sig")
|
||||
m = re.search(r"^## Kept in English.*?$(.*?)(?=^## )", text, re.M | re.S)
|
||||
names = set()
|
||||
for row in re.findall(r"^\| (.*?) \|", m.group(1) if m else "", re.M):
|
||||
for piece in re.split(r"[,/:]|\bplus\b", row.replace("**", "").replace("`", "")):
|
||||
if piece.strip():
|
||||
names.add(_norm(piece.strip()))
|
||||
return names
|
||||
|
||||
|
||||
def cmd_candidates(a):
|
||||
g = Glossary(a.wiki)
|
||||
known = kept_in_english(g)
|
||||
for row, _ in g.matchers:
|
||||
known |= {_norm(x.strip()) for x in base_term(row[0]).split(" / ")}
|
||||
known |= {_singular(k) for k in known}
|
||||
known |= {w for k in known if " " in k for w in k.split()} # "tower" of "prime tower"
|
||||
pending = {}
|
||||
labels = set()
|
||||
for lang in all_langs():
|
||||
c = Catalog(catalog_path(lang))
|
||||
for e in c.active():
|
||||
labels.add(_norm(e.msgid))
|
||||
if needs_translation(e, c.nplurals):
|
||||
pending.setdefault(e.key(), set()).add(lang)
|
||||
counts = {} # phrase -> [strings, max langs, display, example, capitalised mid-sentence]
|
||||
for (ctx, msgid), langs in pending.items():
|
||||
seen = set()
|
||||
for chunk in re.split(r"[.,;:!?()\[\]\"\n—–]+|\s-\s", msgid):
|
||||
words = [re.sub(r"'s$", "", w) for w in re.findall(r"[A-Za-z][A-Za-z0-9-]*(?:'s)?", chunk)]
|
||||
# Capitals only mean a name inside an ordinary sentence, not in a Title Case label.
|
||||
sentence = len(words) >= 4 and sum(w[0].isupper() for w in words) * 2 < len(words)
|
||||
for n in (1, 2, 3):
|
||||
for i in range(len(words) - n + 1):
|
||||
gram = words[i:i + n]
|
||||
if gram[0].lower() in STOP or gram[-1].lower() in STOP or len("".join(gram)) < 4 \
|
||||
or any(w.lower() in STOP and w.lower() != "of" for w in gram):
|
||||
continue
|
||||
key = _norm(" ".join(gram))
|
||||
if key in seen or key in known or _singular(key) in known:
|
||||
continue
|
||||
seen.add(key)
|
||||
d = counts.setdefault(key, [0, 0, " ".join(gram), msgid, False])
|
||||
d[0] += 1
|
||||
d[1] = max(d[1], len(langs))
|
||||
d[4] |= sentence and i > 0 and all(w[0].isupper() for w in gram)
|
||||
# A term is a phrase, or a word capitalised mid-sentence or written as an acronym.
|
||||
keep = {k: v for k, v in counts.items()
|
||||
if v[0] >= a.min_count and (" " in k or v[4] or v[2].isupper())}
|
||||
# Drop fragments only ever seen inside a longer candidate ("IQEX parallel" in "IDEX IQEX parallel").
|
||||
keep = {k: v for k, v in keep.items()
|
||||
if not any(k != k2 and re.search(r"(^| )" + re.escape(k) + r"( |$)", k2) and v2[0] >= v[0] for k2, v2 in keep.items())}
|
||||
rows = sorted(keep.items(), key=lambda kv: (-kv[1][0], -len(kv[0].split())))
|
||||
print("strings langs phrase [label = also a msgid of its own] e.g.")
|
||||
for k, (cnt, langs, text, example, _) in rows[:a.limit]:
|
||||
ex = example.replace("\n", " ")
|
||||
print(f"{cnt:7} {langs:6} {text}{' [label]' if k in labels else ''} e.g. {ex[:70]}{'…' if len(ex) > 70 else ''}")
|
||||
|
||||
|
||||
def cmd_glossary_set(a):
|
||||
wiki = find_wiki(a.wiki)
|
||||
if not wiki:
|
||||
print("No OrcaSlicer_WIKI clone found (looked at --wiki, $ORCA_WIKI_DIR and next to this repository).\n"
|
||||
"The glossary must be updated with these terms before the translations that use them ship:\n"
|
||||
f" {a.term}: " + ", ".join(a.set) + "\n"
|
||||
"Clone https://github.com/OrcaSlicer/OrcaSlicer_WIKI next to this repository, or pass --wiki.", file=sys.stderr)
|
||||
return 2
|
||||
tsv = wiki / GLOSSARY_TSV
|
||||
lock = tsv.with_suffix(".lock")
|
||||
for _ in range(100):
|
||||
try:
|
||||
fd = os.open(lock, os.O_CREAT | os.O_EXCL)
|
||||
break
|
||||
except FileExistsError:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
sys.exit(f"could not lock {lock}; remove it if no other run is active")
|
||||
try:
|
||||
data = tsv.read_bytes()
|
||||
bom = data.startswith(b"\xef\xbb\xbf")
|
||||
text = data.decode("utf-8-sig")
|
||||
nl = "\r\n" if "\r\n" in text else "\n"
|
||||
lines = text.splitlines()
|
||||
header = lines[0].split("\t")
|
||||
values = {}
|
||||
for s in a.set:
|
||||
lang, _, val = s.partition("=")
|
||||
if lang not in header:
|
||||
sys.exit(f"no glossary column '{lang}'")
|
||||
if "\t" in val or "\n" in val:
|
||||
sys.exit("values cannot contain tabs or line breaks")
|
||||
values[header.index(lang)] = val.strip()
|
||||
row_i = next((i for i, l in enumerate(lines[1:], 1) if l.split("\t")[0] == a.term), None)
|
||||
new = row_i is None
|
||||
if new:
|
||||
if not a.description:
|
||||
sys.exit("a new term needs --description (one sentence saying what it is)")
|
||||
row = [a.term, a.description] + [""] * (len(header) - 2)
|
||||
lines.append("")
|
||||
row_i = len(lines) - 1
|
||||
else:
|
||||
row = lines[row_i].split("\t")
|
||||
row += [""] * (len(header) - len(row))
|
||||
if a.description:
|
||||
row[1] = a.description
|
||||
conflicts = [f"{header[i]}: '{row[i]}' -> '{v}'" for i, v in values.items() if row[i].strip() not in MISSING and row[i] != v]
|
||||
if conflicts and not a.replace:
|
||||
sys.exit("established translations differ (pass --replace, and update the catalogs to match):\n " + "\n ".join(conflicts))
|
||||
for i, v in values.items():
|
||||
row[i] = v
|
||||
lines[row_i] = "\t".join(row)
|
||||
tsv.write_bytes((b"\xef\xbb\xbf" if bom else b"") + (nl.join(lines) + nl).encode("utf-8"))
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.unlink(lock)
|
||||
print(f"glossary: {'added' if new else 'updated'} '{a.term}'")
|
||||
gen = wiki / "generate_glossary.py"
|
||||
if gen.exists():
|
||||
r = subprocess.run([sys.executable, str(gen), "--update"], cwd=wiki, capture_output=True, text=True)
|
||||
print((r.stdout + r.stderr).strip())
|
||||
return r.returncode
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("--wiki", help="path to an OrcaSlicer_WIKI clone")
|
||||
p.add_argument("--i18n", help="catalog root to use instead of localization/i18n")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
s = sub.add_parser("status")
|
||||
s.add_argument("--lang", action="append")
|
||||
s.set_defaults(func=cmd_status)
|
||||
|
||||
s = sub.add_parser("pending")
|
||||
s.add_argument("--lang", required=True)
|
||||
s.add_argument("--offset", type=int, default=0)
|
||||
s.add_argument("--limit", type=int, default=60)
|
||||
s.add_argument("--out")
|
||||
s.add_argument("--include-fuzzy", action=argparse.BooleanOptionalAction, default=True,
|
||||
help="list fuzzy entries too (default): users never see them")
|
||||
s.set_defaults(func=cmd_pending)
|
||||
|
||||
s = sub.add_parser("apply")
|
||||
s.add_argument("--lang", required=True)
|
||||
s.add_argument("--input", required=True)
|
||||
s.add_argument("--overwrite", action="store_true", help="also replace existing translations")
|
||||
s.add_argument("--human", action="store_true", help="do not add '# AI Translated' (a person wrote these)")
|
||||
s.add_argument("--dry-run", action="store_true")
|
||||
s.add_argument("--no-glossary", action="store_true")
|
||||
s.set_defaults(func=cmd_apply)
|
||||
|
||||
s = sub.add_parser("check")
|
||||
s.add_argument("--lang", action="append")
|
||||
s.add_argument("--scope", choices=["ai", "all"], default="ai")
|
||||
s.add_argument("--warnings", action="store_true", help="also print warnings")
|
||||
s.add_argument("--no-glossary", action="store_true")
|
||||
s.set_defaults(func=cmd_check)
|
||||
|
||||
s = sub.add_parser("lookup")
|
||||
s.add_argument("--lang", required=True)
|
||||
s.add_argument("pattern")
|
||||
s.add_argument("--msgstr", action="store_true", help="search the translations instead of the English")
|
||||
s.add_argument("--limit", type=int, default=40)
|
||||
s.set_defaults(func=cmd_lookup)
|
||||
|
||||
s = sub.add_parser("glossary")
|
||||
s.add_argument("--lang", required=True)
|
||||
s.add_argument("term", nargs="?")
|
||||
s.set_defaults(func=cmd_glossary)
|
||||
|
||||
s = sub.add_parser("candidates")
|
||||
s.add_argument("--min-count", type=int, default=3)
|
||||
s.add_argument("--limit", type=int, default=80)
|
||||
s.set_defaults(func=cmd_candidates)
|
||||
|
||||
s = sub.add_parser("glossary-set")
|
||||
s.add_argument("--term", required=True)
|
||||
s.add_argument("--description")
|
||||
s.add_argument("--set", action="append", default=[], metavar="LANG=VALUE")
|
||||
s.add_argument("--replace", action="store_true")
|
||||
s.set_defaults(func=cmd_glossary_set)
|
||||
|
||||
a = p.parse_args()
|
||||
if a.i18n:
|
||||
global I18N
|
||||
I18N = Path(a.i18n)
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
sys.exit(a.func(a) or 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -6,7 +6,7 @@ description: Use when writing, modifying, reviewing or debugging any OrcaSlicer
|
||||
# OrcaSlicer wxWidgets GUI
|
||||
|
||||
This skill is how OrcaSlicer's wxWidgets GUI is written, changed, fixed and reviewed. Its core is
|
||||
**wxWidgets 3.3.4 API usage** — the documented contracts, the per-platform behaviour and the known
|
||||
**wxWidgets 3.3.2 API usage** — the documented contracts, the per-platform behaviour and the known
|
||||
limitations of the wx version Orca pins — layered with the OrcaSlicer conventions, wrappers, custom
|
||||
widget library and stable component designs a contributor must follow. Every reference file opens
|
||||
with a numbered **Rules** checklist (what a diff is checked against), followed by sections that give
|
||||
@@ -15,12 +15,9 @@ wrong → right pairs with the commit that fixed each one.
|
||||
|
||||
## Ground truth: the wx tree in deps/
|
||||
|
||||
Orca pins upstream **wxWidgets 3.3.4** (`github.com/wxWidgets/wxWidgets`, tag `v3.3.4`,
|
||||
`deps/wxWidgets/wxWidgets.cmake`), built static (Flatpak: shared). Two in-tree patches are applied: an
|
||||
Apple-only sRGB colour-component fix (`0001-macos-use-srgb-colour-components.patch`) and a Windows-only
|
||||
clang-cl sibling-compiler search fix for the installed `wxWidgetsConfig.cmake`
|
||||
(`0002-clang-cl-sibling-compiler-prefix.patch`); neither applies on Linux. Facts about this build that
|
||||
change how you read the wx docs:
|
||||
Orca pins **wxWidgets 3.3.2** from the fork `github.com/SoftFever/Orca-deps-wxWidgets` (tag `v3.3.2`,
|
||||
`deps/wxWidgets/wxWidgets.cmake`), built static (Flatpak: shared). Facts about this build that change
|
||||
how you read the wx docs:
|
||||
|
||||
- **Linux builds against GTK3** (`DEP_WX_GTK3` defaults ON in `deps/CMakeLists.txt`). Target GTK3 on
|
||||
both X11 and Wayland; GTK-guarded code must still compile on GTK2, an opt-out build Orca does not ship.
|
||||
@@ -36,7 +33,7 @@ Look things up in the source the app is built from — it beats memory, and 3.3
|
||||
WX=$(find -L deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' 2>/dev/null | head -1)
|
||||
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux, Windows: deps/<tree>/dep_wxWidgets-prefix/...
|
||||
# -L follows a worktree's deps/<tree> symlinked to the main checkout. Not a glob: zsh aborts on one that matches nothing.
|
||||
# If deps are not built: git clone --depth 1 -b v3.3.4 https://github.com/wxWidgets/wxWidgets
|
||||
# If deps are not built: git clone --depth 1 -b v3.3.2 https://github.com/SoftFever/Orca-deps-wxWidgets
|
||||
grep -n "CaptureMouse" -A 30 $WX/interface/wx/window.h # documented contract (doxygen source)
|
||||
grep -rn "@onlyfor\|not implemented" $WX/interface/wx/popupwin.h # documented platform limits
|
||||
ls $WX/docs/doxygen/overviews/ # eventhandling.h, sizer.h, high_dpi.md, windowdeletion.h, ...
|
||||
@@ -222,15 +219,14 @@ known class with a pitfall entry and a fixing commit.
|
||||
any wx key event; Control+click arrives as a right-click.
|
||||
- **Windows:** `IsDark()` and `wxSYS_COLOUR_*` follow the system app mode, not Orca's theme — use
|
||||
`dark_mode()`; menu bitmaps follow `check_dark_mode()`; windows are not double-buffered by default in
|
||||
3.3.4; `ProcessLeftDown` is never called for popups; a popup that must close with the frame calls
|
||||
3.3.2; `ProcessLeftDown` is never called for popups; a popup that must close with the frame calls
|
||||
`BindUnfocusEvent()`.
|
||||
- **Linux GTK3:** dialogs without size hints collapse (only sizer-fitting calls, not `Fit()`, are
|
||||
replayed at the first `Show()`); command events from a popup's children are not stopped at the popup
|
||||
(MSW/macOS stop them); chained popups need `transient_for` set to the mapped parent right before
|
||||
showing; native borders leak through custom widgets (`RemoveButtonBorder`/`RemoveInputBorder`).
|
||||
- **Wayland:** no global pointer position, no window positioning, `wxClientDC`, `Update()` and `SetIcon`
|
||||
do nothing, AUI floating is disabled (wx 3.3.4 can drag a floating frame via system drag sessions, but
|
||||
its docking hints still need global positions), GL is EGL only.
|
||||
do nothing, no floating AUI panes, GL is EGL only.
|
||||
|
||||
## Editing this skill
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Contents: [Rules](#rules) · [wxColour](#wxcolour) · [System colours and appear
|
||||
|
||||
**Contract.** Constructors: `()`, `(r, g, b, a = wxALPHA_OPAQUE)`, `(unsigned long|long|int|unsigned int)` ("A
|
||||
packed RGB value", `interface/wx/colour.h:98-101`), `(const wxString&|const char*|const wchar_t*)`;
|
||||
`wxColour(bool) = delete` (`include/wx/colour.h:225-240`; `docs/changes.txt:278-280`: code "unintentionally
|
||||
`wxColour(bool) = delete` (`include/wx/colour.h:225-240`; `docs/changes.txt:246-248`: code "unintentionally
|
||||
and mistakenly using wxColour ctor from bool … doesn't compile any longer").
|
||||
|
||||
| Fact | Cite |
|
||||
@@ -121,7 +121,7 @@ returned colour is always valid" (`interface/wx/settings.h:44-48`, `:398-407`).
|
||||
|
||||
| Method | Contract |
|
||||
|---|---|
|
||||
| `IsDark()` | "checks the appearance of the current application and not the other applications on the system, so under MSW … will return false even if dark mode is used system-wide unless the application opted in using dark mode using wxApp::MSWEnableDarkMode()" (`:332-346`). An incompatible 3.3 change (`docs/changes.txt:77-79`). |
|
||||
| `IsDark()` | "checks the appearance of the current application and not the other applications on the system, so under MSW … will return false even if dark mode is used system-wide unless the application opted in using dark mode using wxApp::MSWEnableDarkMode()" (`:332-346`). An incompatible 3.3 change (`docs/changes.txt:71-73`). |
|
||||
| `AreAppsDark()` (3.3.0) | system-wide app dark mode "even if it's not enabled for this particular application"; same as `IsDark()` off MSW (`:308-321`). |
|
||||
| `IsSystemDark()` (3.3.0) | the "Windows mode", which can differ from the "app mode" (`:348-358`). |
|
||||
| `IsUsingDarkBackground()` | luminance fallback, "generally not very useful to call directly" (`:360-370`). |
|
||||
@@ -233,25 +233,25 @@ child handlers.
|
||||
`@onlyfor{wxmsw}`, `interface/wx/app.h:1418-1465`):
|
||||
- "experimental"; uses "undocumented, and unsupported by Microsoft, functions"; works on Windows 10 later than
|
||||
v1809 (including LTSC 2019) and all Windows 11; testing before 20H1 (v2004) "has been limited".
|
||||
- Flags: `DarkMode_Auto` (default) follows the system ("dark mode is only used if it is the default mode
|
||||
for the applications on the current system"); `DarkMode_Always` forces dark, `DarkMode_Never` forces
|
||||
light. **[source]** the enum also names `DarkMode_Auto = 0` in `include/wx/msw/app.h:46-52`;
|
||||
`DarkMode_Never` is new in 3.3.4 (documented at `interface/wx/app.h:1450-1454`).
|
||||
- Flags: default follows the system ("dark mode is only used if it is the default mode for the applications on
|
||||
the current system"); `DarkMode_Always` forces dark. **[source]** `DarkMode_Auto = 0` exists in
|
||||
`include/wx/msw/app.h:48` although only `DarkMode_Always` is documented.
|
||||
- Returns `true` if enabled, `false` "most likely because the system doesn't support dark mode".
|
||||
- Alternatives: the `msw.dark-mode` system option (1 = `MSWEnableDarkMode()`, 2 = `DarkMode_Always`, settable by
|
||||
environment variable from outside the app, `interface/wx/sysopt.h:85-89`), or `SetAppearance(System|Light|Dark)`.
|
||||
- Known limitations (`interface/wx/app.h:1442-1448`): `wxTimePickerCtrl`, `wxDatePickerCtrl` and
|
||||
`wxCalendarCtrl` stay light; `wxPrintDialog` on Windows 10 stays light; MDI applications are not
|
||||
recommended in dark mode. `wxMessageDialog` gained dark mode in 3.3.3 (#26570) and the common dialogs
|
||||
in 3.3.4 (#26780), so the older "anything `TaskDialog()`-based and the common dialogs stay light" note
|
||||
no longer applies.
|
||||
environment variable from outside the app, `interface/wx/sysopt.h:85-89`), or `SetAppearance(System|Dark)`.
|
||||
- Known limitations (`interface/wx/app.h:1434-1448`): anything `TaskDialog()`-based has no dark mode —
|
||||
`wxMessageBox()`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`, simple `wxAboutBox()` (wx suggests
|
||||
`wxGenericMessageDialog`/`wxGenericProgressDialog`); common-dialog wrappers `wxColourDialog`, `wxFindReplaceDialog`,
|
||||
`wxFontDialog`, `wxPageSetupDialog`, `wxPrintDialog`; `wxTimePickerCtrl`, `wxDatePickerCtrl`, `wxCalendarCtrl`
|
||||
stay light; toolbar items with `wxToolBar::SetDropdownMenu()` draw the drop-down "almost invisible".
|
||||
|
||||
`AppearanceResult wxApp::SetAppearance(Appearance::System|Light|Dark)` (3.3.0, `interface/wx/app.h:1130-1187`):
|
||||
`AppearanceResult wxApp::SetAppearance(Appearance::System|Light|Dark)` (3.3.0, `interface/wx/app.h:1152-1190`):
|
||||
GTK/macOS follow the system by default and the call is immediate and "affects all the existing windows as well
|
||||
as any windows created after this call"; "Under MSW, the default appearance is always light" and an app that
|
||||
wants to follow the system must call it with `Appearance::System`; on MSW it maps to `MSWEnableDarkMode()` and
|
||||
returns `Ok`/`Failure` (`interface/wx/app.h:1166-1169`). `Failure` e.g. "because `GTK_THEME` is defined";
|
||||
`AppearanceResult::CannotChange` is "currently never returned" (`interface/wx/app.h:1151, 1179-1183`).
|
||||
wants to follow the system must call it with `Appearance::System`; "the appearance can be only set before any
|
||||
windows are created and calling this function too late will return AppearanceResult::CannotChange" (only wxMSW
|
||||
returns it);
|
||||
`Failure` e.g. "because `GTK_THEME` is defined".
|
||||
|
||||
`wxDarkModeSettings` (`interface/wx/msw/darkmode.h:30-111`), passed to `MSWEnableDarkMode()`: `GetColour(wxSystemColour)`
|
||||
(defaults "not documented and are subject to change"; the doc example names `0x202020` as the default background);
|
||||
@@ -260,40 +260,34 @@ returns `Ok`/`Failure` (`interface/wx/app.h:1166-1169`). `Failure` e.g. "because
|
||||
which "doesn't look very well in dark mode"; the base returns grey.
|
||||
|
||||
**[source] facts the docs do not state:**
|
||||
- As of 3.3.3 (#26516) and 3.3.4 (#26734), MSW dark mode can be switched at runtime: `SetAppearance()` maps to
|
||||
`MSWEnableDarkMode()` and returns `Ok`/`Failure`, never `CannotChange` (`src/msw/darkmode.cpp:354-375`).
|
||||
`MSWEnableDarkMode()` re-applies the mode to an existing window by sending it `WM_SYSCOLORCHANGE` (`:345-349`),
|
||||
and the window restyles through `wxMSWDarkMode::HasChanged()`/`NotifySysColorChange()`
|
||||
(`src/msw/window.cpp:5337, 5528-5534`), so a control created before the switch is re-themed too. macOS `System`
|
||||
sets `NSApp.appearance` to `[NSAppearance currentAppearance]` rather than nil, pinning the current look
|
||||
(`src/osx/cocoa/utils.mm:485-513`); GTK3 maps to the portal colour-scheme machinery; GTK2 always returns
|
||||
`Failure` (`src/gtk/app.cpp:355-381`).
|
||||
- `MSWEnableDarkMode` may be called any time; dark title bars are applied when a TLW is created
|
||||
- `SetAppearance` on MSW returns `CannotChange` when any TLW exists **or `MSWEnableDarkMode` was already called**
|
||||
(`gs_appMode != AppMode_Default`); `SetAppearance(Light)` returns `Ok` without doing anything
|
||||
(`src/msw/darkmode.cpp` `wxApp::SetAppearance`). macOS `System` sets `NSApp.appearance` to
|
||||
`[NSAppearance currentAppearance]` rather than nil, pinning the current look (`src/osx/cocoa/utils.mm:485-513`);
|
||||
GTK3 maps to the portal colour-scheme machinery; GTK2 always returns `Failure` (`src/gtk/app.cpp:355-381`).
|
||||
- `MSWEnableDarkMode` may be called any time, but dark title bars are applied only to TLWs **created** afterwards
|
||||
(`EnableForTLW` at the end of TLW creation, `src/msw/toplevel.cpp:513`) and controls are dark-enabled at creation
|
||||
(`MSWCreateControl` → `AllowForWindow` and, for some controls, `SetForegroundColour(LISTBOXTEXT)`,
|
||||
`src/msw/control.cpp:133-140`).
|
||||
`src/msw/control.cpp:133-140`). wx cannot flip existing windows — the reason `SetAppearance` refuses late calls.
|
||||
- wx takes ownership of the settings pointer (`wxDarkModeModule::SetSettings`) — allocate it with `new`.
|
||||
- `msw.dark-mode` is read in `wxApp::Initialize` (`src/msw/app.cpp:492-494`): a user's `WX_MSW_DARK_MODE`
|
||||
environment variable enables wx dark mode before Orca's own call.
|
||||
- wx's owner-drawn menu path keys on `wxMSWDarkMode::IsActive()` (`src/msw/menuitem.cpp` `wxMenuItem::OnDrawItem`,
|
||||
`GetColourToUse`; `src/msw/menu.cpp`; menu-bar UAH drawing in `src/msw/darkmode.cpp`).
|
||||
|
||||
3.3.x dark-mode fix log: 3.3.4 wxMSW (`docs/changes.txt:349-358`: dynamic dark mode, dark mode for
|
||||
wxFontPickerCtrl, wxNotebook, wxSpinButton and the common dialogs), 3.3.3 (`:433` "Implement support for
|
||||
switching between light/dark modes"; `:426` wxMessageDialog, `:429` combo/listbox on Windows 11, `:431`
|
||||
wxCheckBox label in screen readers), 3.3.2 (`:492-501`: checkbox accessibility in dark mode, rendering of
|
||||
several controls, toolbar, menus; also "Revert use of WS_EX_COMPOSITED"), 3.3.1 (`:553-565`:
|
||||
wxStaticBitmap-in-notebook crash, disabled wxButton bitmaps and wxStaticText, notebook high-contrast
|
||||
background, wxDataViewCtrl light-mode border regression, selected toolbar buttons, wxComboCtrl, wxTE_RICH
|
||||
wxTextCtrl), 3.3.0 (`:578` "Add experimental dark mode support to wxMSW"); XRC dark colour variants (`:687`).
|
||||
3.3.x dark-mode fix log: 3.3.2 wxMSW (`docs/changes.txt:297-308`: checkbox accessibility in dark mode, rendering of
|
||||
several controls, toolbar, menus; also "Revert use of WS_EX_COMPOSITED"), 3.3.1 (`:358-372`: wxStaticBitmap-in-notebook
|
||||
crash, disabled wxButton bitmaps and wxStaticText, notebook high-contrast background, wxDataViewCtrl light-mode
|
||||
border regression, selected toolbar buttons, wxComboCtrl, wxTE_RICH wxTextCtrl), 3.3.0 (`:385` "Add experimental dark
|
||||
mode support to wxMSW"); XRC dark colour variants (`:494`).
|
||||
|
||||
**OrcaSlicer.** `GUI_App::on_init_inner` (`#ifdef __WINDOWS__`) calls `MSWEnableDarkMode(DarkMode_Auto)` and then
|
||||
`NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled)`. wx's call exists so that wx-drawn
|
||||
menus get dark borders; NppDarkMode does Orca's theming (title bars, explorer theme, scrollbars, list headers).
|
||||
The code comment "Orca: todo switch to native dark mode support in wxWidgets and remove NppDarkMode" records the
|
||||
intent. The old blocker — a live Preferences toggle would be restart-only because `SetAppearance` returned
|
||||
`CannotChange` and existing windows could not be restyled — is gone as of 3.3.3/3.3.4 (above). Orca still keeps
|
||||
NppDarkMode, and Orca's own `MsgDialog` family and `ProgressDialog` remain its message/progress UI.
|
||||
`NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled)`. wx's call exists only so that wx-drawn
|
||||
menus get dark borders; NppDarkMode does the theming (title bars, explorer theme, scrollbars, list headers) because
|
||||
it can switch live and wx cannot. The code comment "Orca: todo switch to native dark mode support in wxWidgets and
|
||||
remove NppDarkMode" records the intent; the blocker is that a live Preferences toggle would become restart-only
|
||||
(`SetAppearance` returns `CannotChange` after startup, existing windows cannot be restyled) and TaskDialog/common
|
||||
dialogs/pickers stay light anyway — which is also why Orca has its own `MsgDialog` family and `ProgressDialog`.
|
||||
**[source]** consequences of `DarkMode_Auto`: wx's internal "dark" stays `AllowDark` and follows the **system app
|
||||
mode**; NppDarkMode's later `SetPreferredAppMode` call does not touch wx's `gs_appMode`. So with Windows light +
|
||||
Orca dark, wx's dark machinery (menus, `GetColour()`, `IsDark()`) stays off; with Windows dark + Orca light it is
|
||||
@@ -318,15 +312,11 @@ on: `GetColour()` returns the dark palette, new native controls are dark-enabled
|
||||
```
|
||||
Cite: bf397a0632 (`GUI_App.cpp`, `GUI_App::on_init_inner`).
|
||||
- **Rule:** Do not call `wxTheApp->SetAppearance(...)` in Orca.
|
||||
**Why:** MSW would switch wx's own mode out of step with NppDarkMode and `dark_color_mode` (wx's mode is driven
|
||||
by `MSWEnableDarkMode(DarkMode_Auto)` and follows the system), GTK2 fails, and macOS pins
|
||||
**Why:** MSW returns `CannotChange` (Orca already called `MSWEnableDarkMode`), GTK2 fails, and macOS pins
|
||||
`NSApp.appearance` while `GUI_App::dark_mode()` keeps reading the system `AppleInterfaceStyle` — the two would
|
||||
disagree.
|
||||
- **Rule:** Do not show wx's TaskDialog-based or common dialogs; use `MessageDialog` and friends
|
||||
(`MsgDialog.hpp`, see `references/windows-dialogs.md`).
|
||||
**Why:** Orca's dialogs are owner-drawn and consistent with the rest of the UI. The old dark-mode gap
|
||||
closed — `wxMessageDialog` gained dark mode in 3.3.3 (#26570) and the common dialogs in 3.3.4 (#26780) —
|
||||
but the convention stands.
|
||||
- **Rule:** Do not show wx's TaskDialog-based or common dialogs where dark mode matters; use `MessageDialog` and
|
||||
friends (`MsgDialog.hpp`, see `references/windows-dialogs.md`).
|
||||
|
||||
## Window colours, inheritance and native-control limits
|
||||
|
||||
@@ -381,7 +371,7 @@ macOS paints the system grey.
|
||||
|
||||
| Port | Behaviour |
|
||||
|---|---|
|
||||
| MSW | `wxButton`/`wxToggleButton` `SetBackgroundColour`/`SetForegroundColour` switch the native button to `BS_OWNERDRAW` (`src/msw/anybutton.cpp` `wxAnyButton::MakeOwnerDrawn`, `:1324-1390`) — the colour works but native theming is gone. `wxCheckBox`/`wxRadioButton` foreground makes them owner-drawn when themes are active (`src/msw/control.cpp` `MSWMakeOwnerDrawnIfNecessary`); 3.3.2 fixed checkbox accessibility in that mode (`docs/changes.txt:492-493`). Static-text-like children paint the ancestor brush only if that ancestor's background is inheritable. |
|
||||
| MSW | `wxButton`/`wxToggleButton` `SetBackgroundColour`/`SetForegroundColour` switch the native button to `BS_OWNERDRAW` (`src/msw/anybutton.cpp` `wxAnyButton::MakeOwnerDrawn`, `:1324-1390`) — the colour works but native theming is gone. `wxCheckBox`/`wxRadioButton` foreground makes them owner-drawn when themes are active (`src/msw/control.cpp` `MSWMakeOwnerDrawnIfNecessary`); 3.3.2 fixed checkbox accessibility in that mode (`docs/changes.txt:299-300`). Static-text-like children paint the ancestor brush only if that ancestor's background is inheritable. |
|
||||
| macOS | `SetBackgroundColour` reaches the NSView only if it `respondsToSelector:setBackgroundColor:` and the style is not `wxBG_STYLE_TRANSPARENT` (`src/osx/cocoa/window.mm:3514-3531`): **NSButton (`wxButton`) ignores the background colour**. Foreground uses `setTextColor:` when the view has it (`:3885-3887`); stock NSButton has none. `wxStaticText` is an NSTextField with `setDrawsBackground:NO` — its background is never painted (`src/osx/cocoa/stattext.mm:154`). `wxTextCtrl`'s `setEnabled:` resets the text colour: multi-line (`wxNSTextView`) always, to `controlTextColor`/`disabledControlTextColor`; single-line (`wxNSTextField`) when it does not draw its background, to `controlTextColor`/`secondarySelectedControlColor` (`src/osx/cocoa/textctrl.mm`). |
|
||||
| GTK3 | `SetBackgroundColour/SetForegroundColour/SetFont` install a per-widget CSS provider `*{color:..;background:..;font:..}` at `GTK_STYLE_PROVIDER_PRIORITY_APPLICATION` (`src/gtk/window.cpp` `wxWindowGTK::GTKApplyStyle`); `wxButton`/`wxCheckBox` apply it to their inner label too (`src/gtk/button.cpp:325-335`, `src/gtk/checkbox.cpp:233-237`). User CSS (priority USER) can still override. |
|
||||
| GTK2 | colours applied with `gtk_widget_modify_style` — pixmap-engine themes may ignore them. |
|
||||
|
||||
@@ -106,7 +106,7 @@ The general wx rule is that setters do not send events. The exceptions are the b
|
||||
| `wxTextEntry::SetValue(s)` | **yes**, one `wxEVT_TEXT`, even when `s` equals the current text | `interface/wx/textentry.h:539-542`; [source] `src/common/textentrycmn.cpp:236-254`, `src/msw/textctrl.cpp:1133-1145` |
|
||||
| `wxTextEntry::ChangeValue(s)` | no | `interface/wx/textentry.h:177-178` |
|
||||
| `wxTextEntry::Clear()` | yes (= `SetValue("")`) | `interface/wx/textentry.h:193-194` |
|
||||
| `wxTextCtrl::SetLabel/SetLabelText` | nothing at all (3.3) | `docs/changes.txt:117-119`; `src/common/textcmn.cpp:934-937` |
|
||||
| `wxTextCtrl::SetLabel/SetLabelText` | nothing at all (3.3) | `docs/changes.txt:111-113`; `src/common/textcmn.cpp:934-937` |
|
||||
| `SetSelection/SetStringSelection` on wxChoice, wxComboBox, wxListBox, wxRadioBox | no | `interface/wx/ctrlsub.h:102-103,126` |
|
||||
| `wxComboBox::SetValue` | `wxEVT_TEXT` if editable; none with `wxCB_READONLY` | `interface/wx/combobox.h:256-272` |
|
||||
| `wxComboBox::Popup()/Dismiss()` | DROPDOWN/CLOSEUP, except on wxOSX | `interface/wx/combobox.h:279-281,292-294` |
|
||||
@@ -184,7 +184,7 @@ Pitfalls:
|
||||
ctrl->GetTextCtrl()->SetLabel(s); // Wrong: no-op
|
||||
ctrl->GetTextCtrl()->ChangeValue(s); // Right (or SetValue if listeners must react)
|
||||
```
|
||||
Cite: `docs/changes.txt:117-119`.
|
||||
Cite: `docs/changes.txt:111-113`.
|
||||
- **Rule:** Handle `wxEVT_TEXT_ENTER` without `Skip()` unless you want the default button activated as well.
|
||||
**Why:** a skipped Enter falls through to the dialog's default button and closes it.
|
||||
```cpp
|
||||
@@ -213,7 +213,7 @@ Contract:
|
||||
verbatim, and `wxControl::EscapeMnemonics()` escapes it (`interface/wx/control.h:174-195, 379-387`). This applies
|
||||
to `wxStaticText`, `wxCheckBox`, `wxRadioButton`, `wxButton` labels and to book page titles: all `wx*book`
|
||||
classes interpret mnemonics in page text (`interface/wx/bookctrl.h:141-147`), and since 3.3 wxListbook and
|
||||
wxChoicebook do too (`docs/changes.txt:134-136`).
|
||||
wxChoicebook do too (`docs/changes.txt:128-130`).
|
||||
- `SetLabelMarkup()` needs well-formed markup or the label "won't be shown at all" (returns false, keeps the old
|
||||
label). A bare `&` is still a mnemonic. Multi-line markup works only on GTK and macOS; the generic version used on
|
||||
MSW handles single lines (`interface/wx/control.h:338-347`). Quote untrusted text with
|
||||
@@ -383,10 +383,10 @@ Pitfalls:
|
||||
|
||||
- Virtual list (`wxLC_REPORT|wxLC_VIRTUAL`): call `SetItemCount()` and override `OnGetItemText` (optionally
|
||||
`OnGetItemImage`/`OnGetItemAttr`) (`interface/wx/listctrl.h:128-134`). `EditLabel()` asserts without
|
||||
`wxLC_EDIT_LABELS` (`docs/changes.txt:74-75`).
|
||||
`wxLC_EDIT_LABELS` (`docs/changes.txt:68-69`).
|
||||
- Images: prefer `SetImages(std::vector<wxBitmapBundle>)`; `wxImageList` is discouraged
|
||||
(`interface/wx/withimages.h:21-40`) and is measured in physical pixels in 3.3 (`docs/changes.txt:91-94`); calling
|
||||
its methods on an invalid (unsized) list now asserts, i.e. fails silently in Orca (`docs/changes.txt:59-62`).
|
||||
(`interface/wx/withimages.h:21-40`) and is measured in physical pixels in 3.3 (`docs/changes.txt:85-88`); calling
|
||||
its methods on an invalid (unsized) list now asserts, i.e. fails silently in Orca (`docs/changes.txt:53-56`).
|
||||
`Assign*` transfers ownership, `Set*ImageList` does not (`interface/wx/withimages.h:90-110`). Sizing:
|
||||
`references/dpi-bitmaps-fonts.md`.
|
||||
- `wxTreeCtrl`: `SelectItem` emits events ([table](#events-from-programmatic-changes)).
|
||||
@@ -402,7 +402,7 @@ Contract:
|
||||
selection's blocks).
|
||||
- `FreezeTo(row, col)` returns false (an assert, silent in Orca) for out-of-range values, merged cells or the native
|
||||
header (`interface/wx/grid.h:5747-5772`); [source] also when rows/columns were reordered or drag-moving is enabled
|
||||
(`src/generic/grid.cpp:5742-5750`). In 3.3 it freezes even when the grid is too small (`docs/changes.txt:54-57`).
|
||||
(`src/generic/grid.cpp:5742-5750`). In 3.3 it freezes even when the grid is too small (`docs/changes.txt:48-51`).
|
||||
- Editor contract: `EndEdit` must not modify the grid — it stores the value and returns true if it changed;
|
||||
`ApplyEdit` writes it after `wxEVT_GRID_CELL_CHANGING` was not vetoed (`interface/wx/grid.h:610-638`). Editors,
|
||||
renderers and attrs are ref-counted and the setters take ownership (`:1263-1305, 1739-1770, 2681-2701`).
|
||||
@@ -492,7 +492,7 @@ All cites `interface/wx/dataview.h`. MSW uses the generic implementation; wxGTK
|
||||
Other per-port facts: editing starts on a slow double-click or a platform key — "F2 is typical on Windows, Space
|
||||
and/or Enter is common elsewhere" (2604-2607, 1915-1918); a custom renderer's `StartDrag()` is "Not yet supported"
|
||||
(2717-2719); `RenderText()` should be used inside `Render()` so text matches native renderers (2708-2714). Calling
|
||||
`Collapse()` from an event handler was fixed in 3.3.1 (`docs/changes.txt:539`; the generic re-check is described
|
||||
`Collapse()` from an event handler was fixed in 3.3.1 (`docs/changes.txt:346`; the generic re-check is described
|
||||
under [DVC control API](#wxdataviewctrl-control-api)).
|
||||
|
||||
OrcaSlicer: `ParamsViewCtrl` (`EditGCodeDialog`) and `DiffViewCtrl` (`UnsavedChangesDialog`) use
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DPI, bitmaps and fonts
|
||||
|
||||
How wx 3.3.4 maps DIP, logical and physical pixels on each platform, how DPI changes reach a window,
|
||||
How wx 3.3.2 maps DIP, logical and physical pixels on each platform, how DPI changes reach a window,
|
||||
and how OrcaSlicer sizes layout (`FromDIP`, `em_unit`), rescales (`DPIAware`), rasterizes icons
|
||||
(`BitmapCache`, `create_scaled_bitmap`, `ScalableBitmap`) and chooses fonts (`Label` table). Read it
|
||||
for any fixed size, icon, bitmap, image list, font, or `on_dpi_changed` work, and when a bug looks
|
||||
@@ -488,7 +488,7 @@ handler bound there runs once more after every DPI change.
|
||||
inherits the DC's scale (`interface/wx/bitmap.h:370-385`).
|
||||
- `wxBitmap(const wxCursor&)` is invalid on GTK under Wayland (`interface/wx/bitmap.h:388-401`).
|
||||
- wxMSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale
|
||||
(`docs/changes.txt:100-102`).
|
||||
(`docs/changes.txt:94-96`).
|
||||
|
||||
**Offscreen drawing.** "The scaling factor of the bitmap determines the scaling factor used by
|
||||
this device context" (`interface/wx/dcmemory.h:41-58`); `wxMemoryDC(wxDC*)` does **not** inherit
|
||||
@@ -740,7 +740,7 @@ are in `references/popups-menus.md`.
|
||||
in the new code as it doesn't support showing DPI-dependent bitmaps. Please use
|
||||
wxWithImages::SetImages() instead"; "the size is specified in physical pixels and must correspond
|
||||
to the size of bitmaps … that will be added". 3.3 made the size physical and makes calls on an
|
||||
invalid list assert (`docs/changes.txt:59-62, 91-94`) — silently in Orca's assert-free build.
|
||||
invalid list assert (`docs/changes.txt:53-56, 85-88`) — silently in Orca's assert-free build.
|
||||
When a list is unavoidable, `wxBitmapBundle::CreateImageList(win, bundles)` builds one at the
|
||||
consensus size (public but undocumented, [source] `include/wx/bmpbndl.h`).
|
||||
```cpp
|
||||
@@ -761,7 +761,7 @@ wxArtProvider::GetBitmapBundle(wxART_WARNING, wxART_OTHER, wxSize(16, 16));
|
||||
```
|
||||
|
||||
**`wxImage`** resizing (`interface/wx/image.h:28-89`): `wxIMAGE_QUALITY_NEAREST` is no longer an
|
||||
alias of `NORMAL` since 3.3.0 (`docs/changes.txt:104-105`; `wxIMAGE_QUALITY_FAST` is the speed
|
||||
alias of `NORMAL` since 3.3.0 (`docs/changes.txt:98-99`; `wxIMAGE_QUALITY_FAST` is the speed
|
||||
synonym). `NORMAL` (default) = bilinear down to an integer multiple, then box average; `HIGH` =
|
||||
box average when shrinking, bicubic when enlarging; `BILINEAR`, `BICUBIC`, `BOX_AVERAGE` explicit.
|
||||
High-quality scaling "may not work as expected when using a single mask colour for
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Events: binding, dispatch, posting and deferred calls
|
||||
|
||||
How wx 3.3.4 finds, runs, propagates, queues and drops event handlers, and how OrcaSlicer code
|
||||
How wx 3.3.2 finds, runs, propagates, queues and drops event handlers, and how OrcaSlicer code
|
||||
binds, emits and defers. Read it before writing any `Bind`/`Unbind`, `Skip()`, `ProcessEvent`,
|
||||
`wxPostEvent`/`wxQueueEvent` or `CallAfter`, before defining a custom event, and when debugging a
|
||||
handler that never runs, runs twice, runs on a dead object, or swallows a widget's own behaviour.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Mouse, keyboard and focus
|
||||
|
||||
How input reaches wx windows in the wxWidgets 3.3.4 build Orca ships, and the Orca conventions on
|
||||
How input reaches wx windows in the wxWidgets 3.3.2 build Orca ships, and the Orca conventions on
|
||||
top: mouse capture, mouse and key events, accelerators and Orca's shortcut registry, focus,
|
||||
tooltips and cursors. Read it when a widget captures the mouse or tracks hover, when adding or
|
||||
changing a keyboard shortcut, when touching focus, tooltip or cursor code, and when debugging an
|
||||
@@ -297,7 +297,7 @@ Per port [source]:
|
||||
non-enter event while the cached position is invalid is treated as the enter.
|
||||
- **GTK:** crossing events with a grab/ungrab mode are ignored; outside capture wx re-derives the
|
||||
window under the pointer on each motion (`src/gtk/window.cpp:2073-2117, 2384-2456`; fixes #24339
|
||||
and #24931–#24933 are in this release, `docs/changes.txt:730, 740`).
|
||||
and #24931–#24933 are in this release, `docs/changes.txt:537, 547`).
|
||||
- **macOS:** each view's `NSTrackingArea` uses `NSTrackingInVisibleRect`
|
||||
(`src/osx/cocoa/window.mm:3924`) and covers its children, so do not rely on the parent getting
|
||||
LEAVE when the pointer moves onto a child. `NSMouseMoved` is delivered only to the deepest view
|
||||
@@ -334,7 +334,7 @@ A composite's hover state must therefore track its children. Orca's `StateHandle
|
||||
`CS_DBLCLKS`, `src/msw/app.cpp:584`). GTK drops the surplus press before `GDK_2BUTTON_PRESS`
|
||||
(`src/gtk/window.cpp:1798-1817`), and GTK2 also suppresses triple clicks (`:1818-1829`). On wxOSX the
|
||||
third press of a triple click is a DOWN again, as on MSW (`src/osx/cocoa/window.mm:4105-4140`;
|
||||
#25886, `docs/changes.txt:509`). `GetClickCount()` is "implemented only in wxMac and returns -1
|
||||
#25886, `docs/changes.txt:316`). `GetClickCount()` is "implemented only in wxMac and returns -1
|
||||
for the other platforms" (`interface/wx/event.h:2965-2974`).
|
||||
- **Context menu:** "under MSW the context menu event is generated after EVT_RIGHT_UP … but under
|
||||
GTK … after EVT_RIGHT_DOWN", so a window handling `wxEVT_CONTEXT_MENU` must not handle (or must
|
||||
@@ -398,7 +398,7 @@ A diagonal trackpad scroll on macOS sends two events, vertical first, then one w
|
||||
| `ClientToScreen()`, `ScreenToClient()`, `GetScreenRect()` | — | add the top-level window's `gdk_window_get_origin()` (`src/gtk/window.cpp:4630-4698`). Without a global origin the result is only meaningful relative to the same top-level window: fine for deltas and hit tests inside one window, wrong across windows or for absolute placement |
|
||||
| `wxFindWindowAtPoint(pt)` | deepest window at a screen point; disabled children count, hidden ones are skipped (`interface/wx/utils.h:385-394`) | built on screen rectangles, so unreliable. On GTK it is `wxGenericFindWindowAtPoint`, a walk over every top-level window and child (`src/gtk/utilsgtk.cpp:98-101`, `src/common/utilscmn.cpp:1294-1345`) |
|
||||
| `wxGetKeyState(key)` | "In wxGTK, this function can be only used with modifier keys … when not using X11 backend" (`interface/wx/utils.h:352-358`) | Ctrl/Alt/Shift and Caps/Num/Scroll Lock only; any other key returns false (`src/unix/utilsx11.cpp:2596-2662`) |
|
||||
| `WarpPointer()` | Apple's HIG forbids it; on Wayland it works only with a compositor implementing the pointer-warp protocol, and mutter also needs a pressed button (`interface/wx/window.h:3895-3914`; `docs/changes.txt:487`) | — |
|
||||
| `WarpPointer()` | Apple's HIG forbids it; on Wayland it works only with a compositor implementing the pointer-warp protocol, and mutter also needs a pressed button (`interface/wx/window.h:3895-3914`; `docs/changes.txt:294`) | — |
|
||||
| `wxUIActionSimulator` | "doesn't work when using Wayland" (`interface/wx/uiaction.h:20`) | — |
|
||||
| `PopupMenu(x, y)` | — | GTK ≥ 3.22 positions it relative to the window (`gtk_menu_popup_at_rect`, `src/gtk/window.cpp:6520-6565`), so it is safe |
|
||||
|
||||
@@ -540,7 +540,7 @@ The full Esc/close path is in `references/windows-dialogs.md`.
|
||||
layout"; specials are their `WXK_*` (`:1371-1394`). A Cyrillic `ц` gives `'W'`, so Ctrl+letter
|
||||
shortcuts work across layouts, but an AZERTY key reports its own label (`$` where US has `]`), so
|
||||
`Ctrl-;`-style punctuation accelerators may be untypeable on some layouts (`:1396-1407`). wxGTK
|
||||
got the non-Latin mapping in 3.3.0 (#23379, `docs/changes.txt:733`).
|
||||
got the non-Latin mapping in 3.3.0 (#23379, `docs/changes.txt:540`).
|
||||
- `wxEVT_CHAR` reflects Shift and the layout. Ctrl+letter gives 1..26 (`WXK_CONTROL_A` …,
|
||||
`:1409-1421`). Exception: on macOS Cmd+letter's CHAR is the letter itself (`'a'`/`'A'`); only the
|
||||
physical Control key yields 1..26 (`src/osx/cocoa/window.mm:304-307`).
|
||||
@@ -629,14 +629,8 @@ Space/Return holds the mouse capture until its KEY_UP reaches the button. On MSW
|
||||
|
||||
### Contract
|
||||
|
||||
- "An accelerator takes precedence over normal processing" (`interface/wx/accel.h:178`). Since 3.3.4 this
|
||||
holds on every port (wxGTK used to let the focused window handle the key first), unless the focused
|
||||
window claims the key. The accelerator walk still runs after `wxEVT_CHAR_HOOK` (table above). The new
|
||||
`wxEVT_ACCELERATOR_KEY` event (`interface/wx/event.h:1740-1801`, `@since 3.3.4`) is sent to the focus
|
||||
window — and propagated to its parents — before an accelerator is used; it is sent after
|
||||
`wxEVT_CHAR_HOOK` on most ports and before it on macOS. Its handler can call `UseAccelerator()`, and a
|
||||
window that must keep the key overrides `wxWindow::ClaimsKeyBeforeAccelerator()`
|
||||
(`interface/wx/window.h:4554-4583`).
|
||||
- "An accelerator takes precedence over normal processing" (`interface/wx/accel.h:178`), but it
|
||||
runs after `wxEVT_CHAR_HOOK` on every port (table above).
|
||||
- [source] On GTK and macOS a hit is sent as `wxEVT_MENU` with the entry's id and, if unprocessed,
|
||||
retried as `wxEVT_BUTTON`; macOS treats the key as consumed either way
|
||||
(`src/gtk/window.cpp:1340-1366`, `src/osx/window_osx.cpp:2563-2590`). On MSW the `WM_COMMAND` goes to
|
||||
@@ -660,16 +654,11 @@ Space/Return holds the mouse capture until its KEY_UP reaches the button. On MSW
|
||||
them in `wxMenuBar::RebuildAccelTable` (`src/msw/menu.cpp:1264-1292`); GTK adds the menu's accel
|
||||
group to the top-level window in `AttachToFrame` (`src/gtk/menu.cpp:239-247`). A menu shown with
|
||||
`PopupMenu` treats `"\tCtrl+X"` as display text.
|
||||
- **Text-entry controls claim their editing keys on every port since 3.3.4.** A `wxTextCtrl`, a
|
||||
non-read-only `wxComboBox` or a `wxSpinCtrl` overrides `ClaimsKeyBeforeAccelerator()` to return
|
||||
`wxTextEntryBase::IsUsedForEditing()` (`src/common/textcmn.cpp:136-140`;
|
||||
`src/common/spinctrlcmn.cpp:160-167`; `src/msw/combobox.cpp:389-395`, `src/gtk/combobox.cpp:205-211`):
|
||||
Alt+Backspace; any printable key with no modifier or Shift (except Delete, so Esc/Enter/Tab stay
|
||||
usable); Backspace/Delete/Home/End with no modifier or Shift; Ctrl+A/C/V/X; and Insert/Delete/Home/
|
||||
End/Left/Right with Ctrl or Shift. A matching menu accelerator does not fire while such a control has
|
||||
focus. The set lives in `IsUsedForEditing` (`src/common/textentrycmn.cpp:441-509`).
|
||||
- **GTK:** the accelerator walk now consults `ClaimsKeyBeforeAccelerator()` too
|
||||
(`src/gtk/window.cpp:1524-1539`), so the text-control exemption above applies here since 3.3.4. As menu
|
||||
- **MSW:** accelerators are not translated while a `wxTextCtrl`/`wxComboBox`/`wxSpinCtrl` has focus and
|
||||
the key is a text-editing key: Ctrl+A/C/V/X/Ins/Del/Home/End/Left/Right, Shift+those navigation
|
||||
keys, bare Del/Home/End, Alt+Backspace (`src/msw/textentry.cpp:1073-1150`). Multi-line controls also
|
||||
keep Enter (`src/msw/textctrl.cpp:2110-2135`).
|
||||
- **GTK:** the accelerator walk has no such exemption (`src/gtk/window.cpp:1340-1366`). As menu
|
||||
accelerators, Shift with non-alphabetic keys does not work, bare arrow keys do not work, and the
|
||||
listed keys (Tab, the modifiers, locks …) are unsupported (`interface/wx/menuitem.h:562-575`).
|
||||
- **macOS:** menu-bar items become `NSMenuItem` key equivalents
|
||||
|
||||
@@ -69,12 +69,9 @@ Contents: [The stack](#the-stack-orcas-gui-is-built-on) ·
|
||||
|
||||
## The stack Orca's GUI is built on
|
||||
|
||||
- **wxWidgets 3.3.4, upstream.** `deps/wxWidgets/wxWidgets.cmake` fetches
|
||||
`https://github.com/wxWidgets/wxWidgets` at tag `v3.3.4` and builds it static
|
||||
(`-DwxBUILD_SHARED=OFF`); Flatpak builds build it shared. Two in-tree platform patches apply on
|
||||
Apple and Windows only (`0001-macos-use-srgb-colour-components.patch`,
|
||||
`0002-clang-cl-sibling-compiler-prefix.patch`) — `references/platforms.md`. Linux builds against
|
||||
**GTK3** by default
|
||||
- **wxWidgets 3.3.2, SoftFever fork.** `deps/wxWidgets/wxWidgets.cmake` fetches
|
||||
`https://github.com/SoftFever/Orca-deps-wxWidgets` at tag `v3.3.2` and builds it static
|
||||
(`-DwxBUILD_SHARED=OFF`); Flatpak builds build it shared. Linux builds against **GTK3** by default
|
||||
(`option(DEP_WX_GTK3 "Build wxWidgets against GTK3" ON)` in `deps/CMakeLists.txt`, `SLIC3R_GTK`
|
||||
default `"3"`, Flatpak uses gtk3). GTK2 exists only as an opt-out (`-DDEP_WX_GTK3=OFF`) and loses
|
||||
EGL, WebKit2 and DIP pixels. Code guarded for GTK should still compile on GTK2, but GTK3 under X11
|
||||
@@ -88,12 +85,11 @@ Contents: [The stack](#the-stack-orcas-gui-is-built-on) ·
|
||||
- **Orca's own widget library.** New UI code largely does not use raw wx controls: the owner-drawn
|
||||
widgets in `src/slic3r/GUI/Widgets/` (`Button`, `CheckBox`, `ComboBox`, `TextInput`, `SpinInput`,
|
||||
`SwitchButton`, `RadioGroup`, `Label`, `DialogButtons`, …) replace them. Reasons: native controls
|
||||
cannot follow Orca's look or its app-level dark-mode toggle; on Windows wx's native dark mode reaches
|
||||
the `TaskDialog()`-based boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`,
|
||||
`wxProgressDialog`) since 3.3.3 and the wrapped common dialogs (`wxColourDialog`, `wxFontDialog`, …)
|
||||
since 3.3.4, but the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications
|
||||
stay light (`interface/wx/app.h:1442-1448`), so Orca shows the `MsgDialog` family and its own generic
|
||||
`Widgets/ProgressDialog` for a consistent look; and on GTK the theme's borders bleed through wrapped native
|
||||
cannot follow Orca's look or its app-level dark-mode toggle; on Windows wx's native dark mode does
|
||||
not reach anything built on `TaskDialog()` (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`,
|
||||
`wxProgressDialog`) nor the wrapped common dialogs (`wxColourDialog`, `wxFontDialog`, …)
|
||||
(`interface/wx/app.h:1434-1443`), so Orca shows the `MsgDialog` family and its own generic
|
||||
`Widgets/ProgressDialog` instead; and on GTK the theme's borders bleed through wrapped native
|
||||
controls (the widgets call `RemoveButtonBorder` / `RemoveInputBorder` under `__WXGTK__`).
|
||||
Plain containers stay raw (`wxPanel`, `wxBoxSizer`, `wxScrolledWindow`).
|
||||
- **Namespaces.** Most widgets are in the global namespace; a few (`DialogButtons`, `HyperLink`,
|
||||
|
||||
@@ -67,10 +67,9 @@ Contents: [Rules](#rules) · [Why the library exists](#why-the-library-exists)
|
||||
Native controls cannot carry Orca's flat, rounded, palette-coloured look, and they cannot follow all of Orca's
|
||||
theming:
|
||||
- On Windows the theme is an app-level setting (Preferences "Enable dark Mode", Windows-only) layered on
|
||||
`MSWEnableDarkMode`; wx's MSW dark mode now reaches the `TaskDialog()`-based dialogs (`wxMessageBox`,
|
||||
`wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) since 3.3.3 and the common dialogs since 3.3.4,
|
||||
but the date/time pickers, `wxPrintDialog` on Windows 10 and MDI applications stay light
|
||||
(`interface/wx/app.h:1442-1448`). Hence the `MsgDialog` family (`references/windows-dialogs.md`) and Orca's own
|
||||
`MSWEnableDarkMode`, and wx's MSW dark mode does not reach `TaskDialog()`-based dialogs (`wxMessageBox`,
|
||||
`wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`), the common dialogs or the date/time pickers
|
||||
(`interface/wx/app.h:1436-1445`). Hence the `MsgDialog` family (`references/windows-dialogs.md`) and Orca's own
|
||||
`ProgressDialog`.
|
||||
- On macOS and Linux Orca follows the system appearance; native controls are themed by the toolkit, but any
|
||||
light palette colour set on them still needs the `UpdateDarkUI` pass (`references/colours-dark-mode.md`).
|
||||
|
||||
@@ -6,7 +6,7 @@ to write a custom control on the wx side and how to author an Orca widget on the
|
||||
`StaticBox`/`StateHandler` foundation. Read it before writing or reviewing any `wxEVT_PAINT`
|
||||
handler, `render`/`doRender` method, `messureSize`, or a new class under `src/slic3r/GUI/Widgets/`.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth).
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every
|
||||
wx assert below is compiled out and `wxCHECK*` returns silently, so paint misuse shows up only as
|
||||
wrong, missing or stale pixels, never as an assert dialog.
|
||||
@@ -116,7 +116,7 @@ Bind(wxEVT_PAINT, [this](wxPaintEvent&) {
|
||||
|
||||
| Situation | MSW | macOS | GTK3 (X11 and Wayland) | GTK2 (opt-out build) |
|
||||
|---|---|---|---|---|
|
||||
| Handler creates no `wxPaintDC` | `wxWindowMSW::HandlePaint` sees no DC and lets `DefWindowProc` validate the region: no `WM_PAINT` storm in 3.3.4, but drawing is unclipped and the contract is broken (`src/msw/window.cpp` `HandlePaint`) | no error; the window shows only what the background step left (undetermined under `wxBG_STYLE_PAINT`) | same | same |
|
||||
| Handler creates no `wxPaintDC` | `wxWindowMSW::HandlePaint` sees no DC and lets `DefWindowProc` validate the region: no `WM_PAINT` storm in 3.3.2, but drawing is unclipped and the contract is broken (`src/msw/window.cpp` `HandlePaint`) | no error; the window shows only what the background step left (undetermined under `wxBG_STYLE_PAINT`) | same | same |
|
||||
| Handler creates a DC, then `Skip()`s | default `OnPaint` (native painting) runs as well | — | — | — |
|
||||
| `wxPaintDC` outside a paint event | `wxCHECK_RET(!paintStack.empty(), "wxPaintDC can't be created outside wxEVT_PAINT handler")` (also for a window other than the one being repainted): the DC gets no HDC and every draw call is a silent no-op (`src/msw/dcclient.cpp` `wxPaintDCImpl::wxPaintDCImpl`) | no visible effect | `wxCHECK_RET(cr, "using wxPaintDC without being in a native paint event")`, every draw call is a silent no-op (`src/gtk/dc.cpp:518`) | works (GDK) |
|
||||
| `wxClientDC` drawing | works on non-composited windows; the doc lists "wxMSW when using double buffering" (`WS_EX_COMPOSITED`, see below) as non-working (`interface/wx/dcclient.h:82-84`), yet `CanBeUsedForDrawing` returns `true` unconditionally (`include/wx/msw/dcclient.h:60`), so it cannot detect that case | never; `CanBeUsedForDrawing` is `false` (`include/wx/osx/dcclient.h:58`) | X11 works; Wayland has no effect, `CanBeUsedForDrawing` is `false` on `wxDisplayWayland` (`src/gtk/dc.cpp` `wxClientDCImpl::CanBeUsedForDrawing`) | works |
|
||||
@@ -283,8 +283,8 @@ void MyCard::OnPaint(wxPaintEvent&) {
|
||||
ancestor up to the TLW (`src/msw/window.cpp` `wxWindowMSW::IsDoubleBuffered`);
|
||||
`SetDoubleBuffered(on)` toggles that style; `MSWDisableComposited()` removes it up the chain.
|
||||
`WS_EX_COMPOSITED` makes the whole subtree paint bottom-up into one buffer.
|
||||
- wx 3.3.0 enabled double buffering for all MSW windows (#22851, `docs/changes.txt:750`) and 3.3.2
|
||||
reverted it (#25808, `docs/changes.txt:501`). In 3.3.4 nothing in wx sets `WS_EX_COMPOSITED`
|
||||
- wx 3.3.0 enabled double buffering for all MSW windows (#22851, `docs/changes.txt:557`) and 3.3.2
|
||||
reverted it (#25808, `docs/changes.txt:308`). In 3.3.2 nothing in wx sets `WS_EX_COMPOSITED`
|
||||
unless the app calls `SetDoubleBuffered(true)`: MSW windows are not double-buffered by default,
|
||||
exactly as in 3.2.
|
||||
- macOS: always double-buffered, `IsDoubleBuffered()` returns `true` (`include/wx/osx/window.h:161`);
|
||||
@@ -439,7 +439,7 @@ Two variants exist:
|
||||
(`src/msw/gdiimage.cpp` `wxGDIImage::GetScaleFactor`, `src/msw/dcmemory.cpp`; details in
|
||||
`references/dpi-bitmaps-fonts.md §wxBitmap`), while `GetLogicalSize()` stays the physical size there.
|
||||
- MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC content scale; the size is
|
||||
physical (`docs/changes.txt:100-102`).
|
||||
physical (`docs/changes.txt:94-96`).
|
||||
|
||||
**Usage — portable back buffer.**
|
||||
```cpp
|
||||
@@ -556,7 +556,7 @@ already prepared ([Buffered painting](#buffered-painting-and-double-buffering)).
|
||||
must set the appropriate fonts and colours" (`interface/wx/dc.h:775-780`). `GetFont()` returns `wxNullFont` until
|
||||
`SetFont()` is called (`interface/wx/dc.h:715-720`).
|
||||
- wx 3.3 moved the non-drawing members to `wxReadOnlyDC` and several overridable measuring virtuals
|
||||
now take it (`docs/changes.txt:260-263`); see `references/wx-33-changes.md`.
|
||||
now take it (`docs/changes.txt:228-231`); see `references/wx-33-changes.md`.
|
||||
|
||||
**OrcaSlicer.** Orca widgets measure in `messureSize()`. A `messureSize()` that builds a
|
||||
`wxClientDC dc(this)` (`Button::messureSize`, `TextInput::messureSize`) measures correctly on every
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Platforms, the wx build, and platform-specific code
|
||||
|
||||
Read this when code has to differ per platform, when a bug shows up on only one OS, toolkit or display
|
||||
server, or when you need to know how Orca's wxWidgets is built. It covers the wx source pin and its build
|
||||
server, or when you need to know how Orca's wxWidgets is built. It covers the wx fork and its build
|
||||
options, platform macros, per-platform summaries that point into the topic files, and where
|
||||
platform-specific code lives. It also owns runtime X11/Wayland detection, the Wayland gap list, custom
|
||||
title bars and window decoration, GTK native-chrome removal, and the cross-platform test checklist.
|
||||
@@ -58,46 +58,37 @@ Contents: [Rules](#rules) · [The wx build Orca uses](#the-wx-build-orca-uses)
|
||||
|
||||
## The wx build Orca uses
|
||||
|
||||
### Source, pin and local patches
|
||||
### Source, pin and local patch
|
||||
|
||||
- `deps/wxWidgets/wxWidgets.cmake` builds upstream `https://github.com/wxWidgets/wxWidgets` at tag
|
||||
**`v3.3.4`** (`GIT_SHALLOW ON`, submodules `3rdparty/catch`, `3rdparty/pcre` and `3rdparty/libwebp`
|
||||
only). Two in-tree patches under `deps/wxWidgets/` are applied, one per platform, so neither applies
|
||||
on Linux: `0001-macos-use-srgb-colour-components.patch` on Apple and
|
||||
`0002-clang-cl-sibling-compiler-prefix.patch` on Windows. The clang-cl patch is the old fork's
|
||||
clang-cl fix ported back to a local patch: it changes the MSVC lib-dir selection in the installed
|
||||
`wxWidgetsConfig.cmake` so it looks for `<prefix>_<arch>_lib` (or `_dll`) under the consuming
|
||||
compiler's prefix first and then the sibling one (`clang` ↔ `vc`), because cl and clang-cl share an
|
||||
ABI and either can consume either build (`build/cmake/wxWidgetsConfig.cmake.in:53-73`). Because the
|
||||
pin is upstream, every other build fix must live as a local patch under `deps/wxWidgets/`.
|
||||
- **The Apple patch** is `deps/wxWidgets/0001-macos-use-srgb-colour-components.patch`, applied
|
||||
- `deps/wxWidgets/wxWidgets.cmake` builds `https://github.com/SoftFever/Orca-deps-wxWidgets` at tag
|
||||
**`v3.3.2`** (`GIT_SHALLOW ON`, submodules `3rdparty/catch`, `3rdparty/pcre` and `3rdparty/libwebp`
|
||||
only). The fork carries Orca's build fixes, the clang-cl fix among them; do not duplicate
|
||||
a fork fix as a local patch under `deps/wxWidgets/` (cc390f11ee removed the local
|
||||
`0001-Clang-CL-fix.patch` once the fork carried the fix). The fork's clang-cl fix is the MSVC lib-dir
|
||||
selection in the installed `wxWidgetsConfig.cmake`: it looks for `<prefix>_<arch>_lib` (or `_dll`)
|
||||
under the consuming compiler's prefix first and then the sibling one (`clang` ↔ `vc`), because cl
|
||||
and clang-cl share an ABI and either can consume either build
|
||||
(`build/cmake/wxWidgetsConfig.cmake.in:53-73`).
|
||||
- **The one local patch** is `deps/wxWidgets/0001-macos-use-srgb-colour-components.patch`, applied
|
||||
only `if (APPLE)`. The `PATCH_COMMAND` first runs `git checkout -f -- src/osx/cocoa/colour.mm` and
|
||||
then `git apply`, so the step can run again safely (a7775296b0). The patch makes the wxOSX
|
||||
`wxColour` component getters (`wxNSColorRefData::Red/Green/Blue/Alpha` and `IsSolid`) convert the
|
||||
`NSColor` with `[NSColorSpace sRGBColorSpace]` instead of `NSCalibratedRGBColorSpace`, so colours
|
||||
read back on macOS match their sRGB values (custom-colour accuracy). Colour usage:
|
||||
`references/colours-dark-mode.md`.
|
||||
- **The Windows patch** is `deps/wxWidgets/0002-clang-cl-sibling-compiler-prefix.patch`, applied only
|
||||
`if (WIN32)`. Its `PATCH_COMMAND` first runs
|
||||
`git checkout -f -- build/cmake/wxWidgetsConfig.cmake.in` and then `git apply`, so the step can run
|
||||
again safely. It rewrites the `WIN32_MSVC_NAMING` branch of `build/cmake/wxWidgetsConfig.cmake.in`
|
||||
to try the consuming compiler's prefix first and then the sibling one (`clang` ↔ `vc`) when locating
|
||||
the installed `<prefix>_<arch>_lib`/`_dll` directory.
|
||||
- The checked-out source is the tree that every wx citation in this skill refers to:
|
||||
`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets` on macOS and
|
||||
`deps/<tree>/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux and Windows (`deps/build` for a
|
||||
release build; `build_win.bat` names the others). Locate it with the bash or PowerShell lookup in
|
||||
`SKILL.md` §Ground truth. On macOS its
|
||||
`src/osx/cocoa/colour.mm` already has the Apple patch applied; on Windows its
|
||||
`build/cmake/wxWidgetsConfig.cmake.in` already has the clang-cl patch applied.
|
||||
`src/osx/cocoa/colour.mm` already has the patch applied.
|
||||
- **Flatpak builds wx separately.** `deps/CMakeLists.txt` leaves `dep_wxWidgets` out of the deps
|
||||
target when `FLATPAK` is set. Instead, `scripts/flatpak/com.orcaslicer.OrcaSlicer.yml` has its own
|
||||
`wxWidgets` module whose config-opts "mirror deps/wxWidgets/wxWidgets.cmake with FLATPAK=ON,
|
||||
DEP_WX_GTK3=ON": `-DwxBUILD_TOOLKIT=gtk3`, a shared build (`wxBUILD_SHARED=ON`,
|
||||
`BUILD_SHARED_LIBS=ON`, `d` debug postfix), and `wxUSE_LIBWEBP=sys`, because the builtin webp
|
||||
libraries are installed only by static builds. It links with lld and pins upstream `v3.3.4` at
|
||||
commit `6a42a4b999b127cf28a8b5fe686da6b8ca93a9b8`. Option and version changes must be made in both
|
||||
files; it applies neither patch (GTK3, no MSVC).
|
||||
libraries are installed only by static builds. It links with lld and pins the fork's tag
|
||||
`orca-3.3.2` at a fixed commit. Option and version changes must be made in both files.
|
||||
|
||||
### Toolkit per platform
|
||||
|
||||
@@ -197,7 +188,7 @@ Its `wx/gtk/private*` includes are commented out. The per-port headers outside t
|
||||
`wx/msw/private.h` (`BitmapComboBox.cpp`, `PresetComboBoxes.cpp`, Windows-guarded) and
|
||||
`wx/osx/private.h` (`Utils/MacDarkMode.mm`), come with the regular wx install. Private headers are
|
||||
port-specific and unversioned. Include one only under the same macro wx uses for that port or
|
||||
feature, and re-check it whenever the wx pin is bumped.
|
||||
feature, and re-check it whenever the fork is bumped.
|
||||
|
||||
### wxInspector
|
||||
|
||||
@@ -277,8 +268,8 @@ Each bullet names the mechanism and the file that owns it.
|
||||
popups with it are dismissed on deactivation, deferred through `CallAfter`. No key dismisses a
|
||||
popup, and `ProcessLeftDown` is never called. `PopupWindow::BindUnfocusEvent` is MSW-only →
|
||||
`references/popups-menus.md` §5.
|
||||
- **Painting:** in 3.3.4 windows are not double-buffered by default (the 3.3.0 global
|
||||
`WS_EX_COMPOSITED` was reverted, `docs/changes.txt:501`). Custom widgets buffer by hand →
|
||||
- **Painting:** in 3.3.2 windows are not double-buffered by default (the 3.3.0 global
|
||||
`WS_EX_COMPOSITED` was reverted, `docs/changes.txt:308`). Custom widgets buffer by hand →
|
||||
`references/painting-custom-widgets.md`.
|
||||
- **Building large panels:** every control is a native child window, and outside a sizer pass a move
|
||||
or resize is immediate, repainting when the window is shown [source: `src/msw/window.cpp:2036`
|
||||
@@ -293,9 +284,8 @@ Each bullet names the mechanism and the file that owns it.
|
||||
- **Mouse capture:** `wxEVT_MOUSE_CAPTURE_LOST` and `wxEVT_MOUSE_CAPTURE_CHANGED` are delivered →
|
||||
`references/mouse-keyboard-focus.md`.
|
||||
- **Controls:** `wxDataViewCtrl` is the generic implementation (`references/controls-dataview.md`).
|
||||
TaskDialog-based dialogs (since 3.3.3) and common dialogs (since 3.3.4) follow wx dark mode; the
|
||||
date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications stay light
|
||||
(`interface/wx/app.h:1442-1448`); Orca's `MsgDialog` family is owner-drawn →
|
||||
TaskDialog-based dialogs and common dialogs stay light in wx dark mode
|
||||
(`interface/wx/app.h:1434-1448`); Orca's `MsgDialog` family is owner-drawn →
|
||||
`references/windows-dialogs.md`.
|
||||
- **WebView:** Edge (WebView2). It needs the runtime (checked by `GUI_App::init_webview_runtime`),
|
||||
creates asynchronously, serves custom schemes as `https://<scheme>.wxsite`, and allows one script
|
||||
@@ -626,16 +616,16 @@ headers. Rows marked [source] or "protocol" come from the implementation or from
|
||||
| Area | What happens on Wayland | Cite | Orca handling / owner |
|
||||
|---|---|---|---|
|
||||
| Global pointer position | `wxGetMousePosition()` / `wxGetMouseState()` call `gdk_device_get_position` [source], but Wayland gives clients no global pointer position, so the result is not a screen position | `src/gtk/window.cpp` `wxGetMousePosition` | use event coordinates + `ClientToScreen` (`BBLTopbar`); dismiss popups by focus tracking (`SearchDialog::Dismiss`) → `references/mouse-keyboard-focus.md` |
|
||||
| Top-level position | the compositor places windows, and `SetPosition()` / `Move()` on a TLW have no effect (protocol). 3.3.4 added geometry restore through the Wayland XDG session-management protocol (`SaveGeometry`/`RestoreToGeometry` with the new `SaveString`/`RestoreString` fields) | `interface/wx/toplevel.h:439-455, 474-490` | `GUI_App::window_pos_restore` restores only size and maximised state; moves go through `gtk_window_begin_move_drag` |
|
||||
| Top-level position | the compositor places windows, and `SetPosition()` / `Move()` on a TLW have no effect (protocol) | — | `GUI_App::window_pos_restore` restores only size and maximised state; moves go through `gtk_window_begin_move_drag` |
|
||||
| Window icon | `SetIcon()` / `SetIcons()` "doesn't do anything when using Wayland … create a `.desktop` file" | `interface/wx/toplevel.h:517-521, 538-542` | `src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop` (`Icon=OrcaSlicer`, `StartupWMClass=orca-slicer`) |
|
||||
| App id | `wxAppConsole::SetClassName()` is the xdg `app_id` with GTK ≥ 3.24.22 (and the AUMID on Windows); it must be set before any TLW. wx applies it when a TLW is mapped, and only if it is non-empty [source: `wxTopLevelWindowGTK::GTKHandleMapped`] | `interface/wx/app.h:765-812` | Orca calls only `SetAppName(SLIC3R_APP_KEY)`, so GTK's default applies. On Windows, `SetClassName` would also change shell behaviour (MRU, Shift+middle-click) |
|
||||
| `wxClientDC` | deprecated in 3.3 ("please use wxInfoDC instead for obtaining information", `interface/wx/dcclient.h:43-46`). Drawing through it "simply doesn't have any effect" on GTK3/Wayland or wxOSX. `CanBeUsedForDrawing()` [source]: false on Wayland only for wxGTK (`src/gtk/dc.cpp`), always false on wxOSX, always true on wxMSW (`include/wx/{osx,msw}/dcclient.h`), although its doc also lists "wxMSW when using double buffering" | `interface/wx/dcclient.h:48-53, 80-88` | draw only in `wxPaintDC` after `Refresh()`/`RefreshRect()` → `references/painting-custom-widgets.md` |
|
||||
| `wxWindow::Update()` | "doesn't do anything in wxGTK port when using Wayland". [source] wx skips the GDK update calls there because they broke later updates (#25036) | `interface/wx/window.h:2405-2407` | never rely on `Update()` to paint synchronously |
|
||||
| `WarpPointer()` | works only if the compositor implements the pointer-warp protocol; mutter also needs a mouse button held | `interface/wx/window.h:3902-3907`; `docs/changes.txt:487` | Orca never warps the pointer |
|
||||
| `WarpPointer()` | works only if the compositor implements the pointer-warp protocol; mutter also needs a mouse button held | `interface/wx/window.h:3902-3907`; `docs/changes.txt:294` | Orca never warps the pointer |
|
||||
| `wxUIActionSimulator` | "currently doesn't work when using Wayland with wxGTK" | `interface/wx/uiaction.h:20` | not used; Orca also builds `wxUSE_XTEST=OFF` |
|
||||
| `wxBitmap(const wxCursor&)` | creates an invalid bitmap | `interface/wx/bitmap.h:393-395` | — |
|
||||
| OpenGL | only EGL; `PreferGLX()` has no effect. Without EGL in the build, wxGTK's `wxGLCanvas` shows a fatal message and refuses to work [source: `src/gtk/glcanvas.cpp` `IsAvailable`]. The EGL surface is a subsurface over the canvas, ready only after map and a frame callback [source: `src/unix/glegl.cpp`] | `interface/wx/glcanvas.h:1094-1095` | `CLI::run` forces X11 when wx lacks EGL; overlays are drawn in GL/ImGui, never as wx children over the canvas → `references/webview-gl-aui-media.md` |
|
||||
| AUI | the doc note "live resize is always used … for wxOSX and wxGTK3 when using Wayland" is obsolete: "As of wxWidgets 3.3.0 this function always returns false", and `wxAUI_MGR_LIVE_RESIZE` is in the default flags. AUI's docking-hint logic still needs global positions; 3.3.4 added system drag sessions (`StartDragSession`) so wx can move a pane's floating frame under Wayland anyway | `interface/wx/aui/framemanager.h:336-345`; `src/aui/framemanager.cpp:4574-4599` | `Plater::priv::priv` clears `wxAUI_MGR_ALLOW_FLOATING`; `sanitize_window_layout_for_wayland` strips floating state from the saved layout → `references/webview-gl-aui-media.md` §wxAuiManager |
|
||||
| AUI | the doc note "live resize is always used … for wxOSX and wxGTK3 when using Wayland" is obsolete: "As of wxWidgets 3.3.0 this function always returns false", and `wxAUI_MGR_LIVE_RESIZE` is in the default flags. Floating panes need global positions | `interface/wx/aui/framemanager.h:336-345` | `Plater::priv::priv` clears `wxAUI_MGR_ALLOW_FLOATING`; `sanitize_window_layout_for_wayland` strips floating state from the saved layout → `references/webview-gl-aui-media.md` §wxAuiManager |
|
||||
| Popups | a GTK popup is an `xdg_popup` only for COMBO/DROPDOWN/POPUP_MENU hints, so wx creates popups with `GDK_WINDOW_TYPE_HINT_COMBO` [source]. A chained popup's parent must be the mapped popup, and mapping it with a grab deactivates the toplevel (Orca's comments in `DropDown::mouseMove`, `DropDown::ShouldDismissOnTopWindowDeactivate`) | `src/gtk/popupwin.cpp:110-114` | `DropDown` transient-for chain, `ShouldDismissOnTopWindowDeactivate` → `references/popups-menus.md` |
|
||||
| Fractional scale | arrives as an integer GDK scale | `docs/doxygen/overviews/high_dpi.md:348-351` | → `references/dpi-bitmaps-fonts.md` |
|
||||
| Window decorations | some desktop environments draw a title bar on undecorated windows anyway | — | [Undecorated windows](#wayland-undecorated-top-level-windows-splash) |
|
||||
@@ -646,9 +636,7 @@ were two-finger scrolling (703, 3.1.3), the EGL-based `wxGLCanvas` for Wayland (
|
||||
support (498, both 3.1.5). New with the upgrade: "Many bug fixes for Wayland-specific problem" (397)
|
||||
and a `wxMediaCtrl` fix (402) in 3.1.6, GDK errors from `PopupMenu()` avoided (317, 3.1.7),
|
||||
`wxCURSOR_SIZING` fixed (261, 3.2.0). In 3.3, `WarpPointer()` on supported compositors
|
||||
(`docs/changes.txt:487`) and the EGL/Wayland high-DPI scale fix (`changes.txt:731`); in 3.3.4, a fix for
|
||||
the crash on pointer/seat disconnect (`changes.txt:371`), AUI pane dragging via system drag sessions
|
||||
(`:372`) and window-geometry restore via XDG session management (`:373`).
|
||||
(`docs/changes.txt:294`) and the EGL/Wayland high-DPI scale fix (`changes.txt:538`).
|
||||
|
||||
## Window decoration and custom title bars
|
||||
|
||||
@@ -670,7 +658,7 @@ There is no `wxCAPTION` on any platform; each port then needs its own handling.
|
||||
**Contract** [source + documented]. Since wx **3.3.0**, `wxTopLevelWindowMSW::MSWGetStyle` adds
|
||||
`WS_CAPTION` whenever any of `wxCAPTION | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX` is set
|
||||
(`src/msw/toplevel.cpp:133-135`). The 3.3.0 wxMSW change list says "Turn wxCAPTION on automatically if
|
||||
required by other styles (#23575)" (`docs/changes.txt:774`). Commit eefdabcd98 attributes this to
|
||||
required by other styles (#23575)" (`docs/changes.txt:581`). Commit eefdabcd98 attributes this to
|
||||
3.3.2, but it is a 3.3.0 change. `SetWindowStyleFlag()` recomputes the native style through
|
||||
`MSWGetStyle` and turns the bits back on (`src/msw/window.cpp` `wxWindowMSW::MSWUpdateStyle`).
|
||||
|
||||
@@ -880,11 +868,10 @@ do both). Cite: 6148ba16b3, 988b500f33.
|
||||
|
||||
## wx 3.3 migration notes
|
||||
|
||||
Orca moved from **3.1.5** to **3.3.2** in 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system
|
||||
in 2d7e26292b); the pin is now upstream **3.3.4** (`deps/wxWidgets/wxWidgets.cmake`, tag `v3.3.4`), so
|
||||
the 3.1.6–3.2.0 incompatible changes (`docs/changes_32.txt`) apply as well as `docs/changes.txt`. The
|
||||
version digest, every migration commit as a rule for new code, and the post-upgrade regressions to
|
||||
watch are owned by `references/wx-33-changes.md` (§10); the 3.3.3 and 3.3.4 changes are its §8–§9. The
|
||||
Orca moved from **3.1.5** to 3.3.2 in 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system
|
||||
in 2d7e26292b), so the 3.1.6–3.2.0 incompatible changes (`docs/changes_32.txt`) apply as well as
|
||||
`docs/changes.txt`. The version digest, every migration commit as a rule for new code, and the
|
||||
post-upgrade regressions to watch are owned by `references/wx-33-changes.md` (§8). The
|
||||
platform-specific ones are described in this file: the `MainFrame` `WS_CAPTION` handling
|
||||
([MSW title bar](#msw-the-mainframe-custom-title-bar)), the macOS `kAEGetURL` re-registration and the
|
||||
`SidePopup` anchoring ([ifdef landscape](#the-ifdef-landscape)), and the GTK criticals filter
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Popups, transient windows and menus
|
||||
|
||||
How wx 3.3.4 popups and menus behave on each port, and the Orca wrappers built on them: `PopupWindow`,
|
||||
How wx 3.3.2 popups and menus behave on each port, and the Orca wrappers built on them: `PopupWindow`,
|
||||
`DropDown`/`ComboBox`, `SidePopup`, the search popups, frameless dialogs used as popups, `MenuFactory`,
|
||||
`append_menu_item`, `Plater::PopupMenu` and the macOS menubar versus `BBLTopbar`. Read it before you add
|
||||
or change anything that opens over other UI and must close by itself, or any context menu or menubar item.
|
||||
@@ -570,6 +570,11 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
- On GTK the sub binds an idle handler. While both are shown and the cursor is over main but not over the
|
||||
sub, it builds a `wxMouseEvent(wxEVT_MOTION)` from `wxGetMousePosition()`, calls `mainDropDown->mouseMove(ev)`
|
||||
**directly** (nothing is posted), and calls `e.RequestMore()` to keep polling.
|
||||
- On GTK the sub also receives the wheel events the user raises over main: GDK sends a `wl_pointer.axis` only
|
||||
to the surface the pointer is over, and GTK then hands that `GDK_SCROLL` to the chain's grab widget, which is
|
||||
the sub. The sub installs a `GtkWidget::event` handler that replays the event on main with
|
||||
`gtk_propagate_event(main->GetConnectWidget(), event)` when the event's GDK window belongs to main
|
||||
(`DropDown::messureSize`).
|
||||
- **GTK wrapper sizing.** `messureSize` calls `gtk_window_resize` on `m_widget` after `SetSize`, because wxGTK's
|
||||
`DoSetSize` only uses `gtk_widget_set_size_request` ("gtk_window_resize does not work for GTK_WINDOW_POPUP",
|
||||
`src/gtk/popupwin.cpp:181-188`).
|
||||
@@ -607,6 +612,32 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
```
|
||||
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
||||
`PopupWindow::ShouldDismissOnTopWindowDeactivate`).
|
||||
- **Rule:** On GTK, route a chained popup's wheel event by the GDK window it names, not by the grab widget and not
|
||||
by the global cursor position.
|
||||
**Why:** While the sub holds the grab, `gtk_main_do_event` hands every `GDK_SCROLL` of the chain to the grab
|
||||
widget (the sub) with its original, un-rebased coordinates [source] (`gtkmain.c:1789-1792`, 1923-1924), while
|
||||
GDK Wayland sends the axis to the surface under the pointer only (`pointer_handle_axis` → `create_scroll_event`,
|
||||
`gdkdevice-wayland.c:1929, 1527`). The sub therefore sees and consumes wheel events raised over main, so the
|
||||
main list never scrolls, on X11 and Wayland alike. A hit test against `wxGetMousePosition()` cannot replace the
|
||||
window test: global coordinates are unreliable on Wayland (`mouse-keyboard-focus.md`). `DropDown` connects a
|
||||
`GtkWidget::event` handler on the sub's `GtkWindow` (its `m_widget`), which is emitted before wx's
|
||||
`::scroll-event` handler on the same widget and suppresses it when it returns TRUE (`gtkwidget.c:7696`), then
|
||||
delivers the event with `gtk_propagate_event(main->GetConnectWidget(), event)`. Returning TRUE there also stops
|
||||
the parent-chain propagation.
|
||||
```cpp
|
||||
// Wrong: emitted on the popup's outer GtkWindow, where wx has no handlers at all
|
||||
g_signal_emit_by_name(main->m_widget, "scroll-event", event, &handled);
|
||||
// Right (wxGTK): replay GTK's own delivery on the widget wx listens to
|
||||
gtk_propagate_event(main->GetConnectWidget(), event);
|
||||
```
|
||||
**Where wx listens [source].** `wxPopupWindow::Create` builds a popup from two widgets: `m_widget` is the outer
|
||||
`GtkWindow` and `m_wxwindow` is a `wxPizza` child added to it. `wxWindowGTK::GetConnectWidget()` is
|
||||
`m_wxwindow ? m_wxwindow : m_widget`, and `ConnectWidget()` — which binds `button_press_event`, `motion_notify_event`
|
||||
and `scroll_event` — is called on that widget (`src/gtk/window.cpp` `Create`, `GetConnectWidget`). A signal emitted
|
||||
on the outer `GtkWindow` therefore reaches no wx handler; only events GTK delivers to the inner widget, or to a
|
||||
descendant of the grab widget, are seen by wx. `GTKGetConnectWindow()`/`GTKGetMainWindow()` are the matching
|
||||
GdkWindows.
|
||||
Cite: `DropDown::messureSize`, `DropDown::mouseWheelMoved`.
|
||||
- **Rule:** When shrinking a dropdown to fit above the display bottom, give up if fewer than two rows would
|
||||
remain.
|
||||
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
||||
@@ -791,7 +822,7 @@ created with `wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxF
|
||||
`wxWindow`. "The window automatically destroys itself when the user clicks on it or it loses the focus"
|
||||
(`interface/wx/tipwin.h:13-14`). Since 3.3.2 use `static Ref New(parent, text, maxLength, rectBound)`. The
|
||||
returned `wxTipWindow::Ref` is "guaranteed to become invalid when the tip window is closed", and the raw
|
||||
constructor is deprecated (`tipwin.h:27-132`; `docs/changes.txt:479`). `Create` positions the tip at the mouse
|
||||
constructor is deprecated (`tipwin.h:27-132`; `docs/changes.txt:286`). `Create` positions the tip at the mouse
|
||||
and pops it up immediately (`src/generic/tipwin.cpp:171-240`); on GTK it also captures the mouse.
|
||||
`SetBoundingRect` closes the tip when the mouse leaves that rectangle (`tipwin.h:160-172`). Orca:
|
||||
`Button::OnParentMotion` (MSW-only tooltips for disabled buttons) keeps a `wxTipWindow::Ref`.
|
||||
@@ -800,11 +831,11 @@ created with `wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxF
|
||||
`wxRichToolTipPopup` (`src/generic/richtooltipg.cpp`). Re-check it on every wx update.
|
||||
- **`wxInfoBar`** "calls its parent wxWindow::Layout() method and assumes that it will change the parent
|
||||
layout": add it to the parent's sizer (`interface/wx/infobar.h:38-44`). It is generic on all ports and native
|
||||
in wxGTK. Use `ShowMessage(msg, flags)`/`Dismiss()`. 3.3 adds `ShowCheckBox` (`docs/changes.txt:653`).
|
||||
in wxGTK. Use `ShowMessage(msg, flags)`/`Dismiss()`. 3.3 adds `ShowCheckBox` (`docs/changes.txt:460`).
|
||||
- **`wxNotificationMessage`** is "not a window", native on MSW, macOS and GTK (`interface/wx/notifmsg.h:12-18`).
|
||||
MSW recommends `MSWUseToasts()` before the first notification, macOS needs the alert notification style for
|
||||
actions, and GTK ignores the timeout for warnings and errors (`notifmsg.h:23-38`, 179-182). 3.3 reports the
|
||||
dismissal reason (`docs/changes.txt:681`). Orca does not use it. Its in-app notifications are the GL-canvas
|
||||
dismissal reason (`docs/changes.txt:488`). Orca does not use it. Its in-app notifications are the GL-canvas
|
||||
`NotificationManager`.
|
||||
|
||||
## 13. wxMenu and wxMenuItem
|
||||
@@ -862,7 +893,7 @@ submenu (it leaks), `Destroy(id)` deletes it, and `Remove()` returns ownership (
|
||||
an item switch it to owner-drawn (`MSWMustUseOwnerDrawn`, `src/msw/menuitem.cpp:1371-1377`). "Owner drawn items
|
||||
are also incompatible with dark mode support and using them makes the entire menu containing them to use
|
||||
standard light mode colours even in dark mode." 3.3.2 reworked MSW dark-mode menu rendering
|
||||
(`docs/changes.txt:498-499`); owner-drawn items still opt out of it.
|
||||
(`docs/changes.txt:305-306`); owner-drawn items still opt out of it.
|
||||
|
||||
**Labels and accelerators** (`menuitem.h:460-490`, 555-570).
|
||||
- The syntax is `"&Label\tCtrl+X"`; `&&` is a literal ampersand. `CTRL` means Cmd on macOS, and `RAWCTRL` gives
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sizers and layout
|
||||
|
||||
How wx 3.3.4 computes window sizes and lays out children, and how OrcaSlicer builds layouts on top of
|
||||
How wx 3.3.2 computes window sizes and lays out children, and how OrcaSlicer builds layouts on top of
|
||||
that: sizers and flags, best/min size, the fitting functions (`SetSizerAndFit`, `SetSizeHints`, `Fit`,
|
||||
`Layout`, `FitInside`) per platform, show/hide relayout, Freeze/Thaw, scrolled windows, `wxStaticText`
|
||||
wrapping, layout on DPI change, and Orca's layout idioms. Read it when building or reviewing any dialog
|
||||
@@ -604,7 +604,7 @@ Ordinary dialogs and the sidebar use plain `wxScrolledWindow`.
|
||||
without spaces never wraps with `wxStaticText::Wrap`.
|
||||
- `wxST_WRAP` (new in 3.3.2): "Wrap label text on multiple lines if necessary, using the available
|
||||
horizontal space. This style only works when the control is used inside a sizer"
|
||||
(`interface/wx/stattext.h:46-49`; `docs/changes.txt:473`). It is opt-in; labels without it are
|
||||
(`interface/wx/stattext.h:46-49`; `docs/changes.txt:280`). It is opt-in; labels without it are
|
||||
unaffected. It is implemented with `GetMinSizeFromKnownDirection` (`stattextcmn.cpp:285-312`).
|
||||
**[source]** The initial `CalcMin` still uses the unwrapped best size, so a fitted dialog grows to the
|
||||
full one-line width and nothing wraps; constrain the width another way (a fixed or min width on the
|
||||
|
||||
@@ -44,8 +44,7 @@ Contents: [Rules](#rules) · [Build facts](#build-facts-that-decide-string-behav
|
||||
12. One complete sentence per msgid, placeholders instead of concatenated fragments, the number
|
||||
placeholder in both plural forms, `// TRN` for ambiguous strings, a context for homonyms.
|
||||
→ §Plurals and contexts
|
||||
13. A wrapper that converts a translation (`wxString::FromUTF8`, `ToUTF8()`, `format_wxstr`) returns
|
||||
by value; `wxGetTranslation()` itself returns `const wxString&`. → §Translation
|
||||
13. A wrapper around `wxGetTranslation` returns `wxString` by value. → §Translation
|
||||
14. User-supplied text (preset, filament, file, printer names) in a mnemonic-interpreting label
|
||||
goes through `SetLabelText` or `wxControl::EscapeMnemonics`. → §Labels
|
||||
15. File dialogs: filters from `file_wildcards(FT_*)`, default dir `from_u8(app_config->get_last_dir())`,
|
||||
@@ -187,11 +186,11 @@ was the splash screen's event filter, fixed in 4088a36095; see `references/threa
|
||||
**modifies the string**. Use `s[n].GetValue()` or an explicit `int`/`wchar_t` type
|
||||
(`interface/wx/string.h:171-226`).
|
||||
- Since 3.3 `wxstr = {"Hello", 2}` is ambiguous (the `string_view` constructor); write
|
||||
`wxString{"Hello", 2}` (`docs/changes.txt:223-226`).
|
||||
`wxString{"Hello", 2}` (`docs/changes.txt:191-194`).
|
||||
- Never pass a `wxString`, `c_str()` or `mb_str()` to a real C vararg function (`printf`); use
|
||||
`wxString::Format`/`wxPrintf` or convert explicitly (`interface/wx/string.h:262-300`).
|
||||
- `wxUSE_STL` no longer exists; wx 3.3 re-enables implicit `wxString` → `std::string` only through
|
||||
`wxUSE_STD_STRING_CONV_IN_WXSTRING=1` (`docs/changes.txt:195-198`). Orca keeps it 0 and adds
|
||||
`wxUSE_STD_STRING_CONV_IN_WXSTRING=1` (`docs/changes.txt:163-166`). Orca keeps it 0 and adds
|
||||
`wxNO_UNSAFE_WXSTRING_CONV`; do not enable either conversion, the compile error is the guard.
|
||||
|
||||
### Pitfalls
|
||||
@@ -323,13 +322,10 @@ language's decimal separator; `wxString::ToCDouble`/`FromCDouble` do not.
|
||||
**wx contract** (`interface/wx/translation.h:577-640`): `wxGetTranslation(string, domain = "",
|
||||
context = "")` returns the original string when no catalog has it; a non-empty context needs a
|
||||
matching `msgctxt` in the catalog; the plural overload returns `string` for `n == 1` and `plural`
|
||||
otherwise when no catalog is found; "This function is thread-safe". It returns **`const wxString&`**
|
||||
(`interface/wx/translation.h:615`; `include/wx/translation.h:277-309`). 3.3.2 changed this to a
|
||||
by-value `wxString` and **3.3.3 reverted it** to the 3.2.x reference, so the reference points at the
|
||||
catalog's own string or at `wxTranslations::GetUntranslatedString()`, both of which outlive the call.
|
||||
Forwarding that reference is therefore safe; what must return by value is a wrapper that constructs a
|
||||
temporary — `wxString::FromUTF8`, `ToUTF8()`, `format_wxstr`. Orca's `I18N::translate` overloads
|
||||
return `wxString` by value and `translate_utf8`/`_u8L` return `std::string`.
|
||||
otherwise when no catalog is found; "This function is thread-safe". Since 3.3 it returns
|
||||
**`wxString` by value**, not a const reference: "please change the return type of the function to
|
||||
wxString" (`docs/changes.txt:139-142`). Orca's `I18N::translate` overloads already return by value;
|
||||
keep any new wrapper that way, a `const wxString&` return now dangles.
|
||||
|
||||
**OrcaSlicer macros** (`src/slic3r/GUI/I18N.hpp`):
|
||||
|
||||
@@ -433,7 +429,7 @@ using this class doesn't affect the native controls and dialogs", and on macOS 1
|
||||
C locale can break the menus (`interface/wx/intl.h:282-292`): native file dialogs, the app menu and
|
||||
standard buttons follow the system language. `wxLocale::IsAvailable` builds a region tag and asks
|
||||
`wxUILocale(...).IsSupported()` (`src/common/intl.cpp` `wxLocale::IsAvailable`), which on Unix no
|
||||
longer falls back to another region of the same language (`docs/changes.txt:81-85`); keep the
|
||||
longer falls back to another region of the same language (`docs/changes.txt:75-79`); keep the
|
||||
fallbacks in `load_language`.
|
||||
|
||||
- **Rule:** translate at use; never store a translated string in a namespace-scope or function-local
|
||||
@@ -461,7 +457,7 @@ call `SetLabel(label)`) [source]. `SetLabelMarkup` also treats an
|
||||
unescaped `&` as a mnemonic, needs `&`/`<` for literal characters, strips the markup where it
|
||||
is unsupported, and leaves the label unchanged (returns false) when the string is not well-formed
|
||||
(`interface/wx/control.h:200-360`); user text inside markup must be XML-escaped.
|
||||
Since 3.3 wxListbook/wxChoicebook also interpret mnemonics in page titles (`docs/changes.txt:134-136`);
|
||||
Since 3.3 wxListbook/wxChoicebook also interpret mnemonics in page titles (`docs/changes.txt:128-130`);
|
||||
Orca uses neither, but the rule is the same for every book control.
|
||||
|
||||
**OrcaSlicer.** `Label` (`Widgets/Label.cpp`) derives from `wxStaticText`; `Label::SetLabel` stores
|
||||
@@ -499,7 +495,7 @@ so build it from the wide form (`into_path(w).wstring()`) or stay with boost. At
|
||||
expands `$VAR`/`%VAR%` inside file names (`interface/wx/filename.h:90-103`); use `MakeAbsolute()` or
|
||||
explicit `wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE`. 3.3 adds `IsMSWExtendedLengthPath()` for `\\?\`
|
||||
paths "avoiding the 260 character path length restriction" (`interface/wx/filename.h:1040-1051`,
|
||||
`docs/changes.txt:744`). `wxFileSystem::FileNameToURL`/`URLToFileName` convert to and from `file:`
|
||||
`docs/changes.txt:551`). `wxFileSystem::FileNameToURL`/`URLToFileName` convert to and from `file:`
|
||||
URLs (`interface/wx/filesys.h:96-103, 175-180`).
|
||||
|
||||
**`wxStandardPaths`** (`interface/wx/stdpaths.h`): the directories "may or may not exist"
|
||||
@@ -556,9 +552,9 @@ for (const wxString& p : paths)
|
||||
|
||||
| | Behaviour |
|
||||
|---|---|
|
||||
| MSW | New-style `IFileDialog`: wx calls `SetDefaultExtension` with the selected filter's first extension and no longer calls `AppendExtension` itself (`src/msw/filedlg.cpp:1644-1656, 1738-1740`; `docs/changes.txt:749`). `SetExtraControlCreator` → XP-style dialog. |
|
||||
| MSW | New-style `IFileDialog`: wx calls `SetDefaultExtension` with the selected filter's first extension and no longer calls `AppendExtension` itself (`src/msw/filedlg.cpp:1644-1656, 1738-1740`; `docs/changes.txt:556`). `SetExtraControlCreator` → XP-style dialog. |
|
||||
| macOS | Open dialogs show **no filter choice** and apply all wildcards at once unless `wxSystemOptions::SetOption(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES, 1)`, and even then non-matching files are only greyed (`interface/wx/filedlg.h:117-128`). Matching compares the lower-cased last path extension (`wxOpenSavePanelDelegate panel:shouldEnableURL:`, `src/osx/cocoa/filedlg.mm:58-88`) [source]: case-insensitive, and a multi-dot pattern such as `*.gcode.3mf` or `*.zip.amf` never matches by itself. `wxFD_OVERWRITE_PROMPT` is always on (`:172-175`); `wxFD_OPEN` always behaves as `wxFD_FILE_MUST_EXIST` (`:184-189`). The save panel replaces the initial file name's extension with the first one in the wildcard (Orca's comment on `file_wildcards`). The native panel runs `runModal` and does not re-raise the parent dialog afterwards (`src/osx/cocoa/filedlg.mm` `ShowModal`); see `references/windows-dialogs.md` for the deferred re-raise. |
|
||||
| GTK3 | `GtkFileChooserNative` (portal-capable, e.g. Flatpak) when GTK ≥ 3.20 at runtime and neither `wxFD_PREVIEW` nor an extra control/customize hook is used (`src/gtk/filedlg.cpp:265-274, 437-443`; `src/gtk/dirdlg.cpp:122-130`; `docs/changes.txt:586`). It runs through `gtk_native_dialog_run`: there is no wx window, so size/position calls do nothing (`wxFileDialog::DoSetSize` is empty). Patterns go to `gtk_file_filter_add_pattern` per token and are **case-sensitive** (`src/gtk/filectrl.cpp:169`). The chooser is transient for the parent left after `GetParentForModalDialog`, which replaces a null (or hidden, dying or `wxWS_EX_TRANSIENT`) parent with the active top-level window, else the app's main top window (`src/gtk/filedlg.cpp:206, 223-225`; `src/common/dlgcmn.cpp:180-203` `DoGetParentForDialog`) [source]. `wxFD_PREVIEW` is GTK-only (`interface/wx/filedlg.h:195-197`). |
|
||||
| GTK3 | `GtkFileChooserNative` (portal-capable, e.g. Flatpak) when GTK ≥ 3.20 at runtime and neither `wxFD_PREVIEW` nor an extra control/customize hook is used (`src/gtk/filedlg.cpp:265-274, 437-443`; `src/gtk/dirdlg.cpp:122-130`; `docs/changes.txt:393`). It runs through `gtk_native_dialog_run`: there is no wx window, so size/position calls do nothing (`wxFileDialog::DoSetSize` is empty). Patterns go to `gtk_file_filter_add_pattern` per token and are **case-sensitive** (`src/gtk/filectrl.cpp:169`). The chooser is transient for the parent left after `GetParentForModalDialog`, which replaces a null (or hidden, dying or `wxWS_EX_TRANSIENT`) parent with the active top-level window, else the app's main top window (`src/gtk/filedlg.cpp:206, 223-225`; `src/common/dlgcmn.cpp:180-203` `DoGetParentForDialog`) [source]. `wxFD_PREVIEW` is GTK-only (`interface/wx/filedlg.h:195-197`). |
|
||||
| GTK2 (opt-out build) | never uses the native chooser; patterns case-sensitive. |
|
||||
|
||||
**OrcaSlicer:**
|
||||
@@ -654,7 +650,7 @@ dtor, and `!lock` tests `IsOpened()`.
|
||||
|
||||
| | Behaviour |
|
||||
|---|---|
|
||||
| MSW | `SetData`/`GetData` do not check `Open()` (`src/msw/clipbrd.cpp` `wxClipboard::SetData`), so code that forgets it works here only. `wxTextDataObject::SetData()` size must now include the 2-byte NUL (`docs/changes.txt:68-72`); use `SetText()` or the ctor. |
|
||||
| MSW | `SetData`/`GetData` do not check `Open()` (`src/msw/clipbrd.cpp` `wxClipboard::SetData`), so code that forgets it works here only. `wxTextDataObject::SetData()` size must now include the 2-byte NUL (`docs/changes.txt:62-66`); use `SetText()` or the ctor. |
|
||||
| macOS | without `Open()`, `SetData`/`AddData`/`GetData` return false through `wxCHECK_MSG(m_open, …)` without taking the object (`src/osx/carbon/clipbrd.cpp:84-105, 140-147`); a successful write is flushed to the pasteboard immediately (`:110-114`). |
|
||||
| GTK | same `wxCHECK_MSG(m_open, …)` early return (`src/gtk/clipbrd.cpp:643-647, 775`). `GetData`/`IsSupported` are asynchronous underneath: `wxClipboardSync` spins `YieldFor(wxEVT_CATEGORY_CLIPBOARD)` until GTK answers and forbids re-entrancy (`src/gtk/clipbrd.cpp:68-92`). PRIMARY selection exists. Under Wayland, Wayland MIME types are advertised next to the X11 atoms (`src/gtk/clipbrd.cpp:655-700`). |
|
||||
|
||||
@@ -797,8 +793,8 @@ from a non-main thread on wxOSX (`:39-40`); `wxCANCEL_DEFAULT` is ignored on wxO
|
||||
`wxID_OK/wxID_CANCEL/wxID_YES/wxID_NO/wxID_HELP` (`:269-275`), but **`wxMessageBox()` returns
|
||||
`wxYES/wxNO/wxCANCEL/wxOK/wxHELP`** (`:308-312`). `wxRichMessageDialog` is native only on MSW and
|
||||
generic elsewhere (`interface/wx/richmsgdlg.h:18-23`). On MSW every `TaskDialog`-based dialog
|
||||
(`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) has followed dark mode
|
||||
since 3.3.3 (`interface/wx/app.h:1442-1448`).
|
||||
(`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) ignores dark mode
|
||||
(`interface/wx/app.h:1436-1440`).
|
||||
|
||||
**OrcaSlicer.** UI code uses the MsgDialog family (`MessageDialog`, `RichMessageDialog`,
|
||||
`WarningDialog`, `ErrorDialog`, `InfoDialog`; `references/windows-dialogs.md` §MsgDialog family) for
|
||||
@@ -852,7 +848,7 @@ Orca does not use `wxConfig`/`wxFileConfig` (so the 3.3 change of the Unix defau
|
||||
|
||||
**Contract** (`interface/wx/secretstore.h`): on Unix it needs libsecret and a running secret service, so
|
||||
always check `IsOk(&errmsg)` (`:193-210, 257`); libsecret is no longer required at run time
|
||||
(`docs/changes.txt:726`), so a missing library or service shows up only as `IsOk()` false.
|
||||
(`docs/changes.txt:533`), so a missing library or service shows up only as `IsOk()` false.
|
||||
`GetDefault()` "may show a dialog to the user under some platforms, so it can take an arbitrarily long
|
||||
time to return" (`:243-246`). One username per service (`:260-268`).
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ yields and nested event loops, progress dialogs, startup, shutdown and exception
|
||||
screen, and the access rules for `wxGetApp()` and `app_config`. Read it whenever code runs off the
|
||||
main thread, defers work, starts a timer, yields, shows progress, or runs during startup or shutdown.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth).
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`, so `wxASSERT`
|
||||
is compiled out and `wxCHECK*` returns silently: a timer started off the main thread never fires on macOS,
|
||||
`Exit()` on a loop that is not the active one is ignored, `Start(0)` on macOS fails — all without
|
||||
@@ -454,12 +454,12 @@ and aborts the whole app. Wrap the entire body; log and convert to a status the
|
||||
`wxMessageQueue::Post(T&&)` accepts move-only types (`interface/wx/msgqueue.h:90-97`).
|
||||
- `wxTHREAD_WAIT_DEFAULT` is now `wxTHREAD_WAIT_BLOCK`; `wxTHREAD_WAIT_YIELD` "is dangerous" and "is
|
||||
only implemented in wxMSW and simply ignored under the other platforms" (`interface/wx/thread.h:648-675`),
|
||||
so the 3.3 default change (`docs/changes.txt:45-47`) only affects MSW code calling
|
||||
so the 3.3 default change (`docs/changes.txt:39-41`) only affects MSW code calling
|
||||
`Wait()`/`Delete()` from the GUI thread.
|
||||
- `wxCRITICAL_SECTION(name)` declares a function-static critical section, which is initialised
|
||||
safely only if the function is first called from the main thread before other threads call it;
|
||||
otherwise make it a global (`interface/wx/thread.h:1762-1779`). Prefer `std::mutex`.
|
||||
- `wxSecretStore` documents no thread-affinity notes in 3.3.4 (`interface/wx/secretstore.h`).
|
||||
- `wxSecretStore` documents no thread-affinity notes in 3.3.2 (`interface/wx/secretstore.h`).
|
||||
|
||||
## The Jobs framework
|
||||
|
||||
@@ -829,8 +829,8 @@ nothing) (`interface/wx/utils.h:1203-1212, 1243-1248`; see `references/strings-i
|
||||
|
||||
**Platforms.** On MSW `wxProgressDialog` is native: a TaskDialog run by a
|
||||
`wxProgressDialogTaskRunner` **wxThread**, yielding `ALL & ~USER_INPUT` (TIMER and THREAD included)
|
||||
(`src/msw/progdlg.cpp` `DispatchEvents`) **[source]**; the native `TaskDialog` follows wx dark mode
|
||||
since 3.3.3 (`interface/wx/app.h:1442-1448`). On macOS and GTK it is `wxGenericProgressDialog`, which creates
|
||||
(`src/msw/progdlg.cpp` `DispatchEvents`) **[source]**; anything based on `TaskDialog()` has no dark
|
||||
mode (`interface/wx/app.h:1436-1440`). On macOS and GTK it is `wxGenericProgressDialog`, which creates
|
||||
a temporary event loop if none is active (`src/generic/progdlgg.cpp:360-366`). On MSW (native
|
||||
dialog or Orca's generic one) and macOS, `CallAfter` bodies run inside `Update()`/`Pulse()`; on GTK
|
||||
the masked yield runs no queued event, so they wait until control returns to an event loop
|
||||
@@ -839,7 +839,7 @@ the masked yield runs no queued event, so they wait until control returns to an
|
||||
**OrcaSlicer.**
|
||||
- `Slic3r::GUI::ProgressDialog` (`Widgets/ProgressDialog.hpp`) is a themed fork of
|
||||
`wxGenericProgressDialog`: same `YieldFor(UI | USER_INPUT)` semantics, temporary event loop when
|
||||
none is active, generic on every platform (so it is themed by Orca, not the native TaskDialog).
|
||||
none is active, generic on every platform (so no TaskDialog dark-mode gap).
|
||||
- `detail::run_off_thread_with_progress` (`PluginsDialog.hpp`, wrapped by
|
||||
`PluginsDialog::run_with_dialog`/`run_with_dialog_wait`): a heap wx `wxProgressDialog` (the native
|
||||
TaskDialog on MSW, the generic dialog on macOS and GTK) pulsed by a
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# WebView, OpenGL canvas, AUI docking and media
|
||||
|
||||
How wxWebView, wxGLCanvas/wxGLContext, wxAuiManager/wxAuiToolBar and the camera view behave in the
|
||||
wx 3.3.4 tree Orca builds, and the Orca wrappers built on them: `WebView`, `WebViewHostDialog` and
|
||||
wx 3.3.2 tree Orca builds, and the Orca wrappers built on them: `WebView`, `WebViewHostDialog` and
|
||||
the web dialogs/panels, `OpenGLManager`, `GLCanvas3D` and its ImGui layer, the Plater docking
|
||||
manager, `BBLTopbar`, `wxMediaCtrl3`. Read it before touching any browser view, GL canvas, docked
|
||||
pane, the top bar or the camera view, or when debugging a blank web page, a black or garbled 3D
|
||||
@@ -242,7 +242,7 @@ a single message handler and the Chromium and IE backends do not support script
|
||||
| `Add…` return | `false` if *any* handler already exists, whatever its name (`src/msw/webview_edge.cpp:1635-1645`) | always `true` (`src/osx/webview_webkit.mm:620-629`); a duplicate name makes WebKit raise an NSException that cannot be caught and kills the app ([external]: Apple behaviour, recorded in the `WebView::CreateWebView` comment) | `false` on a duplicate or with no view, **but** `g_signal_connect` runs before registration, so a duplicate add leaves a second connection (`src/gtk/webview_webkit2.cpp:1638-1657`) |
|
||||
| `Remove…` | clears the single name (`1647-1652`) | removes the native handler (`632-636`) | unregisters but **does not disconnect** the signal (`1659-1664`): Remove + Add delivers every later message **twice** |
|
||||
| `window.<name>` alias | user script `window.<name> = window.chrome.webview` + `ExecuteScript` (`1030-1046`) | `AddUserScript` + **synchronous** `RunScript` (`626-628`) | `AddUserScript` + **synchronous** `RunScript` (`1650-1654`) |
|
||||
| Delivery | queued with `AddPendingEvent` (`785-813`); [documented] "Queue Edge wxWebView events" (`docs/changes.txt:718`) | `ProcessWindowEvent` **inside** the `WKScriptMessageHandler` delegate (`1324-1361`) | `HandleWindowEvent` **inside** the GTK signal (`397-409`) |
|
||||
| Delivery | queued with `AddPendingEvent` (`785-813`); [documented] "Queue Edge wxWebView events" (`docs/changes.txt:525`) | `ProcessWindowEvent` **inside** the `WKScriptMessageHandler` delegate (`1324-1361`) | `HandleWindowEvent` **inside** the GTK signal (`397-409`) |
|
||||
| Event fields | handler name and event object set | name set, **no event object** | **empty `GetMessageHandler()`, no event object** |
|
||||
| Body | `get_WebMessageAsJson`, JSON-decoded when it is a string (`798-809`) | NSString as is; dict/array → JSON; NSNumber → `stringValue` (booleans arrive as `1`/`0`); NSNull → `"null"` | object → JSON string, otherwise `ToString` (`64-94`) |
|
||||
|
||||
@@ -710,8 +710,8 @@ the environment variable `wx_opengl_egl=0` does the same; it exists only when `w
|
||||
returns true only when EGL is in use (false when `wxHAS_EGL` is undefined); `GetGLXVersion()` returns 0
|
||||
when GLX is not in use; both exist only when `wxHAS_GLX` is defined (`interface/wx/glcanvas.h:999-1030`).
|
||||
`wxGLCanvas::CreateSurface()` was removed "as exposing it doesn't make sense when wxGLCanvas may use
|
||||
either EGL or GLX" (`docs/changes.txt:282-284`); "Allow using GLX and EGL in same program (#26023)" is
|
||||
an upstream 3.3.2 wxGTK change (`docs/changes.txt:485`).
|
||||
either EGL or GLX" (`docs/changes.txt:250-252`); "Allow using GLX and EGL in same program (#26023)" is
|
||||
an upstream 3.3.2 wxGTK change (`docs/changes.txt:290-292`).
|
||||
|
||||
**Source** [source]:
|
||||
- When both backends are built, **EGL is the default even on X11** unless `PreferGLX()` or the
|
||||
@@ -727,7 +727,7 @@ an upstream 3.3.2 wxGTK change (`docs/changes.txt:485`).
|
||||
(`src/unix/glegl.cpp:801-860`).
|
||||
- The EGL-based `wxGLCanvas` for Wayland dates from 3.1.5 (`docs/changes_32.txt:497`), so it predates
|
||||
Orca's upgrade; its
|
||||
high-DPI scale under EGL/Wayland was fixed in 3.3.0 (`docs/changes.txt:731`).
|
||||
high-DPI scale under EGL/Wayland was fixed in 3.3.0 (`docs/changes.txt:538`).
|
||||
|
||||
**OrcaSlicer.**
|
||||
- `deps/wxWidgets/wxWidgets.cmake` passes `-DwxUSE_GLCANVAS_EGL=ON` (also in the Flatpak manifest).
|
||||
@@ -956,13 +956,13 @@ widgets beside the canvas (a sibling, not a child over it), or in a popup.
|
||||
`src/aui/framemanager.cpp:910-920`; `OnPaneButton`); `DestroyOnClose` panes are detached and `Destroy()`ed
|
||||
(`ClosePane`, `1431-1467`). With several handlers bound, each must `Skip()`.
|
||||
- **3.3 changes.** `wxAUI_MGR_HINT_FADE` left the default flags (`interface/wx/aui/framemanager.h:43-50`;
|
||||
`docs/changes.txt:138`); `wxAUI_MGR_LIVE_RESIZE` joined them (`interface/wx/aui/framemanager.h:59-62`); `AlwaysUsesLiveResize()` "always
|
||||
`docs/changes.txt:132`); `wxAUI_MGR_LIVE_RESIZE` joined them (`interface/wx/aui/framemanager.h:59-62`); `AlwaysUsesLiveResize()` "always
|
||||
returns false" as of 3.3.0 (`interface/wx/aui/framemanager.h:345`; `src/aui/framemanager.cpp:711-714`), non-live resize being
|
||||
restored (`docs/changes.txt:683`) — the older "live resize is always used on wxOSX and GTK3/Wayland"
|
||||
restored (`docs/changes.txt:490`) — the older "live resize is always used on wxOSX and GTK3/Wayland"
|
||||
statement (`interface/wx/aui/framemanager.h:336-340`) is obsolete. Docking hints for floating frames use
|
||||
`::wxGetMousePosition()` (`src/aui/framemanager.cpp:4139-4148`, `4242-4251`), which Wayland cannot supply.
|
||||
- `wxAuiNotebook` (not used by Orca): page indices are logical; use `GetPagePosition()` for on-screen
|
||||
order (`docs/changes.txt:128-132`); the default tab art changed (`41-43`).
|
||||
order (`docs/changes.txt:121-126`); the default tab art changed (`35-37`).
|
||||
|
||||
**OrcaSlicer.** Orca's docks use `AuiMgr` (`AuiMgr.hpp`), a `wxAuiManager` whose `CreateFloatingFrame`
|
||||
returns a `wxAuiFloatingFrame` that applies `UpdateFrameDarkUI`. `init(window)` calls
|
||||
@@ -1034,10 +1034,8 @@ Plater (Prepare and Preview); `DesignPanel::m_aui` manages the Design tab's body
|
||||
```
|
||||
- **Rule:** Disable floating on Wayland (clear `wxAUI_MGR_ALLOW_FLOATING`, `Floatable(false)`, strip
|
||||
floating state from saved layouts).
|
||||
**Why:** AUI's docking-hint logic needs global pointer positions and top-level window moves
|
||||
(`wxGetMousePosition`, `wxEVT_MOVE`), which Wayland does not provide. 3.3.4 added system drag sessions
|
||||
so wx can move a pane's floating frame there anyway (`src/aui/framemanager.cpp` `StartDragSession`),
|
||||
but the drop/docking hints still cannot work, so Orca keeps floating disabled.
|
||||
**Why:** AUI's floating and docking-hint logic needs global pointer positions and top-level window
|
||||
moves (`wxGetMousePosition`, `wxEVT_MOVE`), which Wayland does not provide.
|
||||
|
||||
## wxAuiToolBar and BBLTopbar
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ port; what Orca's `DPIDialog`/`DPIFrame` add; the Orca dialog recipe; and the `M
|
||||
Read it before writing or reviewing any dialog, frame, close handler, `Destroy()`/`delete`, or code
|
||||
that keeps a pointer to a window across an event, a `CallAfter` or a modal loop.
|
||||
|
||||
wx cites are relative to the pinned wx 3.3.4 tree (located as in `SKILL.md` §Ground truth). wx is
|
||||
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth). wx is
|
||||
built with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert
|
||||
quoted below is compiled out, so misuse fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
|
||||
means wxGTK as Orca builds it on Linux: GTK3 by default (X11 or Wayland); GTK2 is only an opt-out.
|
||||
@@ -424,7 +424,7 @@ mapped (`src/gtk/toplevel.cpp:1141-1243`).
|
||||
|
||||
**Raise.** "only requests the window manager to raise this window… If the window is currently hidden,
|
||||
this function does *not* show it", top-level windows only (`interface/wx/window.h:3013-3033`); true on all ports
|
||||
since 3.3 (`docs/changes.txt:145-147`). **[source]** MSW = `::SetForegroundWindow`, subject to the
|
||||
since 3.3 (`docs/changes.txt:144-146`). **[source]** MSW = `::SetForegroundWindow`, subject to the
|
||||
foreground lock — Windows may only flash the taskbar button (`src/msw/toplevel.cpp:650-655`); GTK =
|
||||
`gtk_window_present` only if shown (`src/gtk/toplevel.cpp:1301-1310`; during a deferred X11 first show it
|
||||
already counts as shown); macOS = `makeKeyAndOrderFront` only if shown (`src/osx/nonownedwnd_osx.cpp:289-295`, `src/osx/cocoa/nonownedwnd.mm:897-899`),
|
||||
@@ -910,9 +910,8 @@ buttons are chosen — in numeric id order, so `{"Save", "OK"}` makes Save prima
|
||||
**Rule.** Never `wxMessageBox`/`wxMessageDialog`/`wxRichMessageDialog` once the GUI exists; use the themed
|
||||
replacements in `src/slic3r/GUI/MsgDialog.hpp` (`Slic3r::GUI`), rooted in `MsgDialog : DPIDialog` (logo on the
|
||||
left, content on the right, `Button` row underneath, dark-mode and DPI aware). Why: on MSW the TaskDialog-based
|
||||
native boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) now follow dark mode
|
||||
since 3.3.3, so only the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI applications stay
|
||||
light (`interface/wx/app.h:1442-1448`); native boxes cannot match Orca's look. `wxMessageBox` remains only for
|
||||
native boxes (`wxMessageBox`, `wxMessageDialog`, `wxRichMessageDialog`, `wxProgressDialog`) ignore dark mode
|
||||
(`interface/wx/app.h:1436-1446`), and native boxes cannot match Orca's look. `wxMessageBox` remains only for
|
||||
failures before the GUI exists (e.g. `GUI_App::load_language`). Native message-box style limits:
|
||||
`references/strings-i18n-files.md`. Return-value trap when reading old code: `wxMessageBox()` returns
|
||||
`wxYES/wxNO/wxCANCEL/wxOK/wxHELP`, while `ShowModal()` returns `wxID_YES/…` (`interface/wx/msgdlg.h:269-276` vs `309-311`).
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
# wx 3.1.5 → 3.3.4: changes Orca code can trip on
|
||||
# wx 3.1.5 → 3.3.2: changes Orca code can trip on
|
||||
|
||||
OrcaSlicer moved from wxWidgets **3.1.5** to **3.3.2** (8248b06337, #12941) and the pin is now upstream
|
||||
**3.3.4** (`deps/wxWidgets/wxWidgets.cmake`, tag `v3.3.4`). This file digests the
|
||||
OrcaSlicer moved from wxWidgets **3.1.5** to **3.3.2** (8248b06337, #12941). This file digests the
|
||||
changes between those versions that matter to GUI code — the 3.1.6–3.2.0 incompatible changes, the
|
||||
3.3 incompatible changes, and the notable 3.3.0/3.3.1/3.3.2/3.3.3/3.3.4 changes — each with what it
|
||||
means for Orca, plus the migration already done in Orca as rules for new code. Read it when code
|
||||
written from older wx knowledge behaves oddly, when a wx doc says "since 3.3", or when reviewing code
|
||||
near the workarounds in §10. Where this file and prior wx knowledge disagree, this file wins.
|
||||
3.3 incompatible changes, and the notable 3.3.0/3.3.1/3.3.2 changes — each with what it means for
|
||||
Orca, plus the migration already done in Orca as rules for new code. Read it when code written
|
||||
from older wx knowledge behaves oddly, when a wx doc says "since 3.3", or when reviewing code near
|
||||
the workarounds in §8. Where this file and prior wx knowledge disagree, this file wins.
|
||||
|
||||
Contents: [Rules](#rules) · [1 Reading the change logs](#1-reading-the-change-logs) ·
|
||||
[2 3.1.6 → 3.2.0](#2-changes-from-316-to-320) · [3 3.3 behaviour changes](#3-33-behaviour-changes-that-compile) ·
|
||||
[4 3.3 build changes](#4-33-changes-that-break-the-build) · [5 3.3.0](#5-330-notable-changes) ·
|
||||
[6 3.3.1](#6-331-notable-fixes) · [7 3.3.2](#7-332-notable-changes) ·
|
||||
[8 3.3.3](#8-333-notable-changes) · [9 3.3.4](#9-334-notable-changes) ·
|
||||
[10 Migration done in Orca](#10-migration-already-done-in-orca)
|
||||
[8 Migration done in Orca](#8-migration-already-done-in-orca)
|
||||
|
||||
All `docs/`, `interface/`, `include/`, `src/`, `build/` cites are relative to the pinned wx tree
|
||||
(located as in `SKILL.md` §Ground truth), except paths
|
||||
@@ -23,8 +21,8 @@ explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca
|
||||
|
||||
1. Read every "now asserts" in the change logs as "now silently returns or does nothing" in Orca:
|
||||
wx asserts are compiled out. Validate arguments yourself. → [§1](#1-reading-the-change-logs)
|
||||
2. Target 3.3.4 only. Do not add `wxCHECK_VERSION` / `wxVERSION_NUMBER` /
|
||||
`wxVERSION_EQUAL_OR_GREATER_THAN` branches for older wx. → [§10](#10-migration-already-done-in-orca)
|
||||
2. Target 3.3.2 only. Do not add `wxCHECK_VERSION` / `wxVERSION_NUMBER` /
|
||||
`wxVERSION_EQUAL_OR_GREATER_THAN` branches for older wx. → [§8](#8-migration-already-done-in-orca)
|
||||
3. Mark every override of a wx virtual `override`. 3.2/3.3 changed parameter types (`wxBitmapBundle`,
|
||||
`wxReadOnlyDC`, `wxWindowBase*`, `wxVersionContext`); a stale signature must fail to compile, not
|
||||
become a silent overload. → [§4](#4-33-changes-that-break-the-build)
|
||||
@@ -41,36 +39,33 @@ explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca
|
||||
`WX_GL_SAMPLES`); `wxGLAttributes::Defaults()` no longer includes it. → [§3](#3-33-behaviour-changes-that-compile)
|
||||
9. Size a `wxImageList` in physical pixels (the bitmaps' `GetSize()`), or use the `wxBitmapBundle`
|
||||
image APIs. → [§3](#3-33-behaviour-changes-that-compile)
|
||||
10. `wxGetTranslation()` returns `const wxString&` (3.3.2 made it by-value; 3.3.3 reverted that); a
|
||||
wrapper that converts a translation returns by value. → [§3](#3-33-behaviour-changes-that-compile)
|
||||
10. Return translated strings as `wxString` by value. → [§3](#3-33-behaviour-changes-that-compile)
|
||||
11. Escape `&` in user text used as a control label or book page title. → [§3](#3-33-behaviour-changes-that-compile)
|
||||
12. `wxDynamicCast` only on a pointer whose static type derives from `wxObject`; use `dynamic_cast`
|
||||
for mixin interfaces (`wxComboPopup`, `wxItemContainer`, `wxTextEntry`). → [§4](#4-33-changes-that-break-the-build)
|
||||
13. Build a `wxArrayString` with `Add()` or an initializer list, walk wx lists with
|
||||
`compatibility_iterator` or range-for, and make a `wxString` the first operand of a mixed
|
||||
`+` chain. → [§4](#4-33-changes-that-break-the-build)
|
||||
14. MSW windows are not double-buffered by default in 3.3.4; a custom-painted control buffers itself.
|
||||
14. MSW windows are not double-buffered by default in 3.3.2; a custom-painted control buffers itself.
|
||||
→ [§5](#5-330-notable-changes)
|
||||
15. `wxEVT_DPI_CHANGED` now fires on GTK3 too; such handlers must be correct on GTK, and every handler
|
||||
you bind calls `Skip()` (`DPIAware`'s own is the one exception). → [§5](#5-330-notable-changes)
|
||||
16. wx MSW dark mode became runtime-switchable in 3.3.3/3.3.4 (`SetAppearance`/`MSWEnableDarkMode`
|
||||
restyle existing windows); Orca still keeps NppDarkMode for its own chrome.
|
||||
→ [§5](#5-330-notable-changes), [§9](#9-334-notable-changes)
|
||||
16. wx MSW dark mode cannot be switched at runtime; do not replace Orca's NppDarkMode with it without
|
||||
keeping live theme switching. → [§5](#5-330-notable-changes)
|
||||
17. After `wxStaticText::SetLabel`, re-wrap with `Wrap(-1); Wrap(w);` (or use `Label`); `wxST_WRAP`
|
||||
is opt-in and works only inside a sizer. → [§7](#7-332-notable-changes)
|
||||
18. On Linux/X11 call `wxGLCanvas::PreferGLX()` before any GL use; on Unix set a swap interval explicitly
|
||||
if frame pacing matters (wx forces 0). → [§7](#7-332-notable-changes)
|
||||
19. Compare `wxGrid::GetSelectedBlocks()` `begin()` with `end()` before dereferencing.
|
||||
→ [§10](#10-migration-already-done-in-orca)
|
||||
→ [§8](#8-migration-already-done-in-orca)
|
||||
20. Keep the post-upgrade workarounds (MainFrame `WS_CAPTION` masking, macOS deep-link handler,
|
||||
`MSWEnableDarkMode` ordering, `SidePopup` anchoring, MSW `GLCanvas3D::on_paint` render) when
|
||||
touching that code. → [§10](#10-migration-already-done-in-orca)
|
||||
touching that code. → [§8](#8-migration-already-done-in-orca)
|
||||
|
||||
## 1 Reading the change logs
|
||||
|
||||
**Sources.** `docs/changes.txt` lists changes since 3.2: incompatible behaviour changes :11-178,
|
||||
build-breaking changes :181-292, 3.3.4 :295-385, 3.3.3 :388-445, 3.3.2 :448-523, 3.3.1 :526-570,
|
||||
3.3.0 :573-815.
|
||||
**Sources.** `docs/changes.txt` lists changes since 3.2: incompatible behaviour changes :11-146,
|
||||
build-breaking changes :149-252, 3.3.2 :255-330, 3.3.1 :333-377, 3.3.0 :380-622.
|
||||
`docs/changes_32.txt` covers 3.x → 3.2.0: the cumulative "INCOMPATIBLE CHANGES SINCE 3.0.x" list
|
||||
:9-233, 3.2.0 :235-276, 3.1.7 :279-340, 3.1.6 :343-444. wxQt, wxiOS, wxUniv, wxMotif and wxGTK1
|
||||
items are left out here.
|
||||
@@ -78,14 +73,11 @@ items are left out here.
|
||||
**Coverage gaps.**
|
||||
- The "since 3.0.x" list in `changes_32.txt` is cumulative over all of 3.1.x. Most of it was already
|
||||
in force in 3.1.5; §2.1 lists only what was added after 3.1.5, §2.3 the older items worth knowing.
|
||||
- `changes_32.txt` in this tree stops at 3.2.0. The 3.2.1–3.2.12 maintenance fixes are not listed
|
||||
- `changes_32.txt` in this tree stops at 3.2.0. The 3.2.1–3.2.10 maintenance fixes are not listed
|
||||
anywhere in the tree.
|
||||
- 3.3.0's list is relative to 3.2.8 (`changes.txt:576`). 3.3.2's list is relative to 3.2.10, and the
|
||||
- 3.3.0's list is relative to 3.2.8 (`changes.txt:383`). 3.3.2's list is relative to 3.2.10, and the
|
||||
full set of changes since 3.3.1 *also includes* the 3.2.9 and 3.2.10 fixes, which are **not**
|
||||
listed in this file (`changes.txt:451-452`). 3.3.3's list is relative to 3.2.11, and its changes
|
||||
since 3.3.2 *also include* the 3.2.10–3.2.11 fixes (`changes.txt:391-392`); 3.3.4's list is
|
||||
relative to 3.2.11 too, and its changes since 3.3.3 *also include* the 3.2.11–3.2.12 fixes
|
||||
(`changes.txt:298-299`).
|
||||
listed in this file (`changes.txt:258-259`).
|
||||
|
||||
**"Asserts" mean silence in Orca.** wx is built with `-DwxBUILD_DEBUG_LEVEL=0`
|
||||
(`deps/wxWidgets/wxWidgets.cmake` → `build/cmake/init.cmake:245-246`), and `libslic3r_gui` gets
|
||||
@@ -125,7 +117,7 @@ the 3.1.7-specific section.
|
||||
| `wxSpinCtrl::SetValue(wxString)` sends no events on MSW (as documented) | :129-130 | do not rely on setter events (`controls-dataview.md`) |
|
||||
| `wxButton::GetBitmap{Current,Disabled,Focus,Pressed}()` return a valid bitmap on MSW only if set | :132-134 | check `IsOk()` |
|
||||
| `wxFileName::GetVolume()` returns `\\share` for UNC paths and `\\?\Volume{GUID}` for GUID paths | :136-140 | path code comparing volumes |
|
||||
| `wxBitmapComboBoxBase::SetItemBitmap()` takes `wxBitmapBundle` | :152-153 | the bundle change also retyped `OnAddBitmap` (5f365b5c6b, §10) |
|
||||
| `wxBitmapComboBoxBase::SetItemBitmap()` takes `wxBitmapBundle` | :152-153 | the bundle change also retyped `OnAddBitmap` (5f365b5c6b, §8) |
|
||||
| MSW also links `oleacc` (3.1.5 already needed `shlwapi`, `uxtheme`, `version`) | :158-163 | automatic with MSVC |
|
||||
| Xcode projects drop i386, add arm64 | :205-207 | none (CMake build) |
|
||||
| `wxImage` ctor from XPM data is `explicit` | :225-226 | write `wxImage(xpm)` |
|
||||
@@ -144,7 +136,7 @@ the 3.1.7-specific section.
|
||||
| MSW: TLW resizing on DPI change improved and overridable — a handler that sizes the TLW itself does not `Skip()` (3.2.0) | `changes_32.txt:269`; `interface/wx/event.h` (`wxDPIChangedEvent`) | Orca's `DPIAware` handler does not `Skip()` (`dpi-bitmaps-fonts.md`) |
|
||||
| `wxUILocale` (3.1.6); `wxLocale::IsAvailable` is now implemented through it | `changes_32.txt:348`; `src/common/intl.cpp:740-781` | see `wxUILocale::IsSupported` in §3 |
|
||||
| `wxWebView::RunScriptAsync()` (3.1.6) | `changes_32.txt:382` | `webview-gl-aui-media.md` |
|
||||
| wxOSX: "Allow user input in `wxPopupTransientWindow`" (3.1.7) — the popup's child holds mouse capture while the cursor is outside it and releases it inside, toggled from `OnIdle` [source] | `changes_32.txt:334`; `src/common/popupcmn.cpp:438-471` | how wxOSX transient popups track the mouse; the macOS gap-dismissal worked around in `SidePopup::Popup` is not traced to a specific wx change (§10; `popups-menus.md`) |
|
||||
| wxOSX: "Allow user input in `wxPopupTransientWindow`" (3.1.7) — the popup's child holds mouse capture while the cursor is outside it and releases it inside, toggled from `OnIdle` [source] | `changes_32.txt:334`; `src/common/popupcmn.cpp:438-471` | how wxOSX transient popups track the mouse; the macOS gap-dismissal worked around in `SidePopup::Popup` is not traced to a specific wx change (§8; `popups-menus.md`) |
|
||||
| wxGTK: Wayland fixes (3.1.6), no GDK errors from `PopupMenu()` on Wayland (3.1.7), `wxCURSOR_SIZING` on Wayland (3.2.0) | `changes_32.txt:397, 317, 261` | `platforms.md` |
|
||||
| MSW: all native modal dialogs are app-modal (3.1.6) | `changes_32.txt:409` | `windows-dialogs.md` |
|
||||
| Also new: `wxKeyEvent::IsAutoRepeat()`, `wxSpinCtrl::GetTextValue()/SetIncrement()`, `wxTopLevelWindow::SetContentProtection()`, `wxEVT_SPLITTER_SASH_POS_RESIZE`, OSX full-screen view options, OSX `wxEVT_CHAR` from `wxDataViewCtrl` | `changes_32.txt:364-436` | available API |
|
||||
@@ -175,7 +167,7 @@ These were already in force in 3.1.5, so the upgrade did not change them, but co
|
||||
|
||||
## 3 3.3 behaviour changes that compile
|
||||
|
||||
`changes.txt` "Changes in behaviour not resulting in compilation errors" (:11-178). Each row: the
|
||||
`changes.txt` "Changes in behaviour not resulting in compilation errors" (:11-146). Each row: the
|
||||
change, its line, and what it means for Orca.
|
||||
|
||||
| Change | Line | Orca relevance |
|
||||
@@ -185,30 +177,31 @@ change, its line, and what it means for Orca.
|
||||
| `wxGLCanvas` no longer multisamples by default | :21-23 | pitfall below |
|
||||
| `wxFileConfig` on Unix defaults to XDG `~/.config/appname.conf` (old file still used; `wxCONFIG_USE_XDG`/`wxCONFIG_USE_HOME`, `MigrateLocalFile()`) | :25-29 | none: Orca uses `AppConfig`, no `wxConfig` (`strings-i18n-files.md`) |
|
||||
| `wxColourDatabase` uses CSS values; `UseScheme()` restores the old ones (`interface/wx/gdicmn.h:845, 973-996`) | :31-33 | none: stock colours (`*wxGREEN`, …) are fixed RGB (`src/common/gdicmn.cpp:789-830`) and Orca builds colours from hex/RGB. Do not build colours from names |
|
||||
| `wxAuiNotebook` default art is the new flat art; `wxAuiNativeTabArt` (or `"native"` in XRC) keeps the old look | :41-43 | none: no `wxAuiNotebook` in Orca |
|
||||
| `wxTHREAD_WAIT_DEFAULT` is `wxTHREAD_WAIT_BLOCK`: `wxThread::Delete()/Wait()` no longer pump events | :45-47 | Orca uses std/boost threads (`wxThread` only for `IsMain()`); a new `wxThread` whose exit needs the main loop would deadlock |
|
||||
| `wxDocument::OnCloseDocument()` runs once, after the views are destroyed (it used to run twice when the document was closed from the menu); an override must not rely on any view existing | :49-52 | none: no doc/view |
|
||||
| `wxGrid::FreezeTo()` asserts on out-of-range counts, and freezes even when the grid is too small | :54-57 | silent `false` in Orca; it also refuses reordered or drag-movable rows/columns [source] (`src/generic/grid.cpp:5742-5750`). Clamp arguments (`controls-dataview.md`) |
|
||||
| Invalid `wxImageList` calls assert | :59-62 | silent in Orca; `wxCHECK` returns (generic `Add` → -1). Create the list with a valid size before use |
|
||||
| `wxTRANSPARENT_WINDOW` does nothing (`#define wxTRANSPARENT_WINDOW 0`, `include/wx/defs.h:1449`); MSW code that needs it can set `WS_EX_TRANSPARENT` | :64-66 | pitfall below; 026b105dcb |
|
||||
| MSW `wxTextDataObject::SetData()` size includes the 2-byte NUL (consistent with `GetDataSize()`); an old-style size chops the last character | :68-72 | none: Orca uses the `wxTextDataObject(text)` ctor; use `SetText()`, never `SetData()` |
|
||||
| `wxListCtrl::EditLabel()` asserts without `wxLC_EDIT_LABELS` | :74-75 | silent no-op in Orca; add the style where editing is intended |
|
||||
| MSW `wxSystemAppearance::IsDark()` reports the app's own mode; `AreAppsDark()`/`IsSystemDark()` report the OS | :77-79 | pitfall below |
|
||||
| Unix `wxUILocale::IsSupported()` no longer falls back to another region of the same language; pass just `"fr"` to accept any `fr_XX` | :81-85 | `wxLocale::IsAvailable` builds the region-qualified tag (`GetCanonicalWithRegion()`) and calls `IsSupported()` [source] (`src/common/intl.cpp:740-781`); `GUI_App::load_language` relies on it, so on Linux a language whose canonical locale (e.g. `fr_FR`) is not installed reports unavailable. Keep the fallbacks in `load_language` |
|
||||
| Deprecated `wxPGCellRenderer::DrawCaptionSelectionRect()` overload not called; override the overload taking `wxWindow*`, or enable 3.0 compatibility | :87-89 | none: no propgrid in Orca's own code |
|
||||
| `wxImageList` size is in physical pixels | :91-94 | pitfall below |
|
||||
| Mac `wxWebRequest` no longer uses persistent storage. The entry names `wxWebRequest::EnablePersistentStorage()`; the real API is `wxWebSession::EnablePersistentStorage(bool)` (`interface/wx/webrequest.h:1612-1629`; also `wxWebSessionSync`, :1835), macOS-only, before the first request | :96-98 | none: Orca uses `wxWebRequest` only for image downloads (`wxWebSession::GetDefault().CreateRequest` in `StatusPanel`, `SliceInfoPanel`, `ReleaseNote`, `DeviceErrorDialog`); network and login agents use libcurl (`Slic3r::Http`) |
|
||||
| MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale; the size is physical | :100-102 | remove compensating scaling; use `CreateWithLogicalSize` for logical sizes |
|
||||
| `wxIMAGE_QUALITY_NEAREST` has a new value and is no longer `wxIMAGE_QUALITY_NORMAL`; `NORMAL` (the `Scale`/`Rescale` default) is bilinear + box average (`interface/wx/image.h:31-67`) | :104-105 | never store or compare the enum numerically; default-quality thumbnails and icons look smoother than under 3.1.5; pass `wxIMAGE_QUALITY_NEAREST` for pixel-exact scaling |
|
||||
| `wxTextCtrl::{Save,Load}File()` treat `.rtf` as RTF | :107-110 | pass `wxTEXT_TYPE_PLAIN` to keep plain text |
|
||||
| `wxClientDC`/`wxPaintDC` offset their origin by a `wxFrame` toolbar on every port | :112-115 | none: Orca frames have no native toolbar (`BBLTopbar` is a child `wxAuiToolBar`) |
|
||||
| `wxTextCtrl::SetLabel()` does nothing and asserts on every port (MSW used to act as `SetValue`) | :117-119 | pitfall below |
|
||||
| `wxAuiGenericTabArt` subclasses (also via `wxAuiMSWTabArt`) override `DrawPageTab()`/`GetPageTabSize()` instead of `DrawTab()`/`GetTabSize()`; direct `wxAuiTabArt` subclasses still work | :121-126 | none: no Orca tab art |
|
||||
| `wxAuiNotebook` page index is logical (reorder-independent); `GetPagePosition()` gives the screen position | :128-132 | none; index math under `wxAUI_NB_TAB_MOVE` is what breaks |
|
||||
| `wxListbook`/`wxChoicebook` interpret mnemonics in page titles "just as the other wx*book classes already did" | :134-136 | Orca uses neither (`BedShapeDialog` uses `wxSimplebook` + a combo); every book interprets `&` (`interface/wx/bookctrl.h:141-147`) → rule 11 |
|
||||
| `wxAUI_MGR_HINT_FADE` is not in the default `wxAuiManager` style | :138-139 | Orca's `AuiMgr` keeps the default flags (minus `wxAUI_MGR_ALLOW_FLOATING` on Wayland, `AuiMgr::init`), so the docking hint no longer fades; add the flag if wanted |
|
||||
| `wxPrintDialogData::SetAllPages(false)`/`SetSelection(false)` changed meaning | :141-143 | none |
|
||||
| `wxWindow::Raise()` no longer shows a hidden window on any port | :145-147 | pitfall below; ba867cc534 |
|
||||
| `wxAuiNotebook` default art is the new flat art; `wxAuiNativeTabArt` (or `"native"` in XRC) keeps the old look | :35-37 | none: no `wxAuiNotebook` in Orca |
|
||||
| `wxTHREAD_WAIT_DEFAULT` is `wxTHREAD_WAIT_BLOCK`: `wxThread::Delete()/Wait()` no longer pump events | :39-41 | Orca uses std/boost threads (`wxThread` only for `IsMain()`); a new `wxThread` whose exit needs the main loop would deadlock |
|
||||
| `wxDocument::OnCloseDocument()` runs once, after the views are destroyed (it used to run twice when the document was closed from the menu); an override must not rely on any view existing | :43-46 | none: no doc/view |
|
||||
| `wxGrid::FreezeTo()` asserts on out-of-range counts, and freezes even when the grid is too small | :48-51 | silent `false` in Orca; it also refuses reordered or drag-movable rows/columns [source] (`src/generic/grid.cpp:5742-5750`). Clamp arguments (`controls-dataview.md`) |
|
||||
| Invalid `wxImageList` calls assert | :53-56 | silent in Orca; `wxCHECK` returns (generic `Add` → -1). Create the list with a valid size before use |
|
||||
| `wxTRANSPARENT_WINDOW` does nothing (`#define wxTRANSPARENT_WINDOW 0`, `include/wx/defs.h:1449`); MSW code that needs it can set `WS_EX_TRANSPARENT` | :58-60 | pitfall below; 026b105dcb |
|
||||
| MSW `wxTextDataObject::SetData()` size includes the 2-byte NUL (consistent with `GetDataSize()`); an old-style size chops the last character | :62-66 | none: Orca uses the `wxTextDataObject(text)` ctor; use `SetText()`, never `SetData()` |
|
||||
| `wxListCtrl::EditLabel()` asserts without `wxLC_EDIT_LABELS` | :68-69 | silent no-op in Orca; add the style where editing is intended |
|
||||
| MSW `wxSystemAppearance::IsDark()` reports the app's own mode; `AreAppsDark()`/`IsSystemDark()` report the OS | :71-73 | pitfall below |
|
||||
| Unix `wxUILocale::IsSupported()` no longer falls back to another region of the same language; pass just `"fr"` to accept any `fr_XX` | :75-79 | `wxLocale::IsAvailable` builds the region-qualified tag (`GetCanonicalWithRegion()`) and calls `IsSupported()` [source] (`src/common/intl.cpp:740-781`); `GUI_App::load_language` relies on it, so on Linux a language whose canonical locale (e.g. `fr_FR`) is not installed reports unavailable. Keep the fallbacks in `load_language` |
|
||||
| Deprecated `wxPGCellRenderer::DrawCaptionSelectionRect()` overload not called; override the overload taking `wxWindow*`, or enable 3.0 compatibility | :81-83 | none: no propgrid in Orca's own code |
|
||||
| `wxImageList` size is in physical pixels | :85-88 | pitfall below |
|
||||
| Mac `wxWebRequest` no longer uses persistent storage. The entry names `wxWebRequest::EnablePersistentStorage()`; the real API is `wxWebSession::EnablePersistentStorage(bool)` (`interface/wx/webrequest.h:1612-1629`; also `wxWebSessionSync`, :1835), macOS-only, before the first request | :90-92 | none: Orca uses `wxWebRequest` only for image downloads (`wxWebSession::GetDefault().CreateRequest` in `StatusPanel`, `SliceInfoPanel`, `ReleaseNote`, `DeviceErrorDialog`); network and login agents use libcurl (`Slic3r::Http`) |
|
||||
| MSW `wxBitmap::Create(size, dc)` no longer multiplies by the DC's content scale; the size is physical | :94-96 | remove compensating scaling; use `CreateWithLogicalSize` for logical sizes |
|
||||
| `wxIMAGE_QUALITY_NEAREST` has a new value and is no longer `wxIMAGE_QUALITY_NORMAL`; `NORMAL` (the `Scale`/`Rescale` default) is bilinear + box average (`interface/wx/image.h:31-67`) | :98-99 | never store or compare the enum numerically; default-quality thumbnails and icons look smoother than under 3.1.5; pass `wxIMAGE_QUALITY_NEAREST` for pixel-exact scaling |
|
||||
| `wxTextCtrl::{Save,Load}File()` treat `.rtf` as RTF | :101-104 | pass `wxTEXT_TYPE_PLAIN` to keep plain text |
|
||||
| `wxClientDC`/`wxPaintDC` offset their origin by a `wxFrame` toolbar on every port | :106-109 | none: Orca frames have no native toolbar (`BBLTopbar` is a child `wxAuiToolBar`) |
|
||||
| `wxTextCtrl::SetLabel()` does nothing and asserts on every port (MSW used to act as `SetValue`) | :111-113 | pitfall below |
|
||||
| `wxAuiGenericTabArt` subclasses (also via `wxAuiMSWTabArt`) override `DrawPageTab()`/`GetPageTabSize()` instead of `DrawTab()`/`GetTabSize()`; direct `wxAuiTabArt` subclasses still work | :115-120 | none: no Orca tab art |
|
||||
| `wxAuiNotebook` page index is logical (reorder-independent); `GetPagePosition()` gives the screen position | :122-126 | none; index math under `wxAUI_NB_TAB_MOVE` is what breaks |
|
||||
| `wxListbook`/`wxChoicebook` interpret mnemonics in page titles "just as the other wx*book classes already did" | :128-130 | Orca uses neither (`BedShapeDialog` uses `wxSimplebook` + a combo); every book interprets `&` (`interface/wx/bookctrl.h:141-147`) → rule 11 |
|
||||
| `wxAUI_MGR_HINT_FADE` is not in the default `wxAuiManager` style | :132-133 | Orca's `AuiMgr` keeps the default flags (minus `wxAUI_MGR_ALLOW_FLOATING` on Wayland, `AuiMgr::init`), so the docking hint no longer fades; add the flag if wanted |
|
||||
| `wxPrintDialogData::SetAllPages(false)`/`SetSelection(false)` changed meaning | :135-137 | none |
|
||||
| `wxGetTranslation()` returns `wxString` by value | :139-142 | pitfall below |
|
||||
| `wxWindow::Raise()` no longer shows a hidden window on any port | :144-146 | pitfall below; ba867cc534 |
|
||||
|
||||
**Pitfalls**
|
||||
|
||||
@@ -284,7 +277,7 @@ change, its line, and what it means for Orca.
|
||||
// Wrong: m_input_ip->GetTextCtrl()->SetLabelText(m_obj->get_dev_ip());
|
||||
// Right: m_input_ip->GetTextCtrl()->ChangeValue(m_obj->get_dev_ip()); // no wxEVT_TEXT
|
||||
```
|
||||
Cite: `docs/changes.txt:117-119`; `controls-dataview.md`.
|
||||
Cite: `docs/changes.txt:111-113`; `controls-dataview.md`.
|
||||
|
||||
- **Rule:** Size a `wxImageList` from the bitmaps' physical size, or use
|
||||
`SetImages(std::vector<wxBitmapBundle>)`.
|
||||
@@ -300,23 +293,18 @@ change, its line, and what it means for Orca.
|
||||
// Right: wxBitmap bmp = create_scaled_bitmap("icon", this, 16);
|
||||
// m_images = new wxImageList(bmp.GetWidth(), bmp.GetHeight(), false);
|
||||
```
|
||||
Cite: `docs/changes.txt:91-94`; `Tab` builds its list from `bmp().GetWidth()/GetHeight()`;
|
||||
Cite: `docs/changes.txt:85-88`; `Tab` builds its list from `bmp().GetWidth()/GetHeight()`;
|
||||
`dpi-bitmaps-fonts.md`.
|
||||
|
||||
- **Rule:** A wrapper that converts a translation returns by value; `wxGetTranslation()` itself
|
||||
returns `const wxString&`.
|
||||
**Why:** 3.3.2 changed `wxGetTranslation()` to return `wxString` by value, and **3.3.3 reverted it**
|
||||
(`interface/wx/translation.h:615`, `include/wx/translation.h:277-309`), so the return type is the
|
||||
3.2.x `const wxString&` again. That reference points at the catalog's own string or at
|
||||
`wxTranslations::GetUntranslatedString()`, both of which outlive the call, so forwarding it as a
|
||||
reference is safe. What still dangles is a temporary you construct: `wxString::FromUTF8`,
|
||||
`ToUTF8()`, `format_wxstr`. Orca's `I18N::translate` overloads return `wxString` by value and
|
||||
`translate_utf8`/`_u8L` return `std::string`.
|
||||
- **Rule:** A function that returns a translation returns `wxString` by value.
|
||||
**Why:** `wxGetTranslation()` returns by value now (`include/wx/translation.h:278-321`); returning
|
||||
it as `const wxString&` dangles. Orca's `I18N::translate` overloads and `_L` already return by
|
||||
value.
|
||||
```cpp
|
||||
// Wrong: const wxString& path() { return wxString::FromUTF8(raw); } // temporary
|
||||
// Right: wxString path() { return wxString::FromUTF8(raw); }
|
||||
// Wrong: const wxString& title() { return _L("Printer"); }
|
||||
// Right: wxString title() { return _L("Printer"); }
|
||||
```
|
||||
Cite: §8 (the 3.3.3 revert); `I18N.hpp`.
|
||||
Cite: `docs/changes.txt:139-142`; `I18N.hpp`.
|
||||
|
||||
- **Rule:** Escape `&` in user data (preset, filament, printer, file names) shown as a control label or
|
||||
book page title.
|
||||
@@ -326,37 +314,37 @@ change, its line, and what it means for Orca.
|
||||
// Wrong: book->AddPage(page, preset_name);
|
||||
// Right: book->AddPage(page, wxControl::EscapeMnemonics(preset_name)); // or label->SetLabelText(name)
|
||||
```
|
||||
Cite: `docs/changes.txt:134-136`; `interface/wx/bookctrl.h:141-147`; `controls-dataview.md`.
|
||||
Cite: `docs/changes.txt:128-130`; `interface/wx/bookctrl.h:141-147`; `controls-dataview.md`.
|
||||
|
||||
## 4 3.3 changes that break the build
|
||||
|
||||
`changes.txt` "Changes in behaviour which may result in build errors" (:181-292).
|
||||
`changes.txt` "Changes in behaviour which may result in build errors" (:149-252).
|
||||
|
||||
| Change | Line | Orca relevance |
|
||||
|---|---|---|
|
||||
| 3.0-deprecated symbols disabled by default (`WXWIN_COMPATIBILITY_3_0=1` at wx build time re-enables them), 2.8-deprecated removed | :184-186 | Orca builds `WXWIN_COMPATIBILITY_3_0 0`, `_3_2 1`: port off 3.0-deprecated API |
|
||||
| `wxUSE_UNICODE=0` unsupported | :188 | none |
|
||||
| `wxUSE_STD_CONTAINERS=1` by default ("Container Classes" overview); building wx with 0 keeps the old containers | :190-193 | Orca builds with 1 → pitfall below |
|
||||
| `wxUSE_STL` gone; implicit `wxString` → `std::[w]string` only with `wxUSE_STD_STRING_CONV_IN_WXSTRING=1` at wx build time | :195-198 | Orca builds 0: convert explicitly (`into_u8`, `ToStdString`, `ToUTF8()`) |
|
||||
| MSW links `gdiplus.lib`, `msimg32.lib` | :200-204 | automatic with MSVC/wx-config; only static non-MSVC builds add them |
|
||||
| wxMotif, wxGTK1 removed | :206-207 | none |
|
||||
| Private containers (e.g. `wxSimpleDataObjectList`) removed; object arrays (`wxImageArray`) compare values in `Index()` | :209-215 | use `std::vector`/`std::list` |
|
||||
| Operators on wx types are hidden (not global) | :217-221 | pitfall below |
|
||||
| `wxString` from `std::string_view` makes `wxstr = {"Hello", 2}` ambiguous | :223-226 | write `wxString{"Hello", 2}` |
|
||||
| Generic `wxSearchCtrl` lost multi-line-only methods | :228-229 | none |
|
||||
| Wide-filename `wxOnAssert()` overload removed | :231-232 | none |
|
||||
| 64-bit DLLs carry an `x64` suffix in all build systems | :234-236 | packaging scripts matching DLL names; Orca links wx statically (Flatpak builds it shared in its own manifest) |
|
||||
| CMake config installs to `lib/cmake/wxWidgets-3.3`; plain `find_package(wxWidgets)` is unaffected, hard-coded paths break | :238-243 | Orca: `find_package(wxWidgets 3.3 CONFIG …)` on Windows/macOS, `wx-config --toolkit=gtk${SLIC3R_GTK}` on Linux (Orca's `src/CMakeLists.txt`) |
|
||||
| Memory-tracing options removed | :245-248 | use ASan |
|
||||
| `wxTEST_DIALOG()` needs a trailing `;` | :250-251 | none |
|
||||
| `wxWindow::GetDefaultBorderForControl()` not virtual | :253-255 | do not override; use `wxBORDER_THEME` |
|
||||
| GTK `wxDirButton::Create()` lost `wildcard` | :257-258 | none |
|
||||
| Several virtuals take `wxReadOnlyDC` | :260-263 | pitfall below |
|
||||
| `wxSizer::Detach()` takes `wxWindowBase*` | :265-268 | only custom sizer subclasses |
|
||||
| `wx/cursor.h` no longer includes `wx/utils.h` | :270-272 | include `<wx/utils.h>` explicitly (8248b06337 added it to `GLCanvas3D.cpp`) |
|
||||
| `wxStyledTextCtrl::AddSelection()` returns void | :274-276 | none: `wxUSE_STC=OFF` |
|
||||
| `wxColour` from `bool` no longer compiles | :278-280 | use the RGB or string ctor explicitly |
|
||||
| `wxGLCanvas::CreateSurface()` removed from EGL builds | :282-284 | none: wx chooses EGL or GLX itself (§7) |
|
||||
| 3.0-deprecated symbols disabled by default (`WXWIN_COMPATIBILITY_3_0=1` at wx build time re-enables them), 2.8-deprecated removed | :152-154 | Orca builds `WXWIN_COMPATIBILITY_3_0 0`, `_3_2 1`: port off 3.0-deprecated API |
|
||||
| `wxUSE_UNICODE=0` unsupported | :156 | none |
|
||||
| `wxUSE_STD_CONTAINERS=1` by default ("Container Classes" overview); building wx with 0 keeps the old containers | :158-161 | Orca builds with 1 → pitfall below |
|
||||
| `wxUSE_STL` gone; implicit `wxString` → `std::[w]string` only with `wxUSE_STD_STRING_CONV_IN_WXSTRING=1` at wx build time | :163-166 | Orca builds 0: convert explicitly (`into_u8`, `ToStdString`, `ToUTF8()`) |
|
||||
| MSW links `gdiplus.lib`, `msimg32.lib` | :168-172 | automatic with MSVC/wx-config; only static non-MSVC builds add them |
|
||||
| wxMotif, wxGTK1 removed | :174-175 | none |
|
||||
| Private containers (e.g. `wxSimpleDataObjectList`) removed; object arrays (`wxImageArray`) compare values in `Index()` | :177-183 | use `std::vector`/`std::list` |
|
||||
| Operators on wx types are hidden (not global) | :185-189 | pitfall below |
|
||||
| `wxString` from `std::string_view` makes `wxstr = {"Hello", 2}` ambiguous | :191-194 | write `wxString{"Hello", 2}` |
|
||||
| Generic `wxSearchCtrl` lost multi-line-only methods | :196-197 | none |
|
||||
| Wide-filename `wxOnAssert()` overload removed | :199-200 | none |
|
||||
| 64-bit DLLs carry an `x64` suffix in all build systems | :202-204 | packaging scripts matching DLL names; Orca links wx statically (Flatpak builds it shared in its own manifest) |
|
||||
| CMake config installs to `lib/cmake/wxWidgets-3.3`; plain `find_package(wxWidgets)` is unaffected, hard-coded paths break | :206-211 | Orca: `find_package(wxWidgets 3.3 CONFIG …)` on Windows/macOS, `wx-config --toolkit=gtk${SLIC3R_GTK}` on Linux (Orca's `src/CMakeLists.txt`) |
|
||||
| Memory-tracing options removed | :213-216 | use ASan |
|
||||
| `wxTEST_DIALOG()` needs a trailing `;` | :218-219 | none |
|
||||
| `wxWindow::GetDefaultBorderForControl()` not virtual | :221-223 | do not override; use `wxBORDER_THEME` |
|
||||
| GTK `wxDirButton::Create()` lost `wildcard` | :225-226 | none |
|
||||
| Several virtuals take `wxReadOnlyDC` | :228-231 | pitfall below |
|
||||
| `wxSizer::Detach()` takes `wxWindowBase*` | :233-236 | only custom sizer subclasses |
|
||||
| `wx/cursor.h` no longer includes `wx/utils.h` | :238-240 | include `<wx/utils.h>` explicitly (8248b06337 added it to `GLCanvas3D.cpp`) |
|
||||
| `wxStyledTextCtrl::AddSelection()` returns void | :242-244 | none: `wxUSE_STC=OFF` |
|
||||
| `wxColour` from `bool` no longer compiles | :246-248 | use the RGB or string ctor explicitly |
|
||||
| `wxGLCanvas::CreateSurface()` removed from EGL builds | :250-252 | none: wx chooses EGL or GLX itself (§7) |
|
||||
|
||||
**Pitfalls**
|
||||
|
||||
@@ -378,7 +366,7 @@ change, its line, and what it means for Orca.
|
||||
// Wrong: wxSize GetToolSize(wxDC& dc, wxWindow* w, const wxAuiToolBarItem& it); // never called
|
||||
// Right: wxSize GetToolSize(wxReadOnlyDC& dc, wxWindow* w, const wxAuiToolBarItem& it) override;
|
||||
```
|
||||
Cite: `docs/changes.txt:260-263`; `painting-custom-widgets.md`.
|
||||
Cite: `docs/changes.txt:228-231`; `painting-custom-widgets.md`.
|
||||
|
||||
- **Rule:** With `wxUSE_STD_CONTAINERS=1`, build a `wxArrayString` with `Add()` or an initializer
|
||||
list, and walk `WX_DECLARE_LIST` lists with `compatibility_iterator` or range-for.
|
||||
@@ -398,7 +386,7 @@ change, its line, and what it means for Orca.
|
||||
`wchar_t`, `std::string` or `std::wstring`.
|
||||
**Why:** wx operators are hidden friends now, found only by argument-dependent lookup on a wx type;
|
||||
"preventing them from implicitly being used with types convertible to wx types"
|
||||
(`changes.txt:217-221`). `char + std::wstring + …` compiled under 3.1.5 through the global
|
||||
(`changes.txt:185-189`). `char + std::wstring + …` compiled under 3.1.5 through the global
|
||||
`operator+(char, const wxString&)`, converting the `std::wstring` implicitly; under 3.3 that
|
||||
operator is a hidden friend (`include/wx/string.h:2150`) and no operand is a `wxString`, so it
|
||||
does not.
|
||||
@@ -425,9 +413,9 @@ change, its line, and what it means for Orca.
|
||||
|
||||
## 5 3.3.0 notable changes
|
||||
|
||||
`changes.txt:573-785` (relative to 3.2.8).
|
||||
`changes.txt:380-592` (relative to 3.2.8).
|
||||
|
||||
**Major changes** (:578-587)
|
||||
**Major changes** (:385-394)
|
||||
|
||||
| Change | Orca relevance |
|
||||
|---|---|
|
||||
@@ -441,7 +429,7 @@ change, its line, and what it means for Orca.
|
||||
| Native GTK file dialogs when possible (#24486, #25104) | the portal dialog is used only with GTK ≥ 3.20 at runtime, without `wxFD_PREVIEW` and without an extra control [source] (`src/gtk/filedlg.cpp:265-273, 438-443`); Orca's `CheckboxFileDialog` (`SetExtraControlCreator`, `GUI_Utils.hpp`) therefore gets the non-native GTK dialog |
|
||||
| Native `wxTextCtrl` contents / RTF (#24626, #24912) | available (`GetRTFValue`, `SearchText`) |
|
||||
|
||||
**All** (:591-635)
|
||||
**All** (:398-442)
|
||||
|
||||
| Change | Orca relevance |
|
||||
|---|---|
|
||||
@@ -451,7 +439,7 @@ change, its line, and what it means for Orca.
|
||||
| Environment variables use UTF-8 (#25101) | check round-tripping of non-ASCII paths through `wxGetEnv`/`wxSetEnv` |
|
||||
| Improved locale matching (#24855); thread-safe `wxPlatformInfo::Get()` (#25459); `wxXmlParseError` from `wxXmlDocument::Load()` (#24215); customisable error exit code (#24770) | available |
|
||||
|
||||
**All (GUI)** (:637-693)
|
||||
**All (GUI)** (:444-500)
|
||||
|
||||
| Change | Orca relevance |
|
||||
|---|---|
|
||||
@@ -464,17 +452,17 @@ change, its line, and what it means for Orca.
|
||||
| Non-live resize restored in wxAUI and `wxSplitterWindow` (#24193) | `wxAUI_MGR_LIVE_RESIZE` is in `wxAUI_MGR_DEFAULT` since 3.3.0 (`interface/wx/aui/framemanager.h:59-66`). The style table's "always enabled in wxGTK3 and wxOSX ports as non-live resizing is not implemented in them" (:199-206) is as stale as the `AlwaysUsesLiveResize()` note: that function "always returns false" as of 3.3.0 (:345; `src/aui/framemanager.cpp:711-714`), and the flag decides on every port [source] (`HasLiveResize`, :716-719). See `webview-gl-aui-media.md` §AUI docking |
|
||||
| `wxInfoBar::ShowCheckBox()` (#25394); printing multiple page ranges (#25030); `wxGrid::CopySelection()` (#24124); new default flat AUI tab art (#25316); `wxApp::SetAppearance()` (#24461) | available |
|
||||
|
||||
**All (WebView)** (:703-718) — `wxWebViewConfiguration` + `GetNativeConfiguration()`, `SetProxy()`,
|
||||
**All (WebView)** (:510-525) — `wxWebViewConfiguration` + `GetNativeConfiguration()`, `SetProxy()`,
|
||||
`ShowDevTools()`, `EnablePersistentStorage()`, `EnableBrowserAcceleratorKeys()`, clearing browsing
|
||||
data, advanced requests, child-window handling, `IsTargetMainFrame()`, Edge user agent settable after
|
||||
creation, and **Edge events queued** (#22744, #19075): Edge handlers run later than on the other
|
||||
backends. Edge posts its events with `AddPendingEvent` (script messages: `src/msw/webview_edge.cpp:872`)
|
||||
except the vetoable ones, `wxEVT_WEBVIEW_NAVIGATING` (:655-662) and `wxEVT_WEBVIEW_NEWWINDOW` with its
|
||||
`NEWWINDOW_FEATURES` follow-up (:783-785, :796-798), which stay synchronous; WebKit and
|
||||
backends. Edge posts its events with `AddPendingEvent` (script messages: `src/msw/webview_edge.cpp:811`)
|
||||
except the vetoable ones, `wxEVT_WEBVIEW_NAVIGATING` (:599) and `wxEVT_WEBVIEW_NEWWINDOW` with its
|
||||
`NEWWINDOW_FEATURES` follow-up (:723, :737), which stay synchronous; WebKit and
|
||||
WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:1360`,
|
||||
`src/gtk/webview_webkit2.cpp:408`) [source]. `webview-gl-aui-media.md` owns the details.
|
||||
|
||||
**wxGTK** (:720-740)
|
||||
**wxGTK** (:527-547)
|
||||
|
||||
| Change | Orca relevance |
|
||||
|---|---|
|
||||
@@ -489,53 +477,46 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
| `wxDC::DrawRoundedRectangle()` radius limited to half the smaller side (#24327) | the clamp is in the GTK2 GDK DC (`src/gtk/dcclient.cpp:874-875`, built only in the GTK2 opt-out, `GTK2_LOWLEVEL_SRC` in `build/files`) and also in the common `wxGraphicsPathData::AddRoundedRectangle` (`src/common/graphcmn.cpp:439-440`, absent in 3.1.5) that `wxGraphicsContext::DrawRoundedRectangle` uses, so every `wxGCDC` clamps: GTK3 (Cairo) and macOS window DCs, and Orca's memory-DC + `wxGCDC` paint paths on all ports [source]. A radius larger than half the smaller side is now clamped instead of drawing overlapping arcs |
|
||||
| Read-only `wxBitmapComboBox` height fixed (#25468) | `Slic3r::GUI::BitmapComboBox` on GTK |
|
||||
|
||||
**wxMSW** (:742-776)
|
||||
**wxMSW** (:549-583)
|
||||
|
||||
| Change | Orca relevance |
|
||||
|---|---|
|
||||
| "Enable double buffering for all windows" (#22851) | **reverted in 3.3.2** (#25808) → pitfall below |
|
||||
| `wxOverlay` reimplemented with layered windows (#23261) | none: no `wxOverlay` |
|
||||
| `wxBG_STYLE_TRANSPARENT` implemented (#23412) | as `WS_EX_TRANSPARENT` on non-TLW children [source] (`src/msw/window.cpp:1581-1582`); set before `Create()` (`painting-custom-widgets.md`) |
|
||||
| `wxCAPTION` turned on when min/max/close boxes are set (#23575) | `WS_CAPTION` is added to the style (`src/msw/toplevel.cpp:132-135`); origin of the MainFrame workaround (§10) |
|
||||
| `wxCAPTION` turned on when min/max/close boxes are set (#23575) | `WS_CAPTION` is added to the style (`src/msw/toplevel.cpp:132-135`); origin of the MainFrame workaround (§8) |
|
||||
| `wxButton` default size larger in high DPI (#25297) | raw `wxButton` layouts may grow; Orca dialogs use `Button`/`DialogButtons` |
|
||||
| Markup in `wxStaticText` (#25000); `wxHyperlinkCtrl` colour changeable (#23549) | `SetLabelMarkup` now works on MSW (single line) |
|
||||
| Extended-length paths (#25033); `wxFileDialog` no unwanted extension (#24949); UTF-8 build fixes (#23313); non-BMP strings (#25128) | available |
|
||||
| `wxDisplay` invalidated on display change (#25396); TLW with one child resized on DPI change (#22983); Aero-snapped geometry saved | multi-monitor geometry |
|
||||
| Modern default `wxTreeCtrl` look (#23844); RTL fixes for `wxOverlay`/`wxScrolled` (#25413) and GDI+ (#25431); `wxBitmap::UseAlpha()` returns `bool` (#23919) | available |
|
||||
|
||||
**wxOSX** (:778-785) — `wxEventLoop::OnExit()` is always called (#25409); TLW cursor setting fixed
|
||||
**wxOSX** (:585-592) — `wxEventLoop::OnExit()` is always called (#25409); TLW cursor setting fixed
|
||||
(#25131); Cmd-C no longer activates a "Close" button (#25346); `wxCursor` loadable from resources
|
||||
(#24374); `wxUIActionSimulator` works (#23692), usable for GUI tests.
|
||||
|
||||
**Pitfalls**
|
||||
|
||||
- **Rule:** Orca keeps its own Windows theming (`NppDarkMode`) and only informs wx with
|
||||
`MSWEnableDarkMode(DarkMode_Auto)`; do not replace it with wx's dark mode without accounting for the
|
||||
chrome NppDarkMode themes itself.
|
||||
**Why:** when 8248b06337 landed (3.3.2), wx's MSW dark mode could not be changed once a top-level
|
||||
window existed — `SetAppearance` returned `CannotChange` — so a live Preferences toggle would have
|
||||
been restart-only. That blocker is gone: 3.3.3 (#26516) and 3.3.4 (#26734) made the mode switchable
|
||||
at runtime. `MSWEnableDarkMode()` re-applies the mode to an existing window (sends it
|
||||
`WM_SYSCOLORCHANGE`, `src/msw/darkmode.cpp:345-349`), controls restyle through
|
||||
`wxMSWDarkMode::HasChanged()`/`NotifySysColorChange()` (`src/msw/window.cpp:5337, 5528-5534`), and
|
||||
`SetAppearance()` returns only `Ok`/`Failure`; `AppearanceResult::CannotChange` is no longer
|
||||
returned (`src/msw/darkmode.cpp:289-375`; `interface/wx/app.h:1151, 1179-1183`). The remaining
|
||||
documented limits are the date/time/calendar controls, `wxPrintDialog` on Windows 10 and MDI
|
||||
applications (`interface/wx/app.h:1442-1448`); `wxMessageDialog` (3.3.3, #26570) and the common
|
||||
dialogs (3.3.4, #26780) now do support dark mode. Orca still keeps NppDarkMode because it themes
|
||||
Orca's own chrome (title bars, explorer theme, scrollbars, list headers) and drives the live
|
||||
theme-switch path (§10).
|
||||
- **Rule:** Do not replace Orca's MSW dark mode with wx's (`SetAppearance`/`MSWEnableDarkMode`)
|
||||
unless live theme switching is kept.
|
||||
**Why:** wx's MSW dark mode cannot change once any top-level window exists or a mode was chosen:
|
||||
`SetAppearance` returns `CannotChange` (`src/msw/darkmode.cpp:263-270`;
|
||||
`interface/wx/app.h:1166-1173`). TaskDialog-based dialogs (`wxMessageDialog`, `wxProgressDialog`,
|
||||
simple `wxAboutBox`), the common dialogs (colour, find/replace, font, page setup, print) and the
|
||||
date/time/calendar controls stay light (`interface/wx/app.h:1434-1448`). Orca switches themes at
|
||||
runtime, which is why 8248b06337 kept NppDarkMode and only informs wx with
|
||||
`MSWEnableDarkMode(DarkMode_Auto)` (§8).
|
||||
Cite: 8248b06337 (PR text); `colours-dark-mode.md`.
|
||||
|
||||
- **Rule:** On MSW, a custom-painted control buffers its own drawing (`wxAutoBufferedPaintDC`/
|
||||
`wxBufferedPaintDC` with `wxBG_STYLE_PAINT`, or the Orca memory-DC + `wxGCDC` path), or calls
|
||||
`SetDoubleBuffered(true)` after creation.
|
||||
**Why:** 3.3.0's global `WS_EX_COMPOSITED` was reverted in 3.3.2 (`changes.txt:501`). In 3.3.4
|
||||
**Why:** 3.3.0's global `WS_EX_COMPOSITED` was reverted in 3.3.2 (`changes.txt:308`). In 3.3.2
|
||||
nothing sets it except an explicit `SetDoubleBuffered(true)` [source] (`src/msw/window.cpp:4704-4721`;
|
||||
the only other use is `MSWDisableComposited`, :1643-1652), so MSW windows are not double-buffered by
|
||||
default, exactly as in 3.1.5. `wxAutoBufferedPaintDC` is a `wxBufferedPaintDC` on MSW at compile
|
||||
time (`include/wx/dcbuffer.h:18-23, 215-221`). Nothing tuned against 3.3.0/3.3.1 applies.
|
||||
Cite: `docs/changes.txt:501, 750`; `painting-custom-widgets.md`.
|
||||
Cite: `docs/changes.txt:308, 557`; `painting-custom-widgets.md`.
|
||||
|
||||
- **Rule:** A `wxEVT_DPI_CHANGED` handler must be correct on GTK3, and every handler you bind — on a
|
||||
child, a control or a `DPIDialog`/`DPIFrame` — calls `Skip()`.
|
||||
@@ -548,11 +529,11 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
ctor starts at 1 because `get_dpi_for_window` returns 96 on Linux). Double scaling when a window moves between monitors of
|
||||
different scale is a risk that has not been verified at runtime; test DPI changes on GTK when
|
||||
touching `DPIAware::rescale` paths.
|
||||
Cite: `docs/changes.txt:734`; `dpi-bitmaps-fonts.md` §wxEVT_DPI_CHANGED.
|
||||
Cite: `docs/changes.txt:541`; `dpi-bitmaps-fonts.md` §wxEVT_DPI_CHANGED.
|
||||
|
||||
## 6 3.3.1 notable fixes
|
||||
|
||||
`changes.txt:526-570`.
|
||||
`changes.txt:333-377`.
|
||||
|
||||
| Port | Change | Orca relevance |
|
||||
|---|---|---|
|
||||
@@ -566,7 +547,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
|
||||
## 7 3.3.2 notable changes
|
||||
|
||||
`changes.txt:448-511`, relative to 3.2.10 (see §1 for the unlisted 3.2.9/3.2.10 fixes).
|
||||
`changes.txt:255-318`, relative to 3.2.10 (see §1 for the unlisted 3.2.9/3.2.10 fixes).
|
||||
|
||||
| Port | Change | Orca relevance |
|
||||
|---|---|---|
|
||||
@@ -608,7 +589,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
// Wrong: m_static_valid->SetLabel(info_line); m_static_valid->Wrap(FromDIP(300));
|
||||
// Right: m_static_valid->SetLabel(info_line); m_static_valid->Wrap(-1); m_static_valid->Wrap(FromDIP(300));
|
||||
```
|
||||
Cite: `docs/changes.txt:473`; `sizers-layout.md` §wxStaticText wrapping.
|
||||
Cite: `docs/changes.txt:280`; `sizers-layout.md` §wxStaticText wrapping.
|
||||
|
||||
- **Rule:** On Linux/X11, call `wxGLCanvas::PreferGLX()` before any GL use, attribute objects
|
||||
included; set a swap interval explicitly if the canvas needs VSync.
|
||||
@@ -624,7 +605,7 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
```cpp
|
||||
// Right (if frame pacing is wanted): canvas->SetSwapInterval(1); // before the first SwapBuffers
|
||||
```
|
||||
Cite: `docs/changes.txt:468-469, 485`; `webview-gl-aui-media.md` §EGL vs GLX.
|
||||
Cite: `docs/changes.txt:275-276, 292`; `webview-gl-aui-media.md` §EGL vs GLX.
|
||||
|
||||
- **Rule:** Do not count clicks from `wxEVT_LEFT_DCLICK` alone on macOS; handle `DOWN` and `DCLICK`
|
||||
as on MSW.
|
||||
@@ -632,68 +613,13 @@ WebKit2GTK deliver script messages synchronously (`src/osx/webview_webkit.mm:136
|
||||
(left and right buttons), so a triple click gives `DOWN, DCLICK, DOWN` as on MSW; previously every
|
||||
click with `clickCount > 1` was a `DCLICK` [source] (`src/osx/cocoa/window.mm:4103-4150`). Per-platform click-count ifdefs
|
||||
written for 3.1.5 need rechecking.
|
||||
Cite: `docs/changes.txt:509`; `mouse-keyboard-focus.md`.
|
||||
Cite: `docs/changes.txt:316`; `mouse-keyboard-focus.md`.
|
||||
|
||||
## 8 3.3.3 notable changes
|
||||
## 8 Migration already done in Orca
|
||||
|
||||
`changes.txt:388-445`, relative to 3.2.11 (see §1 for the unlisted 3.2.10/3.2.11 fixes).
|
||||
|
||||
| Port | Change | Orca relevance |
|
||||
|---|---|---|
|
||||
| All | `wxMoveToTrash()` (#26256); `wxSizer::DetachItem()` (#26512); truncated reads are errors in `wxDataInputStream` (#26600); `wxGetOsDescription()` (#26231); C++20 `<=>` for `wxString` (#26306) | available; Orca detaches windows and has its own file deletion |
|
||||
| All | **`wxGetTranslation()` returns `const wxString&` again**, reverting 3.3.2's by-value change | the by-value rule is retired → §3 pitfall |
|
||||
| GUI | `wxWebView::Print()` print settings (#26239) and `PrintToPDF()` (#26583); `wxWebViewConfiguration::DisableGPUAcceleration()` (#26626) | `webview-gl-aui-media.md` |
|
||||
| GUI | `wxGrid::GetFrozen{Row,Col}LabelWindow()` (#26617); `wxSVGFileDC` accessibility (#26379) and `GetSVGDocument()` (#26344) | `controls-dataview.md`; `wxSVGFileDC` available |
|
||||
| wxGTK | Fix regression when using `wxImageList` in high DPI (#26349) | the physical-pixel image-list rule is unchanged (§3) |
|
||||
| wxGTK | Fix `wxStaticText` label wrapping | the base `Wrap()` cache is unchanged (§7); only the GTK label wrapping was fixed |
|
||||
| wxGTK | Assert when the clipboard has non-ASCII targets fixed (#26263); GTK-without-Wayland build fixed (#26269); high-DPI bitmaps in `wxAboutBox` (#22192) | clipboard |
|
||||
| wxMSW | **Light/dark mode switching implemented** (#26516) | see §5: this removes the old "cannot switch at runtime" blocker |
|
||||
| wxMSW | `wxMessageDialog` dark mode (#26570); combo/listbox dark mode on Windows 11 (#26535); `wxCheckBox` label in screen readers (#26270); `wxPD_CAN_SKIP` regression (#26287); Direct2D text regression (#26259) | dark-mode and accessibility fixes |
|
||||
| wxMSW | Stylus events (#26223); colour emoji via Direct2D (#26657) | available |
|
||||
| wxOSX | Data-view icons under macOS 26 (#26374); `wxOverlay` phantoms in Mission Control (#26380); `wxEVT_DATAVIEW_ITEM_EDITING_DONE` value set (#26367); Xcode project for macOS 27 (#26578) | native look and data view |
|
||||
|
||||
## 9 3.3.4 notable changes
|
||||
|
||||
`changes.txt:295-385`, relative to 3.2.11 (see §1 for the unlisted 3.2.11/3.2.12 fixes).
|
||||
|
||||
| Port | Change | Orca relevance |
|
||||
|---|---|---|
|
||||
| All | `wxString` usable with `{fmt}`/`std::format()` (#27180); `wxPATH_RMDIR_PARENTS`; `fsync()` in `wxTempFile` (#27001); `wxDateTime::GetAsDOS()` after 2038 (#4393); TCP `wxIPC` fixes (#24858) | available |
|
||||
| GUI | **Accelerators take priority over the focused window in all ports** (#22630), with the new `wxEVT_ACCELERATOR_KEY` event and the `wxWindow::ClaimsKeyBeforeAccelerator()` override that text controls use to reserve their editing keys (`interface/wx/event.h:1799`, `interface/wx/window.h:4580`) | pitfall below; `mouse-keyboard-focus.md` |
|
||||
| GUI | wxGrid: `wxGridCellChoiceEditor` UI fixes (#12094), best size for auto-wrapped rows (#14072), `MakeCellVisible()` with variable row heights (#4371), editor-change crash (#21986) | `controls-dataview.md`; the choice-editor `Combo()` cast rule is unchanged |
|
||||
| GUI | AUI: no spurious sash jump on click (#4547); an added pane's best size is respected (#4404) | Plater docking |
|
||||
| GUI | `wxListCtrl` (#15411) and `wxStatusBar` (#24305) accessibility; `wxAuiNotebook` selection after a page removal (#9920); `wxPreviewCanvas` crash (#9822); generic `wxCalendarCtrl` background colour (#21885); LunaSVG with filters (#27176) | available; no `wxAuiNotebook` in Orca |
|
||||
| wxMSW | **Dark mode can be enabled dynamically** (#26734); dark mode for `wxFontPickerCtrl` (#26667), `wxNotebook` (#26694, #26695), `wxSpinButton` (#26682, #26683) and the common dialogs (#26780) | §5 pitfall; `colours-dark-mode.md` |
|
||||
| wxMSW | `wxRenameFile()` no longer copies file attributes (`changes.txt:345-347`, incompatible); Esc closes dialogs used by IME/auto-completion (#16059, #27145); IME handling in wxGrid improved (#24558); `wxPersistent` controls DPI-aware (#18752); non-BMP character input (#27016) | file dialogs, keyboard and IME |
|
||||
| wxGTK | Wayland: crash on pointer/seat disconnect fixed (#27069); **AUI panes draggable via system drag sessions** (#26969); **window geometry restorable** via XDG session management (#26324); overlay-scrollbar client size (#26889) | the Wayland gaps are partly narrowed; `webview-gl-aui-media.md` §AUI |
|
||||
| wxOSX | `wxEVT_CHAR` key codes fixed for events with Ctrl (#26810); context-menu accessibility (#13010); custom controls in sandboxed `wxFileDialog` (#26908) | `mouse-keyboard-focus.md`; macOS keys |
|
||||
|
||||
**Pitfalls**
|
||||
|
||||
- **Rule:** On every port an accelerator now runs before the focused window handles the key, unless
|
||||
that window claims the key; a custom control that must keep a key over an accelerator overrides
|
||||
`wxWindow::ClaimsKeyBeforeAccelerator()`, or handles `wxEVT_ACCELERATOR_KEY` and calls
|
||||
`UseAccelerator()`.
|
||||
**Why:** before 3.3.4 only wxGTK let the focused window handle the key first; the other ports
|
||||
already gave accelerators priority. 3.3.4 made every port consistent and added the
|
||||
`wxEVT_ACCELERATOR_KEY` event, sent after `wxEVT_CHAR_HOOK` (before it on macOS) to the focus
|
||||
window and propagated to its parents (`interface/wx/event.h:1740-1801`;
|
||||
`interface/wx/window.h:4554-4583`). Text controls, `wxComboBox` and `wxSpinCtrl` reserve their
|
||||
editing keys by overriding `ClaimsKeyBeforeAccelerator()` and returning
|
||||
`wxTextEntryBase::IsUsedForEditing()` (`src/common/textcmn.cpp:136-140`;
|
||||
`src/common/textentrycmn.cpp:441-509`; `src/common/spinctrlcmn.cpp:160-167`;
|
||||
`src/msw/combobox.cpp:389-395`, `src/gtk/combobox.cpp:205-211`), and the GTK accelerator walk now
|
||||
consults it (`src/gtk/window.cpp:1524-1539`). Orca's topbar menus are popups whose accelerators are
|
||||
display-only and Orca dispatches its own shortcuts, so few sites are affected; the macOS
|
||||
`ObjectList` `wxAcceleratorTable` and the wxInspector table are the live ones.
|
||||
Cite: `docs/changes.txt:321`; `mouse-keyboard-focus.md` §Accelerators.
|
||||
|
||||
## 10 Migration already done in Orca
|
||||
|
||||
The upgrade landed as 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system 2d7e26292b) and
|
||||
the pin later advanced to upstream 3.3.4. Its PR kept Orca's own MSW dark mode to avoid broader
|
||||
changes; wx could then only switch at app start, so a live Orca toggle needed NppDarkMode. Each row is
|
||||
a rule for new code; the owning file has the detail.
|
||||
The upgrade landed as 8248b06337 ("Updated wxWidgets to 3.3.2", #12941; build system 2d7e26292b).
|
||||
Its PR kept Orca's own MSW dark mode to avoid broader changes and because wx's needs an app restart to
|
||||
switch. Each row is a rule for new code; the owning file has the detail.
|
||||
|
||||
| Commit | Rule for new code | Where | Owner |
|
||||
|---|---|---|---|
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: 'Deploy Nightly'
|
||||
description: 'Upload a file to a release, replacing the asset of the same name'
|
||||
inputs:
|
||||
asset_path:
|
||||
description: 'Path of the file to upload'
|
||||
required: true
|
||||
asset_name:
|
||||
description: 'Asset name on the release'
|
||||
required: true
|
||||
content_type:
|
||||
description: 'Content type of the uploaded file'
|
||||
required: false
|
||||
default: 'application/octet-stream'
|
||||
tag:
|
||||
description: 'Tag of the release to upload to'
|
||||
required: false
|
||||
default: 'nightly-builds'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Replace release asset
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ASSET_PATH: ${{ inputs.asset_path }}
|
||||
ASSET_NAME: ${{ inputs.asset_name }}
|
||||
CONTENT_TYPE: ${{ inputs.content_type }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: bash "$GITHUB_ACTION_PATH/deploy.sh"
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# Replaces one asset of a GitHub release, uploading under a temporary name
|
||||
# first so a failed upload leaves the old asset in place.
|
||||
#
|
||||
# Environment: GH_TOKEN, ASSET_PATH, ASSET_NAME, CONTENT_TYPE, RELEASE_TAG and
|
||||
# the GITHUB_* variables Actions sets.
|
||||
|
||||
# The $names inside the single-quoted jq programs are jq variables.
|
||||
# shellcheck disable=SC2016
|
||||
set -euo pipefail
|
||||
|
||||
repo_api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY"
|
||||
|
||||
if [ ! -f "$ASSET_PATH" ]; then
|
||||
echo "::error::$ASSET_PATH does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The speed limit turns a stalled transfer into an error that --retry handles.
|
||||
curl_opts=(-sS --retry 3 --connect-timeout 30 --speed-limit 1024 --speed-time 60
|
||||
-H "Authorization: Bearer $GH_TOKEN")
|
||||
|
||||
# Prints the response body, or reports GitHub's error body and fails.
|
||||
api() {
|
||||
local out
|
||||
if ! out=$(curl "${curl_opts[@]}" --fail-with-body "$@"); then
|
||||
# A workflow command ends at the first newline.
|
||||
echo "::error::GitHub API request failed: ${out//[$'\r\n']/ }" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
http_status() {
|
||||
curl "${curl_opts[@]}" -o /dev/null -w '%{http_code}' "$@"
|
||||
}
|
||||
|
||||
# jq prints CRLF on Windows.
|
||||
jqr() {
|
||||
jq -r "$@" | tr -d '\r'
|
||||
}
|
||||
|
||||
delete_asset() {
|
||||
local code
|
||||
code=$(http_status -X DELETE "$repo_api/releases/assets/$1")
|
||||
case $code in
|
||||
# A 404 means a concurrent run already deleted the asset.
|
||||
204 | 404) ;;
|
||||
*)
|
||||
echo "::error::Deleting release asset $1 failed with HTTP $code" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
list_assets() {
|
||||
local page=1 json
|
||||
while :; do
|
||||
# Callers run this inside $(...), where bash ignores set -e.
|
||||
json=$(api "$repo_api/releases/$release_id/assets?per_page=100&page=$page") || exit 1
|
||||
printf '%s\n' "$json"
|
||||
[ "$(jqr length <<<"$json")" -eq 100 ] || break
|
||||
page=$((page + 1))
|
||||
done | jq -s add
|
||||
}
|
||||
|
||||
release=$(api "$repo_api/releases/tags/$RELEASE_TAG")
|
||||
release_id=$(jqr .id <<<"$release")
|
||||
upload_url=$(jqr .upload_url <<<"$release")
|
||||
upload_url=${upload_url%%\{*}
|
||||
|
||||
# Unique per run attempt, so concurrent runs never upload to the same name.
|
||||
tmp_name="$ASSET_NAME.partial-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
|
||||
|
||||
# Deletes leftover partials older than an hour, an age no upload in progress reaches.
|
||||
stale_ids=$(list_assets | jqr --arg prefix "$ASSET_NAME.partial-" \
|
||||
'.[] | select((.name | startswith($prefix)) and (.created_at | fromdateiso8601) < now - 3600) | .id')
|
||||
for id in $stale_ids; do
|
||||
delete_asset "$id"
|
||||
done
|
||||
|
||||
name_query=$(jqr -n --arg name "$tmp_name" '$name | @uri')
|
||||
# Uploads once, since a retry after an upload that succeeded fails on the taken name.
|
||||
new_id=$(api --retry 0 -X POST -H "Content-Type: $CONTENT_TYPE" -T "$ASSET_PATH" \
|
||||
"$upload_url?name=$name_query" | jqr .id)
|
||||
trap 'delete_asset "$new_id" || true' EXIT
|
||||
|
||||
rename_body=$(jq -n --arg name "$ASSET_NAME" '{name: $name}')
|
||||
# Retries on HTTP 422, which a concurrent run causes by renaming its own upload
|
||||
# between this delete and this rename.
|
||||
for _ in 1 2 3; do
|
||||
old_ids=$(list_assets | jqr --arg name "$ASSET_NAME" '.[] | select(.name == $name) | .id')
|
||||
for id in $old_ids; do
|
||||
delete_asset "$id"
|
||||
done
|
||||
code=$(http_status -X PATCH -d "$rename_body" "$repo_api/releases/assets/$new_id")
|
||||
if [ "$code" = 200 ]; then
|
||||
trap - EXIT
|
||||
exit 0
|
||||
fi
|
||||
[ "$code" = 422 ] || break
|
||||
done
|
||||
echo "::error::Renaming $tmp_name to $ASSET_NAME failed with HTTP $code" >&2
|
||||
exit 1
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
- release/*
|
||||
paths:
|
||||
- 'deps/**'
|
||||
- 'deps_src/**'
|
||||
- 'src/**'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'version.inc'
|
||||
@@ -14,8 +15,10 @@ on:
|
||||
- 'resources/**'
|
||||
- ".github/workflows/build_*.yml"
|
||||
- ".github/workflows/unit_tests*.yml"
|
||||
- 'build_linux.sh'
|
||||
- 'build_win.bat'
|
||||
- 'scripts/test_build_win.ps1'
|
||||
- 'build_release_macos.sh'
|
||||
- 'scripts/build_preset_cache.*'
|
||||
- 'scripts/flatpak/**'
|
||||
- 'scripts/msix/**'
|
||||
@@ -54,7 +57,9 @@ on:
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
||||
# cancel_closed_pr_builds.yml joins this group by name, so update that file
|
||||
# with any change here.
|
||||
group: build-all-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
||||
# Pushes don't cancel a running build, because only a finished branch build
|
||||
# saves caches every pull request can restore.
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -436,14 +441,10 @@ jobs:
|
||||
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
|
||||
- name: Deploy Flatpak to nightly release
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
# The asset is /app (the exes link it at runtime) plus the build tree
|
||||
# slimmed to what ctest needs.
|
||||
- name: Package flatpak test asset
|
||||
|
||||
@@ -1,73 +1,198 @@
|
||||
name: Check Cache
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
required: true
|
||||
type: string
|
||||
arch:
|
||||
required: false
|
||||
type: string
|
||||
compiler:
|
||||
required: false
|
||||
type: string
|
||||
default: msvc
|
||||
build-deps-only:
|
||||
required: false
|
||||
type: boolean
|
||||
force-build:
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
check_cache: # determines if there is a cache and outputs variables used in caching process
|
||||
name: Check Cache
|
||||
runs-on: ${{ inputs.os }}
|
||||
outputs:
|
||||
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
lfs: 'false'
|
||||
|
||||
- name: set outputs
|
||||
id: set_outputs
|
||||
env:
|
||||
# Anything that changes how the tree is built belongs in the key, or a job
|
||||
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
|
||||
# 'linux-clang' keeps the cache it already has.
|
||||
cache-os: ${{ runner.os == 'macOS' && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
|
||||
# The Windows ARM64 deps build in build-arm64, all others under build;
|
||||
# build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat.
|
||||
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
|
||||
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
||||
run: |
|
||||
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
|
||||
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
|
||||
|
||||
- name: load cache
|
||||
id: cache_deps
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
lookup-only: true
|
||||
|
||||
build_deps: # call next step
|
||||
name: Build Deps
|
||||
needs: [check_cache]
|
||||
uses: ./.github/workflows/build_deps.yml
|
||||
with:
|
||||
cache-key: ${{ needs.check_cache.outputs.cache-key }}
|
||||
cache-path: ${{ needs.check_cache.outputs.cache-path }}
|
||||
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
|
||||
os: ${{ inputs.os }}
|
||||
arch: ${{ inputs.arch }}
|
||||
compiler: ${{ inputs.compiler }}
|
||||
build-deps-only: ${{ inputs.build-deps-only }}
|
||||
force-build: ${{ inputs.force-build }}
|
||||
secrets: inherit
|
||||
name: Check Cache
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
required: true
|
||||
type: string
|
||||
arch:
|
||||
required: false
|
||||
type: string
|
||||
compiler:
|
||||
required: false
|
||||
type: string
|
||||
default: msvc
|
||||
build-deps-only:
|
||||
required: false
|
||||
type: boolean
|
||||
force-build:
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
# Read by scripts/ci_deps_cache.sh.
|
||||
env:
|
||||
REPO: ${{ github.repository }}
|
||||
WORKFLOW_REF: ${{ github.workflow_ref }}
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
|
||||
jobs:
|
||||
check_cache: # determines if there is a cache and outputs variables used in caching process
|
||||
name: Check Cache
|
||||
# Hosted macOS runners are scarce, so their lookup runs on Linux.
|
||||
runs-on: ${{ startsWith(inputs.os, 'macos-') && 'ubuntu-24.04' || inputs.os }}
|
||||
outputs:
|
||||
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
|
||||
macos-path: ${{ steps.macos_path.outputs.path }}
|
||||
key-inputs: ${{ steps.wait_check.outputs.key-inputs }}
|
||||
wait-for: ${{ steps.wait_check.outputs.wait-for }}
|
||||
wait-since: ${{ steps.wait_check.outputs.wait-since }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
lfs: 'false'
|
||||
# The wait check compares the merge commit with its first parent.
|
||||
fetch-depth: ${{ startsWith(inputs.os, 'macos-') && 2 || 1 }}
|
||||
# Fetches only deps/, which the key hashes, and the wait script.
|
||||
sparse-checkout: |
|
||||
/deps/
|
||||
/scripts/ci_deps_cache.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: set outputs
|
||||
id: set_outputs
|
||||
env:
|
||||
# Anything that changes how the tree is built belongs in the key, or a job
|
||||
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
|
||||
# 'linux-clang' keeps the cache it already has.
|
||||
cache-os: ${{ (runner.os == 'macOS' || startsWith(inputs.os, 'macos-')) && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
|
||||
# The Windows ARM64 deps build in build-arm64, all others under build;
|
||||
# build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat.
|
||||
dep-folder-name: ${{ (runner.os == 'macOS' || startsWith(inputs.os, 'macos-')) && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
|
||||
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
||||
run: |
|
||||
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
|
||||
# Relative to the workspace, which differs from the build's when this job runs on Linux.
|
||||
echo cache-path=deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
|
||||
|
||||
# actions/cache matches the path too, so a Linux lookup for macOS uses the
|
||||
# workspace of a hosted macOS job.
|
||||
- name: macOS cache path
|
||||
id: macos_path
|
||||
if: ${{ startsWith(inputs.os, 'macos-') }}
|
||||
run: echo "path=/Users/runner/work/${GITHUB_REPOSITORY#*/}/${GITHUB_REPOSITORY#*/}/${{ steps.set_outputs.outputs.cache-path }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: load cache
|
||||
id: cache_deps
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ steps.macos_path.outputs.path || format('{0}/{1}', github.workspace, steps.set_outputs.outputs.cache-path) }}
|
||||
key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
lookup-only: true
|
||||
|
||||
# A pull request that misses the cache can wait for a base branch build of the same deps.
|
||||
- name: check for a base branch build to wait for
|
||||
id: wait_check
|
||||
if: ${{ startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && steps.cache_deps.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash scripts/ci_deps_cache.sh check
|
||||
|
||||
wait_for_deps:
|
||||
name: Wait for base branch deps
|
||||
needs: [check_cache]
|
||||
if: ${{ needs.check_cache.outputs.wait-for != '' }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
WAIT_MINUTES: 120
|
||||
# Must exceed WAIT_MINUTES.
|
||||
timeout-minutes: 130
|
||||
# Waiting runs queue here, and only the first one holds a runner.
|
||||
concurrency:
|
||||
group: deps-wait-${{ github.base_ref }}-${{ needs.check_cache.outputs.cache-key }}
|
||||
queue: max
|
||||
outputs:
|
||||
hit: ${{ steps.lookup.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: scripts/ci_deps_cache.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: wait for the deps cache
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
KEY: ${{ needs.check_cache.outputs.cache-key }}
|
||||
KEY_INPUTS: ${{ needs.check_cache.outputs.key-inputs }}
|
||||
WAIT_FOR: ${{ needs.check_cache.outputs.wait-for }}
|
||||
WAIT_SINCE: ${{ needs.check_cache.outputs.wait-since }}
|
||||
ARCH: ${{ inputs.arch }}
|
||||
run: bash scripts/ci_deps_cache.sh wait
|
||||
|
||||
- name: load cache
|
||||
id: lookup
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ needs.check_cache.outputs.macos-path }}
|
||||
key: ${{ needs.check_cache.outputs.cache-key }}
|
||||
lookup-only: true
|
||||
|
||||
# Hosted macOS runners are scarce, so a pull request's macOS build waits until
|
||||
# one is free that push and nightly builds do not need. See the script.
|
||||
macos_admission:
|
||||
name: Wait for a macOS runner
|
||||
needs: [check_cache, wait_for_deps]
|
||||
if: ${{ !cancelled() && needs.check_cache.result == 'success' && startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'macos-priority') }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
WAIT_MINUTES: 240
|
||||
PRIORITY: false
|
||||
# Must exceed WAIT_MINUTES.
|
||||
timeout-minutes: 250
|
||||
# One line for every arch of every pull request, and only its first job polls.
|
||||
concurrency:
|
||||
group: macos-admission
|
||||
queue: max
|
||||
steps: &macos_admission_steps
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: scripts/ci_macos_admission.py
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: wait for a macOS runner
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
MACOS_RUNNER_LIMIT: ${{ vars.MACOS_RUNNER_LIMIT }}
|
||||
# -u, or the log shows nothing until the wait ends.
|
||||
run: python3 -u scripts/ci_macos_admission.py
|
||||
|
||||
# The macos-priority label, when the run starts, puts a pull request in its own
|
||||
# line, which the normal one yields to. The names are fixed, because a skipped
|
||||
# job is listed under its unevaluated name, and the script matches both.
|
||||
macos_admission_priority:
|
||||
name: Wait for a macOS runner (priority)
|
||||
needs: [check_cache, wait_for_deps]
|
||||
if: ${{ !cancelled() && needs.check_cache.result == 'success' && startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'macos-priority') }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
WAIT_MINUTES: 240
|
||||
PRIORITY: true
|
||||
# Must exceed WAIT_MINUTES.
|
||||
timeout-minutes: 250
|
||||
concurrency:
|
||||
group: macos-admission-priority
|
||||
queue: max
|
||||
steps: *macos_admission_steps
|
||||
|
||||
build_deps: # call next step
|
||||
name: Build Deps
|
||||
needs: [check_cache, wait_for_deps, macos_admission, macos_admission_priority]
|
||||
# A failed wait counts as no cache, so the deps are built here. A failed
|
||||
# admission still builds rather than skip macOS.
|
||||
if: ${{ !cancelled() && needs.check_cache.result == 'success' }}
|
||||
uses: ./.github/workflows/build_deps.yml
|
||||
with:
|
||||
cache-key: ${{ needs.check_cache.outputs.cache-key }}
|
||||
cache-path: ${{ needs.check_cache.outputs.cache-path }}
|
||||
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' || needs.wait_for_deps.outputs.hit == 'true' }}
|
||||
os: ${{ inputs.os }}
|
||||
arch: ${{ inputs.arch }}
|
||||
compiler: ${{ inputs.compiler }}
|
||||
build-deps-only: ${{ inputs.build-deps-only }}
|
||||
force-build: ${{ inputs.force-build }}
|
||||
secrets: inherit
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: load cached deps
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ inputs.cache-path }}
|
||||
path: ${{ github.workspace }}/${{ inputs.cache-path }}
|
||||
key: ${{ inputs.cache-key }}
|
||||
|
||||
- uses: lukka/get-cmake@latest
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
choco install strawberryperl
|
||||
}
|
||||
# cache-path is the install directory inside the deps build directory.
|
||||
$deps = (Split-Path "${{ inputs.cache-path }}").Replace('\', '/')
|
||||
$deps = (Split-Path "${{ github.workspace }}/${{ inputs.cache-path }}").Replace('\', '/')
|
||||
# -l compiles with Visual Studio's clang-cl and -x builds with Ninja; --msvc --msbuild is cl under the Visual Studio generator.
|
||||
$flags = if ("${{ inputs.compiler }}" -eq "clang") { "-l", "-x" } else { "--msvc", "--msbuild" }
|
||||
.\build_win.bat -d --arch ${{ inputs.arch }} --deps-dir $deps @flags
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
if: ${{ !(runner.os == 'macOS' && inputs.macos-combine-only) }}
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ inputs.cache-path }}
|
||||
path: ${{ github.workspace }}/${{ inputs.cache-path }}
|
||||
key: ${{ inputs.cache-key }}
|
||||
fail-on-cache-miss: true
|
||||
|
||||
@@ -179,6 +179,13 @@ jobs:
|
||||
if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED
|
||||
run: |
|
||||
df -hI /
|
||||
# Deleting the unused Xcodes takes minutes, so only do it when the
|
||||
# build might not fit. A cold build uses under 4 GiB after this step.
|
||||
avail_gib=$(df -g / | awk 'NR == 2 { print $4 }')
|
||||
if [ "$avail_gib" -ge 15 ]; then
|
||||
echo "$avail_gib GiB free, enough for the build"
|
||||
exit 0
|
||||
fi
|
||||
# Keep only the selected Xcode
|
||||
xcode=$(basename "$(cd "$(xcode-select -p)/../.." && pwd -P)")
|
||||
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "$xcode" -exec rm -rf {} +
|
||||
@@ -334,12 +341,12 @@ jobs:
|
||||
|
||||
# Notarize main DMG
|
||||
xcrun notarytool submit "OrcaSlicer_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
|
||||
xcrun stapler staple OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
retry xcrun stapler staple OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
|
||||
# Notarize profile validator DMG if it exists
|
||||
if [ -f "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" ]; then
|
||||
xcrun notarytool submit "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
|
||||
xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
retry xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
fi
|
||||
|
||||
- name: Create DMG without notary
|
||||
@@ -390,25 +397,18 @@ jobs:
|
||||
|
||||
- name: Deploy Mac release
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
asset_name: OrcaSlicer_Mac_universal_nightly.dmg
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
|
||||
# The DMG is only built when the validator app was.
|
||||
- name: Deploy Mac OrcaSlicer_profile_validator DMG release
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED && hashFiles(format('OrcaSlicer_profile_validator_Mac_universal_{0}.dmg', env.ver)) != ''
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
asset_name: OrcaSlicer_profile_validator_Mac_universal_nightly.dmg
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
# Windows
|
||||
- name: Set Windows build variables
|
||||
@@ -453,14 +453,10 @@ jobs:
|
||||
- name: Build slicer Win
|
||||
if: runner.os == 'Windows'
|
||||
working-directory: ${{ github.workspace }}
|
||||
# Orca: Removed Netfabb STL fixing service support in favor of CGAL.
|
||||
# env:
|
||||
# WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\'
|
||||
# WindowsSDKVersion: '10.0.26100.0\'
|
||||
# --tests builds the unit tests too; the unit_tests_windows_* jobs run them.
|
||||
run: |
|
||||
# cache-path is the install directory inside the deps build directory.
|
||||
$deps = (Split-Path "${{ inputs.cache-path }}").Replace('\', '/')
|
||||
$deps = (Split-Path "${{ github.workspace }}/${{ inputs.cache-path }}").Replace('\', '/')
|
||||
# -l compiles with Visual Studio's clang-cl and -x builds with Ninja; --msvc --msbuild is cl under the Visual Studio generator.
|
||||
$flags = if ("${{ inputs.compiler }}" -eq "clang") { "-l", "-x" } else { "--msvc", "--msbuild" }
|
||||
.\build_win.bat -s --tests -i --arch ${{ inputs.arch }} --build-dir $env:BUILD_DIR --deps-dir $deps @flags
|
||||
@@ -539,36 +535,27 @@ jobs:
|
||||
|
||||
- name: Deploy Windows release portable
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip
|
||||
asset_name: OrcaSlicer_Windows${{ env.ARCH_SUFFIX }}_nightly_portable.zip
|
||||
asset_content_type: application/x-zip-compressed
|
||||
max_releases: 1
|
||||
content_type: application/x-zip-compressed
|
||||
|
||||
- name: Deploy Windows release installer
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe
|
||||
asset_name: OrcaSlicer_Windows_Installer${{ env.ARCH_SUFFIX }}_nightly.exe
|
||||
asset_content_type: application/x-msdownload
|
||||
max_releases: 1
|
||||
content_type: application/x-msdownload
|
||||
|
||||
- name: Deploy Windows OrcaSlicer_profile_validator release
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/build/src/Release/OrcaSlicer_profile_validator.exe
|
||||
asset_name: OrcaSlicer_profile_validator_Windows_nightly.exe
|
||||
asset_content_type: application/x-msdownload
|
||||
max_releases: 1
|
||||
content_type: application/x-msdownload
|
||||
|
||||
- name: Build MSIX Store package Win
|
||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
@@ -697,14 +684,10 @@ jobs:
|
||||
|
||||
- name: Deploy Ubuntu release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage
|
||||
asset_name: OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_nightly.AppImage
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
- name: Deploy Ubuntu release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
run: |
|
||||
@@ -713,36 +696,24 @@ jobs:
|
||||
|
||||
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ./build/src/Release/OrcaSlicer_profile_validator
|
||||
asset_name: OrcaSlicer_profile_validator_Linux${{ env.ubuntu-ver-str }}_nightly
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
- name: Deploy Ubuntu generate_system_cache release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ./build/src/dev-utils/Release/generate_system_cache
|
||||
asset_name: generate_system_cache_Linux${{ env.ubuntu-ver-str }}_nightly
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
- name: Deploy orca_custom_preset_tests
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
uses: ./.github/actions/deploy-nightly
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ${{ github.workspace }}/resources/profiles/orca_custom_preset_tests.zip
|
||||
asset_name: orca_custom_preset_tests.zip
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
# The build has just touched everything it can use, so an object
|
||||
# untouched for a week is dead, usually orphaned by a flag change.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Cancel builds of closed pull requests
|
||||
|
||||
on:
|
||||
# pull_request_target needs no approval for first-time contributors and must never
|
||||
# check out pull request code.
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
cancel:
|
||||
# Matches the concurrency group in build_all.yml, so joining it cancels the pull
|
||||
# request's unfinished Build all run.
|
||||
concurrency:
|
||||
group: build-all-pull_request-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Joined concurrency group build-all-pull_request-${{ github.event.pull_request.number }}."
|
||||
@@ -13,6 +13,7 @@ on:
|
||||
# both it and its tests live in scripts/, so a PR touching only those must still run
|
||||
# this workflow.
|
||||
- 'resources/printers/**'
|
||||
- 'resources/info/legacy_compatible_printers.json'
|
||||
- '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.
|
||||
@@ -59,6 +60,21 @@ jobs:
|
||||
python3 ./scripts/orca_profile_tool.py check 2>&1 | tee ${{ runner.temp }}/profile_tool.log
|
||||
exit ${PIPESTATUS[0]}
|
||||
|
||||
# The app migrates user filament presets made against 2.4.2 back to the printers their
|
||||
# parent listed then, from resources/info/legacy_compatible_printers.json. A PR that
|
||||
# renames a filament preset or narrows its compatible_printers changes what that map
|
||||
# must say, so it has to regenerate it.
|
||||
- name: Check the legacy compatible printers map
|
||||
id: legacy_compat
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -o pipefail
|
||||
base="${{ runner.temp }}/v2.4.2"
|
||||
git fetch --no-tags --depth=1 origin refs/tags/v2.4.2:refs/tags/v2.4.2
|
||||
mkdir -p "$base"
|
||||
git archive v2.4.2 resources/profiles | tar -x -C "$base"
|
||||
python3 ./scripts/orca_profile_tool.py legacy-compat --base "$base/resources/profiles" --check 2>&1 | tee ${{ runner.temp }}/legacy_compat.log
|
||||
|
||||
# download
|
||||
- name: Download
|
||||
working-directory: ${{ github.workspace }}
|
||||
@@ -217,7 +233,7 @@ jobs:
|
||||
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
|
||||
|
||||
- name: Prepare comment artifact
|
||||
if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
||||
if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.legacy_compat.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
||||
run: |
|
||||
{
|
||||
# Marker matched by check_profiles_comment.yml to delete prior comments.
|
||||
@@ -234,6 +250,17 @@ jobs:
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "${{ steps.legacy_compat.outcome }}" = "failure" ]; then
|
||||
echo "### Legacy Compatible Printers Map Out of Date"
|
||||
echo ""
|
||||
echo '```'
|
||||
head -c 30000 ${{ runner.temp }}/legacy_compat.log || echo "No output captured"
|
||||
echo '```'
|
||||
echo ""
|
||||
echo 'Regenerate it: `git archive v2.4.2 resources/profiles | tar -x -C /tmp/v2.4.2`, then `python3 scripts/orca_profile_tool.py legacy-compat --base /tmp/v2.4.2/resources/profiles`.'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then
|
||||
echo "### System Profile Validation Failed"
|
||||
echo ""
|
||||
@@ -283,7 +310,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
- name: Fail if any check failed
|
||||
if: ${{ always() && (steps.profile_tool.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
||||
if: ${{ always() && (steps.profile_tool.outcome == 'failure' || steps.legacy_compat.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
||||
run: |
|
||||
echo "One or more profile checks failed; see the step logs above."
|
||||
echo 'Reproduce the whole run locally with scripts/check_profile.sh (scripts\check_profile.bat on Windows).'
|
||||
|
||||
@@ -16,6 +16,15 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# Listing caches and builds while waiting for a deps cache.
|
||||
actions: read
|
||||
|
||||
# Read by scripts/ci_deps_cache.sh.
|
||||
env:
|
||||
REPO: ${{ github.repository }}
|
||||
WORKFLOW: build_all.yml
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
JOB: build_linux (${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }})
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
@@ -35,7 +44,7 @@ jobs:
|
||||
# The PR merge commit plus its first parent, the base it is diffed against.
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Look for changed C++ files
|
||||
- name: Look for changed files
|
||||
id: changes
|
||||
run: |
|
||||
if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
|
||||
@@ -43,18 +52,75 @@ jobs:
|
||||
else
|
||||
echo "No C++ changes under src/ or tests/."
|
||||
fi
|
||||
if ! git diff --quiet HEAD^1 -- deps; then
|
||||
echo "deps=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "deps-key=linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Parsing needs the dependency headers, not a build of this PR's deps/, so
|
||||
# a PR that changes deps/ falls back to the newest cache main has.
|
||||
# Parsing needs the dependency headers, not a build of this PR's deps/.
|
||||
# Without an exact match the job waits for the build that saves it: this
|
||||
# PR's own when it changes deps/, otherwise a running push build of the
|
||||
# base branch with the same deps. With neither, a PR that keeps deps/
|
||||
# falls back to the newest cache.
|
||||
- name: Restore cached deps
|
||||
id: deps_cache
|
||||
if: steps.changes.outputs.cpp == 'true'
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
||||
key: linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}
|
||||
key: ${{ steps.changes.outputs.deps-key }}
|
||||
|
||||
- name: Look for a build to wait for
|
||||
id: wait_check
|
||||
if: steps.changes.outputs.cpp == 'true' && steps.deps_cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: bash scripts/ci_deps_cache.sh ${{ steps.changes.outputs.deps == 'true' && 'own' || 'check' }}
|
||||
|
||||
# Linux Build Deps takes about 35 minutes on GitHub-hosted runners. The
|
||||
# wait ends early when the deps job fails.
|
||||
- name: Wait for the deps cache
|
||||
if: steps.wait_check.outputs.wait-for != ''
|
||||
timeout-minutes: 100
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
KEY: ${{ steps.changes.outputs.deps-key }}
|
||||
CACHE_REF: ${{ steps.changes.outputs.deps == 'true' && github.ref || format('refs/heads/{0}', github.base_ref) }}
|
||||
KEY_INPUTS: ${{ steps.wait_check.outputs.key-inputs }}
|
||||
WAIT_FOR: ${{ steps.wait_check.outputs.wait-for }}
|
||||
WAIT_SINCE: ${{ steps.wait_check.outputs.wait-since }}
|
||||
WAIT_MINUTES: 90
|
||||
run: bash scripts/ci_deps_cache.sh wait
|
||||
|
||||
- name: Restore the deps cache after waiting
|
||||
id: waited_cache
|
||||
if: steps.wait_check.outputs.wait-for != ''
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
||||
key: ${{ steps.changes.outputs.deps-key }}
|
||||
|
||||
- name: Fail without this PR's deps cache
|
||||
if: steps.changes.outputs.deps == 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "::error::No deps cache ${{ steps.changes.outputs.deps-key }}. Check this PR's Linux Build Deps job, then rerun clang-tidy."
|
||||
exit 1
|
||||
|
||||
- name: Restore the newest cached deps
|
||||
id: newest_cache
|
||||
if: steps.changes.outputs.deps != 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
||||
key: ${{ steps.changes.outputs.deps-key }}
|
||||
restore-keys: linux-clang-cache-orcaslicer_deps-build-
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Warn about older deps
|
||||
if: steps.newest_cache.outcome == 'success' && steps.newest_cache.outputs.cache-hit != 'true'
|
||||
run: echo "::warning title=Older deps::No cache matches this PR's deps/, so clang-tidy parsed against ${{ steps.newest_cache.outputs.cache-matched-key }}. If it reports errors in code that uses a dependency, rerun clang-tidy once the base branch's Build all has saved the new deps."
|
||||
|
||||
- name: Apt-Install Dependencies
|
||||
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
|
||||
uses: ./.github/actions/apt-install-deps
|
||||
|
||||
@@ -67,6 +67,7 @@ ctest --test-dir ./tests/fff_print -C Release
|
||||
- Keep code concise and clear. Manually simplify AI generated bloated codes before review.
|
||||
- Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults.
|
||||
- For profile changes (`resources/profiles/<Vendor>/**`), check that `version` in the sibling `resources/profiles/<Vendor>.json` was bumped.
|
||||
- For new or changed UI strings, check they follow [Writing translatable strings in the source](#writing-translatable-strings-in-the-source): no concatenated fragments, plural forms for counts, contexts for ambiguous words.
|
||||
- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) for that language.
|
||||
|
||||
## Localization & translations
|
||||
@@ -74,6 +75,19 @@ ctest --test-dir ./tests/fff_print -C Release
|
||||
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
|
||||
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_guide.md) for the human-facing version of these principles.
|
||||
|
||||
### Writing translatable strings in the source
|
||||
|
||||
Translators see one msgid at a time and cannot change the code around it, so each message must be complete in a single msgid. Use the wrapper that fits instead of assembling text from pieces:
|
||||
|
||||
- **One msgid per sentence, with placeholders.** Never build a message by concatenating translated fragments (`_L("A") + name + _L(" is B")`) or by splicing a translated word such as `_L("left")` into another sentence: the translator can't reorder or inflect the parts. Write `format(_L("%1% is too close to others"), name)`.
|
||||
- **A count needs a plural form.** Don't write `file(s)` or leave a verb unchanged after a number. Use `_L_PLURAL("%1% object", "%1% objects", n)`. libslic3r has no plural lookup, so word the message so it doesn't depend on a count, or build it in the GUI.
|
||||
- **An ambiguous word needs a context.** When a short string means different things in different places (verb or noun, a mode name, a CAD term), give each sense its own context with `_L_CONTEXT`/`_u8L_CONTEXT`/`L_CONTEXT`. Keep the most common sense without a context so its existing translations stay valid.
|
||||
- **Mark every user-visible string** (`_L`/`_u8L`, or `L` where it is translated later) and list new source files in `localization/i18n/list.txt`: files that aren't listed are never extracted.
|
||||
- Add a `// TRN` comment above a new string whose meaning or placeholders aren't obvious.
|
||||
- Never choose behavior by comparing translated text; compare ids or flags.
|
||||
|
||||
See `.claude/skills/orca-translations/references/source-audit.md` for the full checklist.
|
||||
|
||||
### Terminology
|
||||
|
||||
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
|
||||
|
||||
@@ -497,47 +497,6 @@ endif()
|
||||
# Once we get rid of Perl and libslic3r is linked statically, we can get rid of -fPIC
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
|
||||
# We pick it from environment if it is not defined in another way
|
||||
# ORCA: Removed Netfabb STL fixing service support in favor of CGAL.
|
||||
# if(WIN32)
|
||||
# if(NOT DEFINED WIN10SDK_PATH)
|
||||
# if(DEFINED ENV{WIN10SDK_PATH})
|
||||
# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
|
||||
# endif()
|
||||
# endif()
|
||||
# if(DEFINED WIN10SDK_PATH)
|
||||
# #BBS: modify win10sdk_path
|
||||
# if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h")
|
||||
# set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}")
|
||||
# else()
|
||||
# message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
|
||||
# message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found")
|
||||
# message("STL fixing by the Netfabb service will not be compiled")
|
||||
# unset(WIN10SDK_PATH)
|
||||
# endif()
|
||||
# else()
|
||||
# # Try to use the default Windows 10 SDK path.
|
||||
# if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion})
|
||||
# set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}")
|
||||
# else ()
|
||||
# set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0")
|
||||
# endif ()
|
||||
# if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h")
|
||||
# message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found")
|
||||
# message("STL fixing by the Netfabb service will not be compiled")
|
||||
# unset(WIN10SDK_INCLUDE_PATH)
|
||||
# endif()
|
||||
# endif()
|
||||
# if(WIN10SDK_INCLUDE_PATH)
|
||||
# message("Building with Win10 Netfabb STL fixing service support")
|
||||
# add_definitions(-DHAS_WIN10SDK)
|
||||
# include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}")
|
||||
# else()
|
||||
# message("Building without Win10 Netfabb STL fixing service support")
|
||||
# endif()
|
||||
# endif()
|
||||
|
||||
if (APPLE)
|
||||
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
|
||||
if (CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||
|
||||
@@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
||||
pushd "${SCRIPT_PATH}" > /dev/null
|
||||
|
||||
function usage() {
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-J N][-p][-r][-s][-t][-u][-l][-L]"
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-J N][-p][-r][-s][-t][-u][-l][-L] [-- CMAKE_ARGS...]"
|
||||
echo " -1: limit builds to one core (where possible)"
|
||||
echo " -j N: limit builds to N cores (where possible)"
|
||||
echo " -J N: build up to N dependencies at a time, each still using -j jobs (default: 1)"
|
||||
@@ -29,11 +29,15 @@ function usage() {
|
||||
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
||||
echo " -l: use Clang instead of GCC (default: GCC)"
|
||||
echo " -L: use ld.lld as linker (if available)"
|
||||
echo " -- CMAKE_ARGS...: pass everything after '--' to the cmake configure step of the deps (-d) and OrcaSlicer (-s/-t) builds,"
|
||||
echo " e.g. -DCMAKE_CXX_FLAGS=\"-O2 -march=native\", -DCMAKE_CXX_COMPILER=g++-14, -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=mold."
|
||||
echo " They come last on the cmake command line, so they override what -l and -L set."
|
||||
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
|
||||
echo "Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first."
|
||||
echo "Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version."
|
||||
echo "DEPS_EXTRA_BUILD_ARGS and ORCA_EXTRA_BUILD_ARGS add whitespace-separated cmake configure arguments to the deps and OrcaSlicer builds; prefer '--' for arguments that contain spaces."
|
||||
}
|
||||
|
||||
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||
@@ -131,6 +135,20 @@ while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
|
||||
esac
|
||||
done
|
||||
|
||||
# getopts stops at '--'; whatever follows is handed to cmake unchanged.
|
||||
if [[ ${OPTIND} -le $# ]] ; then
|
||||
separator_index=$((OPTIND-1))
|
||||
if [[ "${!separator_index}" != "--" ]] ; then
|
||||
echo "Unexpected argument '${!OPTIND}': cmake arguments must follow '--', aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
shift $((OPTIND-1))
|
||||
EXTRA_CMAKE_ARGS=("$@")
|
||||
if [[ ${#EXTRA_CMAKE_ARGS[@]} -gt 0 ]] ; then
|
||||
FORWARDED_ARGS+=("--" "${EXTRA_CMAKE_ARGS[@]}")
|
||||
fi
|
||||
|
||||
if [ ${OPTIND} -eq 1 ] ; then
|
||||
usage
|
||||
exit 1
|
||||
@@ -547,7 +565,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
||||
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
||||
fi
|
||||
|
||||
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
|
||||
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}" "${EXTRA_CMAKE_ARGS[@]}"
|
||||
# The top-level build runs one dependency at a time by default, which keeps the console
|
||||
# output readable and lets that dependency's own build use all of CMAKE_BUILD_PARALLEL_LEVEL.
|
||||
# -J raises the top level instead, and -j still applies in full to every dependency, so the
|
||||
@@ -593,7 +611,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
||||
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||
-DORCA_TOOLS=ON \
|
||||
"${COLORED_OUTPUT}" \
|
||||
"${BUILD_ARGS[@]}"
|
||||
"${BUILD_ARGS[@]}" \
|
||||
"${EXTRA_CMAKE_ARGS[@]}"
|
||||
echo "done"
|
||||
if [[ -n "${BUILD_ORCA}" ]]; then
|
||||
echo "Building OrcaSlicer ..."
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
|
||||
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
|
||||
# no headers for that object. ccache reproduces /showIncludes output on a hit.
|
||||
#
|
||||
# Clang also records the modification time of every input in a precompiled header
|
||||
# and rejects the header once one differs. ccache caches the compile that creates
|
||||
# the header, so a tree that regenerates cmake_pch.cxx gets back a header stamped
|
||||
# with the old file's time, and every compile that uses it fails. The build system
|
||||
# already rebuilds the header when an input changes. This covers the dependencies,
|
||||
# such as wxWidgets; PrecompiledHeader.cmake does the same for OrcaSlicer's targets.
|
||||
foreach (_lang C CXX)
|
||||
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
|
||||
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
|
||||
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
|
||||
list(APPEND CMAKE_${_lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -fno-pch-timestamp)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in
|
||||
index 2f3a735d920c..2593c045b89b 100644
|
||||
--- a/build/cmake/wxWidgetsConfig.cmake.in
|
||||
+++ b/build/cmake/wxWidgetsConfig.cmake.in
|
||||
@@ -50,12 +50,28 @@ if(WIN32_MSVC_NAMING)
|
||||
set(wxARCH_SUFFIX "_${wxPLATFORM_ARCH}")
|
||||
endif()
|
||||
|
||||
- # use wxWidgets_USE_STATIC to force static libraries, otherwise shared is searched first
|
||||
- if(NOT wxWidgets_USE_STATIC AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_dll/@PROJECT_NAME@Targets.cmake")
|
||||
- set(wxPLATFORM_LIB_DIR "/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_dll")
|
||||
- elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_lib/@PROJECT_NAME@Targets.cmake")
|
||||
- set(wxPLATFORM_LIB_DIR "/${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}_lib")
|
||||
+ # wxCOMPILER_PREFIX names the consuming compiler; the installed layout is named
|
||||
+ # after the compiler that built wxWidgets. cl and clang-cl share an ABI, so
|
||||
+ # either can consume either build.
|
||||
+ set(wxCOMPILER_PREFIXES "${wxCOMPILER_PREFIX}")
|
||||
+ if(wxCOMPILER_PREFIX STREQUAL "clang")
|
||||
+ list(APPEND wxCOMPILER_PREFIXES "vc")
|
||||
+ elseif(wxCOMPILER_PREFIX STREQUAL "vc")
|
||||
+ list(APPEND wxCOMPILER_PREFIXES "clang")
|
||||
endif()
|
||||
+
|
||||
+ # use wxWidgets_USE_STATIC to force static libraries, otherwise shared is searched first
|
||||
+ foreach(wxPREFIX IN LISTS wxCOMPILER_PREFIXES)
|
||||
+ if(NOT wxWidgets_USE_STATIC AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxPREFIX}${wxARCH_SUFFIX}_dll/@PROJECT_NAME@Targets.cmake")
|
||||
+ set(wxPLATFORM_LIB_DIR "/${wxPREFIX}${wxARCH_SUFFIX}_dll")
|
||||
+ break()
|
||||
+ elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${wxPREFIX}${wxARCH_SUFFIX}_lib/@PROJECT_NAME@Targets.cmake")
|
||||
+ set(wxPLATFORM_LIB_DIR "/${wxPREFIX}${wxARCH_SUFFIX}_lib")
|
||||
+ break()
|
||||
+ endif()
|
||||
+ endforeach()
|
||||
+ unset(wxPREFIX)
|
||||
+ unset(wxCOMPILER_PREFIXES)
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
@@ -28,18 +28,12 @@ if (APPLE)
|
||||
COMMAND ${GIT_EXECUTABLE} apply --verbose
|
||||
${CMAKE_CURRENT_LIST_DIR}/0001-macos-use-srgb-colour-components.patch
|
||||
)
|
||||
elseif (WIN32)
|
||||
set(_wx_patch_command
|
||||
${GIT_EXECUTABLE} checkout -f -- build/cmake/wxWidgetsConfig.cmake.in
|
||||
COMMAND ${GIT_EXECUTABLE} apply --verbose
|
||||
${CMAKE_CURRENT_LIST_DIR}/0002-clang-cl-sibling-compiler-prefix.patch
|
||||
)
|
||||
endif ()
|
||||
|
||||
orcaslicer_add_cmake_project(
|
||||
wxWidgets
|
||||
GIT_REPOSITORY "https://github.com/wxWidgets/wxWidgets"
|
||||
GIT_TAG v3.3.4
|
||||
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
|
||||
GIT_TAG v3.3.2
|
||||
GIT_SHALLOW ON
|
||||
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
|
||||
PATCH_COMMAND ${_wx_patch_command}
|
||||
|
||||
@@ -88,18 +88,6 @@ struct NfpPConfig {
|
||||
*/
|
||||
bool explore_holes = false;
|
||||
|
||||
/**
|
||||
* @brief Keep the final pile on the bin.
|
||||
*
|
||||
* The final alignment centres the pile on the alignment target. A target
|
||||
* near an edge (a belt printer starts its parts at the leading end of the
|
||||
* belt) would push part of a pile that is larger than the room around that
|
||||
* point off the bed; with this set the pile stops at the edge instead, and a
|
||||
* pile that does not fit along an axis is centred on it. Off by default, so
|
||||
* the alignment of every other printer is unchanged.
|
||||
*/
|
||||
bool clamp_to_bin = false;
|
||||
|
||||
/**
|
||||
* @brief If true, use all CPUs available. Run on a single core otherwise.
|
||||
*/
|
||||
@@ -1125,22 +1113,22 @@ private:
|
||||
|
||||
auto d = cb - ci;
|
||||
|
||||
// Keep the pile on the bin (see Config::clamp_to_bin). The items' boxes carry
|
||||
// their inflation, which is the margin left at the edge.
|
||||
if (config_.clamp_to_bin) {
|
||||
auto on_bin = [](Coord lo, Coord hi, Coord bin_lo, Coord bin_hi, Coord shift) {
|
||||
if (hi - lo >= bin_hi - bin_lo)
|
||||
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
|
||||
if (lo + shift < bin_lo)
|
||||
shift = bin_lo - lo;
|
||||
if (hi + shift > bin_hi)
|
||||
shift = bin_hi - hi;
|
||||
return shift;
|
||||
};
|
||||
setX(d, on_bin(getX(bb.minCorner()), getX(bb.maxCorner()), getX(bbin.minCorner()), getX(bbin.maxCorner()), getX(d)));
|
||||
setY(d, on_bin(getY(bb.minCorner()), getY(bb.maxCorner()), getY(bbin.minCorner()), getY(bbin.maxCorner()), getY(d)));
|
||||
cb = ci + d;
|
||||
}
|
||||
// Keep the pile on the bin. Centring it on a target near an edge (a belt
|
||||
// printer starts its parts at the leading end of the belt) would push a pile
|
||||
// larger than the room around that point off the bed, so it stops at the
|
||||
// edge instead; a pile that does not fit along an axis is centred on it.
|
||||
// The items' boxes carry their inflation, which is the margin left at the edge.
|
||||
auto on_bin = [](Coord lo, Coord hi, Coord bin_lo, Coord bin_hi, Coord shift) {
|
||||
if (hi - lo >= bin_hi - bin_lo)
|
||||
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
|
||||
if (lo + shift < bin_lo)
|
||||
shift = bin_lo - lo;
|
||||
if (hi + shift > bin_hi)
|
||||
shift = bin_hi - hi;
|
||||
return shift;
|
||||
};
|
||||
setX(d, on_bin(getX(bb.minCorner()), getX(bb.maxCorner()), getX(bbin.minCorner()), getX(bbin.maxCorner()), getX(d)));
|
||||
setY(d, on_bin(getY(bb.minCorner()), getY(bb.maxCorner()), getY(bbin.minCorner()), getY(bbin.maxCorner()), getY(d)));
|
||||
|
||||
// BBS make sure the item won't clash with excluded regions
|
||||
// do we have wipe tower after arranging?
|
||||
@@ -1169,9 +1157,7 @@ private:
|
||||
return;
|
||||
}
|
||||
Item objs_convex_hull_item(objs_convex_hull);
|
||||
Vertex objs_convex_hull_ref = objs_convex_hull_item.referenceVertex();
|
||||
Vertex diff = objs_convex_hull_ref - sl::boundingBox(objs_convex_hull).center();
|
||||
Vertex ref_aligned = cb + diff; // reference point when pile center aligned with bed center
|
||||
Vertex ref_aligned = objs_convex_hull_item.referenceVertex() + d;
|
||||
bool ref_aligned_is_ok = std::any_of(nfps.begin(), nfps.end(), [&ref_aligned](auto& nfp) {return sl::isInside(ref_aligned, nfp); });
|
||||
if (!ref_aligned_is_ok) {
|
||||
// ref_aligned is not good, then find a nearest point on nfp boundary
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# Mesh repair — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
Mesh repair turns a broken triangle mesh into closed, outward oriented shells that bound a solid, so that the
|
||||
mesh slices predictably and the other mesh tools (booleans, cut, painting) get valid input. It runs on demand:
|
||||
the *Fix Model* command of the context menu of an object or part and of the warning icon in the object list, *Fix
|
||||
All* and *Fix All Plates* in the context menu of a plate, the prompt the Cut tool shows when a cut leaves open edges,
|
||||
the *Repair and import* choice of texture import, and the `--repair` option of the command line. It is built on CGAL
|
||||
and is the only repair the application has, on every platform.
|
||||
|
||||
`repair_mesh()` in `src/libslic3r/MeshRepair.cpp` does the work on one `TriangleMesh`, and `ModelVolume::repair()`
|
||||
applies it to a part: it replaces the mesh only when the repair changed it, and remaps or drops the painting of a
|
||||
replaced mesh. The GUI side, `fix_model_with_cgal_gui()` in `src/slic3r/Utils/FixModelByCgal.cpp`, runs it on a
|
||||
worker thread for each volume of an object, or for one volume, behind a progress dialog. `repair_mesh()` reports its
|
||||
progress between stages through a callback, which cancels the repair by throwing.
|
||||
|
||||
## What the repaired mesh is
|
||||
|
||||
The repair keeps what the slicer would print from the input. The slicer fills a layer where the winding number of
|
||||
the input surface is not zero (`MeshSlicingParams::SlicingMode::Regular` uses Clipper's nonzero fill rule), so:
|
||||
|
||||
- Shells crossing each other print as their union. The repair keeps them as separate shells, as the parts of an
|
||||
object are kept, and makes each of them an outward oriented solid.
|
||||
- A shell inside a solid that is oriented like it adds nothing, and the repair removes it.
|
||||
- A shell inside a solid that is oriented inward is a cavity, and stays one.
|
||||
- A whole mesh turned inside out prints as a solid, and the repair turns it outward.
|
||||
|
||||
On top of that, it closes holes and cracks, gives inconsistently oriented triangles the orientation of their
|
||||
neighbours, drops degenerate, duplicate and stray flat geometry, and removes small self-intersections inside a shell,
|
||||
such as slivers and folds. Where none of this applies, it reports the mesh as unmodified and the caller keeps the
|
||||
original mesh, its painting and its object id.
|
||||
|
||||
A volume is repaired in place and is never split into several parts: splitting by connectivity would turn a cavity
|
||||
into a separate solid part and fill it. Each solid of the result is a shell of its own, so *Split to parts* still
|
||||
separates them. Open pieces of the input whose borders meet exactly, which are pieces of one surface rather than
|
||||
solids, come out as the one shell they form.
|
||||
|
||||
## Pipeline
|
||||
|
||||
The input is treated as a triangle soup. Every stage after the first only runs on what the previous ones could
|
||||
not settle, so a valid mesh passes through quickly.
|
||||
|
||||
### Valid parts first
|
||||
|
||||
Parts of the input that are connected by vertex indices and are already closed, consistently oriented 2-manifolds
|
||||
skip the soup stages and go straight to the shell stages. Merging their points with coincident points of other
|
||||
parts would only make non-manifold junctions out of solids that touch, for example parts of an assembly exported
|
||||
together or voxels written as separate cubes.
|
||||
|
||||
### Soup clean-up
|
||||
|
||||
The rest of the soup has its coincident points merged. Triangles with repeated corners are dropped. Coincident
|
||||
triangles of opposite orientation are a wall between two solids touching face to face, so they cancel in pairs;
|
||||
of the remaining copies of a triangle one is kept. A connected region that would vanish entirely through
|
||||
cancelling is a two-sided export instead (every face written in both orientations), and keeps one copy of each
|
||||
triangle.
|
||||
|
||||
Open edges then get their vertices welded within a tolerance of 1e-4 of the bounding box diagonal, which closes the
|
||||
cracks some exporters leave between triangles that should share corners. A vertex is never welded further than a
|
||||
quarter of its shortest edge, so welding cannot collapse fine detail. A soup that stays mostly open after welding has
|
||||
cracks wider than that tolerance, and is welded again with ten and then a hundred times the tolerance.
|
||||
|
||||
### Work units
|
||||
|
||||
The soup is cut into units repaired independently and in parallel: its connected parts, except that open parts whose
|
||||
bounding boxes touch share a unit, so that cracks and T-junctions between separately exported patches still close.
|
||||
|
||||
### Building the mesh
|
||||
|
||||
A unit whose edges are each shared by at most two triangles, running in opposite directions, and whose vertices each
|
||||
have a single fan of triangles, becomes a `Surface_mesh` directly. Any other unit is oriented first. Triangles are
|
||||
flipped so that neighbours across an edge shared by two of them agree, each part keeping the orientation the
|
||||
majority of its area had, which keeps inward oriented cavities inward. Edges where that is impossible or that more
|
||||
than two triangles share are cut, and each fan of triangles around a vertex gets its own copy of the vertex. If that
|
||||
still leaves an invalid mesh, CGAL's `orient_polygon_soup()` orients the unit instead, and the parts it flipped
|
||||
against their majority are reversed back. The linear pass exists because `orient_polygon_soup()` is several times
|
||||
slower on large soups that need only a few fixes.
|
||||
|
||||
### Closing
|
||||
|
||||
Compatible borders are stitched, parts that can be stitched once reversed are merged, and borders still open are
|
||||
snapped onto each other within the weld tolerance, which closes T-junctions. Exactly degenerate faces are removed.
|
||||
|
||||
Holes are filled last. Planar border loops that face each other in one plane, one inside the other, bound a single
|
||||
region, such as a missing annulus at the end of a tube or a cap with features standing on it. Such a group is
|
||||
triangulated at once with a 2D constrained Delaunay triangulation that fills only the faces at odd nesting depth,
|
||||
so the openings inside the region stay open. The patch is used only if it closes every loop with the orientation
|
||||
of the triangles around it. Every other loop is filled on its own with `triangulate_hole()`, which uses a 2D
|
||||
constrained Delaunay triangulation when the loop is planar. Otherwise it searches the best triangulation among all
|
||||
triangles over the loop for loops of up to 100 edges, and among the triangles of a 3D Delaunay triangulation for
|
||||
longer ones, as the full search is cubic in the length of the loop. Beyond 200 edges the full search is not even
|
||||
used as a fallback, and a fan around the loop's centroid closes a loop nothing else could.
|
||||
|
||||
The closed unit is split into its shells. The largest stays in place and the others are copied out, as converting
|
||||
the whole mesh again costs seconds on large models.
|
||||
|
||||
### Shells
|
||||
|
||||
The shells of all units, together with the valid parts set aside at the start, are handled independently and in
|
||||
parallel. Shells enclosing no volume, which hole filling makes out of stray flat patches, are dropped.
|
||||
|
||||
Self-intersections inside a shell are removed locally with `experimental::remove_self_intersections()`, which
|
||||
remeshes the region around them. It is used only for local defects: a shell whose intersections involve more than
|
||||
5 % of its faces is left as it is, and a result that changes the enclosed volume by more than 0.1 % is undone. On
|
||||
heavily overlapping ornamental models the remeshing otherwise cuts parts down to slivers, while on genuine defects
|
||||
it changes the volume by thousandths of a percent. Overlaps left in place slice correctly. Remeshing can cut
|
||||
small pieces off a shell, so a shell it changed is split again and its pieces enclosing no volume are dropped.
|
||||
|
||||
Shells crossing others are found through their bounding boxes and an AABB tree per shell, and are made outward
|
||||
oriented solids. For every other shell the winding number just outside it is summed from the orientation of the
|
||||
shells containing it, tested from a point on its largest face. The shell bounds the solid only if the winding
|
||||
number is zero on exactly one of its sides; it is then oriented so that the solid is inside, or dropped otherwise.
|
||||
|
||||
Crossing shells are not united. Slicing unites them anyway, uniting them exactly costs tens of seconds on models
|
||||
made of many overlapping pieces, and it would fuse parts that *Split to parts* should keep apart.
|
||||
|
||||
## Robustness
|
||||
|
||||
The repair avoids `experimental::autorefine_and_remove_self_intersections()` and corefinement, which can crash on
|
||||
messy input, and uses only steps whose failures it can detect. A step that fails keeps its input. CGAL precondition
|
||||
failures surface as `repair_mesh()` returning false with the message as the error. The output is written in float,
|
||||
as every mesh in the application is.
|
||||
|
||||
The result does not depend on thread scheduling. `libslic3r_cgal` is built with `CGAL_LINKED_WITH_TBB`. Units and
|
||||
shells are processed in parallel, but each on its own data, and their results are gathered in a fixed order;
|
||||
sorts compare complete keys, and the detected self-intersections are put in order before they are used. Snapping
|
||||
stays sequential, as its parallel version gives different results from run to run. Holes are filled with the full
|
||||
search wherever it is affordable, because the search among Delaunay triangles can choose between equally good fills
|
||||
differently from run to run, so only non-planar holes of more than 100 edges may still vary. On some inputs
|
||||
`experimental::remove_self_intersections()` itself places a few vertices differently from run to run.
|
||||
|
||||
## Verification
|
||||
|
||||
`tests/libslic3r/test_mesh_repair.cpp` covers each kind of defect above on small meshes with known volumes.
|
||||
@@ -82,9 +82,7 @@ there.
|
||||
strong point is placed, no later strong modifier and no weak modifier is
|
||||
processed for that perimeter.
|
||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
||||
Blocked modifier that fully contains a perimeter is the exception: it is
|
||||
skipped there (see [Full containment](#full-containment)).
|
||||
priority to the highest, so the highest one overwrites overlapping zones.
|
||||
|
||||
A strong modifier without a usable segment, even one whose fragments were all
|
||||
discarded, passes the turn to the next one.
|
||||
@@ -232,20 +230,23 @@ begin and end positions on the source contour.
|
||||
### Full containment
|
||||
|
||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
||||
follows seam painting, where painting a whole perimeter green is a meaningful
|
||||
choice and forbidding the seam all round is not:
|
||||
follows seam painting, where a whole perimeter can be painted in either color:
|
||||
|
||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||
clearing painting and lower zones.
|
||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
||||
override anything below it: lower zones and painting stay in effect.
|
||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
||||
intersection to place the point on.
|
||||
- **Seam Blocked** types the whole perimeter Blocked, like a perimeter painted
|
||||
red all round, overriding painting and lower zones. The seam cannot avoid such
|
||||
a perimeter: the seam placer compares Blocked candidates with each other like
|
||||
Neutral ones, so the seam is placed as on an unmarked perimeter. If the
|
||||
perimeter stays Blocked after all weak zones, it is reported in the user
|
||||
warning (see [Diagnostics and warnings](#diagnostics-and-warnings)), so the
|
||||
user can check the modifier.
|
||||
- **Seam Center, Left and Right** are skipped with the full-containment warning:
|
||||
there is no intersection to place the point on.
|
||||
|
||||
Enforced and Neutral take part in the usual priority order (see
|
||||
Enforced, Neutral and Blocked take part in the usual priority order (see
|
||||
[Weak modifiers](#weak-modifiers)).
|
||||
|
||||
The perimeter is fully contained when the united intervals cover every source
|
||||
@@ -311,9 +312,9 @@ into every perimeter that has a strong seam.
|
||||
`collect_weak_modifier_segments()` extracts the segments of every weak modifier
|
||||
before the polygon is modified, so all positions refer to the same contour. Each
|
||||
segment becomes a zone with a type and two boundaries, kept in application
|
||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
||||
order, lowest priority first. Full containment of a weak modifier becomes a
|
||||
whole-perimeter zone at its place in that order: it has no boundaries and takes
|
||||
part in no insertion or helper step below. The boundaries
|
||||
carry their positions on the source contour; these remain as provenance after
|
||||
insertion and are not indices into the modified polygon.
|
||||
|
||||
@@ -384,9 +385,10 @@ G-code export issues it as one non-critical warning with the ID
|
||||
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||
placement may differ from expected.", because the export warnings dialog shows
|
||||
only the first line of each warning. Repeated warning events replace the
|
||||
notification instead of appending to it. Except for the "had no effect" cause,
|
||||
the causes name the modifier types involved, as the menu names them, in menu
|
||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
||||
notification instead of appending to it. Except for the "covers an entire
|
||||
perimeter" and "had no effect" causes, the causes name the modifier types
|
||||
involved, as the menu names them, in menu order and each type once, for example
|
||||
"(Seam Left, Seam Enforced)".
|
||||
The causes are:
|
||||
|
||||
- **failed to process some intersections (types):** at least one fragment was
|
||||
@@ -395,8 +397,18 @@ The causes are:
|
||||
a Seam Center, Left or Right modifier had more than one segment on a
|
||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
||||
(types):** a Seam Center, Left, Right or Blocked modifier was skipped for a
|
||||
(types):** a Seam Center, Left or Right modifier was skipped for a
|
||||
perimeter (see [Full containment](#full-containment)).
|
||||
- **Seam Blocked modifier "<name>" of "<object>" covers an entire perimeter:**
|
||||
a Seam Blocked modifier covered a whole perimeter (see
|
||||
[Full containment](#full-containment)). Only perimeters whose candidates are
|
||||
all Blocked after all weak zones contribute to this warning, and only through
|
||||
the zone in effect there: `apply_weak_modifiers_to_perimeter()` checks the
|
||||
final types and flags the modifier of the highest whole-perimeter zone when it
|
||||
is Blocked. A lower whole zone is overwritten by it and is not reported. Only
|
||||
the first flagged modifier in print and volume order is named, followed by
|
||||
"(N in total)" when there are several. The type name reuses the menu's
|
||||
translation.
|
||||
- **modifier "<name>" of "<object>" had no effect on the seam (it might not reach
|
||||
the centerline of the printed perimeter):** a modifier was evaluated on at
|
||||
least one perimeter and never gave a segment, full containment or a discarded
|
||||
@@ -420,6 +432,8 @@ The log records the following diagnostic markers:
|
||||
- `[PreciseSeamNoEffect]` for every modifier of the "had no effect" cause, with
|
||||
the object and modifier names. Unlike the user warning, the log lists all of
|
||||
them.
|
||||
- `[PreciseSeamFullyBlocked]` for every modifier of the "covers an entire
|
||||
perimeter" cause, listed the same way.
|
||||
|
||||
Failures and recoveries are counted separately. The first 10 of each per
|
||||
`init()` call are logged in detail, in parallel processing order; if a limit is
|
||||
@@ -563,12 +577,14 @@ last modifier would.
|
||||
priorities, weak boundaries that coincide or share an edge, enforced
|
||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||
zones over painting's oversampled candidates, the warning type masks, usage
|
||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
||||
groups, restoration of strong points after alignment, raft layer indexing and
|
||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
||||
tracking for the "had no effect" and whole-perimeter Blocked warnings, volume
|
||||
sorting of strong and weak groups, restoration of strong points after
|
||||
alignment, raft layer indexing and structured slices. End-to-end tests slice
|
||||
a real object with Precise Seam
|
||||
volumes and check the outer wall starts in the exported G-code: every strong
|
||||
mode under several seam positions and with a raft, Enforced and Blocked zones,
|
||||
a modifier with a hole, and the user warning.
|
||||
a perimeter blocked all round under every seam position, a modifier with a
|
||||
hole, and the user warning.
|
||||
- [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
|
||||
|
||||
@@ -72,6 +72,8 @@ src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
|
||||
@@ -110,6 +112,7 @@ src/slic3r/GUI/Jobs/ArrangeJob.cpp
|
||||
src/slic3r/GUI/Jobs/OrientJob.cpp
|
||||
src/slic3r/GUI/Jobs/FillBedJob.cpp
|
||||
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
|
||||
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
|
||||
src/slic3r/GUI/Jobs/BindJob.cpp
|
||||
src/slic3r/GUI/Jobs/PrintJob.cpp
|
||||
src/slic3r/GUI/Jobs/SendJob.cpp
|
||||
@@ -138,6 +141,7 @@ src/slic3r/GUI/DeviceErrorDialog.cpp
|
||||
src/slic3r/GUI/ExtraRenderers.cpp
|
||||
src/slic3r/GUI/EditGCodeDialog.cpp
|
||||
src/slic3r/GUI/Field.cpp
|
||||
src/slic3r/GUI/Field.hpp
|
||||
src/slic3r/GUI/GCodeViewer.cpp
|
||||
src/slic3r/GUI/GLCanvas3D.cpp
|
||||
src/slic3r/GUI/Calibration.cpp
|
||||
@@ -165,6 +169,7 @@ src/slic3r/GUI/PrintOptionsDialog.cpp
|
||||
src/slic3r/GUI/SafetyOptionsDialog.cpp
|
||||
src/slic3r/GUI/ParamsPanel.cpp
|
||||
src/slic3r/GUI/IMEXModesCtrl.cpp
|
||||
src/slic3r/GUI/IMEXFilamentPickerPopover.cpp
|
||||
src/slic3r/GUI/PartPlate.cpp
|
||||
src/slic3r/GUI/Plater.cpp
|
||||
src/slic3r/GUI/Preferences.cpp
|
||||
@@ -212,10 +217,14 @@ src/libslic3r/GCode/SeamPlacer.cpp
|
||||
src/libslic3r/ExtrusionEntity.cpp
|
||||
src/libslic3r/Flow.cpp
|
||||
src/libslic3r/Format/AMF.cpp
|
||||
src/libslic3r/Format/3mf.cpp
|
||||
src/libslic3r/Format/bbs_3mf.cpp
|
||||
src/libslic3r/miniz_extension.cpp
|
||||
src/libslic3r/IMEXHelpers.cpp
|
||||
src/libslic3r/Preset.cpp
|
||||
src/libslic3r/Print.cpp
|
||||
src/libslic3r/BeltPurge.cpp
|
||||
src/libslic3r/AppConfig.cpp
|
||||
src/libslic3r/PrintBase.cpp
|
||||
src/libslic3r/PrintConfig.cpp
|
||||
src/libslic3r/Zipper.cpp
|
||||
@@ -253,6 +262,7 @@ src/slic3r/Utils/MKS.cpp
|
||||
src/slic3r/Utils/Moonraker.cpp
|
||||
src/slic3r/Utils/OctoPrint.cpp
|
||||
src/slic3r/Utils/Repetier.cpp
|
||||
src/slic3r/Utils/ESP3D.cpp
|
||||
src/slic3r/GUI/SendMultiMachinePage.cpp
|
||||
src/slic3r/GUI/MultiMachinePage.cpp
|
||||
src/slic3r/GUI/MultiMachineManagerPage.cpp
|
||||
@@ -307,6 +317,8 @@ src/slic3r/GUI/AuxiliaryDataViewModel.cpp
|
||||
src/slic3r/GUI/AuxiliaryDialog.cpp
|
||||
src/slic3r/GUI/BBLStatusBarPrint.cpp
|
||||
src/slic3r/GUI/BBLStatusBarSend.cpp
|
||||
src/slic3r/GUI/BBLStatusBar.cpp
|
||||
src/slic3r/GUI/BeltPurgeTower.cpp
|
||||
src/slic3r/GUI/BaseTransparentDPIFrame.cpp
|
||||
src/slic3r/GUI/CloneDialog.cpp
|
||||
src/slic3r/GUI/ColorDecomposeDialog.cpp
|
||||
@@ -333,6 +345,8 @@ src/slic3r/GUI/OAuthDialog.cpp
|
||||
src/slic3r/GUI/PluginPickerDialog.cpp
|
||||
src/slic3r/GUI/PluginsConfigDialog.cpp
|
||||
src/slic3r/GUI/PluginsDialog.cpp
|
||||
src/slic3r/GUI/PrinterWebView.cpp
|
||||
src/slic3r/GUI/Project.cpp
|
||||
src/slic3r/GUI/PublishSettingsDialog.hpp
|
||||
src/slic3r/GUI/PurgeModeDialog.cpp
|
||||
src/slic3r/GUI/TerminalDialog.cpp
|
||||
@@ -341,16 +355,31 @@ src/slic3r/GUI/TextureLibrary.cpp
|
||||
src/slic3r/GUI/TextureProjectorFrame.cpp
|
||||
src/slic3r/GUI/UVEditorCanvas.cpp
|
||||
src/slic3r/GUI/UserManager.cpp
|
||||
src/slic3r/GUI/WebDownPluginDlg.cpp
|
||||
src/slic3r/GUI/WebViewDialog.cpp
|
||||
src/slic3r/GUI/Widgets/AMSItem.cpp
|
||||
src/slic3r/GUI/Widgets/DialogButtons.cpp
|
||||
src/slic3r/Utils/NetworkAgentFactory.cpp
|
||||
src/slic3r/Utils/MoonrakerPrinterAgent.cpp
|
||||
src/slic3r/Utils/BBLNetworkPlugin.cpp
|
||||
src/slic3r/Utils/OrcaCloudServiceAgent.cpp
|
||||
src/slic3r/plugin/PluginAuditManager.cpp
|
||||
src/slic3r/plugin/PluginConfig.cpp
|
||||
src/slic3r/plugin/PluginResolver.cpp
|
||||
src/slic3r/plugin/CloudPluginService.cpp
|
||||
src/slic3r/plugin/PluginFsUtils.cpp
|
||||
src/slic3r/plugin/PluginHooks.cpp
|
||||
src/slic3r/plugin/PluginLoader.cpp
|
||||
src/slic3r/plugin/PluginManager.cpp
|
||||
src/slic3r/plugin/PythonPluginBridge.cpp
|
||||
src/libslic3r/CAD/SketchEngine.cpp
|
||||
src/libslic3r/CAD/GeometryEngine.cpp
|
||||
src/libslic3r/FilamentMixer.cpp
|
||||
src/libslic3r/Config.cpp
|
||||
src/libslic3r/Format/STEP.cpp
|
||||
src/libslic3r/Format/svg.cpp
|
||||
src/libslic3r/Extruder.cpp
|
||||
src/libslic3r/MeshBoolean.cpp
|
||||
src/libslic3r/utils.cpp
|
||||
src/slic3r/GUI/CAD/DesignOffer.hpp
|
||||
src/slic3r/GUI/CAD/DesignSketchTool.cpp
|
||||
src/slic3r/GUI/EncodedFilament.cpp
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="27" viewBox="0 0 28 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="matrix(0.9 0 0 0.9 1.4 1.35)" d="M11.9888 2.01601C12.8984 0.494662 15.1024 0.494662 16.012 2.01601L18.8677 6.79237C19.1958 7.34113 19.7339 7.73201 20.3572 7.87447L25.7821 9.11446C27.5101 9.50943 28.1913 11.6056 27.0254 12.9408L23.3652 17.1327C22.9449 17.6143 22.7394 18.2469 22.7964 18.8836L23.2936 24.4263C23.4519 26.1918 21.6687 27.4873 20.0386 26.7911L14.9209 24.6056C14.3329 24.3545 13.6679 24.3545 13.0799 24.6056L7.96209 26.7911C6.33196 27.4873 4.54887 26.1918 4.70722 24.4263L5.20434 18.8836C5.26145 18.2469 5.05595 17.6143 4.63544 17.1327L0.97533 12.9408C-0.19048 11.6056 0.490599 9.50943 2.21859 9.11446L7.64364 7.87447C8.2669 7.73201 8.80494 7.34113 9.13302 6.79237L11.9888 2.01601Z" stroke="#949494" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M2.5,2.5h11v11h-11z" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M8,2.5v11M2.5,8h11" style="fill:none;stroke:#949494;stroke-dasharray:1.5,1.5"/><path d="M4.5,11.5l3.5-6l3.5,6z" style="fill:#009688"/></svg>
|
||||
|
After Width: | Height: | Size: 340 B |
@@ -0,0 +1,795 @@
|
||||
{
|
||||
"presets": {
|
||||
"Anycubic Generic ABS": [
|
||||
"Anycubic Kobra 3 0.4 nozzle"
|
||||
],
|
||||
"Anycubic Generic TPU": [
|
||||
"Anycubic Kobra 3 0.4 nozzle"
|
||||
],
|
||||
"Bambu ABS @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu ABS @BBL X1C": [
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu ASA @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL X1C": [
|
||||
"Bambu Lab P1P 0.6 nozzle",
|
||||
"Bambu Lab P1P 0.8 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab P1S 0.8 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.8 nozzle"
|
||||
],
|
||||
"Bambu PA6-CF @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle",
|
||||
"Bambu Lab P1P 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab P1S 0.8 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.8 nozzle"
|
||||
],
|
||||
"Bambu PAHT-CF @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PC @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PETG @0.2 nozzle": [
|
||||
"Qidi Q1 Pro 0.2 nozzle"
|
||||
],
|
||||
"Bambu PETG Basic @BBL H2D 0.6 nozzle": [
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Basic @BBL H2DP 0.6 nozzle": [
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Basic @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Basic @BBL P2S 0.6 nozzle": [
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Basic @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG HF @BBL P2S 0.6 nozzle": [
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG HF @BBL X1C": [
|
||||
"Bambu Lab P1P 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.4 nozzle"
|
||||
],
|
||||
"Bambu PETG HF @BBL X1C 0.8 nozzle": [
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PETG HF @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Translucent @BBL H2D 0.6 nozzle": [
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Translucent @BBL H2DP 0.6 nozzle": [
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Translucent @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Translucent @BBL P2S 0.6 nozzle": [
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG Translucent @BBL X1C 0.8 nozzle": [
|
||||
"Bambu Lab P1P 0.6 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PETG-CF @BBL H2D 0.6 nozzle": [
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG-CF @BBL H2DP 0.6 nozzle": [
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG-CF @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PETG-CF @BBL P2S 0.6 nozzle": [
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Basic @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Basic @BBL X1C": [
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Basic @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Dynamic @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Dynamic @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Galaxy @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Galaxy @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Glow @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Glow @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Lite @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Lite @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Lite @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Lite @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Lite @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Marble @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle",
|
||||
"Bambu Lab P1P 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Marble @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Matte @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Matte @BBL X1C": [
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Matte @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Metal @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Metal @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL X1C": [
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Silk+ @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Sparkle @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Sparkle @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Tough+ @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Tough+ @BBL X1C": [
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Tough+ @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Translucent @BBL H2D 0.8 nozzle": [
|
||||
"Bambu Lab H2D 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Translucent @BBL H2DP 0.8 nozzle": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Translucent @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA Wood @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA Wood @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu PLA-CF @BBL H2D 0.6 nozzle": [
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA-CF @BBL H2DP 0.6 nozzle": [
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA-CF @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA-CF @BBL P2S 0.6 nozzle": [
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PLA-CF @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PPA-CF @BBL X1C": [
|
||||
"Bambu Lab P1P 0.6 nozzle",
|
||||
"Bambu Lab P1P 0.8 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab P1S 0.8 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.8 nozzle"
|
||||
],
|
||||
"Bambu PVA @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu PVA @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu PVA @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PVA @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu PVA @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA/PETG @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA/PETG @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA/PETG @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA/PETG @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support For PLA/PETG @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support for ABS @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support for ABS @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support for ABS @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support for ABS @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu Support for ABS @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 90A @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 90A @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 95A HF @BBL H2D": [
|
||||
"Bambu Lab H2D 0.4 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 95A HF @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.4 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 95A HF @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 95A HF @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU 95A HF @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL H2D": [
|
||||
"Bambu Lab H2D 0.6 nozzle",
|
||||
"Bambu Lab H2D 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL H2DP": [
|
||||
"Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"Bambu Lab H2D Pro 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL H2S": [
|
||||
"Bambu Lab H2S 0.6 nozzle",
|
||||
"Bambu Lab H2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL P1P": [
|
||||
"Bambu Lab P1P 0.6 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL P2S": [
|
||||
"Bambu Lab P2S 0.6 nozzle",
|
||||
"Bambu Lab P2S 0.8 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL X1C": [
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.6 nozzle"
|
||||
],
|
||||
"Bambu TPU for AMS @BBL X2D": [
|
||||
"Bambu Lab X2D 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic ABS": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic ASA": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic ASA-CF": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PA": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PA-CF": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PC": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PETG": [
|
||||
"BLOCKS RD50 V2 0.4 nozzle",
|
||||
"BLOCKS RD50 V2 0.6 nozzle",
|
||||
"BLOCKS RD50 V2 0.8 nozzle",
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PLA": [
|
||||
"BLOCKS RD50 V2 0.4 nozzle",
|
||||
"BLOCKS RD50 V2 0.6 nozzle",
|
||||
"BLOCKS RD50 V2 0.8 nozzle",
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PLA-CF": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic PVA": [
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Blocks Generic TPU": [
|
||||
"BLOCKS RD50 V2 0.4 nozzle",
|
||||
"BLOCKS RD50 V2 0.6 nozzle",
|
||||
"BLOCKS RD50 V2 0.8 nozzle",
|
||||
"BLOCKS RF50 0.4 nozzle",
|
||||
"BLOCKS RF50 0.6 nozzle",
|
||||
"BLOCKS RF50 0.8 nozzle"
|
||||
],
|
||||
"Creality Generic ABS": [
|
||||
"Creality CR-10 SE 0.2 nozzle",
|
||||
"Creality CR-10 SE 0.4 nozzle",
|
||||
"Creality CR-10 SE 0.6 nozzle",
|
||||
"Creality CR-10 SE 0.8 nozzle",
|
||||
"Creality CR-6 SE 0.2 nozzle",
|
||||
"Creality CR-6 SE 0.4 nozzle",
|
||||
"Creality CR-6 SE 0.6 nozzle",
|
||||
"Creality CR-6 SE 0.8 nozzle",
|
||||
"Creality CR-M4 0.4 nozzle",
|
||||
"Creality Ender-3 V2 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic ABS @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle"
|
||||
],
|
||||
"Creality Generic ABS @Hi-all": [
|
||||
"Creality Hi 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic ABS @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic ABS @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Plus 0.6 nozzle",
|
||||
"Creality K2 Plus 0.8 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic ASA @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle"
|
||||
],
|
||||
"Creality Generic ASA @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic ASA @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PA-CF @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle"
|
||||
],
|
||||
"Creality Generic PA-CF @K1-all": [
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PA-CF @K2-all": [
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Plus 0.6 nozzle",
|
||||
"Creality K2 Plus 0.8 nozzle"
|
||||
],
|
||||
"Creality Generic PC @K1-all": [
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PETG": [
|
||||
"Creality CR-6 SE 0.2 nozzle",
|
||||
"Creality CR-6 SE 0.4 nozzle",
|
||||
"Creality CR-6 SE 0.6 nozzle",
|
||||
"Creality CR-6 SE 0.8 nozzle",
|
||||
"Creality CR-M4 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PETG @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 0.4 nozzle",
|
||||
"Creality Ender-3 V3 0.6 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.2 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.4 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.6 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.8 nozzle"
|
||||
],
|
||||
"Creality Generic PETG @Hi-all": [
|
||||
"Creality Hi 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PETG @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PETG @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Plus 0.6 nozzle",
|
||||
"Creality K2 Plus 0.8 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PLA": [
|
||||
"Creality CR-10 SE 0.2 nozzle",
|
||||
"Creality CR-10 SE 0.4 nozzle",
|
||||
"Creality CR-10 SE 0.6 nozzle",
|
||||
"Creality CR-10 SE 0.8 nozzle",
|
||||
"Creality CR-6 SE 0.2 nozzle",
|
||||
"Creality CR-6 SE 0.4 nozzle",
|
||||
"Creality CR-6 SE 0.6 nozzle",
|
||||
"Creality CR-6 SE 0.8 nozzle",
|
||||
"Creality CR-M4 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PLA @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 0.4 nozzle",
|
||||
"Creality Ender-3 V3 0.6 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle"
|
||||
],
|
||||
"Creality Generic PLA @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PLA @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Plus 0.6 nozzle",
|
||||
"Creality K2 Plus 0.8 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PLA-CF @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic PLA-CF @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic TPU": [
|
||||
"Creality CR-10 SE 0.2 nozzle",
|
||||
"Creality CR-10 SE 0.4 nozzle",
|
||||
"Creality CR-10 SE 0.6 nozzle",
|
||||
"Creality CR-10 SE 0.8 nozzle"
|
||||
],
|
||||
"Creality Generic TPU @Ender-3V3-all": [
|
||||
"Creality Ender-3 V3 0.4 nozzle",
|
||||
"Creality Ender-3 V3 0.6 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.4 nozzle",
|
||||
"Creality Ender-3 V3 Plus 0.6 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.2 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.4 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.6 nozzle",
|
||||
"Creality Ender-3 V3 SE 0.8 nozzle"
|
||||
],
|
||||
"Creality Generic TPU @K1-all": [
|
||||
"Creality K1 SE 0.4 nozzle",
|
||||
"Creality K1C 0.4 nozzle"
|
||||
],
|
||||
"Creality Generic TPU @K2-all": [
|
||||
"Creality K2 0.4 nozzle",
|
||||
"Creality K2 Plus 0.4 nozzle",
|
||||
"Creality K2 Pro 0.4 nozzle"
|
||||
],
|
||||
"Cubicon ABS @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon ABS-A100 @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon ABSk @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PA-CF @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PC @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PETG @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PLA @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PLA+ @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Cubicon PLAi21 @base": [
|
||||
"Cubicon xCeler-Plus 0.4 nozzle"
|
||||
],
|
||||
"Flashforge Generic ABS @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle"
|
||||
],
|
||||
"Flashforge Generic ASA @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle"
|
||||
],
|
||||
"Flashforge Generic HIPS": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle"
|
||||
],
|
||||
"Flashforge Generic PETG @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle",
|
||||
"Flashforge Guider 3 Ultra 0.8 Nozzle"
|
||||
],
|
||||
"Flashforge Generic PETG-CF @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle",
|
||||
"Flashforge Guider 3 Ultra 0.8 Nozzle"
|
||||
],
|
||||
"Flashforge Generic PLA @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle",
|
||||
"Flashforge Guider 3 Ultra 0.8 Nozzle"
|
||||
],
|
||||
"Flashforge Generic PLA-CF @G3U": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle",
|
||||
"Flashforge Guider 3 Ultra 0.8 Nozzle"
|
||||
],
|
||||
"Flashforge HS PLA": [
|
||||
"Flashforge AD5X 0.4 nozzle",
|
||||
"Flashforge AD5X 0.6 nozzle",
|
||||
"Flashforge AD5X 0.8 nozzle"
|
||||
],
|
||||
"Flashforge PETG-CF": [
|
||||
"Flashforge AD5X 0.4 nozzle",
|
||||
"Flashforge AD5X 0.6 nozzle",
|
||||
"Flashforge AD5X 0.8 nozzle"
|
||||
],
|
||||
"Flashforge PLA-CF": [
|
||||
"Flashforge AD5X 0.4 nozzle",
|
||||
"Flashforge AD5X 0.6 nozzle",
|
||||
"Flashforge AD5X 0.8 nozzle"
|
||||
],
|
||||
"Flashforge TPU 95A": [
|
||||
"Flashforge AD5X 0.4 nozzle",
|
||||
"Flashforge AD5X 0.6 nozzle",
|
||||
"Flashforge AD5X 0.8 nozzle"
|
||||
],
|
||||
"FusRock Generic PAHT-CF": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle"
|
||||
],
|
||||
"FusRock Generic PET-CF": [
|
||||
"Flashforge Guider 3 Ultra 0.6 Nozzle"
|
||||
],
|
||||
"Generic PETG HF @BBL X1C": [
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.6 nozzle",
|
||||
"Bambu Lab X1 0.4 nozzle",
|
||||
"Bambu Lab X1 0.6 nozzle",
|
||||
"Bambu Lab X1 0.8 nozzle"
|
||||
],
|
||||
"Generic PLA @Hi-all": [
|
||||
"Creality Hi 0.4 nozzle",
|
||||
"Creality Hi 0.6 nozzle"
|
||||
],
|
||||
"HATCHBOX PETG @0.2 nozzle": [
|
||||
"Qidi Q1 Pro 0.2 nozzle"
|
||||
],
|
||||
"Prusa Generic PA": [
|
||||
"Prusa MINIIS 0.25 nozzle",
|
||||
"Prusa MINIIS 0.4 nozzle",
|
||||
"Prusa MINIIS 0.6 nozzle",
|
||||
"Prusa MINIIS 0.8 nozzle"
|
||||
],
|
||||
"Prusa Generic PA-CF": [
|
||||
"Prusa MINIIS 0.25 nozzle",
|
||||
"Prusa MINIIS 0.4 nozzle",
|
||||
"Prusa MINIIS 0.6 nozzle",
|
||||
"Prusa MINIIS 0.8 nozzle"
|
||||
],
|
||||
"Prusa Generic PLA-CF": [
|
||||
"Prusa MINIIS 0.25 nozzle",
|
||||
"Prusa MINIIS 0.4 nozzle",
|
||||
"Prusa MINIIS 0.6 nozzle",
|
||||
"Prusa MINIIS 0.8 nozzle"
|
||||
],
|
||||
"QIDI PETG Basic @0.2 nozzle": [
|
||||
"Qidi Q1 Pro 0.2 nozzle"
|
||||
],
|
||||
"QIDI PETG Rapido @0.2 nozzle": [
|
||||
"Qidi Q1 Pro 0.2 nozzle"
|
||||
],
|
||||
"QIDI PETG Translucent @0.2 nozzle": [
|
||||
"Qidi Q1 Pro 0.2 nozzle"
|
||||
],
|
||||
"Sovol Zero PETG HS Nozzle": [
|
||||
"Sovol Zero 0.4 nozzle"
|
||||
],
|
||||
"Sovol Zero PLA Basic HS Nozzle": [
|
||||
"Sovol Zero 0.4 nozzle"
|
||||
],
|
||||
"Sovol Zero PLA Silk HS Nozzle": [
|
||||
"Sovol Zero 0.4 nozzle"
|
||||
],
|
||||
"re3D Greengate rPETG": [
|
||||
"re3D GigabotX 2 1.75 nozzle",
|
||||
"re3D GigabotX 2 XLT 1.75 nozzle",
|
||||
"re3D TerabotX 2 1.75 nozzle"
|
||||
],
|
||||
"re3D PC": [
|
||||
"re3D Gigabot 4 0.8 nozzle",
|
||||
"re3D Gigabot 4 XLT 0.8 nozzle",
|
||||
"re3D Terabot 4 0.8 nozzle"
|
||||
],
|
||||
"re3D PETG": [
|
||||
"re3D Gigabot 4 0.8 nozzle",
|
||||
"re3D Gigabot 4 XLT 0.8 nozzle",
|
||||
"re3D Terabot 4 0.8 nozzle"
|
||||
],
|
||||
"re3D PLA": [
|
||||
"re3D Gigabot 4 0.8 nozzle",
|
||||
"re3D Gigabot 4 XLT 0.8 nozzle",
|
||||
"re3D Terabot 4 0.8 nozzle"
|
||||
],
|
||||
"re3D rPP": [
|
||||
"re3D GigabotX 2 1.75 nozzle",
|
||||
"re3D GigabotX 2 XLT 1.75 nozzle",
|
||||
"re3D TerabotX 2 1.75 nozzle"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Bambulab",
|
||||
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
||||
"version": "02.08.00.14",
|
||||
"version": "02.08.00.15",
|
||||
"force_update": "0",
|
||||
"description": "the initial version of BBL configurations",
|
||||
"machine_model_list": [
|
||||
|
||||
@@ -0,0 +1,790 @@
|
||||
{
|
||||
"name": "FELIX Printers",
|
||||
"version": "1.0.0",
|
||||
"force_update": "0",
|
||||
"description": "Configuration file for printers by FELIX Printers",
|
||||
"machine_model_list": [
|
||||
{
|
||||
"name": "FELIX Food Double",
|
||||
"sub_path": "machine/FELIX Food Double.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single",
|
||||
"sub_path": "machine/FELIX Food Single.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch",
|
||||
"sub_path": "machine/FELIX Food Switch.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro 3",
|
||||
"sub_path": "machine/FELIX Pro 3.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro L 3.0",
|
||||
"sub_path": "machine/FELIX Pro L 3.0.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro XL 3.0",
|
||||
"sub_path": "machine/FELIX Pro XL 3.0.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Tec 4",
|
||||
"sub_path": "machine/FELIX Tec 4.json"
|
||||
}
|
||||
],
|
||||
"machine_list": [
|
||||
{
|
||||
"name": "fdm_all_machines_common",
|
||||
"sub_path": "machine/fdm_all_machines_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_food_common",
|
||||
"sub_path": "machine/fdm_felix_food_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_food_double_common",
|
||||
"sub_path": "machine/fdm_felix_food_double_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_food_switch_common",
|
||||
"sub_path": "machine/fdm_felix_food_switch_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_plastic_common",
|
||||
"sub_path": "machine/fdm_felix_plastic_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_pro_3_common",
|
||||
"sub_path": "machine/fdm_felix_pro_3_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_pro_l_3_common",
|
||||
"sub_path": "machine/fdm_felix_pro_l_3.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_pro_xl_3_common",
|
||||
"sub_path": "machine/fdm_felix_pro_xl_3_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_felix_tec_common",
|
||||
"sub_path": "machine/fdm_felix_tec_common.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Single 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single 1.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Single 1.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single 2.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Single 2.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single 3.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Single 3.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Single 4.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Single 4.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Double 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Double 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Double 1.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Double 1.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Double 2.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Double 2.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Double 3.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Double 3.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Double 4.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Double 4.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Switch 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch 1.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Switch 1.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch 2.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Switch 2.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch 3.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Switch 3.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Switch 4.00mm nozzle",
|
||||
"sub_path": "machine/FELIX Food Switch 4.00mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro 3 0.25mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro 3 0.25mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro 3 0.35mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro 3 0.35mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro 3 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro 3 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro 3 0.70mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro 3 0.70mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro L 3.0 0.25mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro L 3.0 0.25mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro L 3.0 0.35mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro L 3.0 0.35mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro L 3.0 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro L 3.0 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro L 3.0 0.70mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro L 3.0 0.70mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro XL 3.0 0.25mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro XL 3.0 0.25mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro XL 3.0 0.35mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro XL 3.0 0.35mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro XL 3.0 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro XL 3.0 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Pro XL 3.0 0.70mm nozzle",
|
||||
"sub_path": "machine/FELIX Pro XL 3.0 0.70mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Tec 4 0.25mm nozzle",
|
||||
"sub_path": "machine/FELIX Tec 4 0.25mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Tec 4 0.35mm nozzle",
|
||||
"sub_path": "machine/FELIX Tec 4 0.35mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Tec 4 0.50mm nozzle",
|
||||
"sub_path": "machine/FELIX Tec 4 0.50mm nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Tec 4 0.70mm nozzle",
|
||||
"sub_path": "machine/FELIX Tec 4 0.70mm nozzle.json"
|
||||
}
|
||||
],
|
||||
"process_list": [
|
||||
{
|
||||
"name": "fdm_process_common",
|
||||
"sub_path": "process/fdm_process_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_process_felix_food_common",
|
||||
"sub_path": "process/fdm_process_felix_food_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_process_felix_plastic_common",
|
||||
"sub_path": "process/fdm_process_felix_plastic_common.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Coarse @ 0.50mm",
|
||||
"sub_path": "process/FELIX Food Coarse @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Coarse @ 1.00mm",
|
||||
"sub_path": "process/FELIX Food Coarse @ 1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Coarse @ 2.00mm",
|
||||
"sub_path": "process/FELIX Food Coarse @ 2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Coarse @ 3.00mm",
|
||||
"sub_path": "process/FELIX Food Coarse @ 3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Coarse @ 4.00mm",
|
||||
"sub_path": "process/FELIX Food Coarse @ 4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Fine @ 0.50mm",
|
||||
"sub_path": "process/FELIX Food Fine @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Fine @ 1.00mm",
|
||||
"sub_path": "process/FELIX Food Fine @ 1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Fine @ 2.00mm",
|
||||
"sub_path": "process/FELIX Food Fine @ 2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Fine @ 3.00mm",
|
||||
"sub_path": "process/FELIX Food Fine @ 3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Fine @ 4.00mm",
|
||||
"sub_path": "process/FELIX Food Fine @ 4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Normal @ 0.50mm",
|
||||
"sub_path": "process/FELIX Food Normal @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Normal @ 1.00mm",
|
||||
"sub_path": "process/FELIX Food Normal @ 1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Normal @ 2.00mm",
|
||||
"sub_path": "process/FELIX Food Normal @ 2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Normal @ 3.00mm",
|
||||
"sub_path": "process/FELIX Food Normal @ 3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Food Normal @ 4.00mm",
|
||||
"sub_path": "process/FELIX Food Normal @ 4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Coarse @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Coarse @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Coarse @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Coarse @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Coarse @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Coarse @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Fine @ 0.25mm",
|
||||
"sub_path": "process/FELIX Plastic Fine @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Fine @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Fine @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Fine @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Fine @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Fine @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Fine @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Normal @ 0.25mm",
|
||||
"sub_path": "process/FELIX Plastic Normal @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Normal @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Normal @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Normal @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Normal @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Normal @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Normal @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Slightly Coarse @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Slightly Coarse @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Slightly Coarse @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Slightly Coarse @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Slightly Coarse @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Slightly Coarse @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Super Coarse @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Super Coarse @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Super Fine @ 0.25mm",
|
||||
"sub_path": "process/FELIX Plastic Super Fine @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Super Fine @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Super Fine @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Very Coarse @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Very Coarse @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Very Coarse @ 0.70mm",
|
||||
"sub_path": "process/FELIX Plastic Very Coarse @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Very Fine @ 0.25mm",
|
||||
"sub_path": "process/FELIX Plastic Very Fine @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Very Fine @ 0.35mm",
|
||||
"sub_path": "process/FELIX Plastic Very Fine @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Plastic Very Fine @ 0.50mm",
|
||||
"sub_path": "process/FELIX Plastic Very Fine @ 0.50mm.json"
|
||||
}
|
||||
],
|
||||
"filament_list": [
|
||||
{
|
||||
"name": "fdm_filament_common",
|
||||
"sub_path": "filament/fdm_filament_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_food",
|
||||
"sub_path": "filament/fdm_filament_food.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_abs",
|
||||
"sub_path": "filament/fdm_filament_abs.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_asa",
|
||||
"sub_path": "filament/fdm_filament_asa.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_flex",
|
||||
"sub_path": "filament/fdm_filament_flex.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pa",
|
||||
"sub_path": "filament/fdm_filament_pa.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pa11cf",
|
||||
"sub_path": "filament/fdm_filament_pa11cf.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pc",
|
||||
"sub_path": "filament/fdm_filament_pc.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pccf",
|
||||
"sub_path": "filament/fdm_filament_pccf.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pet",
|
||||
"sub_path": "filament/fdm_filament_pet.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pla",
|
||||
"sub_path": "filament/fdm_filament_pla.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pva",
|
||||
"sub_path": "filament/fdm_filament_pva.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_pvb",
|
||||
"sub_path": "filament/fdm_filament_pvb.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_tpu",
|
||||
"sub_path": "filament/fdm_filament_tpu.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Plastic Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Plastic Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Plastic Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Plastic Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Plastic Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Plastic Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Plastic Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Plastic Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Plastic Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Plastic Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Stainless Steel Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Stainless Steel Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Stainless Steel Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Stainless Steel Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Stainless Steel Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Stainless Steel Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Stainless Steel Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Stainless Steel Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Medium Stainless Steel Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Medium Stainless Steel Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thick Stainless Steel Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thick Stainless Steel Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thick Stainless Steel Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thick Stainless Steel Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thick Stainless Steel Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thick Stainless Steel Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thick Stainless Steel Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thick Stainless Steel Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thick Stainless Steel Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thick Stainless Steel Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Plastic Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Plastic Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Plastic Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Plastic Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Plastic Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Plastic Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Plastic Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Plastic Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Plastic Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Plastic Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Stainless Steel Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Stainless Steel Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Stainless Steel Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Stainless Steel Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Stainless Steel Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Stainless Steel Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Stainless Steel Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Stainless Steel Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Thin Stainless Steel Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Thin Stainless Steel Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thick Stainless Steel Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thick Stainless Steel Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thick Stainless Steel Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thick Stainless Steel Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thick Stainless Steel Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thick Stainless Steel Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thick Stainless Steel Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thick Stainless Steel Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Plastic Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Plastic Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Plastic Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Plastic Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Plastic Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Plastic Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Plastic Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Plastic Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Plastic Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Plastic Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Stainless Steel Syringe @1.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Stainless Steel Syringe @1.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Stainless Steel Syringe @2.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Stainless Steel Syringe @2.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Stainless Steel Syringe @3.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Stainless Steel Syringe @3.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Stainless Steel Syringe @4.00mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Stainless Steel Syringe @4.00mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Food Very Thin Stainless Steel Syringe @0.50mm",
|
||||
"sub_path": "filament/FELIXFood/Food Very Thin Stainless Steel Syringe @0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA TitanX ABS Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA TitanX ABS Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "Ultrafuse ABS Fusion+ Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/Ultrafuse ABS Fusion+ Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA AppoloX ASA Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA AppoloX ASA Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX FLEX Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX FLEX Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX PETG Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX PETG Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Premium PLA Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Premium PLA Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FELIX Wood Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FELIX Wood Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA ePLA Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA ePLA Hardened Steel @ 0.70mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Brass @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Brass @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Brass @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Brass @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Hardened Steel @ 0.25mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Hardened Steel @ 0.25mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Hardened Steel @ 0.35mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Hardened Steel @ 0.35mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Hardened Steel @ 0.50mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Hardened Steel @ 0.50mm.json"
|
||||
},
|
||||
{
|
||||
"name": "FORMFUTURA Aquasolve PVA Hardened Steel @ 0.70mm",
|
||||
"sub_path": "filament/FELIXPlastic/FORMFUTURA Aquasolve PVA Hardened Steel @ 0.70mm.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 512 512" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="512" height="512" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(5,460) scale(0.2)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 808 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 250 195" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="250" height="195" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(2,169) scale(0.1)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 716 KiB |
|
After Width: | Height: | Size: 793 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 300 400" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="300" height="400" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(3,365) scale(0.13)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 235 244" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="235" height="244" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(2,215) scale(0.1)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1023 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 600 400" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="600" height="400" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(4,357) scale(0.16)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" width="180.62mm" height="180.62mm" viewBox="0 0 205 255" xmlns:c2pa="http://c2pa.org/manifest">
|
||||
<rect x="0" y="0" width="205" height="255" fill="none"/>
|
||||
<g fill="#ffffff" transform="translate(3,228) scale(0.1)">
|
||||
<g>
|
||||
<path d="M139.4.8l-.2,62c-34.3,0-63.4,21.8-73.2,51.9C26.4,141,.2,187.8.2,241.3H0l.2-101.3C.2,63.1,62.4.8,139.4.8Z"/>
|
||||
<path d="M139.1,92.5l-.8,62.5c-42.6,0-77.1,38.6-77.1,86.3l1-103.9c0-7.9,1.3-15.5,3.6-22.7,21.2-14.1,46.3-22.3,73.2-22.3Z"/>
|
||||
<path d="M65.9,114.7c-2.3,7.2-3.6,14.8-3.6,22.7l-1,103.9H.2c0-53.5,26.3-100.3,65.7-126.6Z"/>
|
||||
</g>
|
||||
<polygon points="202.8 .4 202.8 92.9 226.2 92.9 226.2 56 259.8 56 259.8 36.1 226.2 36.1 226.2 19.4 265.7 19.4 265.7 0 202.8 .4"/>
|
||||
<polygon points="282.3 -.3 282.3 93 346.1 93 346.1 72.4 306.8 72.4 306.8 55.6 341.7 55.6 341.7 36.4 306.5 36.4 306.5 19.6 346.2 19.6 346.2 -.3 282.3 -.3"/>
|
||||
<polygon points="365.9 92.8 429.5 92.8 429.5 70.9 389.6 70.9 389.6 .2 365 .2 365.9 92.8"/>
|
||||
<polygon points="448 0 471.9 0 471.9 92.5 447.5 92.5 448 0"/>
|
||||
<path d="M485.1,92.5s34.7-44.3,33.4-46.5S485.7,0,485.7,0h29l18.4,24.8L550.1,0h26.6l-29.9,45.4,33.1,46.8h-28.8l-19.3-27.4-18.9,28.1-27.9-.4Z"/>
|
||||
<g>
|
||||
<path d="M210.9,154.1c-1.9,0-3.7-.2-5.3-.7-1.6-.4-3-1-4.2-1.6v-3.9c1.1.6,2.4,1.1,3.9,1.6,1.5.5,3.2.7,5.1.7,3.1,0,5.5-.6,7.2-1.7,1.7-1.2,2.5-2.8,2.5-5s-.8-3.9-2.5-5c-1.7-1.2-4.2-1.7-7.4-1.7h-3.9v-3.7h4c2.6,0,4.5-.5,5.9-1.5,1.4-1,2-2.4,2-4.2s-.3-2.3-1-3.1c-.6-.8-1.6-1.5-2.8-1.9-1.3-.4-2.8-.7-4.6-.7s-3,.2-4.3.6c-1.3.4-2.4.8-3.3,1.3v-3.9c1.1-.5,2.3-.9,3.6-1.3,1.3-.4,2.8-.5,4.5-.5s4,.3,5.5.8c1.5.5,2.7,1.3,3.6,2.2.9.9,1.6,1.9,2,3.1.4,1.1.6,2.3.6,3.4s-.2,2.4-.7,3.3c-.5.9-1,1.7-1.6,2.2-.6.6-1.4,1.1-2.3,1.6,1.3.7,2.3,1.4,3.2,2.1s1.6,1.7,2.2,2.8c.6,1.1,1,2.5,1,4.2s-.2,2.5-.7,3.8c-.5,1.2-1.2,2.4-2.2,3.4s-2.4,1.8-4,2.4c-1.7.6-3.7.9-6.1.9Z"/>
|
||||
<path d="M232.1,153.7v-35.4h11.1c5.5,0,9.7,1.5,12.4,4.4,2.8,3,4.2,7.4,4.2,13.2s-1.4,10.3-4.2,13.2c-2.8,3-6.9,4.5-12.4,4.5h-11.1ZM235.9,150h7c4.5,0,7.8-1.1,9.9-3.4,2-2.3,3.1-5.8,3.1-10.7s-1-8.4-3.1-10.7c-2-2.3-5.3-3.4-9.9-3.4h-7v28.2Z"/>
|
||||
<path d="M291.9,154.4c-.8,0-1.7-.1-2.6-.3-1-.2-1.9-.5-2.8-.8-.9-.3-1.7-.7-2.4-1l1.3-1.3v14.2h-3.8v-33.2c0-1.1.3-2,1-2.7.7-.7,1.6-1.2,2.7-1.6,1.1-.4,2.3-.6,3.5-.8,1.2-.2,2.4-.2,3.5-.2,2.1,0,4.1.4,6,1.3,1.9.9,3.4,2.4,4.6,4.5s1.7,5,1.7,8.6-.5,5.1-1.5,7.1c-1,2-2.4,3.5-4.3,4.6-1.9,1.1-4.2,1.6-6.9,1.6ZM291.9,150.9c1.9,0,3.5-.4,4.8-1.2,1.3-.8,2.2-2,2.8-3.5.6-1.5,1-3.2,1-5.1s-.2-3.7-.6-5.4c-.4-1.7-1.2-3-2.4-4-1.2-1-3-1.5-5.2-1.5s-3.4.2-4.8.6c-1.4.4-2,1-2,1.8v16.4c1.1.6,2.2,1.1,3.3,1.4s2.2.5,3.3.5Z"/>
|
||||
<path d="M311.1,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1c-.8,0-1.6.2-2.4.3-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M330.5,122.5v-4.2h4.2v4.2h-4.2ZM330.8,153.7v-26.4h3.8v26.4h-3.8Z"/>
|
||||
<path d="M342.7,129.3c.8-.4,1.7-.8,2.6-1.1.9-.3,1.8-.6,2.8-.8.9-.2,1.9-.4,2.9-.5,1-.1,2-.2,2.9-.2,2.6,0,4.6.4,6.1,1.1,1.5.8,2.6,1.9,3.3,3.3.7,1.4,1,3.2,1,5.2v17.2h-3.8v-17.2c0-.6,0-1.3-.2-2-.2-.7-.5-1.4-.9-2-.5-.7-1.2-1.2-2.1-1.6s-2.2-.6-3.8-.6-2.2,0-3.4.3-2.4.4-3.6.8v22.4h-3.8v-24.3Z"/>
|
||||
<path d="M380.7,154.1c-1.9,0-3.4-.6-4.7-1.7-1.3-1.1-1.9-2.9-1.9-5.1v-16.5h-4.6v-3.5h4.6v-7.5h3.8v7.5h6.8v3.5h-6.8v15.3c0,1.5.2,2.6.7,3.3s1.4,1.1,2.8,1.1,1.3,0,1.9-.2,1.1-.3,1.5-.5v3.6c-.5.2-1.1.3-1.9.5s-1.5.2-2.1.2Z"/>
|
||||
<path d="M402,154.4c-2.5,0-4.7-.5-6.5-1.6-1.8-1-3.3-2.6-4.2-4.7-1-2.1-1.5-4.7-1.5-7.8,0-4.6,1-8,3.1-10.3,2.1-2.3,4.9-3.4,8.5-3.4s5.1.6,6.8,1.9c1.7,1.3,2.9,3,3.7,5.1.7,2.2,1.1,4.6,1.1,7.3h-21.3l.3-3.5h18.5l-1.5.5c-.1-2.6-.8-4.6-2-5.9-1.2-1.3-3.1-2-5.6-2s-3.2.3-4.3,1c-1.1.6-2,1.7-2.6,3.3s-.8,3.8-.8,6.6.8,5.7,2.3,7.3c1.5,1.7,3.6,2.5,6.3,2.5s2.5,0,3.6-.3,2.1-.5,3-.8c.9-.4,1.7-.7,2.4-1.2v3.9c-1.3.6-2.7,1.1-4.2,1.4-1.5.3-3.2.5-5.1.5Z"/>
|
||||
<path d="M419.5,153.7v-24.3c.4-.2,1.2-.5,2.5-.9,1.2-.4,2.7-.8,4.4-1.2,1.7-.4,3.5-.5,5.3-.5s.6,0,.8.1c.3,0,.6.1.8.2v3.4c-.9-.1-1.8-.2-2.7-.2s-1.7,0-2.5.1-1.6.2-2.4.3c-.8.1-1.6.3-2.4.4v22.5h-3.8Z"/>
|
||||
<path d="M446.4,154.4c-1.9,0-3.5-.2-4.8-.6s-2.6-.9-4-1.6v-4c1.1.6,2.1,1.1,2.9,1.5s1.7.7,2.6.9c.9.2,1.9.3,3,.3,1.8,0,3.2-.4,4.3-1.2,1.1-.8,1.7-1.8,1.7-3s-.3-1.7-.8-2.3-1.2-1-2.1-1.3c-.8-.4-1.7-.7-2.7-1.1-1-.3-2.1-.6-3.1-.9-1-.3-2-.8-2.9-1.3-.9-.5-1.6-1.3-2.1-2.1-.5-.9-.8-2-.8-3.4s.3-2.5.8-3.4c.5-1,1.3-1.7,2.1-2.4.9-.6,1.9-1.1,3-1.4,1.1-.3,2.2-.4,3.3-.4,1.6,0,2.9.2,4.1.6,1.1.4,2.3.9,3.5,1.4v3.8c-1.3-.7-2.5-1.3-3.6-1.7-1.1-.4-2.3-.7-3.8-.7s-2.7.3-3.9.9c-1.1.6-1.7,1.5-1.7,2.7s.3,1.8.9,2.4,1.4,1,2.3,1.4c1,.3,2,.7,3.1,1,.9.3,1.8.6,2.8.9,1,.3,1.8.8,2.7,1.3.8.5,1.5,1.3,2,2.1.5.9.8,2,.8,3.4s-.5,3.4-1.4,4.6-2.2,2.1-3.6,2.7c-1.5.6-3,.8-4.5.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 611 KiB |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Plastic Syringe @0.50mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "IQnumbi0N4BnvMfO",
|
||||
"filament_id": "OFAzgHcF",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 0.50mm nozzle",
|
||||
"FELIX Food Double 0.50mm nozzle",
|
||||
"FELIX Food Switch 0.50mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"31.22"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"0.75"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"7.2"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Plastic Syringe @1.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "p8CG6dHMUxNl7iPL",
|
||||
"filament_id": "OFAzgHcF",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 1.00mm nozzle",
|
||||
"FELIX Food Double 1.00mm nozzle",
|
||||
"FELIX Food Switch 1.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"31.22"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"1.5"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"27.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Plastic Syringe @2.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "s5uPS7I3E57tiRRC",
|
||||
"filament_id": "OFAzgHcF",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 2.00mm nozzle",
|
||||
"FELIX Food Double 2.00mm nozzle",
|
||||
"FELIX Food Switch 2.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"31.22"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"108.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Plastic Syringe @3.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "zlZtghh6ytU7N09m",
|
||||
"filament_id": "OFAzgHcF",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 3.00mm nozzle",
|
||||
"FELIX Food Double 3.00mm nozzle",
|
||||
"FELIX Food Switch 3.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"31.22"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"4.5"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"216.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Plastic Syringe @4.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "fvTZEcREMbs3JW1D",
|
||||
"filament_id": "OFAzgHcF",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 4.00mm nozzle",
|
||||
"FELIX Food Double 4.00mm nozzle",
|
||||
"FELIX Food Switch 4.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"31.22"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"5.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"6.0"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"432.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Stainless Steel Syringe @0.50mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "2XyqnzMqyuRCqpPq",
|
||||
"filament_id": "OFqZSD1S",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 0.50mm nozzle",
|
||||
"FELIX Food Double 0.50mm nozzle",
|
||||
"FELIX Food Switch 0.50mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"25.4"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"2.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"0.75"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"7.2"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Stainless Steel Syringe @1.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "yNxVUGOb1kUbXLiw",
|
||||
"filament_id": "OFqZSD1S",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 1.00mm nozzle",
|
||||
"FELIX Food Double 1.00mm nozzle",
|
||||
"FELIX Food Switch 1.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"25.4"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"2.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"1.5"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"27.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Food Medium Stainless Steel Syringe @2.00mm",
|
||||
"inherits": "fdm_filament_food",
|
||||
"from": "system",
|
||||
"setting_id": "4MhgYDnGFKLfurhc",
|
||||
"filament_id": "OFqZSD1S",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"FELIX Food Single 2.00mm nozzle",
|
||||
"FELIX Food Double 2.00mm nozzle",
|
||||
"FELIX Food Switch 2.00mm nozzle"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"25.4"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1.05"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1.0"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"10.0"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"2.0"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"0.0"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"3.0"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"FELIX Printers"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"108.0"
|
||||
]
|
||||
}
|
||||