Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf16febf6 | ||
|
|
70e52a105f | ||
|
|
b525cb79e1 | ||
|
|
86ab598899 | ||
|
|
ea90f45127 | ||
|
|
c7b4e8ef84 | ||
|
|
8f1103f064 | ||
|
|
6e42a9b0ae | ||
|
|
e77ba5f672 | ||
|
|
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 | ||
|
|
3f78959a0c | ||
|
|
30e48183e3 | ||
|
|
56b559d36a | ||
|
|
ed1f3e52c9 | ||
|
|
8ecfee6a21 | ||
|
|
fbf4652e12 | ||
|
|
be9ff50130 | ||
|
|
dee1f1715f | ||
|
|
fcb1f62bd9 | ||
|
|
bea412fddf | ||
|
|
391c53a8da |
@@ -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()
|
||||||
@@ -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
|
- 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)`
|
sub, it builds a `wxMouseEvent(wxEVT_MOTION)` from `wxGetMousePosition()`, calls `mainDropDown->mouseMove(ev)`
|
||||||
**directly** (nothing is posted), and calls `e.RequestMore()` to keep polling.
|
**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
|
- **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",
|
`DoSetSize` only uses `gtk_widget_set_size_request` ("gtk_window_resize does not work for GTK_WINDOW_POPUP",
|
||||||
`src/gtk/popupwin.cpp:181-188`).
|
`src/gtk/popupwin.cpp:181-188`).
|
||||||
@@ -607,6 +612,32 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
|||||||
```
|
```
|
||||||
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
||||||
`PopupWindow::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
|
- **Rule:** When shrinking a dropdown to fit above the display bottom, give up if fewer than two rows would
|
||||||
remain.
|
remain.
|
||||||
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -441,14 +441,10 @@ jobs:
|
|||||||
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
|
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
|
||||||
- name: Deploy Flatpak to nightly release
|
- name: Deploy Flatpak to nightly release
|
||||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
|
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
|
||||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
uses: ./.github/actions/deploy-nightly
|
||||||
with:
|
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_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||||
asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ 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
|
# The asset is /app (the exes link it at runtime) plus the build tree
|
||||||
# slimmed to what ctest needs.
|
# slimmed to what ctest needs.
|
||||||
- name: Package flatpak test asset
|
- name: Package flatpak test asset
|
||||||
|
|||||||
@@ -341,12 +341,12 @@ jobs:
|
|||||||
|
|
||||||
# Notarize main DMG
|
# 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 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
|
# Notarize profile validator DMG if it exists
|
||||||
if [ -f "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" ]; then
|
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 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
|
fi
|
||||||
|
|
||||||
- name: Create DMG without notary
|
- name: Create DMG without notary
|
||||||
@@ -397,25 +397,18 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy Mac release
|
- name: Deploy Mac release
|
||||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
|
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:
|
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_path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||||
asset_name: OrcaSlicer_Mac_universal_nightly.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
|
- 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
|
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: WebFreak001/deploy-nightly@v3.2.0
|
uses: ./.github/actions/deploy-nightly
|
||||||
with:
|
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_path: ${{ github.workspace }}/OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||||
asset_name: OrcaSlicer_profile_validator_Mac_universal_nightly.dmg
|
asset_name: OrcaSlicer_profile_validator_Mac_universal_nightly.dmg
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
max_releases: 1
|
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
- name: Set Windows build variables
|
- name: Set Windows build variables
|
||||||
@@ -460,10 +453,6 @@ jobs:
|
|||||||
- name: Build slicer Win
|
- name: Build slicer Win
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
working-directory: ${{ github.workspace }}
|
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.
|
# --tests builds the unit tests too; the unit_tests_windows_* jobs run them.
|
||||||
run: |
|
run: |
|
||||||
# cache-path is the install directory inside the deps build directory.
|
# cache-path is the install directory inside the deps build directory.
|
||||||
@@ -546,36 +535,27 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy Windows release portable
|
- name: Deploy Windows release portable
|
||||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
|
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:
|
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_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_name: OrcaSlicer_Windows${{ env.ARCH_SUFFIX }}_nightly_portable.zip
|
||||||
asset_content_type: application/x-zip-compressed
|
content_type: application/x-zip-compressed
|
||||||
max_releases: 1
|
|
||||||
|
|
||||||
- name: Deploy Windows release installer
|
- name: Deploy Windows release installer
|
||||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
|
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:
|
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_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_name: OrcaSlicer_Windows_Installer${{ env.ARCH_SUFFIX }}_nightly.exe
|
||||||
asset_content_type: application/x-msdownload
|
content_type: application/x-msdownload
|
||||||
max_releases: 1
|
|
||||||
|
|
||||||
- name: Deploy Windows OrcaSlicer_profile_validator release
|
- 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
|
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:
|
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_path: ${{ github.workspace }}/build/src/Release/OrcaSlicer_profile_validator.exe
|
||||||
asset_name: OrcaSlicer_profile_validator_Windows_nightly.exe
|
asset_name: OrcaSlicer_profile_validator_Windows_nightly.exe
|
||||||
asset_content_type: application/x-msdownload
|
content_type: application/x-msdownload
|
||||||
max_releases: 1
|
|
||||||
|
|
||||||
- name: Build MSIX Store package Win
|
- name: Build MSIX Store package Win
|
||||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||||
@@ -704,14 +684,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy Ubuntu release
|
- name: Deploy Ubuntu release
|
||||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
|
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:
|
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_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_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
|
- 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' }}
|
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -720,36 +696,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
|
- 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' }}
|
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:
|
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_path: ./build/src/Release/OrcaSlicer_profile_validator
|
||||||
asset_name: OrcaSlicer_profile_validator_Linux${{ env.ubuntu-ver-str }}_nightly
|
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
|
- 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' }}
|
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:
|
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_path: ./build/src/dev-utils/Release/generate_system_cache
|
||||||
asset_name: generate_system_cache_Linux${{ env.ubuntu-ver-str }}_nightly
|
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
|
- 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' }}
|
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:
|
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_path: ${{ github.workspace }}/resources/profiles/orca_custom_preset_tests.zip
|
||||||
asset_name: 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
|
# The build has just touched everything it can use, so an object
|
||||||
# untouched for a week is dead, usually orphaned by a flag change.
|
# untouched for a week is dead, usually orphaned by a flag change.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ on:
|
|||||||
# both it and its tests live in scripts/, so a PR touching only those must still run
|
# both it and its tests live in scripts/, so a PR touching only those must still run
|
||||||
# this workflow.
|
# this workflow.
|
||||||
- 'resources/printers/**'
|
- 'resources/printers/**'
|
||||||
|
- 'resources/info/legacy_compatible_printers.json'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
# orca_profile_tool.py reads the variant key sets from PrintConfig.cpp, and its
|
# 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.
|
# 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
|
python3 ./scripts/orca_profile_tool.py check 2>&1 | tee ${{ runner.temp }}/profile_tool.log
|
||||||
exit ${PIPESTATUS[0]}
|
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
|
# download
|
||||||
- name: Download
|
- name: Download
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
@@ -217,7 +233,7 @@ jobs:
|
|||||||
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
|
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
|
||||||
|
|
||||||
- name: Prepare comment artifact
|
- 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: |
|
run: |
|
||||||
{
|
{
|
||||||
# Marker matched by check_profiles_comment.yml to delete prior comments.
|
# Marker matched by check_profiles_comment.yml to delete prior comments.
|
||||||
@@ -234,6 +250,17 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
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
|
if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then
|
||||||
echo "### System Profile Validation Failed"
|
echo "### System Profile Validation Failed"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -283,7 +310,7 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
- name: Fail if any check failed
|
- 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: |
|
run: |
|
||||||
echo "One or more profile checks failed; see the step logs above."
|
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).'
|
echo 'Reproduce the whole run locally with scripts/check_profile.sh (scripts\check_profile.bat on Windows).'
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
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:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
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.
|
# The PR merge commit plus its first parent, the base it is diffed against.
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Look for changed C++ files
|
- name: Look for changed files
|
||||||
id: changes
|
id: changes
|
||||||
run: |
|
run: |
|
||||||
if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
|
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
|
else
|
||||||
echo "No C++ changes under src/ or tests/."
|
echo "No C++ changes under src/ or tests/."
|
||||||
fi
|
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
|
# Parsing needs the dependency headers, not a build of this PR's deps/.
|
||||||
# a PR that changes deps/ falls back to the newest cache main has.
|
# 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
|
- name: Restore cached deps
|
||||||
|
id: deps_cache
|
||||||
if: steps.changes.outputs.cpp == 'true'
|
if: steps.changes.outputs.cpp == 'true'
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
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-
|
restore-keys: linux-clang-cache-orcaslicer_deps-build-
|
||||||
fail-on-cache-miss: true
|
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
|
- name: Apt-Install Dependencies
|
||||||
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
|
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
|
||||||
uses: ./.github/actions/apt-install-deps
|
uses: ./.github/actions/apt-install-deps
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
name: clang-tidy fix
|
|
||||||
|
|
||||||
# Makes the missing-include fix that /bot fix-tidy (pr-tidy-bot.yml) posts on a
|
|
||||||
# pull request: scripts/run_clang_tidy.sh --fix on the merge commit the
|
|
||||||
# clang-tidy check runs on, uploaded as the clang-tidy-fix artifact.
|
|
||||||
#
|
|
||||||
# Every push to a PR starts this workflow, and its job skips on that first
|
|
||||||
# attempt. The bot re-runs it on request. A re-run keeps the pull_request
|
|
||||||
# context, so the PR's code runs with a read-only token, no secrets and the
|
|
||||||
# PR's own cache, never main's, whoever asked for it.
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- release/*
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
fix:
|
|
||||||
if: github.run_attempt > 1
|
|
||||||
# A hosted runner, so nothing the PR's code leaves behind outlives the job.
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
lfs: 'false'
|
|
||||||
# The PR merge commit plus its first parent, the base it is diffed against.
|
|
||||||
fetch-depth: 2
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
- name: Restore cached deps
|
|
||||||
uses: actions/cache/restore@v6
|
|
||||||
with:
|
|
||||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
|
||||||
key: linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}
|
|
||||||
restore-keys: linux-clang-cache-orcaslicer_deps-build-
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
|
|
||||||
- name: Apt-Install Dependencies
|
|
||||||
uses: ./.github/actions/apt-install-deps
|
|
||||||
|
|
||||||
# The status says whether findings remain that --fix cannot fix.
|
|
||||||
- name: Fix the changed lines
|
|
||||||
run: |
|
|
||||||
mkdir -p "$RUNNER_TEMP/clang-tidy-fix"
|
|
||||||
status=0
|
|
||||||
scripts/run_clang_tidy.sh --base HEAD^1 --yes --fix || status=$?
|
|
||||||
echo "$status" > "$RUNNER_TEMP/clang-tidy-fix/status"
|
|
||||||
git -c core.quotePath=false diff --no-color --no-ext-diff --src-prefix=a/ --dst-prefix=b/ \
|
|
||||||
> "$RUNNER_TEMP/clang-tidy-fix/fix.patch"
|
|
||||||
|
|
||||||
- name: Upload the fix
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: clang-tidy-fix
|
|
||||||
path: ${{ runner.temp }}/clang-tidy-fix/
|
|
||||||
retention-days: 7
|
|
||||||
# A later re-run of the same run replaces the earlier fix.
|
|
||||||
overwrite: true
|
|
||||||
@@ -1,476 +0,0 @@
|
|||||||
name: PR clang-tidy Bot
|
|
||||||
|
|
||||||
# Helps a pull request past the clang-tidy check.
|
|
||||||
#
|
|
||||||
# When the check fails on a PR's changes, a comment asks the author to fix it,
|
|
||||||
# either with scripts/run_clang_tidy.sh --fix or by commenting:
|
|
||||||
# /bot fix-tidy
|
|
||||||
#
|
|
||||||
# That command, from the PR's author or an account with write access, re-runs
|
|
||||||
# the PR's clang-tidy fix run (clang_tidy_fix.yml), which makes the fix in the
|
|
||||||
# PR's own context. When it finishes, the fix is posted as one review: each
|
|
||||||
# include whose place falls on a line the PR's diff shows is a suggestion GitHub
|
|
||||||
# can commit in one click, and the review's body holds the whole patch with a
|
|
||||||
# git apply command. Findings --fix cannot fix, such as a using in a header,
|
|
||||||
# are left for the check to report.
|
|
||||||
#
|
|
||||||
# No code from the PR runs here. Nothing is pushed, and the patch is only
|
|
||||||
# posted when every line it adds is an #include in a C++ file the PR changes.
|
|
||||||
#
|
|
||||||
# workflow_run matches the workflows by their name: keep "clang-tidy" and
|
|
||||||
# "clang-tidy fix" in step with clang_tidy.yml and clang_tidy_fix.yml, and
|
|
||||||
# TIDY_STEP below with the clang-tidy step's name in clang_tidy.yml.
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types:
|
|
||||||
- created
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- clang-tidy
|
|
||||||
- clang-tidy fix
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
request:
|
|
||||||
# Skips the job unless a PR comment mentions the command.
|
|
||||||
if: >-
|
|
||||||
github.repository == 'OrcaSlicer/OrcaSlicer'
|
|
||||||
&& github.event_name == 'issue_comment'
|
|
||||||
&& github.event.issue.pull_request != null
|
|
||||||
&& contains(github.event.comment.body, '/bot fix-tidy')
|
|
||||||
permissions:
|
|
||||||
actions: write # re-run the fix run
|
|
||||||
pull-requests: write # react to and answer the comment, which is the PR's
|
|
||||||
issues: write # feedback comment + reactions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- name: Re-run the PR's clang-tidy fix
|
|
||||||
uses: actions/github-script@v9
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const MARKER = '<!-- pr-tidy-bot -->';
|
|
||||||
const FIX_WORKFLOW = 'clang_tidy_fix.yml';
|
|
||||||
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const issue = context.payload.issue;
|
|
||||||
const comment = context.payload.comment;
|
|
||||||
|
|
||||||
// Ignores a comment whose sender is not its author.
|
|
||||||
if (context.payload.sender.login !== comment.user.login || comment.user.type !== 'User') {
|
|
||||||
core.info('Ignoring a comment not written by its sender, or by a bot.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandLine = (comment.body || '')
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.find((line) => /^\/bot\s+fix-tidy\b/i.test(line));
|
|
||||||
if (!commandLine) {
|
|
||||||
core.info('No /bot fix-tidy command found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commenter = comment.user.login;
|
|
||||||
// Answers only the PR's author and maintainers, so the command cannot be used to spam.
|
|
||||||
if (commenter !== issue.user.login) {
|
|
||||||
const { data: access } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: commenter });
|
|
||||||
if (!['admin', 'write'].includes(access.permission)) {
|
|
||||||
core.info(`Ignoring /bot fix-tidy from @${commenter}: not the PR's author and no write access.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const react = (content) =>
|
|
||||||
github.rest.reactions.createForIssueComment({ owner, repo, comment_id: comment.id, content });
|
|
||||||
async function refuse(reason) {
|
|
||||||
await react('-1');
|
|
||||||
await github.rest.issues.createComment({ owner, repo, issue_number: issue.number, body: `${MARKER}\n@${commenter} ${reason}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
await react('eyes');
|
|
||||||
|
|
||||||
if (!/^\/bot\s+fix-tidy$/i.test(commandLine)) {
|
|
||||||
return refuse('`/bot fix-tidy` takes no arguments.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: issue.number });
|
|
||||||
if (pr.state !== 'open') {
|
|
||||||
return refuse('the PR is not open.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sha = pr.head.sha.slice(0, 10);
|
|
||||||
const { data: { workflow_runs: runs } } = await github.rest.actions.listWorkflowRuns({
|
|
||||||
owner, repo, workflow_id: FIX_WORKFLOW, event: 'pull_request', head_sha: pr.head.sha, per_page: 1
|
|
||||||
});
|
|
||||||
const run = runs[0];
|
|
||||||
// A PR last pushed before clang_tidy_fix.yml existed has no run to re-run.
|
|
||||||
if (!run) {
|
|
||||||
return refuse(`there is no clang-tidy fix run for ${sha}, the PR's latest commit. If you just pushed it, ` +
|
|
||||||
'try again in a minute. Otherwise, push a commit or merge the base branch to start one, then comment `/bot fix-tidy` again.');
|
|
||||||
}
|
|
||||||
if (run.status !== 'completed') {
|
|
||||||
// The first attempt is the one every push starts, and it makes no fix.
|
|
||||||
return refuse(run.run_attempt > 1
|
|
||||||
? `a clang-tidy fix for ${sha} is already [running](${run.html_url}). I'll post it when it finishes.`
|
|
||||||
: `the PR's checks on ${sha} are still starting. Comment \`/bot fix-tidy\` again in a minute.`);
|
|
||||||
}
|
|
||||||
// GitHub refuses to re-run a run created over 30 days ago.
|
|
||||||
try {
|
|
||||||
await github.rest.actions.reRunWorkflow({ owner, repo, run_id: run.id });
|
|
||||||
} catch (error) {
|
|
||||||
core.warning(`Re-running ${run.html_url} failed: ${error.message}`);
|
|
||||||
return refuse(`GitHub won't re-run the [clang-tidy fix run](${run.html_url}) for ${sha} (${error.message}). ` +
|
|
||||||
'Push a commit or merge the base branch to start a new one, then comment `/bot fix-tidy` again.');
|
|
||||||
}
|
|
||||||
core.info(`Re-ran ${run.html_url}`);
|
|
||||||
|
|
||||||
suggest:
|
|
||||||
# The fix run's re-runs only. Its first attempt makes no fix.
|
|
||||||
if: >-
|
|
||||||
github.repository == 'OrcaSlicer/OrcaSlicer'
|
|
||||||
&& github.event_name == 'workflow_run'
|
|
||||||
&& github.event.workflow_run.name == 'clang-tidy fix'
|
|
||||||
&& github.event.workflow_run.event == 'pull_request'
|
|
||||||
&& github.event.workflow_run.run_attempt > 1
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-suggest-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
permissions:
|
|
||||||
actions: read # download the fix
|
|
||||||
contents: read # check out the PR head
|
|
||||||
pull-requests: write # the review
|
|
||||||
issues: write # feedback comment + reactions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- name: Find the PR and who asked
|
|
||||||
id: find
|
|
||||||
uses: actions/github-script@v9
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const MARKER = '<!-- pr-tidy-bot -->';
|
|
||||||
const CPP = /^(src|tests)\/.+\.(cpp|cc|cxx|hpp|h|hxx)$/;
|
|
||||||
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const run = context.payload.workflow_run;
|
|
||||||
|
|
||||||
// workflow_run lists no PR for a fork's branch, so the PR is found by its branch.
|
|
||||||
const { data: prs } = await github.rest.pulls.list({
|
|
||||||
owner, repo, state: 'open', head: `${run.head_repository.owner.login}:${run.head_branch}`
|
|
||||||
});
|
|
||||||
const pr = prs[0];
|
|
||||||
if (!pr) {
|
|
||||||
core.info('No open PR for this run.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The latest /bot fix-tidy comment is the one this run answers.
|
|
||||||
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr.number, per_page: 100 });
|
|
||||||
const request = comments.reverse().find((c) => c.user.type === 'User' && /^\s*\/bot\s+fix-tidy\s*$/im.test(c.body || ''));
|
|
||||||
const mention = request ? `@${request.user.login} ` : '';
|
|
||||||
async function refuse(reason) {
|
|
||||||
if (request) {
|
|
||||||
await github.rest.reactions.createForIssueComment({ owner, repo, comment_id: request.id, content: '-1' });
|
|
||||||
}
|
|
||||||
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: `${MARKER}\n${mention}${reason}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pr.head.sha !== run.head_sha) {
|
|
||||||
return refuse('the PR changed while I was making its clang-tidy fix. Comment `/bot fix-tidy` again.');
|
|
||||||
}
|
|
||||||
if (run.conclusion !== 'success') {
|
|
||||||
return refuse(`I couldn't make the clang-tidy fix. See the [run log](${run.html_url}).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The right-side lines of each changed C++ file's diff: the lines a
|
|
||||||
// review comment can be placed on. A file too large for GitHub to
|
|
||||||
// show its diff has none, and gets no suggestions.
|
|
||||||
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: pr.number, per_page: 100 });
|
|
||||||
const ranges = {};
|
|
||||||
for (const file of files) {
|
|
||||||
if (file.status === 'removed' || !CPP.test(file.filename)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ranges[file.filename] = [...(file.patch || '').matchAll(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gm)]
|
|
||||||
.map((m) => [Number(m[1]), Number(m[1]) + Number(m[2] ?? 1) - 1])
|
|
||||||
.filter(([first, last]) => last >= first);
|
|
||||||
}
|
|
||||||
|
|
||||||
core.setOutput('pr', pr.number);
|
|
||||||
core.setOutput('head-sha', pr.head.sha);
|
|
||||||
core.setOutput('head-repo', run.head_repository.full_name);
|
|
||||||
core.setOutput('ranges', JSON.stringify(ranges));
|
|
||||||
core.setOutput('mention', mention);
|
|
||||||
core.setOutput('request-id', request ? request.id : '');
|
|
||||||
|
|
||||||
- name: Download the fix
|
|
||||||
if: steps.find.outputs.pr != ''
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: clang-tidy-fix
|
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
path: ${{ runner.temp }}/clang-tidy-fix
|
|
||||||
|
|
||||||
# The PR's own files, only to apply the patch to. Nothing in them is run.
|
|
||||||
- name: Checkout the PR head
|
|
||||||
if: steps.find.outputs.pr != ''
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
repository: ${{ steps.find.outputs.head-repo }}
|
|
||||||
ref: ${{ steps.find.outputs.head-sha }}
|
|
||||||
path: head
|
|
||||||
fetch-depth: 1
|
|
||||||
lfs: 'false'
|
|
||||||
persist-credentials: false
|
|
||||||
sparse-checkout: |
|
|
||||||
src
|
|
||||||
tests
|
|
||||||
|
|
||||||
# The fix was made on the PR's merge commit, so it is applied to the head
|
|
||||||
# and diffed again, giving line numbers in the PR's own files.
|
|
||||||
- name: Check the fix and place its suggestions
|
|
||||||
id: place
|
|
||||||
if: steps.find.outputs.pr != ''
|
|
||||||
env:
|
|
||||||
RANGES: ${{ steps.find.outputs.ranges }}
|
|
||||||
run: |
|
|
||||||
python3 -I - "$RUNNER_TEMP/clang-tidy-fix/fix.patch" head "$RUNNER_TEMP" <<'EOF'
|
|
||||||
import json, os, re, subprocess, sys
|
|
||||||
|
|
||||||
patch_path, head, out = sys.argv[1:]
|
|
||||||
ranges = json.loads(os.environ["RANGES"])
|
|
||||||
|
|
||||||
def output(**values):
|
|
||||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
|
|
||||||
for key, value in values.items():
|
|
||||||
f.write(f"{key}={value}\n")
|
|
||||||
|
|
||||||
patch = open(patch_path, encoding="utf-8").read()
|
|
||||||
if not patch.strip():
|
|
||||||
output(result="empty")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# Every line the patch adds must be an #include or blank, in a C++ file the
|
|
||||||
# PR changes, and it may not remove, create, rename or change the mode of
|
|
||||||
# anything.
|
|
||||||
include = re.compile(r'\s*#\s*include\s*(<[\w./+-]+>|"[\w./+-]+")\s*')
|
|
||||||
header = re.compile(r"(index [0-9a-f]+\.\.[0-9a-f]+( \d+)?|@@ -\d+(,\d+)? \+\d+(,\d+)? @@.*)")
|
|
||||||
problems = []
|
|
||||||
path = None
|
|
||||||
for line in patch.splitlines():
|
|
||||||
if line.startswith("diff --git "):
|
|
||||||
match = re.fullmatch(r"diff --git a/(.+) b/\1", line)
|
|
||||||
path = match.group(1) if match else None
|
|
||||||
if path not in ranges:
|
|
||||||
problems.append(f"{line}: not a C++ file the PR changes")
|
|
||||||
# git appends a tab to the --- and +++ lines of a path that contains a space.
|
|
||||||
elif line.removesuffix("\t") in (f"--- a/{path}", f"+++ b/{path}") or header.fullmatch(line):
|
|
||||||
pass
|
|
||||||
elif line.startswith(" ") or line == "\\ No newline at end of file":
|
|
||||||
pass
|
|
||||||
elif line.startswith("+") and (include.fullmatch(line[1:]) or not line[1:].strip()):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
problems.append(f"{path}: {line}")
|
|
||||||
if problems:
|
|
||||||
for problem in problems:
|
|
||||||
print(f"::error::Refused patch line {problem}")
|
|
||||||
output(result="rejected")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if subprocess.run(["git", "-C", head, "apply", "--index", os.path.abspath(patch_path)]).returncode != 0:
|
|
||||||
output(result="apply-failed")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
git = ["git", "-C", head, "-c", "core.quotePath=false", "diff", "--cached", "--no-color", "--no-ext-diff",
|
|
||||||
"--src-prefix=a/", "--dst-prefix=b/"]
|
|
||||||
with open(os.path.join(out, "head.patch"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(subprocess.run(git, check=True, capture_output=True, encoding="utf-8").stdout)
|
|
||||||
|
|
||||||
# Each run of added lines becomes one suggestion on the line it goes after
|
|
||||||
# (before, at the top of a file): that line, replaced by itself and the
|
|
||||||
# includes. It can only sit on a line the PR's diff shows.
|
|
||||||
hunks = []
|
|
||||||
path = None
|
|
||||||
for line in subprocess.run(git + ["-U0"], check=True, capture_output=True, encoding="utf-8").stdout.splitlines():
|
|
||||||
if line.startswith("diff --git "):
|
|
||||||
path = re.fullmatch(r"diff --git a/(.+) b/\1", line).group(1)
|
|
||||||
elif line.startswith("@@ "):
|
|
||||||
hunks.append((path, int(re.match(r"@@ -(\d+)", line).group(1)), []))
|
|
||||||
elif hunks and line.startswith("+") and not line.startswith("+++ "):
|
|
||||||
hunks[-1][2].append(line[1:])
|
|
||||||
|
|
||||||
suggestions = []
|
|
||||||
total = suggested = 0
|
|
||||||
for path, after, added in hunks:
|
|
||||||
count = sum(bool(include.fullmatch(line)) for line in added)
|
|
||||||
total += count
|
|
||||||
original = subprocess.run(["git", "-C", head, "show", f"HEAD:{path}"], check=True, capture_output=True,
|
|
||||||
encoding="utf-8").stdout.splitlines()
|
|
||||||
line = max(after, 1)
|
|
||||||
if line > len(original) or not any(first <= line <= last for first, last in ranges[path]):
|
|
||||||
continue
|
|
||||||
body = "\n".join([original[line - 1], *added] if after else [*added, original[0]])
|
|
||||||
# A fence longer than any run of backticks in the line it repeats.
|
|
||||||
fence = "`" * max([3] + [len(run) + 1 for run in re.findall(r"`+", body)])
|
|
||||||
suggestions.append({"path": path, "line": line, "side": "RIGHT",
|
|
||||||
"body": f"{fence}suggestion\n{body}\n{fence}"})
|
|
||||||
suggested += count
|
|
||||||
with open(os.path.join(out, "suggestions.json"), "w", encoding="utf-8") as f:
|
|
||||||
json.dump(suggestions, f)
|
|
||||||
output(result="ok", includes=total, suggested=suggested)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Post the fix
|
|
||||||
if: always() && steps.find.outputs.pr != ''
|
|
||||||
uses: actions/github-script@v9
|
|
||||||
env:
|
|
||||||
PR: ${{ steps.find.outputs.pr }}
|
|
||||||
MENTION: ${{ steps.find.outputs.mention }}
|
|
||||||
REQUEST_ID: ${{ steps.find.outputs.request-id }}
|
|
||||||
HEAD_SHA: ${{ steps.find.outputs.head-sha }}
|
|
||||||
RESULT: ${{ steps.place.outputs.result }}
|
|
||||||
INCLUDES: ${{ steps.place.outputs.includes }}
|
|
||||||
SUGGESTED: ${{ steps.place.outputs.suggested }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const MARKER = '<!-- pr-tidy-bot -->';
|
|
||||||
// GitHub's limit on a review body is 65536 characters.
|
|
||||||
const MAX_PATCH = 60000;
|
|
||||||
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const { PR, MENTION, REQUEST_ID, HEAD_SHA, RESULT, INCLUDES, SUGGESTED, RUNNER_TEMP } = process.env;
|
|
||||||
const issue_number = Number(PR);
|
|
||||||
const sha = HEAD_SHA.slice(0, 10);
|
|
||||||
const fixUrl = context.payload.workflow_run.html_url;
|
|
||||||
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
|
|
||||||
|
|
||||||
const react = (content) => REQUEST_ID
|
|
||||||
? github.rest.reactions.createForIssueComment({ owner, repo, comment_id: Number(REQUEST_ID), content })
|
|
||||||
: Promise.resolve();
|
|
||||||
async function reply(body, content) {
|
|
||||||
await react(content);
|
|
||||||
await github.rest.issues.createComment({ owner, repo, issue_number, body: `${MARKER}\n${MENTION}${body}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const read = (...parts) => fs.readFileSync(path.join(RUNNER_TEMP, ...parts), 'utf8');
|
|
||||||
const passes = RESULT && read('clang-tidy-fix', 'status').trim() === '0';
|
|
||||||
|
|
||||||
if (RESULT === 'empty') {
|
|
||||||
return passes
|
|
||||||
? reply(`clang-tidy passes on ${sha}. There is nothing to fix.`, '+1')
|
|
||||||
: reply(`clang-tidy has no automatic fix for what it reports on ${sha}. The findings, in the [fix run's log](${fixUrl}), need a manual edit.`, '-1');
|
|
||||||
}
|
|
||||||
if (RESULT === 'rejected') {
|
|
||||||
return reply(`the clang-tidy fix changes more than includes, so I didn't post it. See the [run log](${runUrl}).`, '-1');
|
|
||||||
}
|
|
||||||
if (RESULT === 'apply-failed') {
|
|
||||||
return reply(`the clang-tidy fix doesn't apply to ${sha}. Merge the base branch into the PR, then comment \`/bot fix-tidy\` again.`, '-1');
|
|
||||||
}
|
|
||||||
if (RESULT !== 'ok') {
|
|
||||||
return reply(`something went wrong. See the [run log](${runUrl}).`, '-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
const patch = read('head.patch');
|
|
||||||
const suggestions = JSON.parse(read('suggestions.json'));
|
|
||||||
|
|
||||||
const one = INCLUDES === '1';
|
|
||||||
const includes = one ? '1 missing include' : `${INCLUDES} missing includes`;
|
|
||||||
const inline = SUGGESTED === '0'
|
|
||||||
? (one ? 'It goes on a line this PR does not change, so I can\'t suggest it inline.'
|
|
||||||
: 'They go on lines this PR does not change, so I can\'t suggest them inline.')
|
|
||||||
: SUGGESTED === INCLUDES
|
|
||||||
? (one ? 'It is suggested inline below.' : 'Each is suggested inline below.')
|
|
||||||
: `${SUGGESTED} of them are suggested inline below; the rest go on lines this PR does not change.`;
|
|
||||||
const remaining = passes
|
|
||||||
? `With ${one ? 'it' : 'them'}, clang-tidy passes.`
|
|
||||||
: `Some findings remain that need a manual edit; see the [fix run's log](${fixUrl}).`;
|
|
||||||
// A fence longer than any run of backticks in the patch's context lines.
|
|
||||||
const fence = '`'.repeat(Math.max(3, ...[...patch.matchAll(/`+/g)].map((m) => m[0].length + 1)));
|
|
||||||
const apply = patch.length > MAX_PATCH
|
|
||||||
? `The full patch is too large to show here. Download \`fix.patch\` from the [fix run](${fixUrl}) and run \`git apply fix.patch\`.`
|
|
||||||
: `To apply ${one ? 'it' : 'all of them'}, run this in your checkout of the PR branch:\n\n${fence}sh\ngit apply <<'EOF'\n${patch}EOF\n${fence}`;
|
|
||||||
const body = (withSuggestions) => `${MARKER}\n${MENTION}clang-tidy's fix for ${sha} adds ${includes}. ` +
|
|
||||||
`${withSuggestions ? `${inline} ` : ''}${remaining}\n\n${apply}`;
|
|
||||||
|
|
||||||
const review = { owner, repo, pull_number: issue_number, commit_id: HEAD_SHA, event: 'COMMENT' };
|
|
||||||
try {
|
|
||||||
await github.rest.pulls.createReview({ ...review, body: body(true), comments: suggestions });
|
|
||||||
} catch (error) {
|
|
||||||
// A suggestion GitHub will not place still leaves the patch.
|
|
||||||
core.warning(`Posting the suggestions failed (${error.message}); posting the patch alone.`);
|
|
||||||
await github.rest.pulls.createReview({ ...review, body: body(false) });
|
|
||||||
}
|
|
||||||
await react('+1');
|
|
||||||
|
|
||||||
nudge:
|
|
||||||
if: >-
|
|
||||||
github.repository == 'OrcaSlicer/OrcaSlicer'
|
|
||||||
&& github.event_name == 'workflow_run'
|
|
||||||
&& github.event.workflow_run.name == 'clang-tidy'
|
|
||||||
&& github.event.workflow_run.event == 'pull_request'
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-nudge-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
permissions:
|
|
||||||
actions: read # the check's steps
|
|
||||||
pull-requests: write # comment on the PR
|
|
||||||
issues: write # the comment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- name: Ask for a fix when the check fails
|
|
||||||
uses: actions/github-script@v9
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const MARKER = '<!-- pr-tidy-bot-nudge -->';
|
|
||||||
// The clang-tidy step's name in clang_tidy.yml. A failure in any other
|
|
||||||
// step is the CI's, not the PR's.
|
|
||||||
const TIDY_STEP = 'Run clang-tidy on the changed lines';
|
|
||||||
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const run = context.payload.workflow_run;
|
|
||||||
|
|
||||||
// workflow_run lists no PR for a fork's branch, so the PR is found by its branch.
|
|
||||||
const { data: prs } = await github.rest.pulls.list({
|
|
||||||
owner, repo, state: 'open', head: `${run.head_repository.owner.login}:${run.head_branch}`
|
|
||||||
});
|
|
||||||
const pr = prs.find((p) => p.head.sha === run.head_sha);
|
|
||||||
if (!pr) {
|
|
||||||
core.info('No open PR at this run\'s commit; a newer run will answer.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({ owner, repo, run_id: run.id });
|
|
||||||
const failed = jobs.some((job) => (job.steps || []).some((step) => step.name === TIDY_STEP && step.conclusion === 'failure'));
|
|
||||||
|
|
||||||
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr.number, per_page: 100 });
|
|
||||||
const previous = comments.find((c) => c.user.type === 'Bot' && (c.body || '').startsWith(MARKER));
|
|
||||||
|
|
||||||
if (!failed) {
|
|
||||||
// Removes the request once the check passes.
|
|
||||||
if (previous && run.conclusion === 'success') {
|
|
||||||
await github.rest.issues.deleteComment({ owner, repo, comment_id: previous.id });
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = `${MARKER}\n@${pr.user.login} the clang-tidy check found problems in this PR's changes ` +
|
|
||||||
`at ${run.head_sha.slice(0, 10)} ([log](${run.html_url})). Missing includes can be fixed either way:\n\n` +
|
|
||||||
'- run `scripts/run_clang_tidy.sh --fix` (`scripts\\run_clang_tidy.ps1 -Fix` on Windows) and push the result, or\n' +
|
|
||||||
'- comment `/bot fix-tidy`, and I\'ll suggest the fix here for you to apply.\n\n' +
|
|
||||||
'Other findings, such as a `using` in a header, need a manual edit.';
|
|
||||||
// Edits the earlier request instead of posting another, so each push does not notify again.
|
|
||||||
if (previous) {
|
|
||||||
await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body });
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body });
|
|
||||||
}
|
|
||||||
@@ -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.
|
- 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.
|
- 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 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.
|
- 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
|
## 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`.
|
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.
|
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
|
### 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.
|
- 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
|
# Once we get rid of Perl and libslic3r is linked statically, we can get rid of -fPIC
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
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)
|
if (APPLE)
|
||||||
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
|
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
|
||||||
if (CMAKE_OSX_DEPLOYMENT_TARGET)
|
if (CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
|||||||
pushd "${SCRIPT_PATH}" > /dev/null
|
pushd "${SCRIPT_PATH}" > /dev/null
|
||||||
|
|
||||||
function usage() {
|
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 " -1: limit builds to one core (where possible)"
|
||||||
echo " -j N: limit builds to N cores (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)"
|
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 " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
||||||
echo " -l: use Clang instead of GCC (default: GCC)"
|
echo " -l: use Clang instead of GCC (default: GCC)"
|
||||||
echo " -L: use ld.lld as linker (if available)"
|
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 "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||||
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
|
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 "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 "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"
|
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||||
@@ -131,6 +135,20 @@ while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
|
|||||||
esac
|
esac
|
||||||
done
|
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
|
if [ ${OPTIND} -eq 1 ] ; then
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
@@ -547,7 +565,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
|||||||
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
|
||||||
fi
|
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
|
# 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.
|
# 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
|
# -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} \
|
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||||
-DORCA_TOOLS=ON \
|
-DORCA_TOOLS=ON \
|
||||||
"${COLORED_OUTPUT}" \
|
"${COLORED_OUTPUT}" \
|
||||||
"${BUILD_ARGS[@]}"
|
"${BUILD_ARGS[@]}" \
|
||||||
|
"${EXTRA_CMAKE_ARGS[@]}"
|
||||||
echo "done"
|
echo "done"
|
||||||
if [[ -n "${BUILD_ORCA}" ]]; then
|
if [[ -n "${BUILD_ORCA}" ]]; then
|
||||||
echo "Building OrcaSlicer ..."
|
echo "Building OrcaSlicer ..."
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
|
# 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
|
# 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.
|
# 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)
|
foreach (_lang C CXX)
|
||||||
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
|
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
|
||||||
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
|
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
|
||||||
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
|
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
|
||||||
|
list(APPEND CMAKE_${_lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -fno-pch-timestamp)
|
||||||
endif ()
|
endif ()
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|||||||
@@ -88,18 +88,6 @@ struct NfpPConfig {
|
|||||||
*/
|
*/
|
||||||
bool explore_holes = false;
|
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.
|
* @brief If true, use all CPUs available. Run on a single core otherwise.
|
||||||
*/
|
*/
|
||||||
@@ -1125,9 +1113,11 @@ private:
|
|||||||
|
|
||||||
auto d = cb - ci;
|
auto d = cb - ci;
|
||||||
|
|
||||||
// Keep the pile on the bin (see Config::clamp_to_bin). The items' boxes carry
|
// Keep the pile on the bin. Centring it on a target near an edge (a belt
|
||||||
// their inflation, which is the margin left at the edge.
|
// printer starts its parts at the leading end of the belt) would push a pile
|
||||||
if (config_.clamp_to_bin) {
|
// 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) {
|
auto on_bin = [](Coord lo, Coord hi, Coord bin_lo, Coord bin_hi, Coord shift) {
|
||||||
if (hi - lo >= bin_hi - bin_lo)
|
if (hi - lo >= bin_hi - bin_lo)
|
||||||
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
|
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
|
||||||
@@ -1139,8 +1129,6 @@ private:
|
|||||||
};
|
};
|
||||||
setX(d, on_bin(getX(bb.minCorner()), getX(bb.maxCorner()), getX(bbin.minCorner()), getX(bbin.maxCorner()), getX(d)));
|
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)));
|
setY(d, on_bin(getY(bb.minCorner()), getY(bb.maxCorner()), getY(bbin.minCorner()), getY(bbin.maxCorner()), getY(d)));
|
||||||
cb = ci + d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BBS make sure the item won't clash with excluded regions
|
// BBS make sure the item won't clash with excluded regions
|
||||||
// do we have wipe tower after arranging?
|
// do we have wipe tower after arranging?
|
||||||
@@ -1169,9 +1157,7 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Item objs_convex_hull_item(objs_convex_hull);
|
Item objs_convex_hull_item(objs_convex_hull);
|
||||||
Vertex objs_convex_hull_ref = objs_convex_hull_item.referenceVertex();
|
Vertex ref_aligned = objs_convex_hull_item.referenceVertex() + d;
|
||||||
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
|
|
||||||
bool ref_aligned_is_ok = std::any_of(nfps.begin(), nfps.end(), [&ref_aligned](auto& nfp) {return sl::isInside(ref_aligned, nfp); });
|
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) {
|
if (!ref_aligned_is_ok) {
|
||||||
// ref_aligned is not good, then find a nearest point on nfp boundary
|
// 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
|
strong point is placed, no later strong modifier and no weak modifier is
|
||||||
processed for that perimeter.
|
processed for that perimeter.
|
||||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
priority to the highest, so the highest one overwrites overlapping zones.
|
||||||
Blocked modifier that fully contains a perimeter is the exception: it is
|
|
||||||
skipped there (see [Full containment](#full-containment)).
|
|
||||||
|
|
||||||
A strong modifier without a usable segment, even one whose fragments were all
|
A strong modifier without a usable segment, even one whose fragments were all
|
||||||
discarded, passes the turn to the next one.
|
discarded, passes the turn to the next one.
|
||||||
@@ -232,20 +230,23 @@ begin and end positions on the source contour.
|
|||||||
### Full containment
|
### Full containment
|
||||||
|
|
||||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
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
|
follows seam painting, where a whole perimeter can be painted in either color:
|
||||||
choice and forbidding the seam all round is not:
|
|
||||||
|
|
||||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||||
clearing painting and lower zones.
|
clearing painting and lower zones.
|
||||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
- **Seam Blocked** types the whole perimeter Blocked, like a perimeter painted
|
||||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
red all round, overriding painting and lower zones. The seam cannot avoid such
|
||||||
override anything below it: lower zones and painting stay in effect.
|
a perimeter: the seam placer compares Blocked candidates with each other like
|
||||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
Neutral ones, so the seam is placed as on an unmarked perimeter. If the
|
||||||
intersection to place the point on.
|
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)).
|
[Weak modifiers](#weak-modifiers)).
|
||||||
|
|
||||||
The perimeter is fully contained when the united intervals cover every source
|
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
|
`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
|
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
|
segment becomes a zone with a type and two boundaries, kept in application
|
||||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
order, lowest priority first. Full containment of a weak modifier becomes a
|
||||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
whole-perimeter zone at its place in that order: it has no boundaries and takes
|
||||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
part in no insertion or helper step below. The boundaries
|
||||||
carry their positions on the source contour; these remain as provenance after
|
carry their positions on the source contour; these remain as provenance after
|
||||||
insertion and are not indices into the modified polygon.
|
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
|
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||||
placement may differ from expected.", because the export warnings dialog shows
|
placement may differ from expected.", because the export warnings dialog shows
|
||||||
only the first line of each warning. Repeated warning events replace the
|
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,
|
notification instead of appending to it. Except for the "covers an entire
|
||||||
the causes name the modifier types involved, as the menu names them, in menu
|
perimeter" and "had no effect" causes, the causes name the modifier types
|
||||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
involved, as the menu names them, in menu order and each type once, for example
|
||||||
|
"(Seam Left, Seam Enforced)".
|
||||||
The causes are:
|
The causes are:
|
||||||
|
|
||||||
- **failed to process some intersections (types):** at least one fragment was
|
- **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
|
a Seam Center, Left or Right modifier had more than one segment on a
|
||||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
- **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)).
|
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
|
- **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
|
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
|
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
|
- `[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
|
the object and modifier names. Unlike the user warning, the log lists all of
|
||||||
them.
|
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
|
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
|
`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
|
priorities, weak boundaries that coincide or share an edge, enforced
|
||||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||||
zones over painting's oversampled candidates, the warning type masks, usage
|
zones over painting's oversampled candidates, the warning type masks, usage
|
||||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
tracking for the "had no effect" and whole-perimeter Blocked warnings, volume
|
||||||
groups, restoration of strong points after alignment, raft layer indexing and
|
sorting of strong and weak groups, restoration of strong points after
|
||||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
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
|
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,
|
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
|
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
||||||
enforced-patch selection independent of the contour start, fully painted
|
enforced-patch selection independent of the contour start, fully painted
|
||||||
contours, duplicate removal, and `Print::apply()` synchronization through type
|
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.cpp
|
||||||
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
|
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
|
||||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
|
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.cpp
|
||||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
||||||
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
|
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/OrientJob.cpp
|
||||||
src/slic3r/GUI/Jobs/FillBedJob.cpp
|
src/slic3r/GUI/Jobs/FillBedJob.cpp
|
||||||
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
|
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
|
||||||
|
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
|
||||||
src/slic3r/GUI/Jobs/BindJob.cpp
|
src/slic3r/GUI/Jobs/BindJob.cpp
|
||||||
src/slic3r/GUI/Jobs/PrintJob.cpp
|
src/slic3r/GUI/Jobs/PrintJob.cpp
|
||||||
src/slic3r/GUI/Jobs/SendJob.cpp
|
src/slic3r/GUI/Jobs/SendJob.cpp
|
||||||
@@ -138,6 +141,7 @@ src/slic3r/GUI/DeviceErrorDialog.cpp
|
|||||||
src/slic3r/GUI/ExtraRenderers.cpp
|
src/slic3r/GUI/ExtraRenderers.cpp
|
||||||
src/slic3r/GUI/EditGCodeDialog.cpp
|
src/slic3r/GUI/EditGCodeDialog.cpp
|
||||||
src/slic3r/GUI/Field.cpp
|
src/slic3r/GUI/Field.cpp
|
||||||
|
src/slic3r/GUI/Field.hpp
|
||||||
src/slic3r/GUI/GCodeViewer.cpp
|
src/slic3r/GUI/GCodeViewer.cpp
|
||||||
src/slic3r/GUI/GLCanvas3D.cpp
|
src/slic3r/GUI/GLCanvas3D.cpp
|
||||||
src/slic3r/GUI/Calibration.cpp
|
src/slic3r/GUI/Calibration.cpp
|
||||||
@@ -165,6 +169,7 @@ src/slic3r/GUI/PrintOptionsDialog.cpp
|
|||||||
src/slic3r/GUI/SafetyOptionsDialog.cpp
|
src/slic3r/GUI/SafetyOptionsDialog.cpp
|
||||||
src/slic3r/GUI/ParamsPanel.cpp
|
src/slic3r/GUI/ParamsPanel.cpp
|
||||||
src/slic3r/GUI/IMEXModesCtrl.cpp
|
src/slic3r/GUI/IMEXModesCtrl.cpp
|
||||||
|
src/slic3r/GUI/IMEXFilamentPickerPopover.cpp
|
||||||
src/slic3r/GUI/PartPlate.cpp
|
src/slic3r/GUI/PartPlate.cpp
|
||||||
src/slic3r/GUI/Plater.cpp
|
src/slic3r/GUI/Plater.cpp
|
||||||
src/slic3r/GUI/Preferences.cpp
|
src/slic3r/GUI/Preferences.cpp
|
||||||
@@ -212,10 +217,14 @@ src/libslic3r/GCode/SeamPlacer.cpp
|
|||||||
src/libslic3r/ExtrusionEntity.cpp
|
src/libslic3r/ExtrusionEntity.cpp
|
||||||
src/libslic3r/Flow.cpp
|
src/libslic3r/Flow.cpp
|
||||||
src/libslic3r/Format/AMF.cpp
|
src/libslic3r/Format/AMF.cpp
|
||||||
|
src/libslic3r/Format/3mf.cpp
|
||||||
|
src/libslic3r/Format/bbs_3mf.cpp
|
||||||
src/libslic3r/miniz_extension.cpp
|
src/libslic3r/miniz_extension.cpp
|
||||||
src/libslic3r/IMEXHelpers.cpp
|
src/libslic3r/IMEXHelpers.cpp
|
||||||
src/libslic3r/Preset.cpp
|
src/libslic3r/Preset.cpp
|
||||||
src/libslic3r/Print.cpp
|
src/libslic3r/Print.cpp
|
||||||
|
src/libslic3r/BeltPurge.cpp
|
||||||
|
src/libslic3r/AppConfig.cpp
|
||||||
src/libslic3r/PrintBase.cpp
|
src/libslic3r/PrintBase.cpp
|
||||||
src/libslic3r/PrintConfig.cpp
|
src/libslic3r/PrintConfig.cpp
|
||||||
src/libslic3r/Zipper.cpp
|
src/libslic3r/Zipper.cpp
|
||||||
@@ -253,6 +262,7 @@ src/slic3r/Utils/MKS.cpp
|
|||||||
src/slic3r/Utils/Moonraker.cpp
|
src/slic3r/Utils/Moonraker.cpp
|
||||||
src/slic3r/Utils/OctoPrint.cpp
|
src/slic3r/Utils/OctoPrint.cpp
|
||||||
src/slic3r/Utils/Repetier.cpp
|
src/slic3r/Utils/Repetier.cpp
|
||||||
|
src/slic3r/Utils/ESP3D.cpp
|
||||||
src/slic3r/GUI/SendMultiMachinePage.cpp
|
src/slic3r/GUI/SendMultiMachinePage.cpp
|
||||||
src/slic3r/GUI/MultiMachinePage.cpp
|
src/slic3r/GUI/MultiMachinePage.cpp
|
||||||
src/slic3r/GUI/MultiMachineManagerPage.cpp
|
src/slic3r/GUI/MultiMachineManagerPage.cpp
|
||||||
@@ -307,6 +317,8 @@ src/slic3r/GUI/AuxiliaryDataViewModel.cpp
|
|||||||
src/slic3r/GUI/AuxiliaryDialog.cpp
|
src/slic3r/GUI/AuxiliaryDialog.cpp
|
||||||
src/slic3r/GUI/BBLStatusBarPrint.cpp
|
src/slic3r/GUI/BBLStatusBarPrint.cpp
|
||||||
src/slic3r/GUI/BBLStatusBarSend.cpp
|
src/slic3r/GUI/BBLStatusBarSend.cpp
|
||||||
|
src/slic3r/GUI/BBLStatusBar.cpp
|
||||||
|
src/slic3r/GUI/BeltPurgeTower.cpp
|
||||||
src/slic3r/GUI/BaseTransparentDPIFrame.cpp
|
src/slic3r/GUI/BaseTransparentDPIFrame.cpp
|
||||||
src/slic3r/GUI/CloneDialog.cpp
|
src/slic3r/GUI/CloneDialog.cpp
|
||||||
src/slic3r/GUI/ColorDecomposeDialog.cpp
|
src/slic3r/GUI/ColorDecomposeDialog.cpp
|
||||||
@@ -347,11 +359,27 @@ src/slic3r/GUI/WebViewDialog.cpp
|
|||||||
src/slic3r/GUI/Widgets/AMSItem.cpp
|
src/slic3r/GUI/Widgets/AMSItem.cpp
|
||||||
src/slic3r/GUI/Widgets/DialogButtons.cpp
|
src/slic3r/GUI/Widgets/DialogButtons.cpp
|
||||||
src/slic3r/Utils/NetworkAgentFactory.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/PluginAuditManager.cpp
|
||||||
src/slic3r/plugin/PluginConfig.cpp
|
src/slic3r/plugin/PluginConfig.cpp
|
||||||
src/slic3r/plugin/PluginResolver.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/SketchEngine.cpp
|
||||||
src/libslic3r/CAD/GeometryEngine.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/DesignOffer.hpp
|
||||||
src/slic3r/GUI/CAD/DesignSketchTool.cpp
|
src/slic3r/GUI/CAD/DesignSketchTool.cpp
|
||||||
src/slic3r/GUI/EncodedFilament.cpp
|
src/slic3r/GUI/EncodedFilament.cpp
|
||||||
|
|||||||
@@ -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",
|
"name": "Bambulab",
|
||||||
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
||||||
"version": "02.08.00.14",
|
"version": "02.08.00.15",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "the initial version of BBL configurations",
|
"description": "the initial version of BBL configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"name": "Goofoo",
|
||||||
|
"version": "02.04.00.01",
|
||||||
|
"force_update": "0",
|
||||||
|
"description": "Goofoo configurations",
|
||||||
|
"machine_model_list": [
|
||||||
|
{
|
||||||
|
"name": "Goofoo GP1",
|
||||||
|
"sub_path": "machine/Goofoo GP1.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo TurboM",
|
||||||
|
"sub_path": "machine/Goofoo TurboM.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+",
|
||||||
|
"sub_path": "machine/Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ Tool",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ Tool.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"process_list": [
|
||||||
|
{
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"sub_path": "process/fdm_process_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fdm_process_klipper_common",
|
||||||
|
"sub_path": "process/fdm_process_klipper_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo GP1.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo TurboM.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.20mm Standard @Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.30mm Standard @Goofoo Velo+.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"sub_path": "process/0.10mm Standard @Goofoo Velo+.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filament_list": [],
|
||||||
|
"machine_list": [
|
||||||
|
{
|
||||||
|
"name": "fdm_machine_common",
|
||||||
|
"sub_path": "machine/fdm_machine_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fdm_klipper_common",
|
||||||
|
"sub_path": "machine/fdm_klipper_common.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo GP1 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo GP1 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo TurboM 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo TurboM 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.2 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.4 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ 0.6 nozzle.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo Velo+ Tool 0.4",
|
||||||
|
"sub_path": "machine/Goofoo Velo+ Tool 0.4.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<g id="Layer_1">
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text fill="#0000ff" x="31.38885" y="400.22266" id="svg_1" font-size="24" font-family="Noto Sans JP" text-anchor="start" xml:space="preserve" stroke="#0000ff" transform="matrix(2.19884 0 0 1.66297 -63.896 -416.238)">GP1</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 373 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="600" height="480" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text transform="matrix(2.3401 0 0 1.462 -92.4668 -168.496)" stroke="#000" stroke-width="3" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="421.25588" x="52.33404" fill="#000000">TurboM</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="480" viewBox="0 0 600 480">
|
||||||
|
<g id="Layer_1">
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<text transform="matrix(2.19884 0 0 1.66297 -203.896 -238.238)" stroke="#0000ff" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="400.22266" x="120.07198" fill="#0000ff">VELO+</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 376 B |
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo GP1 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "x4GJ08PooNDQ8WJj",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"printer_model": "Goofoo GP1",
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n;G92 E0",
|
||||||
|
"change_filament_gcode": "G5",
|
||||||
|
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\n;layer\n;{if layer_num == 2}\n;M106 P1 S255\n;{endif}\nSET_PRINT_STATS_INFO CURRENT_LAYER=[layer_num]\n",
|
||||||
|
"machine_end_gcode": "; End G-code\nEND_PRINT",
|
||||||
|
"machine_pause_gcode": "G5",
|
||||||
|
"machine_start_gcode": "M117 Printing...\nM107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM104 S140\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n;G32 ;Load bed mesh\nG28 ;home\nG29 bed leveling\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG0 Z2.0 F600;\nG0 X50 Y10 F8000;\nG92 E0.0 ; reset extruder distance position\nG0 Z0.4 F600;\nG1 X100.0 E10 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG1 X200.0 E15 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG0 Z0.8 F600;\nG1 X100.0 E15 F3000.0 ; intro line\nG1 Z0.4 F600 ;Wipe\nG0 Y12 F6000 ;Wipe\nG1 X100 F6000 ;Wipe\nG0 Y8 F12000 ;Wipe\nG1 X200 F6000 ;Wipe\nG1 X190 Y12 F6000 ;Wipe\nG1 X180 Y8 F6000 ;Wipe\nG1 X170 Y12 F6000 ;Wipe\nG1 X160 Y8 F6000 ;Wipe\nG1 X150 Y12 F6000 ;Wipe\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position\n;START_PRINT",
|
||||||
|
"purge_in_prime_tower": "0",
|
||||||
|
"time_lapse_gcode": ";TIMELAPSE_TAKE_FRAME",
|
||||||
|
"printable_height": "250",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"250x0",
|
||||||
|
"250x250",
|
||||||
|
"0x250"
|
||||||
|
],
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "140",
|
||||||
|
"extruder_clearance_height_to_rod": "36",
|
||||||
|
"extruder_clearance_radius": "65",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"25",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"12",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo GP1",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-GP1.stl",
|
||||||
|
"bed_texture": "Goofoo-GP1-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V003",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo TurboM 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Wmdn5zTy1HH93Q5d",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"nozzle_type": "brass",
|
||||||
|
"printer_model": "Goofoo TurboM",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"230x0",
|
||||||
|
"230x200",
|
||||||
|
"0x200"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_end_gcode": "M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG92 E0.0 ; reset extruder distance position;\nG1 E-1 F2100 ; retract\nG28 X Y; home X Y axis\n;M84 ; disable motors\nM107 ; turn off fan\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=4000",
|
||||||
|
"machine_max_jerk_z": [
|
||||||
|
"3",
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "M107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\n\n\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG28 ;home\n\nG1 X2 Y10 F3000;\n\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG1 Z0.28 F240\n\nG92 E0\n\nG1 Y140 E10 F1500 ; prime the nozzle\n\nG1 X2.3 F5000\nG92 E0\n\nG1 Y10 E10 F1200 ; prime the nozzle\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position;\nM82\n;SET_PRESSURE_ADVANCE ADVANCE=0.05\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=10000\n",
|
||||||
|
"printable_height": "200",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"25",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"12",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo TurboM",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-TurboM.stl",
|
||||||
|
"bed_texture": "Goofoo-TurboM-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V002",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "wSr6VNECZHekeNRi",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.2"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.2",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.16"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.04"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "XzHz1vNOYg3Ofe8X",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||||
|
"inherits": "fdm_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Pauu7JRq7u2OD6kv",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.6"
|
||||||
|
],
|
||||||
|
"nozzle_type": "hardened_steel",
|
||||||
|
"printer_model": "Goofoo Velo+",
|
||||||
|
"printer_variant": "0.6",
|
||||||
|
"default_filament_profile": [
|
||||||
|
"Goofoo ABS @System"
|
||||||
|
],
|
||||||
|
"default_print_profile": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"600x0",
|
||||||
|
"600x500",
|
||||||
|
"0x500"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"printable_height": "600",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_clearance_height_to_lid": "350",
|
||||||
|
"extruder_clearance_height_to_rod": "60",
|
||||||
|
"extruder_clearance_radius": "50",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"20000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"15000",
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"1",
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"5",
|
||||||
|
"9"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"50",
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"730",
|
||||||
|
"200"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"15",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"thumbnails": "300x300/PNG",
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"10000",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.12"
|
||||||
|
],
|
||||||
|
"upward_compatible_machine": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "Goofoo Velo+ Tool 0.4",
|
||||||
|
"inherits": "Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "ww39WeaF5nYks1em",
|
||||||
|
"instantiation": "true",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4",
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"printer_model": "Goofoo Velo+ Tool",
|
||||||
|
"change_filament_gcode": "T[next_extruder]\n;M104 S[nozzle_temperature_initial_layer] T[next_extruder]; 正式设置喷嘴温度(与切片设置一致)\n;M109 S[nozzle_temperature_initial_layer] T[next_extruder] ; 等待喷嘴温度稳定\n",
|
||||||
|
"default_nozzle_volume_type": [
|
||||||
|
"Standard",
|
||||||
|
"Standard"
|
||||||
|
],
|
||||||
|
"deretraction_speed": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_colour": [
|
||||||
|
"#FCE94F",
|
||||||
|
"#FCE94F"
|
||||||
|
],
|
||||||
|
"extruder_offset": [
|
||||||
|
"0x0",
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"extruder_printable_height": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"extruder_type": [
|
||||||
|
"Direct Drive",
|
||||||
|
"Direct Drive"
|
||||||
|
],
|
||||||
|
"extruder_variant_list": [
|
||||||
|
"Direct Drive Standard",
|
||||||
|
"Direct Drive Standard"
|
||||||
|
],
|
||||||
|
"long_retractions_when_cut": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nT0\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 T0 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nM104 S140 T1\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] T0 ; 正式设置喷嘴温度(与切片设置一致)\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T0; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T0 ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零\nG1 Z20 F240 ; Z轴抬升\nG1 X280 Y10 F3000 ; 移动至起始位置\nT1\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T1; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T1; 等待喷嘴温度稳定\nG4 S10 ; 预热等待10秒\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X282.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.32",
|
||||||
|
"0.32"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.08",
|
||||||
|
"0.08"
|
||||||
|
],
|
||||||
|
"nozzle_flush_dataset": [
|
||||||
|
"nil",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"nozzle_type": [
|
||||||
|
"nil",
|
||||||
|
"hardened_steel"
|
||||||
|
],
|
||||||
|
"nozzle_volume": [
|
||||||
|
"nil",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"printable_area": [
|
||||||
|
"0x0",
|
||||||
|
"300x0",
|
||||||
|
"300x300",
|
||||||
|
"0x300"
|
||||||
|
],
|
||||||
|
"printer_extruder_id": [
|
||||||
|
"1",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"printer_extruder_variant": [
|
||||||
|
"Direct Drive Standard",
|
||||||
|
"Direct Drive Standard"
|
||||||
|
],
|
||||||
|
"purge_in_prime_tower": "0",
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"100%",
|
||||||
|
"100%"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"10",
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"retract_lift_above": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_lift_below": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_lift_enforce": [
|
||||||
|
"All Surfaces",
|
||||||
|
"All Surfaces"
|
||||||
|
],
|
||||||
|
"retract_restart_extra": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra_toolchange": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_when_changing_layer": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retraction_distances_when_cut": [
|
||||||
|
"18",
|
||||||
|
"18"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"0.5",
|
||||||
|
"0.5"
|
||||||
|
],
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"50",
|
||||||
|
"50"
|
||||||
|
],
|
||||||
|
"single_extruder_multi_material": "0",
|
||||||
|
"travel_slope": [
|
||||||
|
"3",
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"wipe": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"wipe_distance": [
|
||||||
|
"1",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"z_hop": [
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"z_hop_types": [
|
||||||
|
"Slope Lift",
|
||||||
|
"Slope Lift"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo Velo+ Tool",
|
||||||
|
"nozzle_diameter": "0.4",
|
||||||
|
"bed_model": "Goofoo-Velo+.stl",
|
||||||
|
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V006",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "machine_model",
|
||||||
|
"name": "Goofoo Velo+",
|
||||||
|
"nozzle_diameter": "0.2;0.4;0.6",
|
||||||
|
"bed_model": "Goofoo-Velo+.stl",
|
||||||
|
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||||
|
"family": "Goofoo",
|
||||||
|
"machine_tech": "FFF",
|
||||||
|
"model_id": "V001",
|
||||||
|
"default_materials": "Goofoo ABS @System"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "fdm_klipper_common",
|
||||||
|
"inherits": "fdm_machine_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"gcode_flavor": "klipper",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\n\n",
|
||||||
|
"machine_start_gcode": "G90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM140 S{first_layer_bed_temperature[0]} ; set bed temp\nM104 S140 ; set temporary nozzle temp to prevent oozing\nG4 S10 ; wait 10s for preheating\nG28 ; home all axes\nG1 Z50 F240 ; raise Z axis\nG1 X2 Y10 F3000 ; move to start position\nM104 S{first_layer_temperature[0]} ; set final nozzle temp\nM190 S{first_layer_bed_temperature[0]} ; wait for bed temp\nM109 S{first_layer_temperature[0]} ; wait for nozzle temp\nG1 Z0.28 F240 ; lower nozzle to print height\nG92 E0 ; reset extrusion distance\nG1 Y140 E10 F1500 ; prime the nozzle\nG1 X2.3 F5000 ; slight X adjustment\nG92 E0 ; reset extrusion distance\nG1 Y10 E10 F1200 ; prime again\nG92 E0 ; final extrusion reset",
|
||||||
|
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||||
|
"change_filament_gcode": "PAUSE",
|
||||||
|
"machine_pause_gcode": "PAUSE"
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
{
|
||||||
|
"type": "machine",
|
||||||
|
"name": "fdm_machine_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"printer_technology": "FFF",
|
||||||
|
"deretraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"extruder_colour": [
|
||||||
|
"#FCE94F"
|
||||||
|
],
|
||||||
|
"extruder_offset": [
|
||||||
|
"0x0"
|
||||||
|
],
|
||||||
|
"gcode_flavor": "marlin",
|
||||||
|
"machine_max_acceleration_e": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_extruding": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"1000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_x": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_y": [
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_z": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"machine_min_extruding_rate": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"machine_min_travel_rate": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"max_layer_height": [
|
||||||
|
"0.32"
|
||||||
|
],
|
||||||
|
"min_layer_height": [
|
||||||
|
"0.08"
|
||||||
|
],
|
||||||
|
"printable_height": "250",
|
||||||
|
"extruder_clearance_radius": "65",
|
||||||
|
"extruder_clearance_height_to_rod": "36",
|
||||||
|
"extruder_clearance_height_to_lid": "140",
|
||||||
|
"nozzle_diameter": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"printer_settings_id": "",
|
||||||
|
"printer_variant": "0.4",
|
||||||
|
"retraction_minimum_travel": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retract_before_wipe": [
|
||||||
|
"70%"
|
||||||
|
],
|
||||||
|
"retract_when_changing_layer": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"retract_length_toolchange": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"z_hop": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retract_restart_extra_toolchange": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"single_extruder_multi_material": "1",
|
||||||
|
"change_filament_gcode": "",
|
||||||
|
"wipe": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"default_print_profile": "",
|
||||||
|
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
|
||||||
|
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
|
||||||
|
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||||
|
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||||
|
"machine_pause_gcode": "M601"
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.10mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "dJV4bTrLbOPzW2YB",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.2 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.1",
|
||||||
|
"initial_layer_print_height": "0.1",
|
||||||
|
"bottom_shell_layers": "6",
|
||||||
|
"top_shell_layers": "12",
|
||||||
|
"wall_loops": "4",
|
||||||
|
"line_width": "0.22",
|
||||||
|
"initial_layer_line_width": "0.25",
|
||||||
|
"inner_wall_line_width": "0.22",
|
||||||
|
"outer_wall_line_width": "0.22",
|
||||||
|
"sparse_infill_line_width": "0.22",
|
||||||
|
"internal_solid_infill_line_width": "0.22",
|
||||||
|
"top_surface_line_width": "0.22",
|
||||||
|
"support_line_width": "0.22",
|
||||||
|
"support_top_z_distance": "0.1",
|
||||||
|
"support_bottom_z_distance": "0.1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo GP1",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "zrscQBUxp6L7BlQ9",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo GP1 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"accel_to_decel_enable": "0",
|
||||||
|
"bridge_acceleration": "100%",
|
||||||
|
"bridge_speed": "100",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"gap_infill_speed": "250",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_infill_speed": "100",
|
||||||
|
"initial_layer_speed": "70",
|
||||||
|
"inner_wall_speed": "250",
|
||||||
|
"internal_bridge_speed": "200%",
|
||||||
|
"internal_solid_infill_speed": "250",
|
||||||
|
"outer_wall_acceleration": "5000",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"overhang_4_4_speed": "0",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "1",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "250",
|
||||||
|
"top_surface_acceleration": "5000",
|
||||||
|
"top_surface_speed": "200",
|
||||||
|
"travel_acceleration": "5000"
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo TurboM",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "LVXxUtAcYGCY6irA",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo TurboM 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"bridge_acceleration": "2000",
|
||||||
|
"bridge_speed": "60",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"default_acceleration": "10000",
|
||||||
|
"enable_support": "1",
|
||||||
|
"exclude_object": "0",
|
||||||
|
"gap_infill_speed": "200",
|
||||||
|
"infill_anchor": "600%",
|
||||||
|
"infill_combination": "1",
|
||||||
|
"infill_wall_overlap": "15%",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_line_width": "0.45",
|
||||||
|
"initial_layer_speed": "60",
|
||||||
|
"inner_wall_acceleration": "3000",
|
||||||
|
"inner_wall_line_width": "0.42",
|
||||||
|
"inner_wall_speed": "150",
|
||||||
|
"internal_solid_infill_acceleration": "3000",
|
||||||
|
"internal_solid_infill_line_width": "0.42",
|
||||||
|
"internal_solid_infill_speed": "150",
|
||||||
|
"line_width": "0.42",
|
||||||
|
"min_skirt_length": "0",
|
||||||
|
"minimum_sparse_infill_area": "70",
|
||||||
|
"only_one_wall_top": "1",
|
||||||
|
"outer_wall_acceleration": "2500",
|
||||||
|
"outer_wall_line_width": "0.42",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "4",
|
||||||
|
"small_perimeter_speed": "200",
|
||||||
|
"sparse_infill_acceleration": "3000",
|
||||||
|
"sparse_infill_line_width": "0.42",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "150",
|
||||||
|
"support_base_pattern": "rectilinear",
|
||||||
|
"support_interface_bottom_layers": "-1",
|
||||||
|
"support_interface_pattern": "rectilinear",
|
||||||
|
"support_interface_spacing": "0",
|
||||||
|
"support_interface_speed": "100",
|
||||||
|
"support_interface_top_layers": "3",
|
||||||
|
"support_line_width": "0.35",
|
||||||
|
"support_object_xy_distance": "0.21",
|
||||||
|
"support_speed": "100",
|
||||||
|
"thick_bridges": "1",
|
||||||
|
"top_shell_layers": "6",
|
||||||
|
"top_shell_thickness": "0",
|
||||||
|
"top_surface_acceleration": "2500",
|
||||||
|
"top_surface_line_width": "0.42",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"travel_acceleration": "10000",
|
||||||
|
"travel_speed": "200",
|
||||||
|
"wall_generator": "classic",
|
||||||
|
"wall_loops": "2",
|
||||||
|
"wall_sequence": "outer wall/inner wall"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.20mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "iWjynhCcvbF8MWev",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.4 nozzle",
|
||||||
|
"Goofoo Velo+ Tool 0.4"
|
||||||
|
],
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"initial_layer_print_height": "0.2",
|
||||||
|
"bottom_shell_layers": "3",
|
||||||
|
"top_shell_layers": "6",
|
||||||
|
"bridge_acceleration": "2000",
|
||||||
|
"bridge_speed": "60",
|
||||||
|
"brim_type": "no_brim",
|
||||||
|
"default_acceleration": "10000",
|
||||||
|
"enable_support": "1",
|
||||||
|
"exclude_object": "0",
|
||||||
|
"gap_infill_speed": "200",
|
||||||
|
"infill_anchor": "600%",
|
||||||
|
"infill_combination": "1",
|
||||||
|
"infill_wall_overlap": "15%",
|
||||||
|
"initial_layer_acceleration": "1000",
|
||||||
|
"initial_layer_line_width": "0.45",
|
||||||
|
"initial_layer_speed": "60",
|
||||||
|
"inner_wall_acceleration": "3000",
|
||||||
|
"inner_wall_line_width": "0.42",
|
||||||
|
"inner_wall_speed": "150",
|
||||||
|
"internal_solid_infill_acceleration": "3000",
|
||||||
|
"internal_solid_infill_line_width": "0.42",
|
||||||
|
"internal_solid_infill_speed": "150",
|
||||||
|
"line_width": "0.42",
|
||||||
|
"min_skirt_length": "0",
|
||||||
|
"minimum_sparse_infill_area": "70",
|
||||||
|
"only_one_wall_top": "1",
|
||||||
|
"outer_wall_acceleration": "2500",
|
||||||
|
"outer_wall_line_width": "0.42",
|
||||||
|
"outer_wall_speed": "100",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"skirt_distance": "6",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"skirt_loops": "4",
|
||||||
|
"small_perimeter_speed": "200",
|
||||||
|
"sparse_infill_acceleration": "3000",
|
||||||
|
"sparse_infill_line_width": "0.42",
|
||||||
|
"sparse_infill_pattern": "grid",
|
||||||
|
"sparse_infill_speed": "150",
|
||||||
|
"support_base_pattern": "rectilinear",
|
||||||
|
"support_interface_bottom_layers": "-1",
|
||||||
|
"support_interface_pattern": "rectilinear",
|
||||||
|
"support_interface_spacing": "0",
|
||||||
|
"support_interface_speed": "100",
|
||||||
|
"support_interface_top_layers": "3",
|
||||||
|
"support_line_width": "0.35",
|
||||||
|
"support_object_xy_distance": "0.21",
|
||||||
|
"support_speed": "100",
|
||||||
|
"thick_bridges": "1",
|
||||||
|
"top_shell_thickness": "0",
|
||||||
|
"top_surface_acceleration": "2500",
|
||||||
|
"top_surface_line_width": "0.42",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"travel_acceleration": "10000",
|
||||||
|
"travel_speed": "300",
|
||||||
|
"wall_generator": "classic",
|
||||||
|
"wall_loops": "2",
|
||||||
|
"wall_sequence": "outer wall/inner wall"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "0.30mm Standard @Goofoo Velo+",
|
||||||
|
"inherits": "fdm_process_klipper_common",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "mi8ogYOYpqb2x60z",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"Goofoo Velo+ 0.6 nozzle"
|
||||||
|
],
|
||||||
|
"initial_layer_print_height": "0.3",
|
||||||
|
"layer_height": "0.3"
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"seam_position": "aligned",
|
||||||
|
"resolution": "0.012",
|
||||||
|
"elefant_foot_compensation": "0.2",
|
||||||
|
"precise_outer_wall": "1",
|
||||||
|
"ironing_type": "no ironing",
|
||||||
|
"ironing_speed": "30",
|
||||||
|
"ironing_flow": "10%",
|
||||||
|
"ironing_spacing": "0.15",
|
||||||
|
"reduce_crossing_wall": "1",
|
||||||
|
"max_travel_detour_distance": "0",
|
||||||
|
"detect_overhang_wall": "1",
|
||||||
|
"wall_loops": "3",
|
||||||
|
"top_surface_pattern": "monotonicline",
|
||||||
|
"top_shell_layers": "5",
|
||||||
|
"top_shell_thickness": "0.7",
|
||||||
|
"bottom_surface_pattern": "monotonic",
|
||||||
|
"bottom_shell_layers": "4",
|
||||||
|
"bottom_shell_thickness": "0.5",
|
||||||
|
"sparse_infill_density": "15%",
|
||||||
|
"sparse_infill_pattern": "crosshatch",
|
||||||
|
"infill_wall_overlap": "23%",
|
||||||
|
"infill_direction": "45",
|
||||||
|
"minimum_sparse_infill_area": "15",
|
||||||
|
"infill_combination": "0",
|
||||||
|
"ensure_vertical_shell_thickness": "1",
|
||||||
|
"initial_layer_speed": "50",
|
||||||
|
"initial_layer_infill_speed": "50",
|
||||||
|
"initial_layer_travel_speed": "100%",
|
||||||
|
"outer_wall_speed": "125",
|
||||||
|
"inner_wall_speed": "250",
|
||||||
|
"sparse_infill_speed": "250",
|
||||||
|
"internal_solid_infill_speed": "200",
|
||||||
|
"top_surface_speed": "150",
|
||||||
|
"gap_infill_speed": "150",
|
||||||
|
"support_speed": "125",
|
||||||
|
"support_interface_speed": "80",
|
||||||
|
"enable_overhang_speed": "1",
|
||||||
|
"overhang_1_4_speed": "0",
|
||||||
|
"overhang_2_4_speed": "50",
|
||||||
|
"overhang_3_4_speed": "25",
|
||||||
|
"overhang_4_4_speed": "15",
|
||||||
|
"bridge_speed": "50",
|
||||||
|
"travel_speed": "300",
|
||||||
|
"default_acceleration": "2500",
|
||||||
|
"outer_wall_acceleration": "500",
|
||||||
|
"inner_wall_acceleration": "500",
|
||||||
|
"bridge_acceleration": "500",
|
||||||
|
"travel_acceleration": "2500",
|
||||||
|
"default_jerk": "15",
|
||||||
|
"outer_wall_jerk": "10",
|
||||||
|
"inner_wall_jerk": "15",
|
||||||
|
"infill_jerk": "15",
|
||||||
|
"top_surface_jerk": "12",
|
||||||
|
"initial_layer_jerk": "12",
|
||||||
|
"travel_jerk": "20",
|
||||||
|
"enable_support": "0",
|
||||||
|
"support_type": "normal(auto)",
|
||||||
|
"support_threshold_angle": "30",
|
||||||
|
"support_on_build_plate_only": "0",
|
||||||
|
"raft_layers": "0",
|
||||||
|
"support_filament": "0",
|
||||||
|
"support_interface_filament": "0",
|
||||||
|
"support_top_z_distance": "0.2",
|
||||||
|
"support_base_pattern": "default",
|
||||||
|
"support_base_pattern_spacing": "2.5",
|
||||||
|
"support_interface_top_layers": "2",
|
||||||
|
"support_interface_bottom_layers": "2",
|
||||||
|
"support_interface_spacing": "0.5",
|
||||||
|
"support_object_xy_distance": "0.5",
|
||||||
|
"bridge_no_support": "0",
|
||||||
|
"independent_support_layer_height": "1",
|
||||||
|
"tree_support_branch_angle": "30",
|
||||||
|
"tree_support_wall_count": "0",
|
||||||
|
"skirt_loops": "2",
|
||||||
|
"skirt_distance": "3",
|
||||||
|
"skirt_height": "1",
|
||||||
|
"brim_width": "5",
|
||||||
|
"brim_object_gap": "0.1",
|
||||||
|
"enable_prime_tower": "0",
|
||||||
|
"prime_tower_width": "60",
|
||||||
|
"wipe_tower_no_sparse_layers": "0",
|
||||||
|
"print_sequence": "by layer",
|
||||||
|
"spiral_mode": "0",
|
||||||
|
"reduce_infill_retraction": "1",
|
||||||
|
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_klipper_common",
|
||||||
|
"inherits": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"default_acceleration": "5000",
|
||||||
|
"top_surface_acceleration": "3000",
|
||||||
|
"travel_acceleration": "7000",
|
||||||
|
"inner_wall_acceleration": "5000",
|
||||||
|
"outer_wall_acceleration": "3000",
|
||||||
|
"initial_layer_acceleration": "500",
|
||||||
|
"initial_layer_speed": "50",
|
||||||
|
"initial_layer_infill_speed": "105",
|
||||||
|
"outer_wall_speed": "120",
|
||||||
|
"inner_wall_speed": "200",
|
||||||
|
"internal_solid_infill_speed": "200",
|
||||||
|
"top_surface_speed": "100",
|
||||||
|
"gap_infill_speed": "100",
|
||||||
|
"sparse_infill_speed": "200",
|
||||||
|
"travel_speed": "350",
|
||||||
|
"exclude_object": "1"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "OrcaFilamentLibrary",
|
"name": "OrcaFilamentLibrary",
|
||||||
"version": "02.04.00.15",
|
"version": "02.04.00.16",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Orca Filament Library",
|
"description": "Orca Filament Library",
|
||||||
"filament_list": [
|
"filament_list": [
|
||||||
@@ -1396,6 +1396,10 @@
|
|||||||
"name": "addnorth ABS rABS",
|
"name": "addnorth ABS rABS",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth ABS rABS.json"
|
"sub_path": "filament/addnorth/BBL/addnorth ABS rABS.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ABS @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ABS @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NIT ABS @System",
|
"name": "NIT ABS @System",
|
||||||
"sub_path": "filament/NIT/NIT ABS @System.json"
|
"sub_path": "filament/NIT/NIT ABS @System.json"
|
||||||
@@ -1480,6 +1484,10 @@
|
|||||||
"name": "Elegoo ASA-CF @base",
|
"name": "Elegoo ASA-CF @base",
|
||||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @base.json"
|
"sub_path": "filament/Elegoo/Elegoo ASA-CF @base.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ASA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ASA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Overture ASA @BBL X1",
|
"name": "Overture ASA @BBL X1",
|
||||||
"sub_path": "filament/Overture/BBL/Overture ASA @BBL X1.json"
|
"sub_path": "filament/Overture/BBL/Overture ASA @BBL X1.json"
|
||||||
@@ -1636,10 +1644,22 @@
|
|||||||
"name": "addnorth PVDF Adamant S1",
|
"name": "addnorth PVDF Adamant S1",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PVDF Adamant S1.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PVDF Adamant S1.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PA-CF Adura X",
|
"name": "addnorth PA-CF Adura X",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PA-CF Adura X.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PA-CF Adura X.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-GF10 @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INSLOGIC PA6-66 @BBL P2S",
|
"name": "INSLOGIC PA6-66 @BBL P2S",
|
||||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
||||||
@@ -1712,6 +1732,10 @@
|
|||||||
"name": "COEX PCTG PRIME @System",
|
"name": "COEX PCTG PRIME @System",
|
||||||
"sub_path": "filament/COEX/COEX PCTG PRIME @System.json"
|
"sub_path": "filament/COEX/COEX PCTG PRIME @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PE-CF @System",
|
"name": "Generic PE-CF @System",
|
||||||
"sub_path": "filament/Generic PE-CF @System.json"
|
"sub_path": "filament/Generic PE-CF @System.json"
|
||||||
@@ -2176,6 +2200,10 @@
|
|||||||
"name": "addnorth PETG rPETG Matte",
|
"name": "addnorth PETG rPETG Matte",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PETG rPETG Matte.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PETG rPETG Matte.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PETG HF @System",
|
"name": "Generic PETG HF @System",
|
||||||
"sub_path": "filament/Generic PETG HF @System.json"
|
"sub_path": "filament/Generic PETG HF @System.json"
|
||||||
@@ -2236,6 +2264,10 @@
|
|||||||
"name": "eSUN PETG @System",
|
"name": "eSUN PETG @System",
|
||||||
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PHA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PHA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AliZ PLA @P1-X1",
|
"name": "AliZ PLA @P1-X1",
|
||||||
"sub_path": "filament/AliZ/BBL/AliZ PLA @P1-X1.json"
|
"sub_path": "filament/AliZ/BBL/AliZ PLA @P1-X1.json"
|
||||||
@@ -3120,6 +3152,10 @@
|
|||||||
"name": "addnorth PLA rPLA RE-ADD",
|
"name": "addnorth PLA rPLA RE-ADD",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA rPLA RE-ADD.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA rPLA RE-ADD.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PLA X-PLA High Speed",
|
"name": "addnorth PLA X-PLA High Speed",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA X-PLA High Speed.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA X-PLA High Speed.json"
|
||||||
@@ -3136,6 +3172,10 @@
|
|||||||
"name": "addnorth PLA-CF Carbon Fiber",
|
"name": "addnorth PLA-CF Carbon Fiber",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PLA-CF Carbon Fiber.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PLA-CF Carbon Fiber.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA-CF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INSLOGIC PLA Pro @BBL P2S",
|
"name": "INSLOGIC PLA Pro @BBL P2S",
|
||||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
||||||
@@ -3820,6 +3860,10 @@
|
|||||||
"name": "FILL3D PPCF @System",
|
"name": "FILL3D PPCF @System",
|
||||||
"sub_path": "filament/FILL3D/FILL3D PPCF @System.json"
|
"sub_path": "filament/FILL3D/FILL3D PPCF @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic PP-CF @System",
|
"name": "Generic PP-CF @System",
|
||||||
"sub_path": "filament/Generic PP-CF @System.json"
|
"sub_path": "filament/Generic PP-CF @System.json"
|
||||||
@@ -3840,6 +3884,10 @@
|
|||||||
"name": "Bambu PVA @System",
|
"name": "Bambu PVA @System",
|
||||||
"sub_path": "filament/Bambu/Bambu PVA @System.json"
|
"sub_path": "filament/Bambu/Bambu PVA @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PVA @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PVA @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FDplast SBS @System",
|
"name": "FDplast SBS @System",
|
||||||
"sub_path": "filament/FDplast/FDplast SBS @System.json"
|
"sub_path": "filament/FDplast/FDplast SBS @System.json"
|
||||||
@@ -4112,6 +4160,14 @@
|
|||||||
"name": "addnorth TPU Pro Matte 95A",
|
"name": "addnorth TPU Pro Matte 95A",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth TPU Pro Matte 95A.json"
|
"sub_path": "filament/addnorth/BBL/addnorth TPU Pro Matte 95A.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 90A TPU @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 90A TPU @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 95A TPU @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 95A TPU @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Overture TPU @BBL A1",
|
"name": "Overture TPU @BBL A1",
|
||||||
"sub_path": "filament/Overture/BBL/Overture TPU @BBL A1.json"
|
"sub_path": "filament/Overture/BBL/Overture TPU @BBL A1.json"
|
||||||
@@ -4156,10 +4212,30 @@
|
|||||||
"name": "Overture TPU @System",
|
"name": "Overture TPU @System",
|
||||||
"sub_path": "filament/Overture/Overture TPU @System.json"
|
"sub_path": "filament/Overture/Overture TPU @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ABS @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ABS @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo ASA-CF @System",
|
"name": "Elegoo ASA-CF @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @System.json"
|
"sub_path": "filament/Elegoo/Elegoo ASA-CF @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo ASA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo ASA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PA-GF10 @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo PC-FR @System",
|
"name": "Elegoo PC-FR @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo PC-FR @System.json"
|
"sub_path": "filament/Elegoo/Elegoo PC-FR @System.json"
|
||||||
@@ -4176,6 +4252,14 @@
|
|||||||
"name": "COEX PCTG PRIME @BBL A1M 0.8 nozzle",
|
"name": "COEX PCTG PRIME @BBL A1M 0.8 nozzle",
|
||||||
"sub_path": "filament/COEX/BBL/COEX PCTG PRIME @BBL A1M 0.8 nozzle.json"
|
"sub_path": "filament/COEX/BBL/COEX PCTG PRIME @BBL A1M 0.8 nozzle.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE-CF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AliZ PETG-CF @P1-X1",
|
"name": "AliZ PETG-CF @P1-X1",
|
||||||
"sub_path": "filament/AliZ/BBL/AliZ PETG-CF @P1-X1.json"
|
"sub_path": "filament/AliZ/BBL/AliZ PETG-CF @P1-X1.json"
|
||||||
@@ -4236,10 +4320,26 @@
|
|||||||
"name": "Fiberon PETG-ESD @BBL X1",
|
"name": "Fiberon PETG-ESD @BBL X1",
|
||||||
"sub_path": "filament/Polymaker/BBL/Fiberon PETG-ESD @BBL X1.json"
|
"sub_path": "filament/Polymaker/BBL/Fiberon PETG-ESD @BBL X1.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG HF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG HF @base.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addnorth PETG-CF Rigid X",
|
"name": "addnorth PETG-CF Rigid X",
|
||||||
"sub_path": "filament/addnorth/BBL/addnorth PETG-CF Rigid X.json"
|
"sub_path": "filament/addnorth/BBL/addnorth PETG-CF Rigid X.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PHA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PHA @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "COEX PLA @BBL A1M 0.2 nozzle",
|
"name": "COEX PLA @BBL A1M 0.2 nozzle",
|
||||||
"sub_path": "filament/COEX/BBL/COEX PLA @BBL A1M 0.2 nozzle.json"
|
"sub_path": "filament/COEX/BBL/COEX PLA @BBL A1M 0.2 nozzle.json"
|
||||||
@@ -4304,6 +4404,14 @@
|
|||||||
"name": "Elegoo Rapid PLA+ @System",
|
"name": "Elegoo Rapid PLA+ @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo Rapid PLA+ @System.json"
|
"sub_path": "filament/Elegoo/Elegoo Rapid PLA+ @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA-CF @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Numakers PLA+ @BBL A1",
|
"name": "Numakers PLA+ @BBL A1",
|
||||||
"sub_path": "filament/Numakers/BBL/Numakers PLA+ @BBL A1.json"
|
"sub_path": "filament/Numakers/BBL/Numakers PLA+ @BBL A1.json"
|
||||||
@@ -5188,9 +5296,61 @@
|
|||||||
"name": "COEX PLA+Silk @System",
|
"name": "COEX PLA+Silk @System",
|
||||||
"sub_path": "filament/COEX/COEX PLA+Silk @System.json"
|
"sub_path": "filament/COEX/COEX PLA+Silk @System.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA Silk @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA Silk @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-CF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-CF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-GF @base",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-GF @base.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PVA @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PVA @System.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Elegoo Rapid TPU 95A @System",
|
"name": "Elegoo Rapid TPU 95A @System",
|
||||||
"sub_path": "filament/Elegoo/Elegoo Rapid TPU 95A @System.json"
|
"sub_path": "filament/Elegoo/Elegoo Rapid TPU 95A @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 90A TPU @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 90A TPU @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo 95A TPU @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo 95A TPU @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PE-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PE-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG HF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG HF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PETG-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PETG-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PLA Silk @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PLA Silk @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-CF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-CF @System.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Goofoo PP-GF @System",
|
||||||
|
"sub_path": "filament/Goofoo/Goofoo PP-GF @System.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"process_list": [],
|
"process_list": [],
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 90A TPU @System",
|
||||||
|
"inherits": "Goofoo 90A TPU @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "tjq76AQe9SCzZVo6",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 90A TPU @base",
|
||||||
|
"inherits": "Generic TPU @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFlVOXX3",
|
||||||
|
"instantiation": "false",
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"TPU"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 95A TPU @System",
|
||||||
|
"inherits": "Goofoo 95A TPU @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "2YTvrE1gu1yHHTNM",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo 95A TPU @base",
|
||||||
|
"inherits": "Generic TPU @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFk50t30",
|
||||||
|
"instantiation": "false",
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"TPU"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ABS @System",
|
||||||
|
"inherits": "Goofoo ABS @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "UeINgPGH4eaSJinC",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ABS @base",
|
||||||
|
"inherits": "Generic ABS @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFd3PmW4",
|
||||||
|
"instantiation": "false",
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"110"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"110"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"ABS"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ASA @System",
|
||||||
|
"inherits": "Goofoo ASA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "9ohqNl44uygTPfS9",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo ASA @base",
|
||||||
|
"inherits": "Generic ASA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFyncHIG",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.93"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"ASA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA @System",
|
||||||
|
"inherits": "Goofoo PA @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "1ximJVgZZfGmGyzD",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA @base",
|
||||||
|
"inherits": "Generic PA @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFc2x7Ly",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-CF @System",
|
||||||
|
"inherits": "Goofoo PA-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "US4zznMNti8tLUHG",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-CF @base",
|
||||||
|
"inherits": "Generic PA-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF7skcRj",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-GF10 @System",
|
||||||
|
"inherits": "Goofoo PA-GF10 @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "Lea3hf2lQG0M9eTC",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PA-GF10 @base",
|
||||||
|
"inherits": "Generic PA-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFtjqw2h",
|
||||||
|
"instantiation": "false",
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"230"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PA-GF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE @System",
|
||||||
|
"inherits": "Goofoo PE @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "muNxvgr0vdavMaOJ",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE @base",
|
||||||
|
"inherits": "Generic PE @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFAln723",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PE"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE-CF @System",
|
||||||
|
"inherits": "Goofoo PE-CF @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "58fMbg6AClvalw5o",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PE-CF @base",
|
||||||
|
"inherits": "Generic PE-CF @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OF4Ll1Sk",
|
||||||
|
"instantiation": "false",
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PE-CF"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG @System",
|
||||||
|
"inherits": "Goofoo PETG @base",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "a9OKgJeoJgzfZiAB",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Goofoo PETG @base",
|
||||||
|
"inherits": "Generic PETG @System",
|
||||||
|
"from": "system",
|
||||||
|
"filament_id": "OFHdIbiu",
|
||||||
|
"instantiation": "false",
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Goofoo"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG"
|
||||||
|
]
|
||||||
|
}
|
||||||