mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-19 13:41:03 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
722af0df61 | ||
|
|
b9a5556724 |
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: orca-translations
|
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 developer_reference/glossary), 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.
|
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
|
# OrcaSlicer translations
|
||||||
@@ -9,7 +9,7 @@ The UI strings live in gettext catalogs: `localization/i18n/OrcaSlicer.pot` is t
|
|||||||
`localization/i18n/<lang>/OrcaSlicer_<lang>.po` holds each language. The `en` catalog is empty on
|
`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
|
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),
|
[localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md),
|
||||||
whose source is the per-language files in `developer_reference/glossary/` (`terms.tsv`, `languages.txt` and one `<lang>.tsv`) in the **OrcaSlicer_WIKI** repository.
|
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.
|
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).
|
All catalog editing goes through [`scripts/po_tool.py`](scripts/po_tool.py) (standard library only).
|
||||||
@@ -153,7 +153,7 @@ Report:
|
|||||||
- anything left empty, and why;
|
- anything left empty, and why;
|
||||||
- the glossary rows added or filled.
|
- the glossary rows added or filled.
|
||||||
|
|
||||||
The catalogs (and `OrcaSlicer.pot`) and the glossary (`developer_reference/glossary/*.tsv` and `localization_glossary.md`) are
|
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
|
separate changes in separate repositories. The wiki change should land with, or before, the catalog
|
||||||
change. Commit or open PRs only when asked.
|
change. Commit or open PRs only when asked.
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ with the translators, followed by the catalogs.
|
|||||||
When this skill and the checkouts disagree, the checkouts win:
|
When this skill and the checkouts disagree, the checkouts win:
|
||||||
- `scripts/run_gettext.sh` / `.bat` for extraction and merging;
|
- `scripts/run_gettext.sh` / `.bat` for extraction and merging;
|
||||||
- `localization/i18n/list.txt` for the scanned sources;
|
- `localization/i18n/list.txt` for the scanned sources;
|
||||||
- the wiki's `localization_guide.md` and `localization_glossary.md` and `glossary/*.tsv` for the rules and terms;
|
- 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.
|
- `AGENTS.md` (Localization & translations) for the repository's conventions.
|
||||||
|
|
||||||
## Editing this skill
|
## Editing this skill
|
||||||
|
|||||||
@@ -28,14 +28,13 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import urllib.error
|
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
REPO = Path(__file__).resolve().parents[4]
|
REPO = Path(__file__).resolve().parents[4]
|
||||||
I18N = REPO / "localization" / "i18n"
|
I18N = REPO / "localization" / "i18n"
|
||||||
SKIP_LANGS = {"en"} # the English catalog is intentionally empty
|
SKIP_LANGS = {"en"} # the English catalog is intentionally empty
|
||||||
GLOSSARY_DIR = Path("developer_reference") / "glossary" # terms.tsv, languages.txt and one <lang>.tsv per language
|
GLOSSARY_TSV = Path("developer_reference") / "localization_glossary.tsv"
|
||||||
GLOSSARY_MD = Path("developer_reference") / "localization_glossary.md"
|
GLOSSARY_MD = Path("developer_reference") / "localization_glossary.md"
|
||||||
MISSING = {"", "—"}
|
MISSING = {"", "—"}
|
||||||
RAW_URL = "https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer_WIKI/main/"
|
RAW_URL = "https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer_WIKI/main/"
|
||||||
@@ -253,40 +252,30 @@ def all_langs():
|
|||||||
|
|
||||||
def find_wiki(arg):
|
def find_wiki(arg):
|
||||||
for cand in [arg, os.environ.get("ORCA_WIKI_DIR"), REPO.parent / "OrcaSlicer_WIKI", REPO.parent / "OrcaSlicer.wiki"]:
|
for cand in [arg, os.environ.get("ORCA_WIKI_DIR"), REPO.parent / "OrcaSlicer_WIKI", REPO.parent / "OrcaSlicer.wiki"]:
|
||||||
if cand and (Path(cand) / GLOSSARY_DIR / "terms.tsv").exists():
|
if cand and (Path(cand) / GLOSSARY_TSV).exists():
|
||||||
return Path(cand)
|
return Path(cand)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _download(rel, required=True):
|
def _download(rel):
|
||||||
cache = Path(tempfile.gettempdir()) / "orca_glossary" / rel
|
cache = Path(tempfile.gettempdir()) / ("orca_glossary_" + rel.name)
|
||||||
if not cache.exists() or time.time() - cache.stat().st_mtime > 3600:
|
if not cache.exists() or time.time() - cache.stat().st_mtime > 3600:
|
||||||
try:
|
with urllib.request.urlopen(RAW_URL + rel.as_posix(), timeout=30) as r:
|
||||||
with urllib.request.urlopen(RAW_URL + rel.as_posix(), timeout=30) as r:
|
cache.write_bytes(r.read())
|
||||||
data = r.read()
|
|
||||||
except urllib.error.HTTPError:
|
|
||||||
if required:
|
|
||||||
raise
|
|
||||||
return None
|
|
||||||
cache.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
cache.write_bytes(data)
|
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
|
|
||||||
class Glossary:
|
class Glossary:
|
||||||
"""The glossary: rows of [English, Description, <one translation per language loaded>]."""
|
def __init__(self, wiki_arg=None, quiet=False):
|
||||||
|
|
||||||
def __init__(self, wiki_arg=None, quiet=False, langs=None):
|
|
||||||
self.wiki = find_wiki(wiki_arg)
|
self.wiki = find_wiki(wiki_arg)
|
||||||
self.langs = langs # only these languages' files are read (and downloaded); None means all
|
|
||||||
if self.wiki:
|
if self.wiki:
|
||||||
self.dir, self.md = self.wiki / GLOSSARY_DIR, self.wiki / GLOSSARY_MD
|
self.tsv, self.md = self.wiki / GLOSSARY_TSV, self.wiki / GLOSSARY_MD
|
||||||
if not quiet:
|
if not quiet:
|
||||||
self.report_clone_state()
|
self.report_clone_state()
|
||||||
else:
|
else:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print("note: no OrcaSlicer_WIKI clone found; using the published glossary read-only", file=sys.stderr)
|
print("note: no OrcaSlicer_WIKI clone found; using the published glossary read-only", file=sys.stderr)
|
||||||
self.dir, self.md = _download(GLOSSARY_DIR / "terms.tsv").parent, _download(GLOSSARY_MD)
|
self.tsv, self.md = _download(GLOSSARY_TSV), _download(GLOSSARY_MD)
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def report_clone_state(self):
|
def report_clone_state(self):
|
||||||
@@ -301,23 +290,11 @@ class Glossary:
|
|||||||
if behind and behind != "0":
|
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)
|
print(f"note: the wiki clone is {behind} commits behind origin/main (as of its last fetch)", file=sys.stderr)
|
||||||
|
|
||||||
def _read(self, name, required=True):
|
|
||||||
path = self.dir / name if self.wiki else _download(GLOSSARY_DIR / name, required)
|
|
||||||
return path.read_text(encoding="utf-8-sig").splitlines() if path and path.exists() else None
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
terms = [l.split(" ") for l in self._read("terms.tsv")[1:] if l.strip()]
|
rows = [l.split("\t") for l in self.tsv.read_text(encoding="utf-8-sig").splitlines() if l.strip()]
|
||||||
langs = [l.strip() for l in self._read("languages.txt") if l.strip()]
|
self.header = rows[0]
|
||||||
langs = [l for l in langs if self.langs is None or l in self.langs]
|
# "—" marks a language with no established rendering yet
|
||||||
self.header = ["English", "Description"] + langs
|
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.rows = [[r[0], r[1] if len(r) > 1 else ""] + [""] * len(langs) for r in terms]
|
|
||||||
index = {r[0]: r for r in self.rows}
|
|
||||||
for col, lang in enumerate(langs, start=2):
|
|
||||||
for line in (self._read(f"{lang}.tsv", required=False) or [])[1:]:
|
|
||||||
cells = line.split(" ")
|
|
||||||
# "—" marks a language with no established rendering yet
|
|
||||||
if len(cells) > 2 and cells[0] in index and cells[2].strip() not in MISSING:
|
|
||||||
index[cells[0]][col] = cells[2].strip()
|
|
||||||
self.matchers = []
|
self.matchers = []
|
||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
base = re.sub(r"\s*\([^)]*\)\s*$", "", row[0]).strip()
|
base = re.sub(r"\s*\([^)]*\)\s*$", "", row[0]).strip()
|
||||||
@@ -517,7 +494,7 @@ def similar_entries(c, e, pool, cutoff=0.72):
|
|||||||
|
|
||||||
def cmd_pending(a):
|
def cmd_pending(a):
|
||||||
c = Catalog(catalog_path(a.lang))
|
c = Catalog(catalog_path(a.lang))
|
||||||
g = Glossary(a.wiki, langs=[a.lang])
|
g = Glossary(a.wiki)
|
||||||
todo = [e for e in c.active() if needs_translation(e, c.nplurals, a.include_fuzzy)]
|
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:]
|
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
|
pool = [(x.msgid, x.msgstrs()[0], x.obsolete) for x in c.entries
|
||||||
@@ -556,7 +533,7 @@ def cmd_pending(a):
|
|||||||
|
|
||||||
def cmd_apply(a):
|
def cmd_apply(a):
|
||||||
c = Catalog(catalog_path(a.lang))
|
c = Catalog(catalog_path(a.lang))
|
||||||
g = None if a.no_glossary else Glossary(a.wiki, quiet=True, langs=[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"))
|
data = json.loads(Path(a.input).read_text(encoding="utf-8"))
|
||||||
items = data["entries"] if isinstance(data, dict) else data
|
items = data["entries"] if isinstance(data, dict) else data
|
||||||
idx = c.index()
|
idx = c.index()
|
||||||
@@ -611,7 +588,7 @@ def find_msgfmt():
|
|||||||
|
|
||||||
|
|
||||||
def cmd_check(a):
|
def cmd_check(a):
|
||||||
g = None if a.no_glossary else Glossary(a.wiki, quiet=True, langs=a.lang or None)
|
g = None if a.no_glossary else Glossary(a.wiki, quiet=True)
|
||||||
msgfmt = find_msgfmt()
|
msgfmt = find_msgfmt()
|
||||||
status = 0
|
status = 0
|
||||||
for lang in a.lang or all_langs():
|
for lang in a.lang or all_langs():
|
||||||
@@ -669,7 +646,7 @@ def cmd_lookup(a):
|
|||||||
|
|
||||||
|
|
||||||
def cmd_glossary(a):
|
def cmd_glossary(a):
|
||||||
g = Glossary(a.wiki, langs=[a.lang])
|
g = Glossary(a.wiki)
|
||||||
col = g.column(a.lang)
|
col = g.column(a.lang)
|
||||||
if col is None:
|
if col is None:
|
||||||
print(f"'{a.lang}' has no glossary column yet; add one with glossary-set --set {a.lang}=...")
|
print(f"'{a.lang}' has no glossary column yet; add one with glossary-set --set {a.lang}=...")
|
||||||
@@ -756,17 +733,6 @@ def cmd_candidates(a):
|
|||||||
print(f"{cnt:7} {langs:6} {text}{' [label]' if k in labels else ''} e.g. {ex[:70]}{'…' if len(ex) > 70 else ''}")
|
print(f"{cnt:7} {langs:6} {text}{' [label]' if k in labels else ''} e.g. {ex[:70]}{'…' if len(ex) > 70 else ''}")
|
||||||
|
|
||||||
|
|
||||||
def _edit_tsv(path, edit):
|
|
||||||
"""Apply edit(lines) to a glossary TSV, keeping its BOM and line endings."""
|
|
||||||
data = path.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()
|
|
||||||
edit(lines)
|
|
||||||
path.write_bytes((b"\xef\xbb\xbf" if bom else b"") + (nl.join(lines) + nl).encode("utf-8"))
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_glossary_set(a):
|
def cmd_glossary_set(a):
|
||||||
wiki = find_wiki(a.wiki)
|
wiki = find_wiki(a.wiki)
|
||||||
if not wiki:
|
if not wiki:
|
||||||
@@ -775,21 +741,8 @@ def cmd_glossary_set(a):
|
|||||||
f" {a.term}: " + ", ".join(a.set) + "\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)
|
"Clone https://github.com/OrcaSlicer/OrcaSlicer_WIKI next to this repository, or pass --wiki.", file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
gdir = wiki / GLOSSARY_DIR
|
tsv = wiki / GLOSSARY_TSV
|
||||||
gen = wiki / "generate_glossary.py"
|
lock = tsv.with_suffix(".lock")
|
||||||
if not gen.exists():
|
|
||||||
sys.exit(f"{gen} not found")
|
|
||||||
run_gen = lambda *args: subprocess.run([sys.executable, str(gen), *args], cwd=wiki, capture_output=True, text=True)
|
|
||||||
langs = [l.strip() for l in (gdir / "languages.txt").read_text(encoding="utf-8-sig").splitlines() if l.strip()]
|
|
||||||
values = {}
|
|
||||||
for s in a.set:
|
|
||||||
lang, _, val = s.partition("=")
|
|
||||||
if lang not in langs:
|
|
||||||
sys.exit(f"no glossary language '{lang}' (add it to {GLOSSARY_DIR.as_posix()}/languages.txt)")
|
|
||||||
if "\t" in val or "\n" in val:
|
|
||||||
sys.exit("values cannot contain tabs or line breaks")
|
|
||||||
values[lang] = val.strip()
|
|
||||||
lock = gdir / ".lock"
|
|
||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
try:
|
try:
|
||||||
fd = os.open(lock, os.O_CREAT | os.O_EXCL)
|
fd = os.open(lock, os.O_CREAT | os.O_EXCL)
|
||||||
@@ -799,51 +752,50 @@ def cmd_glossary_set(a):
|
|||||||
else:
|
else:
|
||||||
sys.exit(f"could not lock {lock}; remove it if no other run is active")
|
sys.exit(f"could not lock {lock}; remove it if no other run is active")
|
||||||
try:
|
try:
|
||||||
terms = (gdir / "terms.tsv").read_text(encoding="utf-8-sig").splitlines()
|
data = tsv.read_bytes()
|
||||||
new = not any(l.split("\t")[0] == a.term for l in terms[1:])
|
bom = data.startswith(b"\xef\xbb\xbf")
|
||||||
if new and not a.description:
|
text = data.decode("utf-8-sig")
|
||||||
sys.exit("a new term needs --description (one sentence saying what it is)")
|
nl = "\r\n" if "\r\n" in text else "\n"
|
||||||
# an established translation is only replaced on request
|
lines = text.splitlines()
|
||||||
conflicts = []
|
header = lines[0].split("\t")
|
||||||
for lang, v in values.items():
|
values = {}
|
||||||
for line in (gdir / f"{lang}.tsv").read_text(encoding="utf-8-sig").splitlines()[1:]:
|
for s in a.set:
|
||||||
cells = line.split("\t")
|
lang, _, val = s.partition("=")
|
||||||
if cells[0] == a.term and len(cells) > 2 and cells[2].strip() not in MISSING and cells[2] != v:
|
if lang not in header:
|
||||||
conflicts.append(f"{lang}: '{cells[2]}' -> '{v}'")
|
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:
|
if conflicts and not a.replace:
|
||||||
sys.exit("established translations differ (pass --replace, and update the catalogs to match):\n " + "\n ".join(conflicts))
|
sys.exit("established translations differ (pass --replace, and update the catalogs to match):\n " + "\n ".join(conflicts))
|
||||||
|
for i, v in values.items():
|
||||||
def edit_terms(lines):
|
row[i] = v
|
||||||
for i, l in enumerate(lines):
|
lines[row_i] = "\t".join(row)
|
||||||
if i and l.split("\t")[0] == a.term:
|
tsv.write_bytes((b"\xef\xbb\xbf" if bom else b"") + (nl.join(lines) + nl).encode("utf-8"))
|
||||||
if a.description:
|
|
||||||
lines[i] = f"{a.term}\t{a.description}"
|
|
||||||
return
|
|
||||||
lines.append(f"{a.term}\t{a.description}")
|
|
||||||
if new or a.description:
|
|
||||||
_edit_tsv(gdir / "terms.tsv", edit_terms)
|
|
||||||
r = run_gen("--sync") # copies the term and description into every language file
|
|
||||||
if r.returncode:
|
|
||||||
sys.exit((r.stdout + r.stderr).strip())
|
|
||||||
|
|
||||||
def setter(lang, v):
|
|
||||||
def edit(lines):
|
|
||||||
for i, l in enumerate(lines):
|
|
||||||
cells = l.split("\t")
|
|
||||||
if i and cells[0] == a.term:
|
|
||||||
lines[i] = "\t".join(cells[:2] + [v])
|
|
||||||
return
|
|
||||||
sys.exit(f"{lang}.tsv has no row for '{a.term}'")
|
|
||||||
return edit
|
|
||||||
for lang, v in values.items():
|
|
||||||
_edit_tsv(gdir / f"{lang}.tsv", setter(lang, v))
|
|
||||||
finally:
|
finally:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
os.unlink(lock)
|
os.unlink(lock)
|
||||||
print(f"glossary: {'added' if new else 'updated'} '{a.term}'")
|
print(f"glossary: {'added' if new else 'updated'} '{a.term}'")
|
||||||
r = run_gen("--update")
|
gen = wiki / "generate_glossary.py"
|
||||||
print((r.stdout + r.stderr).strip())
|
if gen.exists():
|
||||||
return r.returncode
|
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():
|
def main():
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ See `.claude/skills/orca-translations/references/source-audit.md` for the full c
|
|||||||
### 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.
|
||||||
- If a term's established translation changes, update both the affected `.po` files and the glossary (`developer_reference/glossary/<lang>.tsv` in the wiki, then regenerate) so they stay in sync.
|
- If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync.
|
||||||
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
||||||
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
||||||
|
|
||||||
|
|||||||
Vendored
+7
-17
@@ -16,8 +16,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(_openssl_dir ${DESTDIR})
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(_openssl_msvc_env CC=cl CXX=cl RC=rc CL=/FS)
|
set(_openssl_msvc_env CC=cl CXX=cl RC=rc CL=/FS)
|
||||||
# OpenSSL's perl Configure honors the CC environment variable, but the
|
# OpenSSL's perl Configure honors the CC environment variable, but the
|
||||||
@@ -45,14 +43,6 @@ if(WIN32)
|
|||||||
else()
|
else()
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
|
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
|
||||||
# OPENSSLDIR is only where OpenSSL looks at runtime for cert.pem, certs/
|
|
||||||
# and openssl.cnf; install_sw installs nothing there. Point it at the
|
|
||||||
# system CA bundle, the one libcurl already uses here, so the bundled
|
|
||||||
# Python's ssl module can verify HTTPS. Elsewhere it stays the prefix:
|
|
||||||
# Windows Python reads the system certificate store, and on Linux, where
|
|
||||||
# the bundle's location differs by distro, CurlGlobalInit points
|
|
||||||
# SSL_CERT_FILE at it at startup.
|
|
||||||
set(_openssl_dir /etc/ssl)
|
|
||||||
else()
|
else()
|
||||||
# A static library that is embedded into a shared object must not export
|
# A static library that is embedded into a shared object must not export
|
||||||
# its symbols. On Linux the running process also loads the system OpenSSL
|
# its symbols. On Linux the running process also loads the system OpenSSL
|
||||||
@@ -91,7 +81,7 @@ ExternalProject_Add(dep_OpenSSL
|
|||||||
# URL_HASH SHA256=8c776993154652d0bb393f506d850b811517c8bd8d24b1008aef57fbe55d3f31
|
# URL_HASH SHA256=8c776993154652d0bb393f506d850b811517c8bd8d24b1008aef57fbe55d3f31
|
||||||
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/OpenSSL
|
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/OpenSSL
|
||||||
CONFIGURE_COMMAND ${_conf_cmd} ${_cross_arch}
|
CONFIGURE_COMMAND ${_conf_cmd} ${_cross_arch}
|
||||||
"--openssldir=${_openssl_dir}"
|
"--openssldir=${DESTDIR}"
|
||||||
"--prefix=${DESTDIR}"
|
"--prefix=${DESTDIR}"
|
||||||
# OpenSSL's linux-x86_64 target sets multilib=64, so it installs to
|
# OpenSSL's linux-x86_64 target sets multilib=64, so it installs to
|
||||||
# <prefix>/lib64 while every other dep uses <prefix>/lib. CPython's
|
# <prefix>/lib64 while every other dep uses <prefix>/lib. CPython's
|
||||||
@@ -125,12 +115,12 @@ ExternalProject_Add_Step(dep_OpenSSL install_cmake_files
|
|||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32 AND NOT APPLE)
|
||||||
# OpenSSL's object rules do not depend on the configured flags or paths, so
|
# OpenSSL's object rules do not depend on CFLAGS, so reconfiguring it (for
|
||||||
# reconfiguring it (a new compiler flag, a different --openssldir) relinks
|
# example to add -fvisibility=hidden) relinks the archives from stale
|
||||||
# the archives from stale objects instead of recompiling them, and the
|
# objects instead of recompiling them, and the change silently has no
|
||||||
# change silently has no effect. Drop the objects whenever this recipe
|
# effect. Drop the objects whenever this recipe changes so the next build
|
||||||
# changes so the next build actually recompiles them.
|
# actually recompiles them.
|
||||||
ExternalProject_Get_Property(dep_OpenSSL SOURCE_DIR)
|
ExternalProject_Get_Property(dep_OpenSSL SOURCE_DIR)
|
||||||
ExternalProject_Add_Step(dep_OpenSSL clean_objects
|
ExternalProject_Add_Step(dep_OpenSSL clean_objects
|
||||||
DEPENDEES configure
|
DEPENDEES configure
|
||||||
|
|||||||
Vendored
+4
-5
@@ -365,11 +365,10 @@ if(TARGET dep_ZLIB)
|
|||||||
add_dependencies(dep_python3 dep_ZLIB)
|
add_dependencies(dep_python3 dep_ZLIB)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32 AND NOT APPLE)
|
||||||
# _ssl and _hashlib embed the static OpenSSL built in deps/ (Windows uses
|
# CPython's Makefile rules for _ssl and _hashlib depend only on their own
|
||||||
# CPython's prebuilt OpenSSL instead). Their Makefile rules depend only on
|
# sources, not on the OpenSSL archives, so a rebuilt OpenSSL does not make
|
||||||
# their own sources, not on the OpenSSL archives, so a rebuilt OpenSSL does
|
# them relink and they keep the previous symbols. On an incremental tree,
|
||||||
# not make them relink and they keep the previous one. On an incremental tree,
|
|
||||||
# drop the built modules and relink them against the current OpenSSL; a
|
# drop the built modules and relink them against the current OpenSSL; a
|
||||||
# fresh build is left alone (its PGO target builds them). "make" alone is a
|
# fresh build is left alone (its PGO target builds them). "make" alone is a
|
||||||
# no-op once PGO has run, so sharedmods is invoked explicitly.
|
# no-op once PGO has run, so sharedmods is invoked explicitly.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-19 09:48-0300\n"
|
"POT-Creation-Date: 2026-10-15 17:40+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -1637,16 +1637,16 @@ msgstr ""
|
|||||||
msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Not valid state please report reproduction steps on GitHub"
|
msgid "Not valid state please report reproduction steps on github"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No embossed file"
|
msgid "No embossed file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Missing SVG file in embossed shape"
|
msgid "Missing svg file in embossed shape"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Missing data of SVG file"
|
msgid "Missing data of svg file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Face the camera"
|
msgid "Face the camera"
|
||||||
@@ -4579,7 +4579,7 @@ msgstr ""
|
|||||||
msgid "Unknown C++ exception."
|
msgid "Unknown C++ exception."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Processing G-code from previous file…"
|
msgid "Processing G-Code from previous file…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Slicing complete"
|
msgid "Slicing complete"
|
||||||
@@ -6049,7 +6049,7 @@ msgstr ""
|
|||||||
msgid "Size:"
|
msgid "Size:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-c-format, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12318,7 +12318,7 @@ msgstr ""
|
|||||||
msgid "Left mouse"
|
msgid "Left mouse"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Available anywhere in the window."
|
msgid "Available anywhere in the window, even while typing in a text field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Run favorite 1 to 10"
|
msgid "Run favorite 1 to 10"
|
||||||
@@ -12415,6 +12415,9 @@ msgstr ""
|
|||||||
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Global shortcuts also apply while typing."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "A key that types a character cannot be a global shortcut."
|
msgid "A key that types a character cannot be a global shortcut."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -15645,12 +15648,6 @@ msgstr ""
|
|||||||
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Outline tolerance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Verbose G-code"
|
msgid "Verbose G-code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -20546,7 +20543,7 @@ msgid "test %s start..."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-c-format, possible-boost-format
|
#, possible-c-format, possible-boost-format
|
||||||
msgid "test %s IP resolved = %s"
|
msgid "test %s ip resolved = %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-c-format, possible-boost-format
|
#, possible-c-format, possible-boost-format
|
||||||
@@ -26498,18 +26495,17 @@ msgstr ""
|
|||||||
msgid "Basic Colors"
|
msgid "Basic Colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
|
||||||
msgid "Orca Palette"
|
msgid "Orca Palette"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
||||||
msgid "RAL Classic"
|
msgid "RAL Classic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
||||||
msgid "Gradients"
|
msgid "Gradients"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Color picker"
|
|
||||||
msgid "Solid"
|
msgid "Solid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -26884,7 +26880,7 @@ msgstr ""
|
|||||||
msgid "Medium violet red"
|
msgid "Medium violet red"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pale violet red"
|
msgid "PaleV violet red"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deep pink"
|
msgid "Deep pink"
|
||||||
@@ -27046,7 +27042,7 @@ msgstr ""
|
|||||||
msgid "Shallow sky"
|
msgid "Shallow sky"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Electric blue"
|
msgid "Electric bel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Dolphin"
|
msgid "Dolphin"
|
||||||
@@ -27115,7 +27111,7 @@ msgstr ""
|
|||||||
msgid "Maize yellow"
|
msgid "Maize yellow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Daffodil yellow"
|
msgid "Dalmatian yellow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Brown beige"
|
msgid "Brown beige"
|
||||||
@@ -27376,7 +27372,7 @@ msgstr ""
|
|||||||
msgid "Turquoise blue"
|
msgid "Turquoise blue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Capri blue"
|
msgid "Capsanthin violet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Ocean blue"
|
msgid "Ocean blue"
|
||||||
@@ -27490,7 +27486,7 @@ msgstr ""
|
|||||||
msgid "Silver grey"
|
msgid "Silver grey"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Olive grey"
|
msgid "Olivestone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Moss grey"
|
msgid "Moss grey"
|
||||||
@@ -28078,19 +28074,10 @@ msgstr ""
|
|||||||
msgid "Built-in"
|
msgid "Built-in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "My textures"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "No textures found."
|
msgid "No textures found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Texture libraries"
|
msgid "My textures"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Explore more texture libraries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Opens OrcaCloud in the Home page to browse for plugins."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
||||||
@@ -30143,9 +30130,6 @@ msgstr ""
|
|||||||
msgid "Slicing Pipeline"
|
msgid "Slicing Pipeline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Resource Texture"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -30261,24 +30245,6 @@ msgstr ""
|
|||||||
msgid "Plugin directory does not contain a .py or .whl entry file"
|
msgid "Plugin directory does not contain a .py or .whl entry file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Resource texture directory is not set"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory could not be resolved"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture path is not a directory"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Plugin folder could not be resolved"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory is outside the plugin folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory could not be read"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -30635,7 +30601,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. TRN %1% is the application name.
|
#. TRN %1% is the application name.
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "Not a G-code file generated by %1%."
|
msgid "Not a gcode file generated by %1%."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN %1% is a tag in the G-code file that must not be translated.
|
#. TRN %1% is a tag in the G-code file that must not be translated.
|
||||||
@@ -30723,7 +30689,7 @@ msgstr ""
|
|||||||
msgid "Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%"
|
msgid "Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Post-processor can't find exported G-code file"
|
msgid "Post-processor can't find exported gcode file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-19 09:48-0300\n"
|
"POT-Creation-Date: 2026-10-15 17:40+0800\n"
|
||||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -1633,16 +1633,16 @@ msgstr ""
|
|||||||
msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Not valid state please report reproduction steps on GitHub"
|
msgid "Not valid state please report reproduction steps on github"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No embossed file"
|
msgid "No embossed file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Missing SVG file in embossed shape"
|
msgid "Missing svg file in embossed shape"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Missing data of SVG file"
|
msgid "Missing data of svg file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Face the camera"
|
msgid "Face the camera"
|
||||||
@@ -4575,7 +4575,7 @@ msgstr ""
|
|||||||
msgid "Unknown C++ exception."
|
msgid "Unknown C++ exception."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Processing G-code from previous file…"
|
msgid "Processing G-Code from previous file…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Slicing complete"
|
msgid "Slicing complete"
|
||||||
@@ -6045,7 +6045,7 @@ msgstr ""
|
|||||||
msgid "Size:"
|
msgid "Size:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, boost-format
|
||||||
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -10653,9 +10653,6 @@ msgstr ""
|
|||||||
msgid "Send Failed"
|
msgid "Send Failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No available printers"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "The name length exceeds the limit."
|
msgid "The name length exceeds the limit."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12314,7 +12311,7 @@ msgstr ""
|
|||||||
msgid "Left mouse"
|
msgid "Left mouse"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Available anywhere in the window."
|
msgid "Available anywhere in the window, even while typing in a text field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Run favorite 1 to 10"
|
msgid "Run favorite 1 to 10"
|
||||||
@@ -12411,6 +12408,9 @@ msgstr ""
|
|||||||
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Global shortcuts also apply while typing."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "A key that types a character cannot be a global shortcut."
|
msgid "A key that types a character cannot be a global shortcut."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -15641,12 +15641,6 @@ msgstr ""
|
|||||||
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Outline tolerance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Verbose G-code"
|
msgid "Verbose G-code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -20542,7 +20536,7 @@ msgid "test %s start..."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "test %s IP resolved = %s"
|
msgid "test %s ip resolved = %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
@@ -26494,18 +26488,17 @@ msgstr ""
|
|||||||
msgid "Basic Colors"
|
msgid "Basic Colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
|
||||||
msgid "Orca Palette"
|
msgid "Orca Palette"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
||||||
msgid "RAL Classic"
|
msgid "RAL Classic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
||||||
msgid "Gradients"
|
msgid "Gradients"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "Color picker"
|
|
||||||
msgid "Solid"
|
msgid "Solid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -26880,7 +26873,7 @@ msgstr ""
|
|||||||
msgid "Medium violet red"
|
msgid "Medium violet red"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pale violet red"
|
msgid "PaleV violet red"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deep pink"
|
msgid "Deep pink"
|
||||||
@@ -27042,7 +27035,7 @@ msgstr ""
|
|||||||
msgid "Shallow sky"
|
msgid "Shallow sky"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Electric blue"
|
msgid "Electric bel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Dolphin"
|
msgid "Dolphin"
|
||||||
@@ -27111,7 +27104,7 @@ msgstr ""
|
|||||||
msgid "Maize yellow"
|
msgid "Maize yellow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Daffodil yellow"
|
msgid "Dalmatian yellow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Brown beige"
|
msgid "Brown beige"
|
||||||
@@ -27372,7 +27365,7 @@ msgstr ""
|
|||||||
msgid "Turquoise blue"
|
msgid "Turquoise blue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Capri blue"
|
msgid "Capsanthin violet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Ocean blue"
|
msgid "Ocean blue"
|
||||||
@@ -27486,7 +27479,7 @@ msgstr ""
|
|||||||
msgid "Silver grey"
|
msgid "Silver grey"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Olive grey"
|
msgid "Olivestone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Moss grey"
|
msgid "Moss grey"
|
||||||
@@ -28074,19 +28067,10 @@ msgstr ""
|
|||||||
msgid "Built-in"
|
msgid "Built-in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "My textures"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "No textures found."
|
msgid "No textures found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Texture libraries"
|
msgid "My textures"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Explore more texture libraries"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Opens OrcaCloud in the Home page to browse for plugins."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
||||||
@@ -30139,9 +30123,6 @@ msgstr ""
|
|||||||
msgid "Slicing Pipeline"
|
msgid "Slicing Pipeline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Resource Texture"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -30257,24 +30238,6 @@ msgstr ""
|
|||||||
msgid "Plugin directory does not contain a .py or .whl entry file"
|
msgid "Plugin directory does not contain a .py or .whl entry file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Resource texture directory is not set"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory could not be resolved"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture path is not a directory"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Plugin folder could not be resolved"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory is outside the plugin folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Resource texture directory could not be read"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -30631,7 +30594,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. TRN %1% is the application name.
|
#. TRN %1% is the application name.
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Not a G-code file generated by %1%."
|
msgid "Not a gcode file generated by %1%."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. TRN %1% is a tag in the G-code file that must not be translated.
|
#. TRN %1% is a tag in the G-code file that must not be translated.
|
||||||
@@ -30719,7 +30682,7 @@ msgstr ""
|
|||||||
msgid "Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%"
|
msgid "Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Post-processor can't find exported G-code file"
|
msgid "Post-processor can't find exported gcode file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, boost-format
|
#, boost-format
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+548
-1045
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Slic3rPE\n"
|
"Project-Id-Version: Slic3rPE\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-19 09:48-0300\n"
|
"POT-Creation-Date: 2026-10-15 17:40+0800\n"
|
||||||
"PO-Revision-Date: 2026-06-11 12:37-0300\n"
|
"PO-Revision-Date: 2026-06-11 12:37-0300\n"
|
||||||
"Last-Translator: Handle <mail@bysb.net>\n"
|
"Last-Translator: Handle <mail@bysb.net>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -1751,7 +1751,7 @@ msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
|||||||
msgstr "形状(%1%)的描边包含不受支持的内容:%2%。"
|
msgstr "形状(%1%)的描边包含不受支持的内容:%2%。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Not valid state please report reproduction steps on GitHub"
|
msgid "Not valid state please report reproduction steps on github"
|
||||||
msgstr "状态无效,请在 GitHub 上报告复现步骤"
|
msgstr "状态无效,请在 GitHub 上报告复现步骤"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -1759,11 +1759,11 @@ msgid "No embossed file"
|
|||||||
msgstr "没有浮雕文件"
|
msgstr "没有浮雕文件"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Missing SVG file in embossed shape"
|
msgid "Missing svg file in embossed shape"
|
||||||
msgstr "浮雕形状中缺少 SVG 文件"
|
msgstr "浮雕形状中缺少 SVG 文件"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Missing data of SVG file"
|
msgid "Missing data of svg file"
|
||||||
msgstr "缺少 SVG 文件数据"
|
msgstr "缺少 SVG 文件数据"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -5008,7 +5008,7 @@ msgid "Unknown C++ exception."
|
|||||||
msgstr "未知的 C++ 异常。"
|
msgstr "未知的 C++ 异常。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Processing G-code from previous file…"
|
msgid "Processing G-Code from previous file…"
|
||||||
msgstr "正在处理之前文件的 G-code…"
|
msgstr "正在处理之前文件的 G-code…"
|
||||||
|
|
||||||
msgid "Slicing complete"
|
msgid "Slicing complete"
|
||||||
@@ -6699,7 +6699,7 @@ msgstr "体积:"
|
|||||||
msgid "Size:"
|
msgid "Size:"
|
||||||
msgstr "尺寸:"
|
msgstr "尺寸:"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, boost-format
|
||||||
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
||||||
msgstr "发现G-code路径在层%d,高度为%.2lf mm处有冲突。请将有冲突的对象分离得更远(%s <-> %s)。"
|
msgstr "发现G-code路径在层%d,高度为%.2lf mm处有冲突。请将有冲突的对象分离得更远(%s <-> %s)。"
|
||||||
|
|
||||||
@@ -12013,10 +12013,6 @@ msgstr "上传文件到打印机存储失败,请重试。"
|
|||||||
msgid "Send Failed"
|
msgid "Send Failed"
|
||||||
msgstr "发送失败"
|
msgstr "发送失败"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "No available printers"
|
|
||||||
msgstr "没有可用的打印机"
|
|
||||||
|
|
||||||
msgid "The name length exceeds the limit."
|
msgid "The name length exceeds the limit."
|
||||||
msgstr "名称长度超过限制。"
|
msgstr "名称长度超过限制。"
|
||||||
|
|
||||||
@@ -13926,8 +13922,8 @@ msgid "Left mouse"
|
|||||||
msgstr "鼠标左键"
|
msgstr "鼠标左键"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Available anywhere in the window."
|
msgid "Available anywhere in the window, even while typing in a text field."
|
||||||
msgstr "在窗口任意位置可用。"
|
msgstr "在窗口任意位置可用,即使正在文本框中输入。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Run favorite 1 to 10"
|
msgid "Run favorite 1 to 10"
|
||||||
@@ -14043,6 +14039,10 @@ msgstr "%s 已分配给 %s。是否将其重新分配给 %s?"
|
|||||||
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
||||||
msgstr "默认的 %s 已分配给 %s。是否将其重新分配给 %s?"
|
msgstr "默认的 %s 已分配给 %s。是否将其重新分配给 %s?"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
msgid "Global shortcuts also apply while typing."
|
||||||
|
msgstr "全局快捷键在输入文字时同样生效。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "A key that types a character cannot be a global shortcut."
|
msgid "A key that types a character cannot be a global shortcut."
|
||||||
msgstr "会输入字符的按键不能用作全局快捷键。"
|
msgstr "会输入字符的按键不能用作全局快捷键。"
|
||||||
@@ -18086,14 +18086,6 @@ msgstr "对象排除"
|
|||||||
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
||||||
msgstr "启用此选项以在G-code中添加EXCLUDE OBJECT命令。"
|
msgstr "启用此选项以在G-code中添加EXCLUDE OBJECT命令。"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Outline tolerance"
|
|
||||||
msgstr "轮廓线公差"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point."
|
|
||||||
msgstr "写入 EXCLUDE_OBJECT_DEFINE 的对象轮廓线与原始轮廓线之间的最大偏差。数值越大,点数越少,有助于避免固件在解析包含大量对象的 G-code 时卡顿。0 表示保留所有点。"
|
|
||||||
|
|
||||||
msgid "Verbose G-code"
|
msgid "Verbose G-code"
|
||||||
msgstr "注释G-code"
|
msgstr "注释G-code"
|
||||||
|
|
||||||
@@ -24012,7 +24004,7 @@ msgstr "开始测试 %s..."
|
|||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "test %s IP resolved = %s"
|
msgid "test %s ip resolved = %s"
|
||||||
msgstr "测试 %s:IP 解析结果 = %s"
|
msgstr "测试 %s:IP 解析结果 = %s"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -31506,21 +31498,20 @@ msgid "Basic Colors"
|
|||||||
msgstr "基本颜色"
|
msgstr "基本颜色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
|
||||||
msgid "Orca Palette"
|
msgid "Orca Palette"
|
||||||
msgstr "Orca 调色板"
|
msgstr "Orca 调色板"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
||||||
msgid "RAL Classic"
|
msgid "RAL Classic"
|
||||||
msgstr "RAL 经典色"
|
msgstr "RAL 经典色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
|
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
||||||
msgid "Gradients"
|
msgid "Gradients"
|
||||||
msgstr "渐变预设"
|
msgstr "渐变预设"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgctxt "Color picker"
|
|
||||||
msgid "Solid"
|
msgid "Solid"
|
||||||
msgstr "纯色"
|
msgstr "纯色"
|
||||||
|
|
||||||
@@ -32019,7 +32010,7 @@ msgid "Medium violet red"
|
|||||||
msgstr "中紫罗兰红"
|
msgstr "中紫罗兰红"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Pale violet red"
|
msgid "PaleV violet red"
|
||||||
msgstr "浅紫罗兰红"
|
msgstr "浅紫罗兰红"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -32235,7 +32226,8 @@ msgid "Shallow sky"
|
|||||||
msgstr "浅空"
|
msgstr "浅空"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Electric blue"
|
# Needs human review: The English “Electric bel” looks like a typo (Electric blue? Electric eel?); the color is #1E90FF, so 电光蓝 was chosen. Recheck once the source name is fixed.
|
||||||
|
msgid "Electric bel"
|
||||||
msgstr "电光蓝"
|
msgstr "电光蓝"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -32327,8 +32319,9 @@ msgid "Maize yellow"
|
|||||||
msgstr "玉米黄"
|
msgstr "玉米黄"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Daffodil yellow"
|
# Needs human review: RAL 1007 is “Daffodil yellow” (水仙黄); the English name in the palette looks wrong. Recheck once the source name is fixed.
|
||||||
msgstr "水仙黄"
|
msgid "Dalmatian yellow"
|
||||||
|
msgstr "达尔马提亚黄"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Brown beige"
|
msgid "Brown beige"
|
||||||
@@ -32675,8 +32668,8 @@ msgid "Turquoise blue"
|
|||||||
msgstr "绿松石蓝"
|
msgstr "绿松石蓝"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Capri blue"
|
msgid "Capsanthin violet"
|
||||||
msgstr "卡普里蓝"
|
msgstr "辣椒红素紫"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Ocean blue"
|
msgid "Ocean blue"
|
||||||
@@ -32827,8 +32820,8 @@ msgid "Silver grey"
|
|||||||
msgstr "银灰色"
|
msgstr "银灰色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Olive grey"
|
msgid "Olivestone"
|
||||||
msgstr "橄榄灰"
|
msgstr "橄榄石色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Moss grey"
|
msgid "Moss grey"
|
||||||
@@ -33604,25 +33597,13 @@ msgstr "从“我的纹理”中移除"
|
|||||||
msgid "Built-in"
|
msgid "Built-in"
|
||||||
msgstr "内置"
|
msgstr "内置"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "My textures"
|
|
||||||
msgstr "我的纹理"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "No textures found."
|
msgid "No textures found."
|
||||||
msgstr "未找到纹理。"
|
msgstr "未找到纹理。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Texture libraries"
|
msgid "My textures"
|
||||||
msgstr "纹理库"
|
msgstr "我的纹理"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Explore more texture libraries"
|
|
||||||
msgstr "探索更多纹理库"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Opens OrcaCloud in the Home page to browse for plugins."
|
|
||||||
msgstr "在主页中打开 OrcaCloud,浏览插件。"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
||||||
@@ -36304,10 +36285,6 @@ msgstr "脚本"
|
|||||||
msgid "Slicing Pipeline"
|
msgid "Slicing Pipeline"
|
||||||
msgstr "切片流水线"
|
msgstr "切片流水线"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource Texture"
|
|
||||||
msgstr "资源纹理"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
||||||
msgstr "插件的配置界面为空。正在显示默认编辑器。"
|
msgstr "插件的配置界面为空。正在显示默认编辑器。"
|
||||||
@@ -36456,30 +36433,6 @@ msgstr "插件目录同时包含 .py 和 .whl 入口文件"
|
|||||||
msgid "Plugin directory does not contain a .py or .whl entry file"
|
msgid "Plugin directory does not contain a .py or .whl entry file"
|
||||||
msgstr "插件目录不包含 .py 或 .whl 入口文件"
|
msgstr "插件目录不包含 .py 或 .whl 入口文件"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory is not set"
|
|
||||||
msgstr "未设置资源纹理目录"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory could not be resolved"
|
|
||||||
msgstr "无法解析资源纹理目录"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture path is not a directory"
|
|
||||||
msgstr "资源纹理路径不是目录"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Plugin folder could not be resolved"
|
|
||||||
msgstr "无法解析插件文件夹"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory is outside the plugin folder"
|
|
||||||
msgstr "资源纹理目录位于插件文件夹之外"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory could not be read"
|
|
||||||
msgstr "无法读取资源纹理目录"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
||||||
@@ -36938,7 +36891,7 @@ msgstr "加载配置文件“%1%”失败:%2%"
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN %1% is the application name.
|
#. TRN %1% is the application name.
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Not a G-code file generated by %1%."
|
msgid "Not a gcode file generated by %1%."
|
||||||
msgstr "不是由 %1% 生成的 G-code 文件。"
|
msgstr "不是由 %1% 生成的 G-code 文件。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -37058,7 +37011,7 @@ msgid "Failed making a temporary copy of G-code file %1% before running a post-p
|
|||||||
msgstr "运行后处理脚本之前,无法创建 G-code 文件 %1% 的临时副本:%2%"
|
msgstr "运行后处理脚本之前,无法创建 G-code 文件 %1% 的临时副本:%2%"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Post-processor can't find exported G-code file"
|
msgid "Post-processor can't find exported gcode file"
|
||||||
msgstr "后处理器找不到导出的 G-code 文件"
|
msgstr "后处理器找不到导出的 G-code 文件"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -37447,40 +37400,6 @@ msgstr ""
|
|||||||
"避免翘曲\n"
|
"避免翘曲\n"
|
||||||
"您知道吗?打印ABS这类易翘曲材料时,适当提高热床温度可以降低翘曲的概率。"
|
"您知道吗?打印ABS这类易翘曲材料时,适当提高热床温度可以降低翘曲的概率。"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
# Needs human review: RAL 1007 is “Daffodil yellow” (水仙黄); the English name in the palette looks wrong. Recheck once the source name is fixed.
|
|
||||||
#~ msgid "Dalmatian yellow"
|
|
||||||
#~ msgstr "达尔马提亚黄"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Capsanthin violet"
|
|
||||||
#~ msgstr "辣椒红素紫"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Olivestone"
|
|
||||||
#~ msgstr "橄榄石色"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Available anywhere in the window, even while typing in a text field."
|
|
||||||
#~ msgstr "在窗口任意位置可用,即使正在文本框中输入。"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Global shortcuts also apply while typing."
|
|
||||||
#~ msgstr "全局快捷键在输入文字时同样生效。"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Solid"
|
|
||||||
#~ msgstr "纯色"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "PaleV violet red"
|
|
||||||
#~ msgstr "浅紫罗兰红"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
# Needs human review: The English “Electric bel” looks like a typo (Electric blue? Electric eel?); the color is #1E90FF, so 电光蓝 was chosen. Recheck once the source name is fixed.
|
|
||||||
#~ msgid "Electric bel"
|
|
||||||
#~ msgstr "电光蓝"
|
|
||||||
|
|
||||||
#~ msgid "Please choose the filament color"
|
#~ msgid "Please choose the filament color"
|
||||||
#~ msgstr "请选择耗材丝颜色"
|
#~ msgstr "请选择耗材丝颜色"
|
||||||
|
|
||||||
@@ -37946,6 +37865,9 @@ msgstr ""
|
|||||||
#~ msgid "no configured tool"
|
#~ msgid "no configured tool"
|
||||||
#~ msgstr "未配置的工具头"
|
#~ msgstr "未配置的工具头"
|
||||||
|
|
||||||
|
#~ msgid "Add end G-Code when finishing the entire print."
|
||||||
|
#~ msgstr "所有打印结束时的结尾G-code"
|
||||||
|
|
||||||
#~ msgid "Maximum X speed"
|
#~ msgid "Maximum X speed"
|
||||||
#~ msgstr "X最大速度"
|
#~ msgstr "X最大速度"
|
||||||
|
|
||||||
@@ -37962,11 +37884,26 @@ msgstr ""
|
|||||||
#~ msgid "Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, including a final G-code post-processing step. Research/experimental."
|
#~ msgid "Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, including a final G-code post-processing step. Research/experimental."
|
||||||
#~ msgstr "在每个切片流水线步骤中调用的 Python 插件,用于读取和修改中间切片数据,包括最终的 G-code 后处理步骤。研究/实验性。"
|
#~ msgstr "在每个切片流水线步骤中调用的 Python 插件,用于读取和修改中间切片数据,包括最终的 G-code 后处理步骤。研究/实验性。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "When enabled, the slicer emits g-code with the primary zone's contents centered on bed origin (0,0). The printer firmware is responsible for placing copies/mirrors at each physical zone position. Enable only on printers whose firmware applies its own zone offsets in copy/mirror mode (e.g. RepRapFirmware IDEX duplication mode). Has no effect in Primary mode."
|
||||||
|
#~ msgstr "启用后,切片软件输出的 G-code 会以热床原点 (0,0) 为中心放置主区域的内容,由打印机固件负责把复制件/镜像件放到各个物理区域。仅当打印机固件在复制/镜像模式下会自行应用区域偏移时才启用(例如 RepRapFirmware 的 IDEX 复制模式)。主模式下无效。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
# Needs human review: "pem-inversion defaults": confirm pem is the physical extruder map and that the default is its inverse.
|
# Needs human review: "pem-inversion defaults": confirm pem is the physical extruder map and that the default is its inverse.
|
||||||
#~ msgid "Per-plate override mapping physical heads to filament slots (1-based). Empty means use pem-inversion defaults."
|
#~ msgid "Per-plate override mapping physical heads to filament slots (1-based). Empty means use pem-inversion defaults."
|
||||||
#~ msgstr "按盘单独设置物理工具头到耗材丝槽位的映射(从 1 开始编号),覆盖默认映射。留空则使用由物理挤出机映射(pem)反推的默认值。"
|
#~ msgstr "按盘单独设置物理工具头到耗材丝槽位的映射(从 1 开始编号),覆盖默认映射。留空则使用由物理挤出机映射(pem)反推的默认值。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "Axis the mesh is rotated about before slicing. This is the belt printer's tilt: an isometric (no distortion) rotation that also drives bed rendering and support gravity tilt, and that the g-code back-transform inverts before the machine-frame shear/scale and remap. X is the typical gantry tilt (belt travels along Y)."
|
||||||
|
#~ msgstr "切片前模型网格的旋转轴。这一旋转即传送带打印机的倾斜:它是保距(无畸变)旋转,同时用于热床渲染和支撑的重力倾斜;生成 G-code 时,逆变换会先还原这一旋转,再进行机器坐标系剪切/缩放和重映射。X 是典型的龙门倾斜轴(传送带沿 Y 方向移动)。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "Expert override: set the machine-frame (g-code shear/scale) tilt angle independently of the pre-slice rotation angle. When disabled, the machine-frame transform is derived from the belt tilt angle, so a single angle drives both stages. Enable only to compensate for a machine whose physical gantry tilt differs from the slicing rotation."
|
||||||
|
#~ msgstr "专家选项:单独设置机器坐标系(G-code 剪切/缩放)的倾斜角度,与切片前的旋转角度互不影响。禁用时,机器坐标系变换由传送带倾斜角度推导,两个阶段使用同一个角度。仅当机器实际的龙门倾角与切片旋转角度不一致时,才需启用此项进行补偿。"
|
||||||
|
|
||||||
|
#~ msgid "The maximum deviation allowed when reducing the resolution for the 'Maximum wall resolution' setting. If you increase this, the print will be less accurate, but the G-Code will be smaller. 'Maximum wall deviation' limits 'Maximum wall resolution', so if the two conflict, 'Maximum wall deviation' takes precedence."
|
||||||
|
#~ msgstr "在为“最大墙体分辨率”设置降低分辨率时所允许的最大偏差。如果增大此值,打印精度会降低,但 G-code 会更小。“最大墙体偏差”会限制“最大墙体分辨率”,因此当两者冲突时,以“最大墙体偏差”为准。"
|
||||||
|
|
||||||
#~ msgid " not in range "
|
#~ msgid " not in range "
|
||||||
#~ msgstr " 不在合理范围内"
|
#~ msgstr " 不在合理范围内"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-19 09:48-0300\n"
|
"POT-Creation-Date: 2026-10-15 17:40+0800\n"
|
||||||
"PO-Revision-Date: 2025-11-28 13:48-0600\n"
|
"PO-Revision-Date: 2025-11-28 13:48-0600\n"
|
||||||
"Last-Translator: tntchn <15895303+tntchn@users.noreply.github.com>\n"
|
"Last-Translator: tntchn <15895303+tntchn@users.noreply.github.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -1826,7 +1826,7 @@ msgid "Stroke of shape (%1%) contains unsupported: %2%."
|
|||||||
msgstr "形狀(%1%)的筆劃包含不支援的內容:%2%。"
|
msgstr "形狀(%1%)的筆劃包含不支援的內容:%2%。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Not valid state please report reproduction steps on GitHub"
|
msgid "Not valid state please report reproduction steps on github"
|
||||||
msgstr "狀態無效,請在 GitHub 上回報重現步驟"
|
msgstr "狀態無效,請在 GitHub 上回報重現步驟"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -1834,11 +1834,11 @@ msgid "No embossed file"
|
|||||||
msgstr "沒有浮雕檔案"
|
msgstr "沒有浮雕檔案"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Missing SVG file in embossed shape"
|
msgid "Missing svg file in embossed shape"
|
||||||
msgstr "浮雕形狀中缺少 SVG 檔案"
|
msgstr "浮雕形狀中缺少 SVG 檔案"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Missing data of SVG file"
|
msgid "Missing data of svg file"
|
||||||
msgstr "缺少 SVG 檔案的資料"
|
msgstr "缺少 SVG 檔案的資料"
|
||||||
|
|
||||||
msgid "Face the camera"
|
msgid "Face the camera"
|
||||||
@@ -5219,7 +5219,7 @@ msgid "Unknown C++ exception."
|
|||||||
msgstr "未知的 C++ 例外。"
|
msgstr "未知的 C++ 例外。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Processing G-code from previous file…"
|
msgid "Processing G-Code from previous file…"
|
||||||
msgstr "正在處理先前檔案的 G-code…"
|
msgstr "正在處理先前檔案的 G-code…"
|
||||||
|
|
||||||
msgid "Slicing complete"
|
msgid "Slicing complete"
|
||||||
@@ -6912,7 +6912,7 @@ msgid "Size:"
|
|||||||
msgstr "尺寸:"
|
msgstr "尺寸:"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, c-format, boost-format
|
#, boost-format
|
||||||
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
msgid "Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s)."
|
||||||
msgstr "在第 %d 層(Z = %.2lf mm)發現 G-code 路徑衝突。請將有衝突的物件分開得更遠(%s <-> %s)。"
|
msgstr "在第 %d 層(Z = %.2lf mm)發現 G-code 路徑衝突。請將有衝突的物件分開得更遠(%s <-> %s)。"
|
||||||
|
|
||||||
@@ -12298,10 +12298,6 @@ msgstr "無法將檔案上傳至列印裝置的儲存空間,請重試。"
|
|||||||
msgid "Send Failed"
|
msgid "Send Failed"
|
||||||
msgstr "傳送失敗"
|
msgstr "傳送失敗"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "No available printers"
|
|
||||||
msgstr "沒有可用的列印裝置"
|
|
||||||
|
|
||||||
msgid "The name length exceeds the limit."
|
msgid "The name length exceeds the limit."
|
||||||
msgstr "名稱長度超過限制。"
|
msgstr "名稱長度超過限制。"
|
||||||
|
|
||||||
@@ -14255,8 +14251,8 @@ msgid "Left mouse"
|
|||||||
msgstr "滑鼠左鍵"
|
msgstr "滑鼠左鍵"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Available anywhere in the window."
|
msgid "Available anywhere in the window, even while typing in a text field."
|
||||||
msgstr "在視窗中任何位置皆可使用。"
|
msgstr "在視窗中任何位置皆可使用,即使正在文字欄位中輸入也可以。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Run favorite 1 to 10"
|
msgid "Run favorite 1 to 10"
|
||||||
@@ -14376,6 +14372,10 @@ msgstr "%s 已指派給 %s。要改為指派給 %s 嗎?"
|
|||||||
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
msgid "The default %s is assigned to %s. Reassign it to %s?"
|
||||||
msgstr "預設的 %s 已指派給 %s。要改為指派給 %s 嗎?"
|
msgstr "預設的 %s 已指派給 %s。要改為指派給 %s 嗎?"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
msgid "Global shortcuts also apply while typing."
|
||||||
|
msgstr "全域快速鍵在輸入文字時也會生效。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "A key that types a character cannot be a global shortcut."
|
msgid "A key that types a character cannot be a global shortcut."
|
||||||
msgstr "會輸入字元的按鍵不能作為全域快速鍵。"
|
msgstr "會輸入字元的按鍵不能作為全域快速鍵。"
|
||||||
@@ -18513,14 +18513,6 @@ msgstr "物件排除"
|
|||||||
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
|
||||||
msgstr "啟用此選項以在 G-code 中加入 EXCLUDE OBJECT 指令。"
|
msgstr "啟用此選項以在 G-code 中加入 EXCLUDE OBJECT 指令。"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Outline tolerance"
|
|
||||||
msgstr "輪廓線公差"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point."
|
|
||||||
msgstr "寫入 EXCLUDE_OBJECT_DEFINE 的物件輪廓線與原始輪廓線之間的最大偏差。數值越大,點數越少,有助於避免韌體在解析包含大量物件的 G-code 時卡住。0 表示保留所有點。"
|
|
||||||
|
|
||||||
msgid "Verbose G-code"
|
msgid "Verbose G-code"
|
||||||
msgstr "詳細 G-code"
|
msgstr "詳細 G-code"
|
||||||
|
|
||||||
@@ -24605,7 +24597,7 @@ msgstr "測試 %s 開始..."
|
|||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "test %s IP resolved = %s"
|
msgid "test %s ip resolved = %s"
|
||||||
msgstr "測試 %s IP 解析結果 = %s"
|
msgstr "測試 %s IP 解析結果 = %s"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -32132,22 +32124,21 @@ msgid "Basic Colors"
|
|||||||
msgstr "基本顏色"
|
msgstr "基本顏色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
|
||||||
msgid "Orca Palette"
|
msgid "Orca Palette"
|
||||||
msgstr "Orca 調色盤"
|
msgstr "Orca 調色盤"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
#. TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
||||||
msgid "RAL Classic"
|
msgid "RAL Classic"
|
||||||
msgstr "RAL 經典色"
|
msgstr "RAL 經典色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
# Needs human review: Palette dropdown entry for the gradient presets (next to 基本顏色 / Orca 調色盤 / RAL 經典色); 漸層配色 keeps it apart from the Solid/Gradient switch label 漸層. Check it reads right in the dropdown.
|
# Needs human review: Palette dropdown entry for the gradient presets (next to 基本顏色 / Orca 調色盤 / RAL 經典色); 漸層配色 keeps it apart from the Solid/Gradient switch label 漸層. Check it reads right in the dropdown.
|
||||||
|
#. TRN: Keep the standard/brand name "RAL" unchanged.
|
||||||
msgid "Gradients"
|
msgid "Gradients"
|
||||||
msgstr "漸層配色"
|
msgstr "漸層配色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgctxt "Color picker"
|
|
||||||
msgid "Solid"
|
msgid "Solid"
|
||||||
msgstr "純色"
|
msgstr "純色"
|
||||||
|
|
||||||
@@ -32646,7 +32637,7 @@ msgid "Medium violet red"
|
|||||||
msgstr "中紫羅蘭紅"
|
msgstr "中紫羅蘭紅"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Pale violet red"
|
msgid "PaleV violet red"
|
||||||
msgstr "淡紫羅蘭紅"
|
msgstr "淡紫羅蘭紅"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -32862,7 +32853,8 @@ msgid "Shallow sky"
|
|||||||
msgstr "淺色天空"
|
msgstr "淺色天空"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Electric blue"
|
# Needs human review: Orca palette: the English is a typo for "Electric blue" (hex #1E90FF); translated as 電光藍. Revisit if the source keeps a different name.
|
||||||
|
msgid "Electric bel"
|
||||||
msgstr "電光藍"
|
msgstr "電光藍"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -32954,8 +32946,9 @@ msgid "Maize yellow"
|
|||||||
msgstr "玉米黃"
|
msgstr "玉米黃"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Daffodil yellow"
|
# Needs human review: RAL 1007 in the RAL Classic list: the English name looks wrong (1007 is Daffodil yellow, 水仙黃); translated literally. Revisit once the source name is fixed.
|
||||||
msgstr "水仙黃"
|
msgid "Dalmatian yellow"
|
||||||
|
msgstr "達爾馬提亞黃"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Brown beige"
|
msgid "Brown beige"
|
||||||
@@ -33303,8 +33296,9 @@ msgid "Turquoise blue"
|
|||||||
msgstr "綠松石藍"
|
msgstr "綠松石藍"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Capri blue"
|
# Needs human review: RAL 5019 in the RAL Classic list: the English name is not a RAL Classic name (5019 is Capri blue); translated literally. Revisit once the source name is fixed.
|
||||||
msgstr "卡布里藍"
|
msgid "Capsanthin violet"
|
||||||
|
msgstr "辣椒紅素紫"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Ocean blue"
|
msgid "Ocean blue"
|
||||||
@@ -33455,8 +33449,8 @@ msgid "Silver grey"
|
|||||||
msgstr "銀灰"
|
msgstr "銀灰"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Olive grey"
|
msgid "Olivestone"
|
||||||
msgstr "橄欖灰"
|
msgstr "橄欖石色"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Moss grey"
|
msgid "Moss grey"
|
||||||
@@ -34232,25 +34226,13 @@ msgstr "從「我的紋理」移除"
|
|||||||
msgid "Built-in"
|
msgid "Built-in"
|
||||||
msgstr "內建"
|
msgstr "內建"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "My textures"
|
|
||||||
msgstr "我的紋理"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "No textures found."
|
msgid "No textures found."
|
||||||
msgstr "找不到紋理。"
|
msgstr "找不到紋理。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Texture libraries"
|
msgid "My textures"
|
||||||
msgstr "紋理庫"
|
msgstr "我的紋理"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Explore more texture libraries"
|
|
||||||
msgstr "探索更多紋理庫"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Opens OrcaCloud in the Home page to browse for plugins."
|
|
||||||
msgstr "在首頁中開啟 OrcaCloud,瀏覽外掛。"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
msgid "Import your own image as a height map. It is converted to the format the slicer bakes from and saved to your personal texture folder, kept separate from the textures shipped with OrcaSlicer."
|
||||||
@@ -36946,10 +36928,6 @@ msgstr "指令碼"
|
|||||||
msgid "Slicing Pipeline"
|
msgid "Slicing Pipeline"
|
||||||
msgstr "切片管線"
|
msgstr "切片管線"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource Texture"
|
|
||||||
msgstr "資源紋理"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
msgid "The plugin's configuration UI was empty. Showing the default editor."
|
||||||
msgstr "外掛的設定介面為空。將顯示預設編輯器。"
|
msgstr "外掛的設定介面為空。將顯示預設編輯器。"
|
||||||
@@ -37097,30 +37075,6 @@ msgstr "外掛目錄同時包含 .py 和 .whl 進入點檔案"
|
|||||||
msgid "Plugin directory does not contain a .py or .whl entry file"
|
msgid "Plugin directory does not contain a .py or .whl entry file"
|
||||||
msgstr "外掛目錄不包含 .py 或 .whl 進入點檔案"
|
msgstr "外掛目錄不包含 .py 或 .whl 進入點檔案"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory is not set"
|
|
||||||
msgstr "未設定資源紋理目錄"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory could not be resolved"
|
|
||||||
msgstr "無法解析資源紋理目錄"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture path is not a directory"
|
|
||||||
msgstr "資源紋理路徑不是目錄"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Plugin folder could not be resolved"
|
|
||||||
msgstr "無法解析外掛資料夾"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory is outside the plugin folder"
|
|
||||||
msgstr "資源紋理目錄位於外掛資料夾之外"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Resource texture directory could not be read"
|
|
||||||
msgstr "無法讀取資源紋理目錄"
|
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
msgid "Slicing pipeline plugin '%1%' error: %2%"
|
||||||
@@ -37579,7 +37533,7 @@ msgstr "載入設定檔「%1%」失敗:%2%"
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#. TRN %1% is the application name.
|
#. TRN %1% is the application name.
|
||||||
#, boost-format
|
#, boost-format
|
||||||
msgid "Not a G-code file generated by %1%."
|
msgid "Not a gcode file generated by %1%."
|
||||||
msgstr "不是由 %1% 產生的 G-code 檔案。"
|
msgstr "不是由 %1% 產生的 G-code 檔案。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -37699,7 +37653,7 @@ msgid "Failed making a temporary copy of G-code file %1% before running a post-p
|
|||||||
msgstr "執行後處理腳本前,無法建立 G-code 檔案 %1% 的暫存副本:%2%"
|
msgstr "執行後處理腳本前,無法建立 G-code 檔案 %1% 的暫存副本:%2%"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Post-processor can't find exported G-code file"
|
msgid "Post-processor can't find exported gcode file"
|
||||||
msgstr "後處理程式找不到匯出的 G-code 檔案"
|
msgstr "後處理程式找不到匯出的 G-code 檔案"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -38095,41 +38049,6 @@ msgstr ""
|
|||||||
"避免翹曲\n"
|
"避免翹曲\n"
|
||||||
"您知道嗎?當列印容易翹曲的材料(如 ABS)時,適當提高熱床溫度可以降低翹曲的機率。"
|
"您知道嗎?當列印容易翹曲的材料(如 ABS)時,適當提高熱床溫度可以降低翹曲的機率。"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
# Needs human review: RAL 1007 in the RAL Classic list: the English name looks wrong (1007 is Daffodil yellow, 水仙黃); translated literally. Revisit once the source name is fixed.
|
|
||||||
#~ msgid "Dalmatian yellow"
|
|
||||||
#~ msgstr "達爾馬提亞黃"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
# Needs human review: RAL 5019 in the RAL Classic list: the English name is not a RAL Classic name (5019 is Capri blue); translated literally. Revisit once the source name is fixed.
|
|
||||||
#~ msgid "Capsanthin violet"
|
|
||||||
#~ msgstr "辣椒紅素紫"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Olivestone"
|
|
||||||
#~ msgstr "橄欖石色"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Available anywhere in the window, even while typing in a text field."
|
|
||||||
#~ msgstr "在視窗中任何位置皆可使用,即使正在文字欄位中輸入也可以。"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Global shortcuts also apply while typing."
|
|
||||||
#~ msgstr "全域快速鍵在輸入文字時也會生效。"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "Solid"
|
|
||||||
#~ msgstr "純色"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#~ msgid "PaleV violet red"
|
|
||||||
#~ msgstr "淡紫羅蘭紅"
|
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
# Needs human review: Orca palette: the English is a typo for "Electric blue" (hex #1E90FF); translated as 電光藍. Revisit if the source keeps a different name.
|
|
||||||
#~ msgid "Electric bel"
|
|
||||||
#~ msgstr "電光藍"
|
|
||||||
|
|
||||||
#~ msgid "Please choose the filament color"
|
#~ msgid "Please choose the filament color"
|
||||||
#~ msgstr "請選擇線材顏色"
|
#~ msgstr "請選擇線材顏色"
|
||||||
|
|
||||||
@@ -38594,6 +38513,9 @@ msgstr ""
|
|||||||
#~ msgid "no configured tool"
|
#~ msgid "no configured tool"
|
||||||
#~ msgstr "未設定的工具頭"
|
#~ msgstr "未設定的工具頭"
|
||||||
|
|
||||||
|
#~ msgid "Add end G-Code when finishing the entire print."
|
||||||
|
#~ msgstr "所有列印結束時的結尾 G-code"
|
||||||
|
|
||||||
#~ msgid "Maximum X speed"
|
#~ msgid "Maximum X speed"
|
||||||
#~ msgstr "X 最大速度"
|
#~ msgstr "X 最大速度"
|
||||||
|
|
||||||
@@ -38610,10 +38532,25 @@ msgstr ""
|
|||||||
#~ msgid "Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, including a final G-code post-processing step. Research/experimental."
|
#~ msgid "Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, including a final G-code post-processing step. Research/experimental."
|
||||||
#~ msgstr "在每個切片管線步驟呼叫的 Python 外掛,用於讀取與修改中介切片資料,包括最終的 G-code 後處理步驟。研究/實驗性質。"
|
#~ msgstr "在每個切片管線步驟呼叫的 Python 外掛,用於讀取與修改中介切片資料,包括最終的 G-code 後處理步驟。研究/實驗性質。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "When enabled, the slicer emits g-code with the primary zone's contents centered on bed origin (0,0). The printer firmware is responsible for placing copies/mirrors at each physical zone position. Enable only on printers whose firmware applies its own zone offsets in copy/mirror mode (e.g. RepRapFirmware IDEX duplication mode). Has no effect in Primary mode."
|
||||||
|
#~ msgstr "啟用後,切片軟體輸出的 G-code 會將主區域的內容以列印板原點 (0,0) 為中心放置,並由列印裝置韌體負責將複製件/鏡像件放到各個實體區域位置。僅在韌體會於複製/鏡像模式下自行套用區域偏移的列印裝置上啟用(例如 RepRapFirmware 的 IDEX 複製模式)。在主模式下無效。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#~ msgid "Per-plate override mapping physical heads to filament slots (1-based). Empty means use pem-inversion defaults."
|
#~ msgid "Per-plate override mapping physical heads to filament slots (1-based). Empty means use pem-inversion defaults."
|
||||||
#~ msgstr "依列印板覆寫實體工具頭到線材槽位的映射(從 1 開始編號)。留空表示使用由 pem 反推的預設映射。"
|
#~ msgstr "依列印板覆寫實體工具頭到線材槽位的映射(從 1 開始編號)。留空表示使用由 pem 反推的預設映射。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "Axis the mesh is rotated about before slicing. This is the belt printer's tilt: an isometric (no distortion) rotation that also drives bed rendering and support gravity tilt, and that the g-code back-transform inverts before the machine-frame shear/scale and remap. X is the typical gantry tilt (belt travels along Y)."
|
||||||
|
#~ msgstr "切片前網格旋轉所繞的軸。這就是輸送帶列印裝置的傾斜:一種等距(無變形)旋轉,同時用於列印板渲染與支撐的重力傾斜;G-code 反向轉換會先還原此旋轉,再進行機器座標系的剪切/縮放與重新映射。X 是典型的龍門傾斜軸(輸送帶沿 Y 方向移動)。"
|
||||||
|
|
||||||
|
# AI Translated
|
||||||
|
#~ msgid "Expert override: set the machine-frame (g-code shear/scale) tilt angle independently of the pre-slice rotation angle. When disabled, the machine-frame transform is derived from the belt tilt angle, so a single angle drives both stages. Enable only to compensate for a machine whose physical gantry tilt differs from the slicing rotation."
|
||||||
|
#~ msgstr "專家覆寫選項:獨立於切片前的旋轉角度,單獨設定機器座標系(G-code 剪切/縮放)的傾斜角度。停用時,機器座標系變換由輸送帶傾斜角度推導,兩個階段使用同一個角度。僅在機器實際的龍門傾角與切片旋轉角度不同時,才啟用此選項進行補償。"
|
||||||
|
|
||||||
|
#~ msgid "The maximum deviation allowed when reducing the resolution for the 'Maximum wall resolution' setting. If you increase this, the print will be less accurate, but the G-Code will be smaller. 'Maximum wall deviation' limits 'Maximum wall resolution', so if the two conflict, 'Maximum wall deviation' takes precedence."
|
||||||
|
#~ msgstr "「最大牆解析度」設定在降低解析度時所允許的最大偏差。若增加此值,列印會較不精確,但 G-Code 會更小。「最大牆偏差」會限制「最大牆解析度」,因此若兩者衝突,將以「最大牆偏差」為準。"
|
||||||
|
|
||||||
#~ msgid " not in range "
|
#~ msgid " not in range "
|
||||||
#~ msgstr " 不在合理的區間"
|
#~ msgstr " 不在合理的區間"
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const cssColors = [
|
|||||||
{hex: "#800080", name: "Purple"},
|
{hex: "#800080", name: "Purple"},
|
||||||
{hex: "#663399", name: "Rebecca purple"},
|
{hex: "#663399", name: "Rebecca purple"},
|
||||||
{hex: "#C71585", name: "Medium violet red"},
|
{hex: "#C71585", name: "Medium violet red"},
|
||||||
{hex: "#DB7093", name: "Pale violet red"},
|
{hex: "#DB7093", name: "PaleV violet red"},
|
||||||
{hex: "#FF1493", name: "Deep pink"},
|
{hex: "#FF1493", name: "Deep pink"},
|
||||||
{hex: "#FF69B4", name: "Hot pink"},
|
{hex: "#FF69B4", name: "Hot pink"},
|
||||||
{hex: "#FFB6C1", name: "Light pink"},
|
{hex: "#FFB6C1", name: "Light pink"},
|
||||||
@@ -193,7 +193,7 @@ const palettes = {
|
|||||||
{hex: "#40E0D0", name: "Turquoise bay"},
|
{hex: "#40E0D0", name: "Turquoise bay"},
|
||||||
{hex: "#2EBDEF", name: "Sky over reef"},
|
{hex: "#2EBDEF", name: "Sky over reef"},
|
||||||
{hex: "#87CEEB", name: "Shallow sky"},
|
{hex: "#87CEEB", name: "Shallow sky"},
|
||||||
{hex: "#1E90FF", name: "Electric blue"},
|
{hex: "#1E90FF", name: "Electric bel"},
|
||||||
{hex: "#4169E1", name: "Dolphin"},
|
{hex: "#4169E1", name: "Dolphin"},
|
||||||
{hex: "#0000CD", name: "Ocean depths"},
|
{hex: "#0000CD", name: "Ocean depths"},
|
||||||
{hex: "#191970", name: "Whale"},
|
{hex: "#191970", name: "Whale"},
|
||||||
@@ -219,7 +219,7 @@ const palettes = {
|
|||||||
{hex: "#EFBF04", name: "1004 Golden yellow"},
|
{hex: "#EFBF04", name: "1004 Golden yellow"},
|
||||||
{hex: "#D7A500", name: "1005 Honey yellow"},
|
{hex: "#D7A500", name: "1005 Honey yellow"},
|
||||||
{hex: "#F2D185", name: "1006 Maize yellow"},
|
{hex: "#F2D185", name: "1006 Maize yellow"},
|
||||||
{hex: "#FDD185", name: "1007 Daffodil yellow"},
|
{hex: "#FDD185", name: "1007 Dalmatian yellow"},
|
||||||
{hex: "#8B7D6B", name: "1011 Brown beige"},
|
{hex: "#8B7D6B", name: "1011 Brown beige"},
|
||||||
{hex: "#F5DB50", name: "1012 Lemon yellow"},
|
{hex: "#F5DB50", name: "1012 Lemon yellow"},
|
||||||
{hex: "#F3E5AB", name: "1013 Oyster white"},
|
{hex: "#F3E5AB", name: "1013 Oyster white"},
|
||||||
@@ -318,7 +318,7 @@ const palettes = {
|
|||||||
{hex: "#0384A7", name: "5015 Sky blue"},
|
{hex: "#0384A7", name: "5015 Sky blue"},
|
||||||
{hex: "#006A9E", name: "5017 Traffic blue"},
|
{hex: "#006A9E", name: "5017 Traffic blue"},
|
||||||
{hex: "#005F87", name: "5018 Turquoise blue"},
|
{hex: "#005F87", name: "5018 Turquoise blue"},
|
||||||
{hex: "#1B5583", name: "5019 Capri blue"},
|
{hex: "#5A3D5B", name: "5019 Capsanthin violet"},
|
||||||
{hex: "#04749F", name: "5020 Ocean blue"},
|
{hex: "#04749F", name: "5020 Ocean blue"},
|
||||||
{hex: "#009AAA", name: "5021 Water blue"},
|
{hex: "#009AAA", name: "5021 Water blue"},
|
||||||
{hex: "#1A3D5F", name: "5022 Night blue"},
|
{hex: "#1A3D5F", name: "5022 Night blue"},
|
||||||
@@ -366,7 +366,7 @@ const palettes = {
|
|||||||
{hex: "#00A550", name: "6039 Mint green"},
|
{hex: "#00A550", name: "6039 Mint green"},
|
||||||
{hex: "#7D7D7D", name: "7000 Squirrel grey"},
|
{hex: "#7D7D7D", name: "7000 Squirrel grey"},
|
||||||
{hex: "#A8A9AD", name: "7001 Silver grey"},
|
{hex: "#A8A9AD", name: "7001 Silver grey"},
|
||||||
{hex: "#6F7368", name: "7002 Olive grey"},
|
{hex: "#6F7368", name: "7002 Olivestone"},
|
||||||
{hex: "#8F9080", name: "7003 Moss grey"},
|
{hex: "#8F9080", name: "7003 Moss grey"},
|
||||||
{hex: "#8E9291", name: "7004 Signal grey"},
|
{hex: "#8E9291", name: "7004 Signal grey"},
|
||||||
{hex: "#8B8788", name: "7005 Mouse grey"},
|
{hex: "#8B8788", name: "7005 Mouse grey"},
|
||||||
|
|||||||
@@ -1432,7 +1432,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
|
|||||||
}
|
}
|
||||||
if (!header_found) {
|
if (!header_found) {
|
||||||
// TRN %1% is the application name.
|
// TRN %1% is the application name.
|
||||||
std::string error_message = format(_u8L("Not a G-code file generated by %1%."), SLIC3R_APP_FULL_NAME);
|
std::string error_message = format(_u8L("Not a gcode file generated by %1%."), SLIC3R_APP_FULL_NAME);
|
||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << error_message;
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << error_message;
|
||||||
throw Slic3r::RuntimeError(error_message.c_str());
|
throw Slic3r::RuntimeError(error_message.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10120,7 +10120,7 @@ bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const Mo
|
|||||||
assert(file_data != nullptr);
|
assert(file_data != nullptr);
|
||||||
if (file_data == nullptr && !svg.path.empty()) file_data = read_from_disk(svg.path);
|
if (file_data == nullptr && !svg.path.empty()) file_data = read_from_disk(svg.path);
|
||||||
if (file_data == nullptr) {
|
if (file_data == nullptr) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Can't write SVG file no filedata";
|
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file no filedata";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::string &file_data_str = *file_data;
|
const std::string &file_data_str = *file_data;
|
||||||
@@ -10136,7 +10136,7 @@ void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume
|
|||||||
stream << " <" << SHAPE_TAG << " ";
|
stream << " <" << SHAPE_TAG << " ";
|
||||||
if (es.svg_file.has_value())
|
if (es.svg_file.has_value())
|
||||||
if (!to_xml(stream, *es.svg_file, volume, archive, export_full_path)) {
|
if (!to_xml(stream, *es.svg_file, volume, archive, export_full_path)) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Can't write SVG file defiden embossed shape into 3mf";
|
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf";
|
||||||
}
|
}
|
||||||
|
|
||||||
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
|
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
|
||||||
|
|||||||
@@ -1421,7 +1421,6 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"initial_layer_travel_acceleration",
|
"initial_layer_travel_acceleration",
|
||||||
"initial_layer_travel_jerk",
|
"initial_layer_travel_jerk",
|
||||||
"exclude_object",
|
"exclude_object",
|
||||||
"exclude_object_tolerance",
|
|
||||||
"slow_down_layers",
|
"slow_down_layers",
|
||||||
"infill_anchor",
|
"infill_anchor",
|
||||||
"infill_anchor_max",
|
"infill_anchor_max",
|
||||||
|
|||||||
@@ -309,7 +309,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
"gcode_comments",
|
"gcode_comments",
|
||||||
"gcode_label_objects",
|
"gcode_label_objects",
|
||||||
"exclude_object",
|
"exclude_object",
|
||||||
"exclude_object_tolerance",
|
|
||||||
"support_material_interface_fan_speed",
|
"support_material_interface_fan_speed",
|
||||||
"internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control
|
"internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control
|
||||||
"ironing_fan_speed",
|
"ironing_fan_speed",
|
||||||
@@ -6930,7 +6929,7 @@ BoundingBoxf3 PrintInstance::get_bounding_box() const {
|
|||||||
|
|
||||||
Polygon PrintInstance::get_convex_hull_2d() {
|
Polygon PrintInstance::get_convex_hull_2d() {
|
||||||
Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix());
|
Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix());
|
||||||
poly.douglas_peucker(scale_(print_object->print()->config().exclude_object_tolerance.value));
|
poly.douglas_peucker(scale_(0.1));
|
||||||
return poly;
|
return poly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4629,17 +4629,6 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
def = this->add("exclude_object_tolerance", coFloat);
|
|
||||||
def->label = L("Outline tolerance");
|
|
||||||
def->tooltip = L("Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the "
|
|
||||||
"original outline. Larger values give fewer points, which helps firmware that stalls "
|
|
||||||
"while parsing G-code with many objects. 0 keeps every point.");
|
|
||||||
def->sidetext = L("mm");
|
|
||||||
def->min = 0;
|
|
||||||
def->max = 5;
|
|
||||||
def->mode = comExpert;
|
|
||||||
def->set_default_value(new ConfigOptionFloat(0.1));
|
|
||||||
|
|
||||||
def = this->add("gcode_comments", coBool);
|
def = this->add("gcode_comments", coBool);
|
||||||
def->label = L("Verbose G-code");
|
def->label = L("Verbose G-code");
|
||||||
def->tooltip = L("Enable this to get a commented G-code file, with each line explained by a descriptive text. "
|
def->tooltip = L("Enable this to get a commented G-code file, with each line explained by a descriptive text. "
|
||||||
|
|||||||
@@ -2130,7 +2130,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionPercents, filament_shrinkage_compensation_z))
|
((ConfigOptionPercents, filament_shrinkage_compensation_z))
|
||||||
((ConfigOptionBool, gcode_label_objects))
|
((ConfigOptionBool, gcode_label_objects))
|
||||||
((ConfigOptionBool, exclude_object))
|
((ConfigOptionBool, exclude_object))
|
||||||
((ConfigOptionFloat, exclude_object_tolerance))
|
|
||||||
((ConfigOptionFloats, grab_length))
|
((ConfigOptionFloats, grab_length))
|
||||||
((ConfigOptionBool, gcode_comments))
|
((ConfigOptionBool, gcode_comments))
|
||||||
((ConfigOptionInt, slow_down_layers))
|
((ConfigOptionInt, slow_down_layers))
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||||||
// BBS: add the logic to process from an existed gcode file
|
// BBS: add the logic to process from an existed gcode file
|
||||||
if (m_print->finished()) {
|
if (m_print->finished()) {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file") % __LINE__;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file") % __LINE__;
|
||||||
m_fff_print->set_status(80, _utf8(L("Processing G-code from previous file\u2026" /* \u2026 -> ... */)));
|
m_fff_print->set_status(80, _utf8(L("Processing G-Code from previous file\u2026")));
|
||||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||||
// Post the Slicing Finished message for the G-code viewer to update.
|
// Post the Slicing Finished message for the G-code viewer to update.
|
||||||
// Passing the timestamp
|
// Passing the timestamp
|
||||||
|
|||||||
@@ -303,12 +303,10 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
return {
|
return {
|
||||||
{"Color Picker", _u8L("Color Picker")},
|
{"Color Picker", _u8L("Color Picker")},
|
||||||
{"Basic Colors", _u8L("Basic Colors")},
|
{"Basic Colors", _u8L("Basic Colors")},
|
||||||
// TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
{"Orca Palette", _u8L("Orca Palette")},// TRN: Keep the brand name "Orca" unchanged; translate only the palette descriptor.
|
||||||
{"Orca Palette", _u8L("Orca Palette")},
|
{"RAL Classic", _u8L("RAL Classic")}, // TRN: Keep the standard/brand name "RAL" unchanged.
|
||||||
// TRN: Keep the standard/brand name "RAL" unchanged.
|
|
||||||
{"RAL Classic", _u8L("RAL Classic")},
|
|
||||||
{"Gradients", _u8L("Gradients")},
|
{"Gradients", _u8L("Gradients")},
|
||||||
{"Solid", _u8L_CONTEXT("Solid", "Color picker")},
|
{"Solid", _u8L("Solid")},
|
||||||
{"Gradient", _u8L("Gradient")},
|
{"Gradient", _u8L("Gradient")},
|
||||||
{"Gradient start", _u8L("Gradient start")},
|
{"Gradient start", _u8L("Gradient start")},
|
||||||
{"Gradient end", _u8L("Gradient end")},
|
{"Gradient end", _u8L("Gradient end")},
|
||||||
@@ -442,7 +440,7 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
{"Purple", _u8L("Purple")},
|
{"Purple", _u8L("Purple")},
|
||||||
{"Rebecca purple", _u8L("Rebecca purple")},
|
{"Rebecca purple", _u8L("Rebecca purple")},
|
||||||
{"Medium violet red", _u8L("Medium violet red")},
|
{"Medium violet red", _u8L("Medium violet red")},
|
||||||
{"Pale violet red", _u8L("Pale violet red")},
|
{"PaleV violet red", _u8L("PaleV violet red")},
|
||||||
{"Deep pink", _u8L("Deep pink")},
|
{"Deep pink", _u8L("Deep pink")},
|
||||||
{"Hot pink", _u8L("Hot pink")},
|
{"Hot pink", _u8L("Hot pink")},
|
||||||
{"Light pink", _u8L("Light pink")},
|
{"Light pink", _u8L("Light pink")},
|
||||||
@@ -498,7 +496,7 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
{"Turquoise bay", _u8L("Turquoise bay")},
|
{"Turquoise bay", _u8L("Turquoise bay")},
|
||||||
{"Sky over reef", _u8L("Sky over reef")},
|
{"Sky over reef", _u8L("Sky over reef")},
|
||||||
{"Shallow sky", _u8L("Shallow sky")},
|
{"Shallow sky", _u8L("Shallow sky")},
|
||||||
{"Electric blue", _u8L("Electric blue")},
|
{"Electric bel", _u8L("Electric bel")},
|
||||||
{"Dolphin", _u8L("Dolphin")},
|
{"Dolphin", _u8L("Dolphin")},
|
||||||
{"Ocean depths", _u8L("Ocean depths")},
|
{"Ocean depths", _u8L("Ocean depths")},
|
||||||
{"Whale", _u8L("Whale")},
|
{"Whale", _u8L("Whale")},
|
||||||
@@ -521,7 +519,7 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
{"Golden yellow", _u8L("Golden yellow")},
|
{"Golden yellow", _u8L("Golden yellow")},
|
||||||
{"Honey yellow", _u8L("Honey yellow")},
|
{"Honey yellow", _u8L("Honey yellow")},
|
||||||
{"Maize yellow", _u8L("Maize yellow")},
|
{"Maize yellow", _u8L("Maize yellow")},
|
||||||
{"Daffodil yellow", _u8L("Daffodil yellow")},
|
{"Dalmatian yellow", _u8L("Dalmatian yellow")},
|
||||||
{"Brown beige", _u8L("Brown beige")},
|
{"Brown beige", _u8L("Brown beige")},
|
||||||
{"Lemon yellow", _u8L("Lemon yellow")},
|
{"Lemon yellow", _u8L("Lemon yellow")},
|
||||||
{"Oyster white", _u8L("Oyster white")},
|
{"Oyster white", _u8L("Oyster white")},
|
||||||
@@ -608,7 +606,7 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
{"Pigeon blue", _u8L("Pigeon blue")},
|
{"Pigeon blue", _u8L("Pigeon blue")},
|
||||||
{"Traffic blue", _u8L("Traffic blue")},
|
{"Traffic blue", _u8L("Traffic blue")},
|
||||||
{"Turquoise blue", _u8L("Turquoise blue")},
|
{"Turquoise blue", _u8L("Turquoise blue")},
|
||||||
{"Capri blue", _u8L("Capri blue")},
|
{"Capsanthin violet", _u8L("Capsanthin violet")},
|
||||||
{"Ocean blue", _u8L("Ocean blue")},
|
{"Ocean blue", _u8L("Ocean blue")},
|
||||||
{"Water blue", _u8L("Water blue")},
|
{"Water blue", _u8L("Water blue")},
|
||||||
{"Night blue", _u8L("Night blue")},
|
{"Night blue", _u8L("Night blue")},
|
||||||
@@ -646,7 +644,7 @@ nlohmann::json color_picker_ui_strings()
|
|||||||
{"Luminous green", _u8L("Luminous green")},
|
{"Luminous green", _u8L("Luminous green")},
|
||||||
{"Squirrel grey", _u8L("Squirrel grey")},
|
{"Squirrel grey", _u8L("Squirrel grey")},
|
||||||
{"Silver grey", _u8L("Silver grey")},
|
{"Silver grey", _u8L("Silver grey")},
|
||||||
{"Olive grey", _u8L("Olive grey")},
|
{"Olivestone", _u8L("Olivestone")},
|
||||||
{"Moss grey", _u8L("Moss grey")},
|
{"Moss grey", _u8L("Moss grey")},
|
||||||
{"Signal grey", _u8L("Signal grey")},
|
{"Signal grey", _u8L("Signal grey")},
|
||||||
{"Mouse grey", _u8L("Mouse grey")},
|
{"Mouse grey", _u8L("Mouse grey")},
|
||||||
|
|||||||
@@ -1245,8 +1245,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
|||||||
if(gcf_is_klipper)
|
if(gcf_is_klipper)
|
||||||
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
|
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
|
||||||
|
|
||||||
toggle_line("exclude_object_tolerance", gcf_is_klipper && config->opt_bool("exclude_object"));
|
|
||||||
|
|
||||||
bool have_make_overhang_printable = config->opt_bool("make_overhang_printable");
|
bool have_make_overhang_printable = config->opt_bool("make_overhang_printable");
|
||||||
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
||||||
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
||||||
|
|||||||
@@ -1350,7 +1350,7 @@ void GLGizmoSVG::draw_window()
|
|||||||
assert(m_volume_id.valid());
|
assert(m_volume_id.valid());
|
||||||
if (m_volume == nullptr ||
|
if (m_volume == nullptr ||
|
||||||
m_volume_id.invalid()) {
|
m_volume_id.invalid()) {
|
||||||
ImGui::Text("%s", _u8L("Not valid state please report reproduction steps on GitHub").c_str());
|
ImGui::Text("%s", _u8L("Not valid state please report reproduction steps on github").c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,13 +1362,13 @@ void GLGizmoSVG::draw_window()
|
|||||||
|
|
||||||
assert(m_volume->emboss_shape->svg_file.has_value());
|
assert(m_volume->emboss_shape->svg_file.has_value());
|
||||||
if (!m_volume->emboss_shape->svg_file.has_value()){
|
if (!m_volume->emboss_shape->svg_file.has_value()){
|
||||||
ImGui::Text("%s", _u8L("Missing SVG file in embossed shape").c_str());
|
ImGui::Text("%s", _u8L("Missing svg file in embossed shape").c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(m_volume->emboss_shape->svg_file->file_data != nullptr);
|
assert(m_volume->emboss_shape->svg_file->file_data != nullptr);
|
||||||
if (m_volume->emboss_shape->svg_file->file_data == nullptr){
|
if (m_volume->emboss_shape->svg_file->file_data == nullptr){
|
||||||
ImGui::Text("%s", _u8L("Missing data of SVG file").c_str());
|
ImGui::Text("%s", _u8L("Missing data of svg file").c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ void NetworkTestDialog::start_test_url(TestJob job, wxString name, wxString url)
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_ip_resolve([this,name,job](std::string ip) {
|
.on_ip_resolve([this,name,job](std::string ip) {
|
||||||
wxString ip_report = wxString::Format(_L("test %s IP resolved = %s"), name, ip);
|
wxString ip_report = wxString::Format(_L("test %s ip resolved = %s"), name, ip);
|
||||||
update_status(job, ip_report);
|
update_status(job, ip_report);
|
||||||
})
|
})
|
||||||
.on_error([this,name,job](std::string body, std::string error, unsigned int status) {
|
.on_error([this,name,job](std::string body, std::string error, unsigned int status) {
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ bool run_post_process_scripts(
|
|||||||
|
|
||||||
auto gcode_file = boost::filesystem::path(path);
|
auto gcode_file = boost::filesystem::path(path);
|
||||||
if (!boost::filesystem::exists(gcode_file))
|
if (!boost::filesystem::exists(gcode_file))
|
||||||
throw Slic3r::RuntimeError(_u8L("Post-processor can't find exported G-code file"));
|
throw Slic3r::RuntimeError(_u8L("Post-processor can't find exported gcode file"));
|
||||||
|
|
||||||
// Store print configuration into environment variables.
|
// Store print configuration into environment variables.
|
||||||
config.setenv_();
|
config.setenv_();
|
||||||
|
|||||||
@@ -3269,7 +3269,6 @@ void TabPrint::build()
|
|||||||
optgroup->append_single_option_line("gcode_comments", "others_settings_g_code_output#verbose-g-code");
|
optgroup->append_single_option_line("gcode_comments", "others_settings_g_code_output#verbose-g-code");
|
||||||
optgroup->append_single_option_line("gcode_label_objects", "others_settings_g_code_output#label-objects");
|
optgroup->append_single_option_line("gcode_label_objects", "others_settings_g_code_output#label-objects");
|
||||||
optgroup->append_single_option_line("exclude_object", "others_settings_g_code_output#exclude-objects");
|
optgroup->append_single_option_line("exclude_object", "others_settings_g_code_output#exclude-objects");
|
||||||
optgroup->append_single_option_line("exclude_object_tolerance", "others_settings_g_code_output#outline-tolerance");
|
|
||||||
option = optgroup->get_option("filename_format");
|
option = optgroup->get_option("filename_format");
|
||||||
// option.opt.full_width = true;
|
// option.opt.full_width = true;
|
||||||
option.opt.is_code = true;
|
option.opt.is_code = true;
|
||||||
@@ -5279,7 +5278,7 @@ void TabPrinter::build_fff()
|
|||||||
option.opt.width = Field::def_width_wider();
|
option.opt.width = Field::def_width_wider();
|
||||||
option.opt.tooltip = L("Select the network agent implementation for printer communication. "
|
option.opt.tooltip = L("Select the network agent implementation for printer communication. "
|
||||||
"Available agents are registered at startup.");
|
"Available agents are registered at startup.");
|
||||||
optgroup->append_single_option_line(option, "printer_basic_information_advanced#printer-agent");
|
optgroup->append_single_option_line(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -296,16 +296,18 @@ void PluginAuditManager::add_denied_filename(const std::string& filename)
|
|||||||
std::vector<std::string> PluginAuditManager::default_denied_filenames()
|
std::vector<std::string> PluginAuditManager::default_denied_filenames()
|
||||||
{
|
{
|
||||||
// AppConfig::config_path() picks .conf vs .ini on the USE_JSON_CONFIG ifdef and the app key
|
// AppConfig::config_path() picks .conf vs .ini on the USE_JSON_CONFIG ifdef and the app key
|
||||||
// on its mode, and is a non-static member we cannot call without an instance. Denying all
|
// on its mode, and is a non-static member we cannot call without an instance. Denying all
|
||||||
// four names is cheaper and more robust than replicating that; the two unused names cost one
|
// four names is cheaper and more robust than replicating that; the two unused names cost one
|
||||||
// string comparison each. Single-sourced here so install_hook() and the tests seed from the
|
// string comparison each. Keep the plugin install-state sidecar here too: it controls which
|
||||||
// same list and cannot drift apart.
|
// packages load on startup and must remain host-managed. Single-sourced so install_hook() and
|
||||||
|
// the tests seed from the same list and cannot drift apart.
|
||||||
return {
|
return {
|
||||||
SLIC3R_APP_KEY ".conf",
|
SLIC3R_APP_KEY ".conf",
|
||||||
GCODEVIEWER_APP_KEY ".conf",
|
GCODEVIEWER_APP_KEY ".conf",
|
||||||
SLIC3R_APP_KEY ".ini",
|
SLIC3R_APP_KEY ".ini",
|
||||||
GCODEVIEWER_APP_KEY ".ini",
|
GCODEVIEWER_APP_KEY ".ini",
|
||||||
secret_constants::USER_SECRET_FILENAME,
|
secret_constants::USER_SECRET_FILENAME,
|
||||||
|
".install_state.json",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,8 +1078,9 @@ void PluginAuditManager::install_hook()
|
|||||||
// "cert" denied-path keyword seeded below.
|
// "cert" denied-path keyword seeded below.
|
||||||
add_global_allowed_root(resources_dir(), /*allow_write=*/false);
|
add_global_allowed_root(resources_dir(), /*allow_write=*/false);
|
||||||
|
|
||||||
// The user's app config and cloud credentials live directly inside data_dir(), so the
|
// The user's app config and cloud credentials live directly inside data_dir(), and plugin
|
||||||
// root just granted would otherwise expose them to any plugin. Deny them by name.
|
// install state lives beneath it, so the root just granted would otherwise expose them to
|
||||||
|
// plugins. Deny them by name.
|
||||||
//
|
//
|
||||||
// Seeded here rather than by each secret's owner because install_hook() runs during lazy
|
// Seeded here rather than by each secret's owner because install_hook() runs during lazy
|
||||||
// interpreter init and therefore provably precedes any plugin bytecode, whereas
|
// interpreter init and therefore provably precedes any plugin bytecode, whereas
|
||||||
|
|||||||
@@ -51,44 +51,6 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The outline written to EXCLUDE_OBJECT_DEFINE is simplified with exclude_object_tolerance. A larger value must
|
|
||||||
// leave fewer points, which keeps the exclude-object array small on printers that stall while parsing it.
|
|
||||||
TEST_CASE("A larger exclude object tolerance writes fewer outline points", "[GCode]")
|
|
||||||
{
|
|
||||||
const auto slice_with_tolerance = [](const std::string &tolerance) {
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({{"gcode_flavor", "klipper"}, {"exclude_object", "1"},
|
|
||||||
{"exclude_object_tolerance", tolerance}});
|
|
||||||
// Default facet angle (2 degrees): 180 hull points, well inside either tolerance.
|
|
||||||
return Test::slice({make_cylinder(20., 5.)}, config);
|
|
||||||
};
|
|
||||||
// Points of the POLYGON=[[x,y],...] on the first EXCLUDE_OBJECT_DEFINE line.
|
|
||||||
const auto polygon_points = [](const std::string &gcode) {
|
|
||||||
const size_t define = gcode.find("EXCLUDE_OBJECT_DEFINE");
|
|
||||||
REQUIRE(define != std::string::npos);
|
|
||||||
const size_t begin = gcode.find("POLYGON=", define);
|
|
||||||
REQUIRE(begin != std::string::npos);
|
|
||||||
const std::string polygon = gcode.substr(begin, gcode.find('\n', begin) - begin);
|
|
||||||
size_t points = 1;
|
|
||||||
for (size_t pos = polygon.find("],["); pos != std::string::npos; pos = polygon.find("],[", pos + 1))
|
|
||||||
++points;
|
|
||||||
return points;
|
|
||||||
};
|
|
||||||
|
|
||||||
const size_t fine = polygon_points(slice_with_tolerance("0.1"));
|
|
||||||
const size_t coarse = polygon_points(slice_with_tolerance("0.4"));
|
|
||||||
INFO("points at 0.1 mm: " << fine << ", at 0.4 mm: " << coarse);
|
|
||||||
CHECK(coarse >= 4);
|
|
||||||
CHECK(coarse < fine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Existing profiles must keep writing the outlines they wrote when the tolerance was a constant 0.1 mm.
|
|
||||||
TEST_CASE("The exclude object tolerance defaults to the former fixed 0.1 mm", "[GCode]")
|
|
||||||
{
|
|
||||||
CHECK_THAT(DynamicPrintConfig::full_print_config().opt_float("exclude_object_tolerance"),
|
|
||||||
Catch::Matchers::WithinAbs(0.1, 1e-9));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firmwares read the compatibility header lines (Anycubic's GoKlipper heats the bed with
|
// Firmwares read the compatibility header lines (Anycubic's GoKlipper heats the bed with
|
||||||
// first_layer_bed_temperature), so they must describe the filament the plate prints, not filament 1.
|
// first_layer_bed_temperature), so they must describe the filament the plate prints, not filament 1.
|
||||||
TEST_CASE("The G-code header names the first layer temperatures of the filament the plate prints", "[GCode]")
|
TEST_CASE("The G-code header names the first layer temperatures of the filament the plate prints", "[GCode]")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <boost/asio/write.hpp>
|
#include <boost/asio/write.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
#include <boost/nowide/cstdlib.hpp>
|
||||||
#include <boost/system/error_code.hpp>
|
#include <boost/system/error_code.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
#include <istream>
|
#include <istream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -194,6 +196,29 @@ private:
|
|||||||
std::vector<std::shared_ptr<Connection>> m_connections;
|
std::vector<std::shared_ptr<Connection>> m_connections;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sets an environment variable for the lifetime of the guard and restores the previous value.
|
||||||
|
class ScopedEnvVar
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedEnvVar(std::string name, const std::string& value) : m_name(std::move(name))
|
||||||
|
{
|
||||||
|
if (const char* old = boost::nowide::getenv(m_name.c_str()))
|
||||||
|
m_previous = old;
|
||||||
|
boost::nowide::setenv(m_name.c_str(), value.c_str(), 1);
|
||||||
|
}
|
||||||
|
~ScopedEnvVar()
|
||||||
|
{
|
||||||
|
if (m_previous)
|
||||||
|
boost::nowide::setenv(m_name.c_str(), m_previous->c_str(), 1);
|
||||||
|
else
|
||||||
|
boost::nowide::unsetenv(m_name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_name;
|
||||||
|
std::optional<std::string> m_previous;
|
||||||
|
};
|
||||||
|
|
||||||
// What the agent reported through its callbacks, recorded on whichever thread delivers it so the
|
// What the agent reported through its callbacks, recorded on whichever thread delivers it so the
|
||||||
// assertions can run on the test thread.
|
// assertions can run on the test thread.
|
||||||
struct CallbackLog
|
struct CallbackLog
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ void seed_denied_keywords()
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit]")
|
TEST_CASE("Plugin audit denies host-managed filenames anywhere", "[audit]")
|
||||||
{
|
{
|
||||||
seed_denied_names();
|
seed_denied_names();
|
||||||
const PluginAuditManager& mgr = PluginAuditManager::instance();
|
const PluginAuditManager& mgr = PluginAuditManager::instance();
|
||||||
@@ -55,6 +55,7 @@ TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit
|
|||||||
CHECK(mgr.is_denied_filename(fs::path(SLIC3R_APP_KEY ".ini")));
|
CHECK(mgr.is_denied_filename(fs::path(SLIC3R_APP_KEY ".ini")));
|
||||||
CHECK(mgr.is_denied_filename(fs::path(GCODEVIEWER_APP_KEY ".ini")));
|
CHECK(mgr.is_denied_filename(fs::path(GCODEVIEWER_APP_KEY ".ini")));
|
||||||
CHECK(mgr.is_denied_filename(fs::path(secret_constants::USER_SECRET_FILENAME)));
|
CHECK(mgr.is_denied_filename(fs::path(secret_constants::USER_SECRET_FILENAME)));
|
||||||
|
CHECK(mgr.is_denied_filename(fs::path(".install_state.json")));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("companions holding the same secrets are denied by the prefix rule")
|
SECTION("companions holding the same secrets are denied by the prefix rule")
|
||||||
@@ -110,6 +111,7 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
|||||||
|
|
||||||
const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf");
|
const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf");
|
||||||
const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME;
|
const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME;
|
||||||
|
const fs::path install_state = fs::path(data_dir()) / "plugins" / "example" / ".install_state.json";
|
||||||
|
|
||||||
SECTION("a non-denied file inside the allowed root is writable (root really grants writes)")
|
SECTION("a non-denied file inside the allowed root is writable (root really grants writes)")
|
||||||
{
|
{
|
||||||
@@ -150,6 +152,17 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
|||||||
CHECK_FALSE(decision.allowed);
|
CHECK_FALSE(decision.allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("plugin install state is blocked for reads and writes despite the allowed root")
|
||||||
|
{
|
||||||
|
AuditDecision read_decision = mgr.check_open(install_state.string(), "r");
|
||||||
|
CHECK_FALSE(read_decision.allowed);
|
||||||
|
CHECK(read_decision.reason == "denied filename");
|
||||||
|
|
||||||
|
AuditDecision write_decision = mgr.check_open(install_state.string(), "w");
|
||||||
|
CHECK_FALSE(write_decision.allowed);
|
||||||
|
CHECK(write_decision.reason == "denied filename");
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("a traversal path resolving to the config is blocked")
|
SECTION("a traversal path resolving to the config is blocked")
|
||||||
{
|
{
|
||||||
const fs::path traversal = fs::path(data_dir()) / "plugins" / ".." / (SLIC3R_APP_KEY ".conf");
|
const fs::path traversal = fs::path(data_dir()) / "plugins" / ".." / (SLIC3R_APP_KEY ".conf");
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <ios>
|
#include <ios>
|
||||||
#include <libslic3r/LifecycleEvents.hpp>
|
#include <libslic3r/LifecycleEvents.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <slic3r/Utils/Http.hpp>
|
|
||||||
#include <slic3r/Utils/NetworkAgentFactory.hpp>
|
#include <slic3r/Utils/NetworkAgentFactory.hpp>
|
||||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
#include <slic3r/plugin/PluginDescriptor.hpp>
|
||||||
#include <slic3r/plugin/PluginManager.hpp>
|
#include <slic3r/plugin/PluginManager.hpp>
|
||||||
@@ -15,13 +14,8 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <catch2/catch_message.hpp>
|
#include <catch2/catch_message.hpp>
|
||||||
#include "plugin_test_utils.hpp"
|
#include "plugin_test_utils.hpp"
|
||||||
#include "test_utils.hpp"
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <pybind11/cast.h>
|
|
||||||
#include <pybind11/gil.h>
|
|
||||||
#include <pybind11/pybind11.h>
|
|
||||||
#include <pybind11/pytypes.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -1074,43 +1068,3 @@ TEST_CASE("Unloading a plugin that is not loaded is a no-op", "[PluginLifecycle]
|
|||||||
CHECK(manager.unload_plugin("No_Such_Plugin"));
|
CHECK(manager.unload_plugin("No_Such_Plugin"));
|
||||||
CHECK_FALSE(manager.is_plugin_loaded("No_Such_Plugin"));
|
CHECK_FALSE(manager.is_plugin_loaded("No_Such_Plugin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// CA certificates in the default client context, as a plugin's urllib or http.client request would
|
|
||||||
// verify against.
|
|
||||||
int default_context_ca_count()
|
|
||||||
{
|
|
||||||
pybind11::gil_scoped_acquire gil;
|
|
||||||
const pybind11::dict stats = pybind11::module_::import("ssl").attr("create_default_context")().attr("cert_store_stats")();
|
|
||||||
return stats["x509_ca"].cast<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("The bundled Python trusts the system CA certificates", "[PluginLifecycle][Python]")
|
|
||||||
{
|
|
||||||
// The app runs this before plugins load; on Linux it is what points OpenSSL at the distro bundle.
|
|
||||||
Http::tls_global_init();
|
|
||||||
ScopedPluginManager plugin_system;
|
|
||||||
if (!plugin_system.initialized)
|
|
||||||
SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error());
|
|
||||||
|
|
||||||
CHECK(default_context_ca_count() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef _WIN32 // Windows adds its system store whatever SSL_CERT_FILE names.
|
|
||||||
TEST_CASE("SSL_CERT_FILE still overrides the bundled Python's default CA certificates", "[PluginLifecycle][Python]")
|
|
||||||
{
|
|
||||||
// Existing but empty: Linux's CurlGlobalInit replaces an SSL_CERT_FILE that names a missing file.
|
|
||||||
ScopedTemporaryFile empty_bundle(".pem");
|
|
||||||
std::ofstream(empty_bundle.string()).close();
|
|
||||||
ScopedEnvVar ssl_cert_file{"SSL_CERT_FILE", empty_bundle.string()};
|
|
||||||
|
|
||||||
ScopedPluginManager plugin_system;
|
|
||||||
if (!plugin_system.initialized)
|
|
||||||
SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error());
|
|
||||||
|
|
||||||
CHECK(default_context_ca_count() == 0);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -10,13 +10,10 @@
|
|||||||
#include <libslic3r/Utils.hpp>
|
#include <libslic3r/Utils.hpp>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/nowide/cstdlib.hpp>
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#if defined(WIN32) || defined(_WIN32)
|
#if defined(WIN32) || defined(_WIN32)
|
||||||
#define PATH_SEPARATOR R"(\)"
|
#define PATH_SEPARATOR R"(\)"
|
||||||
@@ -200,27 +197,4 @@ private:
|
|||||||
boost::filesystem::path m_previous;
|
boost::filesystem::path m_previous;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sets an environment variable for the lifetime of the guard and restores the previous value.
|
|
||||||
class ScopedEnvVar
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ScopedEnvVar(std::string name, const std::string& value) : m_name(std::move(name))
|
|
||||||
{
|
|
||||||
if (const char* old = boost::nowide::getenv(m_name.c_str()))
|
|
||||||
m_previous = old;
|
|
||||||
boost::nowide::setenv(m_name.c_str(), value.c_str(), 1);
|
|
||||||
}
|
|
||||||
~ScopedEnvVar()
|
|
||||||
{
|
|
||||||
if (m_previous)
|
|
||||||
boost::nowide::setenv(m_name.c_str(), m_previous->c_str(), 1);
|
|
||||||
else
|
|
||||||
boost::nowide::unsetenv(m_name.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_name;
|
|
||||||
std::optional<std::string> m_previous;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SLIC3R_TEST_UTILS
|
#endif // SLIC3R_TEST_UTILS
|
||||||
Reference in new issue
Block a user