mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-19 13:41:03 +00:00
Compare commits
1
Commits
main
..
v2.5.0-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3880f2c58e |
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():
|
||||||
|
|||||||
@@ -574,8 +574,7 @@ DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the
|
|||||||
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
||||||
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
||||||
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
||||||
DMABUF renderer. On a hypervisor guest (`/proc/cpuinfo` `hypervisor` flag) and WebKitGTK >= 2.44 it sets
|
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
||||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` alone, since virtual GPUs cannot back the hardware path either. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
|
||||||
is set, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
|
is set, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
|
||||||
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
|
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
|
||||||
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
|
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
|
||||||
|
|||||||
@@ -568,18 +568,13 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
|
|||||||
## WebKitGTK on Linux sessions
|
## WebKitGTK on Linux sessions
|
||||||
|
|
||||||
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
||||||
sets the DMABUF workarounds and then branches on `GDK_BACKEND`:
|
sets the NVIDIA DMABUF workaround and then branches on `GDK_BACKEND`:
|
||||||
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
||||||
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
||||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
||||||
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
||||||
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
||||||
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
||||||
- **Hypervisor guests**, same position: when `is_hypervisor_guest()` (the `hypervisor` flag in
|
|
||||||
`/proc/cpuinfo`) and WebKitGTK >= 2.44, it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` non-replacing.
|
|
||||||
Virtual GPUs (VirtualBox VMSVGA, VMware, QEMU) cannot back the hardware DMABUF path either, and
|
|
||||||
FORCE_SHM alone was confirmed to fix web views in VirtualBox; the `WEBKIT_FORCE_DMABUF_RENDERER` pair
|
|
||||||
is not needed without the Debian `isNVIDIA()` patch. The flag is x86-only, so ARM guests are not covered.
|
|
||||||
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
||||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`, and in that same branch it sets
|
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`, and in that same branch it sets
|
||||||
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
|
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
|
||||||
@@ -595,9 +590,9 @@ Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_
|
|||||||
(see `references/platforms.md`), not these environment variables.
|
(see `references/platforms.md`), not these environment variables.
|
||||||
|
|
||||||
**Pitfall.**
|
**Pitfall.**
|
||||||
- **Rule:** On NVIDIA and hypervisor guests reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
||||||
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver and virtual GPUs cannot back WebKit's
|
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
|
||||||
hardware DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
||||||
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
||||||
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
||||||
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
||||||
|
|||||||
@@ -232,31 +232,6 @@ jobs:
|
|||||||
mkdir -p ${{ runner.temp }}/profile-check-results
|
mkdir -p ${{ runner.temp }}/profile-check-results
|
||||||
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
|
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
|
||||||
|
|
||||||
# Builds a readable diff of the custom G-code the PR changes, which GitHub shows as
|
|
||||||
# one long line per setting, for check_profiles_comment.yml to post.
|
|
||||||
- name: Diff custom G-code
|
|
||||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
run: |
|
|
||||||
git fetch --no-tags --depth=2 origin "$(git rev-parse HEAD)"
|
|
||||||
results=${{ runner.temp }}/profile-check-results
|
|
||||||
diff=${{ runner.temp }}/gcode_diff.md
|
|
||||||
# HEAD is the PR's merge commit, so HEAD^1 is the base it merges onto.
|
|
||||||
python3 ./scripts/orca_profile_tool.py gcode-diff --base HEAD^1 --head HEAD > "$diff"
|
|
||||||
if [ -s "$diff" ]; then
|
|
||||||
{
|
|
||||||
echo "<!-- profile-gcode-diff-comment -->"
|
|
||||||
cat "$diff"
|
|
||||||
echo ""
|
|
||||||
# The comment workflow finds this line as the only one starting with <sub>.
|
|
||||||
echo "<sub>For commit ${PR_HEAD_SHA} of this PR.</sub>"
|
|
||||||
} > "$results/gcode_diff.md"
|
|
||||||
fi
|
|
||||||
# Tells the comment workflow that a missing gcode_diff.md means no G-code changed.
|
|
||||||
touch "$results/gcode_diff_done.txt"
|
|
||||||
|
|
||||||
- name: Prepare comment artifact
|
- name: Prepare comment artifact
|
||||||
if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.legacy_compat.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.legacy_compat.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
GH_REPO: ${{ github.repository }}
|
GH_REPO: ${{ github.repository }}
|
||||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
||||||
run: |
|
run: |
|
||||||
if [ ! -f pr_number.txt ]; then
|
if [ ! -f pr_number.txt ]; then
|
||||||
echo "No pr_number.txt in artifact, skipping."
|
echo "No pr_number.txt in artifact, skipping."
|
||||||
@@ -50,72 +49,18 @@ jobs:
|
|||||||
echo "Invalid PR number: $PR_NUMBER"
|
echo "Invalid PR number: $PR_NUMBER"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# PR code wrote the artifact, so only a run for the named PR's current head may post.
|
|
||||||
if [ -z "$HEAD_SHA" ]; then
|
|
||||||
echo "The triggering run names no head commit."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! PR_HEAD=$(gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}" --jq .head.sha); then
|
|
||||||
echo "Cannot look up PR #${PR_NUMBER}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ "$HEAD_SHA" != "$PR_HEAD" ]; then
|
|
||||||
echo "${HEAD_SHA} is not the head of PR #${PR_NUMBER}; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Lists every bot comment on the PR, one JSON object per line.
|
|
||||||
COMMENTS=$(gh api --paginate "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
|
|
||||||
--jq '.[] | select(.user.login == "github-actions[bot]") | {id, body} | @json')
|
|
||||||
|
|
||||||
# Delete prior comments matching the marker (from check_profiles.yml) or the legacy heading.
|
# Delete prior comments matching the marker (from check_profiles.yml) or the legacy heading.
|
||||||
OLD_IDS=$(printf '%s\n' "$COMMENTS" | jq -r 'select((.body | startswith("<!-- profile-validation-comment -->")) or (.body | startswith("## :x: Profile Validation Errors"))) | .id')
|
OLD_IDS=$(gh api --paginate "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
|
||||||
|
--jq '.[] | select(.user.login == "github-actions[bot]") | select((.body | startswith("<!-- profile-validation-comment -->")) or (.body | startswith("## :x: Profile Validation Errors"))) | .id')
|
||||||
for comment_id in $OLD_IDS; do
|
for comment_id in $OLD_IDS; do
|
||||||
echo "Deleting outdated profile-validation comment ${comment_id}"
|
echo "Deleting outdated profile-validation comment ${comment_id}"
|
||||||
gh api -X DELETE "repos/${GH_REPO}/issues/comments/${comment_id}" || true
|
gh api -X DELETE "repos/${GH_REPO}/issues/comments/${comment_id}" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Post a new comment only when validation failed (pr_comment.md present).
|
# Post a new comment only when validation failed (pr_comment.md present).
|
||||||
# A failed post fails the job only after the G-code diff is handled.
|
|
||||||
failed=0
|
|
||||||
if [ -f pr_comment.md ]; then
|
if [ -f pr_comment.md ]; then
|
||||||
gh pr comment "$PR_NUMBER" --body-file pr_comment.md || failed=1
|
gh pr comment "$PR_NUMBER" --body-file pr_comment.md
|
||||||
else
|
else
|
||||||
echo "Validation succeeded; cleaned up prior comments without posting."
|
echo "Validation succeeded; cleaned up prior comments without posting."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Makes the G-code diff comment match gcode_diff.md, or deletes it if there is none.
|
|
||||||
update_gcode_diff_comment() {
|
|
||||||
if [ ! -f gcode_diff_done.txt ]; then
|
|
||||||
echo "The G-code diff step did not finish; leaving any G-code diff comment as it is."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
GCODE_IDS=$(printf '%s\n' "$COMMENTS" | jq -r 'select(.body | startswith("<!-- profile-gcode-diff-comment -->")) | .id')
|
|
||||||
if [ -f gcode_diff.md ] && [ "$(wc -c < gcode_diff.md)" -gt 65536 ]; then
|
|
||||||
echo "gcode_diff.md is over GitHub's comment limit; not posting it."
|
|
||||||
rm gcode_diff.md
|
|
||||||
fi
|
|
||||||
if [ -f gcode_diff.md ] && [ "$(echo "$GCODE_IDS" | wc -w)" -eq 1 ]; then
|
|
||||||
OLD_BODY=$(printf '%s\n' "$COMMENTS" | jq -r --argjson id "$GCODE_IDS" 'select(.id == $id) | .body' | tr -d '\r')
|
|
||||||
NEW_BODY=$(tr -d '\r' < gcode_diff.md)
|
|
||||||
if [ "$OLD_BODY" = "$NEW_BODY" ]; then
|
|
||||||
echo "G-code diff unchanged; keeping comment ${GCODE_IDS}."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# Edits in place when only the <sub> line differs, since edits notify nobody.
|
|
||||||
if [ "$(grep -v '^<sub>' <<< "$OLD_BODY")" = "$(grep -v '^<sub>' <<< "$NEW_BODY")" ]; then
|
|
||||||
echo "G-code diff unchanged at a new commit; updating comment ${GCODE_IDS}."
|
|
||||||
gh api -X PATCH "repos/${GH_REPO}/issues/comments/${GCODE_IDS}" -F body=@gcode_diff.md > /dev/null
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
for comment_id in $GCODE_IDS; do
|
|
||||||
echo "Deleting outdated G-code diff comment ${comment_id}"
|
|
||||||
gh api -X DELETE "repos/${GH_REPO}/issues/comments/${comment_id}" || true
|
|
||||||
done
|
|
||||||
if [ -f gcode_diff.md ]; then
|
|
||||||
gh pr comment "$PR_NUMBER" --body-file gcode_diff.md
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
update_gcode_diff_comment
|
|
||||||
exit "$failed"
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -288,19 +288,6 @@ if (APPLE)
|
|||||||
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
|
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
|
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
|
||||||
|
|
||||||
# The Xcode generator ignores CMAKE_<LANG>_COMPILER_LAUNCHER, so hand the launcher to
|
|
||||||
# Xcode's own Compiler Launcher setting, which wraps every clang compile (C, C++,
|
|
||||||
# Objective-C++ and the precompiled header) with one program: the C++ launcher wins when
|
|
||||||
# both are set. The setting takes a single program path, not a CMake launcher list.
|
|
||||||
# Xcode versions without it ignore it and build uncached.
|
|
||||||
if (CMAKE_GENERATOR STREQUAL "Xcode")
|
|
||||||
if (CMAKE_CXX_COMPILER_LAUNCHER)
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_CXX_COMPILER_LAUNCHER}")
|
|
||||||
elseif (CMAKE_C_COMPILER_LAUNCHER)
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_C_COMPILER_LAUNCHER}")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
|
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||||
|
|||||||
@@ -111,54 +111,6 @@ else
|
|||||||
export CMAKE_POLICY_COMPAT=""
|
export CMAKE_POLICY_COMPAT=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Compiler cache, as in build_linux.sh. The program comes from CMAKE_CCACHE, else from a
|
|
||||||
# launcher already set in the environment (the CI build sets ccache there), else sccache or
|
|
||||||
# ccache from PATH; one program serves both languages. Each run applies its own choice to the
|
|
||||||
# build directories, replacing a launcher an earlier run or a hand-run configure recorded.
|
|
||||||
CMAKE_CCACHE_ARGS=()
|
|
||||||
CCACHE_VAR=""
|
|
||||||
for var in CMAKE_CCACHE CMAKE_CXX_COMPILER_LAUNCHER CMAKE_C_COMPILER_LAUNCHER; do
|
|
||||||
if [ -n "${!var:-}" ]; then
|
|
||||||
CCACHE_VAR=$var
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ -n "$CCACHE_VAR" ]; then
|
|
||||||
CCACHE_PROGRAM=${!CCACHE_VAR}
|
|
||||||
echo "Checking ${CCACHE_VAR} environment variable for compiler cache program..."
|
|
||||||
CMAKE_CCACHE=$(command -v "${CCACHE_PROGRAM}") || {
|
|
||||||
echo "${CCACHE_VAR} environment variable is set to '${CCACHE_PROGRAM}' but it was not found in PATH."
|
|
||||||
CMAKE_CCACHE=""
|
|
||||||
}
|
|
||||||
elif command -v sccache >/dev/null 2>&1 ; then
|
|
||||||
CMAKE_CCACHE=$(command -v sccache)
|
|
||||||
elif command -v ccache >/dev/null 2>&1 ; then
|
|
||||||
CMAKE_CCACHE=$(command -v ccache)
|
|
||||||
fi
|
|
||||||
if [ -n "${CMAKE_CCACHE}" ] ; then
|
|
||||||
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
|
|
||||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
|
|
||||||
# The same ccache settings as the CI build. Without this sloppiness ccache refuses every
|
|
||||||
# compile that uses the precompiled header, which is most of OrcaSlicer, and skips headers
|
|
||||||
# a checkout has just written. It is added to the sloppiness already configured, and
|
|
||||||
# CCACHE_SLOPPINESS or CCACHE_(NO)DEPEND set in the environment win.
|
|
||||||
if [[ "$(basename "${CMAKE_CCACHE}")" == ccache ]] ; then
|
|
||||||
if [[ -z "${CCACHE_SLOPPINESS+x}" ]] ; then
|
|
||||||
CCACHE_SLOPPINESS=$("${CMAKE_CCACHE}" --get-config sloppiness 2>/dev/null || true)
|
|
||||||
export CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS:+${CCACHE_SLOPPINESS},}pch_defines,time_macros,include_file_mtime,include_file_ctime"
|
|
||||||
fi
|
|
||||||
# Hash the includes the compiler reports instead of preprocessing every miss.
|
|
||||||
if [[ -z "${CCACHE_DEPEND+x}${CCACHE_NODEPEND+x}" ]] ; then
|
|
||||||
export CCACHE_DEPEND=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
|
|
||||||
# Clear a launcher recorded by an earlier configure. The entries must be set empty rather
|
|
||||||
# than removed: CMake fills a missing launcher entry from the environment.
|
|
||||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER:STRING= -DCMAKE_CXX_COMPILER_LAUNCHER:STRING=)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Build params:"
|
echo "Build params:"
|
||||||
echo " - ARCH: $ARCH"
|
echo " - ARCH: $ARCH"
|
||||||
echo " - BUILD_CONFIG: $BUILD_CONFIG"
|
echo " - BUILD_CONFIG: $BUILD_CONFIG"
|
||||||
@@ -210,7 +162,6 @@ function build_deps() {
|
|||||||
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
|
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
|
||||||
${CMAKE_POLICY_COMPAT}
|
${CMAKE_POLICY_COMPAT}
|
||||||
fi
|
fi
|
||||||
cmake --build . --config "$BUILD_CONFIG" --target deps
|
cmake --build . --config "$BUILD_CONFIG" --target deps
|
||||||
@@ -313,7 +264,6 @@ function build_slicer() {
|
|||||||
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
|
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
|
||||||
${CMAKE_POLICY_COMPAT}
|
${CMAKE_POLICY_COMPAT}
|
||||||
fi
|
fi
|
||||||
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
|
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
|
||||||
|
|||||||
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.
|
||||||
|
|||||||
@@ -53,13 +53,6 @@ Space counts as typing. The speed dial's default is the one bare Space, and
|
|||||||
`MainFrame` leaves it to a focused control that uses Space itself (text fields, buttons,
|
`MainFrame` leaves it to a focused control that uses Space itself (text fields, buttons,
|
||||||
combo boxes), so it opens the dial from the canvases and the tab strip only.
|
combo boxes), so it opens the dial from the canvases and the tab strip only.
|
||||||
|
|
||||||
`MainFrame::handle_global_shortcut` skips a chord that `KeyChord::edits_text()` reports
|
|
||||||
while a text field has the focus (a wx text entry, spin control, web view or ImGui
|
|
||||||
InputText), and skips every chord while an ImGui modal is open, as a wx modal dialog
|
|
||||||
would. `ImGuiWrapper::owns_key()` reads the active InputText and the popup stack
|
|
||||||
directly, since `WantCaptureKeyboard` lags a frame, and is called only when the focus is on
|
|
||||||
a visible canvas from `OpenGLManager::create_wxglcanvas()`.
|
|
||||||
|
|
||||||
## Which event a chord matches
|
## Which event a chord matches
|
||||||
|
|
||||||
Letters, digits and special keys match on `wxEVT_KEY_DOWN`. Its key codes do not depend
|
Letters, digits and special keys match on `wxEVT_KEY_DOWN`. Its key codes do not depend
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|
||||||
@@ -10657,9 +10657,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 ""
|
||||||
|
|
||||||
@@ -12318,7 +12315,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 +12412,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 +15645,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 +20540,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 +26492,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 +26877,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 +27039,7 @@ msgstr ""
|
|||||||
msgid "Shallow sky"
|
msgid "Shallow sky"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Electric blue"
|
msgid "Electric bel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Dolphin"
|
msgid "Dolphin"
|
||||||
@@ -27115,7 +27108,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 +27369,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 +27483,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 +28071,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 +30127,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 +30242,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 +30598,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 +30686,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 " 不在合理的區間"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Snapmaker",
|
"name": "Snapmaker",
|
||||||
"version": "02.04.00.18",
|
"version": "02.04.00.17",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Snapmaker configurations",
|
"description": "Snapmaker configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
@@ -49,8 +49,5 @@
|
|||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"100"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,5 @@
|
|||||||
],
|
],
|
||||||
"fan_min_speed": [
|
"fan_min_speed": [
|
||||||
"0"
|
"0"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"100"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
"250"
|
"250"
|
||||||
],
|
],
|
||||||
"temperature_vitrification": [
|
"temperature_vitrification": [
|
||||||
"70"
|
"178"
|
||||||
],
|
],
|
||||||
"fan_max_speed": [
|
"fan_max_speed": [
|
||||||
"20"
|
"20"
|
||||||
|
|||||||
@@ -4,8 +4,5 @@
|
|||||||
"inherits": "fdm_filament_petg",
|
"inherits": "fdm_filament_petg",
|
||||||
"from": "system",
|
"from": "system",
|
||||||
"filament_id": "OFW8lqnb",
|
"filament_id": "OFW8lqnb",
|
||||||
"instantiation": "false",
|
"instantiation": "false"
|
||||||
"temperature_vitrification": [
|
|
||||||
"70"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"250"
|
"250"
|
||||||
],
|
],
|
||||||
"temperature_vitrification": [
|
"temperature_vitrification": [
|
||||||
"70"
|
"178"
|
||||||
],
|
],
|
||||||
"fan_max_speed": [
|
"fan_max_speed": [
|
||||||
"20"
|
"20"
|
||||||
|
|||||||
@@ -34,8 +34,5 @@
|
|||||||
],
|
],
|
||||||
"nozzle_temperature": [
|
"nozzle_temperature": [
|
||||||
"220"
|
"220"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"45"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,5 @@
|
|||||||
],
|
],
|
||||||
"filament_cooling_final_speed": [
|
"filament_cooling_final_speed": [
|
||||||
"60"
|
"60"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"45"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,5 @@
|
|||||||
],
|
],
|
||||||
"nozzle_temperature": [
|
"nozzle_temperature": [
|
||||||
"220"
|
"220"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"45"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,5 @@
|
|||||||
],
|
],
|
||||||
"nozzle_temperature": [
|
"nozzle_temperature": [
|
||||||
"220"
|
"220"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"45"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -31,8 +31,5 @@
|
|||||||
],
|
],
|
||||||
"slow_down_layer_time": [
|
"slow_down_layer_time": [
|
||||||
"8"
|
"8"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"45"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,5 @@
|
|||||||
"additional_cooling_fan_speed": [
|
"additional_cooling_fan_speed": [
|
||||||
"70",
|
"70",
|
||||||
"70"
|
"70"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"30"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
"240"
|
"240"
|
||||||
],
|
],
|
||||||
"temperature_vitrification": [
|
"temperature_vitrification": [
|
||||||
"30"
|
"60"
|
||||||
],
|
],
|
||||||
"nozzle_temperature_range_low": [
|
"nozzle_temperature_range_low": [
|
||||||
"200"
|
"200"
|
||||||
|
|||||||
@@ -302,7 +302,7 @@
|
|||||||
"-1"
|
"-1"
|
||||||
],
|
],
|
||||||
"temperature_vitrification": [
|
"temperature_vitrification": [
|
||||||
"30"
|
"60"
|
||||||
],
|
],
|
||||||
"textured_cool_plate_temp": [
|
"textured_cool_plate_temp": [
|
||||||
"40"
|
"40"
|
||||||
|
|||||||
@@ -29,8 +29,5 @@
|
|||||||
"additional_cooling_fan_speed": [
|
"additional_cooling_fan_speed": [
|
||||||
"70",
|
"70",
|
||||||
"70"
|
"70"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"30"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -49,8 +49,5 @@
|
|||||||
"additional_cooling_fan_speed": [
|
"additional_cooling_fan_speed": [
|
||||||
"70",
|
"70",
|
||||||
"70"
|
"70"
|
||||||
],
|
|
||||||
"temperature_vitrification": [
|
|
||||||
"30"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"20",
|
"20",
|
||||||
"12"
|
"12"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\n;===== top cover purifier =====\n{if min_vitrification_temperature <= 50}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=42 ALARM_TEMP=48 FAN_SPEED=0.6 DELAY_OFF=0 ; strong cooling\n{elsif min_vitrification_temperature <= 70}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=45 ALARM_TEMP=51 FAN_SPEED=0.6 DELAY_OFF=0 ; weak cooling\n{else}\nSET_PURIFIER_MODE MODE=3 DESIRE_TEMP=45 FAN_SPEED=0.6 DELAY_OFF=600 ; keep warm\n{endif}\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\n\n;==== 预挤出耗材 =====\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nT{initial_extruder}\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nSM_PRINT_PREEXTRUDE_FILAMENT INDEX={initial_extruder}\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nM107\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nM107\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nM106 S255\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\nM107\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\nM107\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; AlwLine truncated
|
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0\n{endif} \n\n\n;===== 画起始线 =================\nG90\nG1 Z1.5\nG0 X85 Y1 Z2 F18000\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG1 Z0.2\nM83\nG1 X185 E15 F360\nG1 Z1.5\n\nG90\nM106 S0",
|
||||||
"machine_tool_change_time": "5",
|
"machine_tool_change_time": "5",
|
||||||
"max_layer_height": [
|
"max_layer_height": [
|
||||||
"0.14",
|
"0.14",
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
"20",
|
"20",
|
||||||
"12"
|
"12"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\n;===== top cover purifier =====\n{if min_vitrification_temperature <= 50}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=42 ALARM_TEMP=48 FAN_SPEED=0.6 DELAY_OFF=0 ; strong cooling\n{elsif min_vitrification_temperature <= 70}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=45 ALARM_TEMP=51 FAN_SPEED=0.6 DELAY_OFF=0 ; weak cooling\n{else}\nSET_PURIFIER_MODE MODE=3 DESIRE_TEMP=45 FAN_SPEED=0.6 DELAY_OFF=600 ; keep warm\n{endif}\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\n\n;==== 预挤出耗材 =====\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nT{initial_extruder}\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nSM_PRINT_PREEXTRUDE_FILAMENT INDEX={initial_extruder}\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nM107\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nM107\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nM106 S255\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\nM107\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\nM107\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; AlwLine truncated
|
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0\n{endif} \n\n\n;===== 画起始线 =================\nG90\nG1 Z1.5\nG0 X85 Y1 Z2 F18000\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG1 Z0.2\nM83\nG1 X185 E15 F360\nG1 Z1.5\n\nG90\nM106 S0",
|
||||||
"machine_tool_change_time": "5",
|
"machine_tool_change_time": "5",
|
||||||
"max_layer_height": [
|
"max_layer_height": [
|
||||||
"0.32",
|
"0.32",
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
"20",
|
"20",
|
||||||
"12"
|
"12"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\n;===== top cover purifier =====\n{if min_vitrification_temperature <= 50}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=42 ALARM_TEMP=48 FAN_SPEED=0.6 DELAY_OFF=0 ; strong cooling\n{elsif min_vitrification_temperature <= 70}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=45 ALARM_TEMP=51 FAN_SPEED=0.6 DELAY_OFF=0 ; weak cooling\n{else}\nSET_PURIFIER_MODE MODE=3 DESIRE_TEMP=45 FAN_SPEED=0.6 DELAY_OFF=600 ; keep warm\n{endif}\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\n\n;==== 预挤出耗材 =====\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nT{initial_extruder}\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nSM_PRINT_PREEXTRUDE_FILAMENT INDEX={initial_extruder}\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nM107\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nM107\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nM106 S255\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\nM107\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\nM107\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; AlwLine truncated
|
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0\n{endif} \n\n\n;===== 画起始线 =================\nG90\nG1 Z1.5\nG0 X85 Y1 Z2 F18000\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG1 Z0.2\nM83\nG1 X185 E15 F360\nG1 Z1.5\n\nG90\nM106 S0",
|
||||||
"machine_tool_change_time": "5",
|
"machine_tool_change_time": "5",
|
||||||
"max_layer_height": [
|
"max_layer_height": [
|
||||||
"0.42",
|
"0.42",
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
"20",
|
"20",
|
||||||
"12"
|
"12"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\n;===== top cover purifier =====\n{if min_vitrification_temperature <= 50}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=42 ALARM_TEMP=48 FAN_SPEED=0.6 DELAY_OFF=0 ; strong cooling\n{elsif min_vitrification_temperature <= 70}\nSET_PURIFIER_MODE MODE=1 DESIRE_TEMP=45 ALARM_TEMP=51 FAN_SPEED=0.6 DELAY_OFF=0 ; weak cooling\n{else}\nSET_PURIFIER_MODE MODE=3 DESIRE_TEMP=45 FAN_SPEED=0.6 DELAY_OFF=600 ; keep warm\n{endif}\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\n\n;==== 预挤出耗材 =====\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nT{initial_extruder}\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder]}\nSM_PRINT_PREEXTRUDE_FILAMENT INDEX={initial_extruder}\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nM107\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nM107\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nM106 S255\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\nM107\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\nM107\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; AlwLine truncated
|
"machine_start_gcode": "SET_PRINT_AUTO_BED_LEVELING ENABLE=1\nSET_TIME_LAPSE_CAMERA ENABLE=1\n;===== date: 20260128 =====================\n\nPRINT_START\nDEFECT_DETECTION_START\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER=0\nTIMELAPSE_START\nM140 S{bed_temperature_initial_layer_single}\nM104 T{initial_extruder} S140\nM204 S10000\nG28 X Y\nDEFECT_DETECT_NOODLE_FIRST\n;===== 床面异物检测 ========\nT{initial_extruder}\nG90\nDEFECT_DETECTION_DETECT_BED\n;===== 取放头检测 =================\nSM_PRINT_CHECK_SWITCH_EXTRUDER\n\n;===== 自动进料 & 挤出流量 & 预挤出 ======================\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=1 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=0\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=0\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=2 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=1\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=1\nSM_PRINT_EXTRUDER_PREHEAT EXTRUDER=3 TEMP=140\nSM_PRINT_AUTO_FEED EXTRUDER=2\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=2\nSM_PRINT_AUTO_FEED EXTRUDER=3\nSM_PRINT_FLOW_CALIBRATE EXTRUDER=3\nM104 S0 T0 A0\nM104 S0 T1 A0\nM104 S0 T2 A0\nM104 S0 T3 A0\nM104 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\n\n;===== 粗回零 =================\nT{initial_extruder}\nM106 S255\nM106 P2 S0\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 T{initial_extruder} S{nozzle_temperature[initial_extruder] - 90}\nROUGHLY_CLEAN_NOZZLE_WITH_DISCARD\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nG28 Z I140 J140\n\n;===== 检测钢板 =================\nDETECT_BED_PLATE\n\n;===== 深度清洁喷嘴 =================\nG90\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nM109 S{nozzle_temperature[initial_extruder] - 50}\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_1\nM104 S{nozzle_temperature[initial_extruder] - 90}\nG0 Z5 F10000\nMOVE_TO_DISCARD_FILAMENT_POSITION\nROUGHLY_CLEAN_NOZZLE\nMOVE_TO_XY_IDLE_POSITION_EXTRUDER\nFINELY_CLEAN_NOZZLE_STAGE_2\n\n;===== 精回零 =================\nM106 S255\nM109 S{nozzle_temperature[initial_extruder] - 90}\nM190 S{bed_temperature_initial_layer_single}\nM107 P2\nG90\nG0 Z5 F10000\nWAIT_CHAMBER_TEMP TIMEOUT=180\n{if curr_bed_type==\"High Temp Plate\"} \nG28 Z Z_OFFSET -0.07 \n{else} \nG28 Z \n{endif} \n\n\n;===== 热床调平 =================\n{if curr_bed_type==\"High Temp Plate\"} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0 Z_OFFSET=-0.07\n{else} \n; Always pass `ADAPTIVE_MARGIN=0` because Orca has already handled `adaptive_bed_mesh_margin` internally\n; Make sure to set ADAPTIVE to 0 otherwise Klipper will use it's own adaptive bed mesh logic\nBED_MESH_CALIBRATE mesh_min={adaptive_bed_mesh_min[0]},{adaptive_bed_mesh_min[1]} mesh_max={adaptive_bed_mesh_max[0]},{adaptive_bed_mesh_max[1]} ALGORITHM=[bed_mesh_algo] PROBE_COUNT={bed_mesh_probe_count[0]},{bed_mesh_probe_count[1]} ADAPTIVE=0 ADAPTIVE_MARGIN=0\n{endif} \n\n\n;===== 画起始线 =================\nG90\nG1 Z1.5\nG0 X85 Y1 Z2 F18000\nM109 S{nozzle_temperature_initial_layer[initial_extruder]}\nG1 Z0.2\nM83\nG1 X185 E15 F360\nG1 Z1.5\n\nG90\nM106 S0",
|
||||||
"machine_tool_change_time": "5",
|
"machine_tool_change_time": "5",
|
||||||
"max_layer_height": [
|
"max_layer_height": [
|
||||||
"0.56",
|
"0.56",
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -45,10 +45,28 @@
|
|||||||
<color type="primary" scheme_preference="dark">#00695C</color>
|
<color type="primary" scheme_preference="dark">#00695C</color>
|
||||||
</branding>
|
</branding>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="2.5.0-dev" date="2026-06-22" type="development">
|
<release version="2.5.0-alpha" date="2026-10-14" type="development">
|
||||||
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds</url>
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_5_0_alpha</url>
|
||||||
<description>
|
<description>
|
||||||
<p>See the release page for detailed changelog.</p>
|
<p>See the release page for detailed changelog of OrcaSlicer 2.5.0-alpha release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.2" date="2026-07-26">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_2</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.2 release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.1" date="2026-06-26">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_1</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.1 release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.0" date="2026-06-20">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_0</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.0 release.</p>
|
||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
<release version="2.3.2" date="2025-03-23">
|
<release version="2.3.2" date="2025-03-23">
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ commands:
|
|||||||
trim delete profile files no <vendor>.json list references
|
trim delete profile files no <vendor>.json list references
|
||||||
update-index regenerate the *_list sections of <vendor>.json
|
update-index regenerate the *_list sections of <vendor>.json
|
||||||
legacy-compat write the printers user presets can be migrated back to
|
legacy-compat write the printers user presets can be migrated back to
|
||||||
gcode-diff print a readable markdown diff of changed custom G-code
|
|
||||||
|
|
||||||
options shared by several commands:
|
options shared by several commands:
|
||||||
--vendor VENDOR act on one vendor bundle only; repeatable, empty means all
|
--vendor VENDOR act on one vendor bundle only; repeatable, empty means all
|
||||||
@@ -95,14 +94,11 @@ parent in the OFL map. filament_vendor / filament_type resolve the same way.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import difflib
|
|
||||||
import functools
|
import functools
|
||||||
import html
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
@@ -196,7 +192,6 @@ VECTOR_KEYS = {
|
|||||||
|
|
||||||
PRINT_CONFIG_CPP = os.path.normpath(
|
PRINT_CONFIG_CPP = os.path.normpath(
|
||||||
os.path.join(SCRIPTS_DIR, "..", "src", "libslic3r", "PrintConfig.cpp"))
|
os.path.join(SCRIPTS_DIR, "..", "src", "libslic3r", "PrintConfig.cpp"))
|
||||||
TAB_CPP = os.path.normpath(os.path.join(SCRIPTS_DIR, "..", "src", "slic3r", "GUI", "Tab.cpp"))
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
@@ -318,11 +313,11 @@ REMEDY_HINTS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def print_error(msg, file=None):
|
def print_error(msg):
|
||||||
print(f"\033[91m[ERROR]\033[0m {msg}", file=file) # Red
|
print(f"\033[91m[ERROR]\033[0m {msg}") # Red
|
||||||
|
|
||||||
def print_warning(msg, file=None):
|
def print_warning(msg):
|
||||||
print(f"\033[93m[WARNING]\033[0m {msg}", file=file) # Yellow
|
print(f"\033[93m[WARNING]\033[0m {msg}") # Yellow
|
||||||
|
|
||||||
def print_info(msg):
|
def print_info(msg):
|
||||||
print(f"\033[94m[INFO]\033[0m {msg}") # Blue
|
print(f"\033[94m[INFO]\033[0m {msg}") # Blue
|
||||||
@@ -3214,223 +3209,6 @@ def run_legacy_compat(base_dir, profiles_dir, output, check):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# gcode-diff
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# UTF-8 bytes, kept well under GitHub's 65,536-character comment limit.
|
|
||||||
GCODE_DIFF_BUDGET = 60000
|
|
||||||
# The file names a section's summary shows before "and N more".
|
|
||||||
GCODE_DIFF_SUMMARY_FILES = 3
|
|
||||||
|
|
||||||
GCODE_DIFF_HEADER = (
|
|
||||||
"## Custom G-code changes\n"
|
|
||||||
"\n"
|
|
||||||
"GitHub shows custom G-code in a profile as one long line, so below is a line by line "
|
|
||||||
"diff of each changed G-code or other text setting.\n")
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
|
||||||
def _text_option_keys():
|
|
||||||
"""The string options PrintConfig.cpp defines as G-code or other multi-line text
|
|
||||||
(every *_gcode option, and every one it marks multiline, such as notes), plus the
|
|
||||||
ones Tab.cpp edits as code, such as filename_format."""
|
|
||||||
with open(PRINT_CONFIG_CPP, encoding="utf-8") as f:
|
|
||||||
source = f.read()
|
|
||||||
keys = set()
|
|
||||||
for body in re.split(r"\bdef\s*=\s*this->add(?:_nullable)?\(", source)[1:]:
|
|
||||||
match = re.match(r'"(\w+)",\s*coStrings?\b', body)
|
|
||||||
if match and (match.group(1).endswith("_gcode")
|
|
||||||
or re.search(r"def->multiline\s*=\s*true", body)):
|
|
||||||
keys.add(match.group(1))
|
|
||||||
if "machine_start_gcode" not in keys:
|
|
||||||
raise RuntimeError(f"{PRINT_CONFIG_CPP} no longer defines machine_start_gcode")
|
|
||||||
# Tab.cpp only adds keys, so a missing file is not an error.
|
|
||||||
try:
|
|
||||||
with open(TAB_CPP, encoding="utf-8") as f:
|
|
||||||
gui = f.read()
|
|
||||||
except OSError:
|
|
||||||
gui = ""
|
|
||||||
keys.update(re.findall(r'get_option\("(\w+)"\);(?:(?!get_option\().)*?\bis_code\s*=\s*true',
|
|
||||||
gui, re.DOTALL))
|
|
||||||
return frozenset(keys)
|
|
||||||
|
|
||||||
|
|
||||||
def _git(cwd, *args, stdin=None):
|
|
||||||
return subprocess.run(["git", "-C", cwd, *args], input=stdin, check=True,
|
|
||||||
capture_output=True).stdout
|
|
||||||
|
|
||||||
|
|
||||||
def _blobs_at(profiles_dir, specs):
|
|
||||||
"""{(ref, path): bytes, or None if not a file there} for (ref, path relative to
|
|
||||||
profiles_dir) pairs, all read by one git cat-file process."""
|
|
||||||
prefix = _git(profiles_dir, "rev-parse", "--show-prefix").decode("utf-8").strip()
|
|
||||||
out = _git(profiles_dir, "cat-file", "--batch",
|
|
||||||
stdin="".join(f"{ref}:{prefix}{rel}\n" for ref, rel in specs).encode("utf-8"))
|
|
||||||
blobs, pos = {}, 0
|
|
||||||
for spec in specs:
|
|
||||||
end = out.index(b"\n", pos)
|
|
||||||
header = out[pos:end]
|
|
||||||
pos = end + 1
|
|
||||||
# "<name> missing" and "<name> ambiguous" have no content; "<oid> <type> <size>" does.
|
|
||||||
if header.endswith((b" missing", b" ambiguous")):
|
|
||||||
blobs[spec] = None
|
|
||||||
continue
|
|
||||||
_oid, kind, size = header.split()
|
|
||||||
blobs[spec] = out[pos:pos + int(size)] if kind == b"blob" else None
|
|
||||||
pos += int(size) + 1
|
|
||||||
return blobs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_profile(raw, rel, where):
|
|
||||||
"""The JSON in raw, or None with a warning when it is absent or not JSON."""
|
|
||||||
if raw is None:
|
|
||||||
print_warning(f"{rel}: skipped, not found at {where}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return json.loads(raw.decode("utf-8-sig"))
|
|
||||||
except ValueError as e:
|
|
||||||
print_warning(f"{rel}: skipped, cannot read it at {where}: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _changed_profiles(profiles_dir, base, head):
|
|
||||||
"""(old path, new path) of each JSON file under profiles_dir that differs between
|
|
||||||
base and head (the working tree, untracked files included, when None); the absent
|
|
||||||
side of an added or deleted file is None, and so is a side that is not JSON. Renames
|
|
||||||
are paired."""
|
|
||||||
out = _git(profiles_dir, "diff", "--name-status", "-z", "-M", "--relative", base,
|
|
||||||
*([head] if head else []), "--", ".")
|
|
||||||
fields = out.decode("utf-8").split("\0")
|
|
||||||
pairs, i = [], 0
|
|
||||||
while i + 1 < len(fields):
|
|
||||||
status = fields[i]
|
|
||||||
old = new = fields[i + 1]
|
|
||||||
if status[0] in "RC":
|
|
||||||
new = fields[i + 2]
|
|
||||||
i += 3
|
|
||||||
else:
|
|
||||||
i += 2
|
|
||||||
old = None if status[0] == "A" or not old.endswith(".json") else old
|
|
||||||
new = None if status[0] == "D" or not new.endswith(".json") else new
|
|
||||||
if old or new:
|
|
||||||
pairs.append((old, new))
|
|
||||||
if head is None:
|
|
||||||
out = _git(profiles_dir, "ls-files", "-z", "--others", "--exclude-standard", "--", ".")
|
|
||||||
pairs += [(None, rel) for rel in out.decode("utf-8").split("\0")
|
|
||||||
if rel.endswith(".json") and rel.split("/")[0] != USER_DIR]
|
|
||||||
return pairs
|
|
||||||
|
|
||||||
|
|
||||||
def _text_lines(value):
|
|
||||||
return [line.rstrip() for line in value.split("\n")] if value else []
|
|
||||||
|
|
||||||
|
|
||||||
def _text_changes(old, new):
|
|
||||||
"""(setting, unified diff) for every G-code or other text value that differs
|
|
||||||
between two profiles; a vector option with several values gives one per value."""
|
|
||||||
changes = []
|
|
||||||
for key in list(new) + [k for k in old if k not in new]:
|
|
||||||
a, b = _as_list(old.get(key, [])), _as_list(new.get(key, []))
|
|
||||||
if a == b or not all(isinstance(v, str) for v in a + b):
|
|
||||||
continue
|
|
||||||
if key not in _text_option_keys() and not any("\n" in v for v in a + b):
|
|
||||||
continue
|
|
||||||
width = max(len(a), len(b))
|
|
||||||
for i in range(width):
|
|
||||||
diff = list(difflib.unified_diff(
|
|
||||||
_text_lines(a[i] if i < len(a) else ""), _text_lines(b[i] if i < len(b) else ""),
|
|
||||||
lineterm=""))[2:] # drops the ---/+++ file header
|
|
||||||
if diff:
|
|
||||||
changes.append((key if width == 1 else f"{key}[{i}]", "\n".join(diff)))
|
|
||||||
return changes
|
|
||||||
|
|
||||||
|
|
||||||
def _gcode_diff_section(files, setting, diff):
|
|
||||||
# A fence longer than any backtick run in the G-code cannot be closed by it.
|
|
||||||
fence = "`" * max([3] + [len(run) + 1 for run in re.findall(r"`+", diff)])
|
|
||||||
# In <code>, the "@BBL" of a filament file name does not mention a GitHub user.
|
|
||||||
def code(text):
|
|
||||||
return f"<code>{html.escape(text, quote=False)}</code>"
|
|
||||||
names, listing = ", ".join(map(code, files)), ""
|
|
||||||
if len(files) > GCODE_DIFF_SUMMARY_FILES + 1: # never "and 1 more"
|
|
||||||
listing = f"<p>{names}</p>\n\n" # an HTML block, so markdown in names stays literal
|
|
||||||
names = (", ".join(map(code, files[:GCODE_DIFF_SUMMARY_FILES]))
|
|
||||||
+ f" and {len(files) - GCODE_DIFF_SUMMARY_FILES} more")
|
|
||||||
return (f"\n<details>\n<summary><b>{names} - {code(setting)} (click to expand)</b>"
|
|
||||||
f"</summary>\n\n{listing}{fence}diff\n{diff}\n{fence}\n\n</details>\n")
|
|
||||||
|
|
||||||
|
|
||||||
def gcode_diff(profiles_dir, base, head=None, vendors=None):
|
|
||||||
"""Markdown with one collapsed diff per changed G-code setting between base and
|
|
||||||
head, or "" when none changed. Files whose diff of a setting is identical share
|
|
||||||
one section. Cut to whole sections under GCODE_DIFF_BUDGET."""
|
|
||||||
pairs = [(old, new) for old, new in _changed_profiles(profiles_dir, base, head)
|
|
||||||
if not vendors or any(p and p.split("/")[0] in vendors for p in (old, new))]
|
|
||||||
specs = ([(base, old) for old, _ in pairs if old]
|
|
||||||
+ [(head, new) for _, new in pairs if new and head])
|
|
||||||
blobs = _blobs_at(profiles_dir, specs) if specs else {}
|
|
||||||
|
|
||||||
def load(ref, rel):
|
|
||||||
if rel is None:
|
|
||||||
return {}
|
|
||||||
if ref:
|
|
||||||
return _parse_profile(blobs[(ref, rel)], rel, ref)
|
|
||||||
try:
|
|
||||||
return _parse_profile(Path(profiles_dir, rel).read_bytes(), rel, "the working tree")
|
|
||||||
except OSError as e:
|
|
||||||
print_warning(f"{rel}: skipped, cannot read it: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
groups = {} # (setting, diff) -> files, in the order git lists them
|
|
||||||
for old_rel, new_rel in pairs:
|
|
||||||
old, new = load(base, old_rel), load(head, new_rel)
|
|
||||||
if not isinstance(old, dict) or not isinstance(new, dict):
|
|
||||||
continue
|
|
||||||
for setting, diff in _text_changes(old, new):
|
|
||||||
groups.setdefault((setting, diff), []).append(new_rel or old_rel)
|
|
||||||
if not groups:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
sections = [_gcode_diff_section(files, setting, diff)
|
|
||||||
for (setting, diff), files in groups.items()]
|
|
||||||
budget = GCODE_DIFF_BUDGET - len(GCODE_DIFF_HEADER.encode("utf-8"))
|
|
||||||
shown = 0
|
|
||||||
for section in sections:
|
|
||||||
budget -= len(section.encode("utf-8"))
|
|
||||||
if budget < 0:
|
|
||||||
break
|
|
||||||
shown += 1
|
|
||||||
note, hidden = "", len(sections) - shown
|
|
||||||
if hidden:
|
|
||||||
note = (f"\n{hidden} of {len(sections)} sections {'is' if hidden == 1 else 'are'} not "
|
|
||||||
"shown because the comment would be too long for GitHub. See the Files "
|
|
||||||
"changed tab for the rest.\n")
|
|
||||||
return GCODE_DIFF_HEADER + "".join(sections[:shown]) + note
|
|
||||||
|
|
||||||
|
|
||||||
def _default_gcode_diff_base(profiles_dir, head):
|
|
||||||
for main in ("upstream/main", "origin/main"):
|
|
||||||
try:
|
|
||||||
return _git(profiles_dir, "merge-base", head or "HEAD", main).decode().strip()
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
continue
|
|
||||||
raise RuntimeError("no --base given, and neither upstream/main nor origin/main exists")
|
|
||||||
|
|
||||||
|
|
||||||
def run_gcode_diff(profiles_dir, vendors, base, head):
|
|
||||||
try:
|
|
||||||
markdown = gcode_diff(profiles_dir, base or _default_gcode_diff_base(profiles_dir, head),
|
|
||||||
head, vendors)
|
|
||||||
# UnicodeDecodeError means git listed a file name that is not UTF-8.
|
|
||||||
except (OSError, RuntimeError, UnicodeDecodeError, subprocess.CalledProcessError) as e:
|
|
||||||
stderr = getattr(e, "stderr", None)
|
|
||||||
print_error(stderr.decode(errors="replace").strip() if stderr else str(e), file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
sys.stdout.write(markdown)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# CLI
|
# CLI
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -3455,9 +3233,6 @@ examples:
|
|||||||
orca_profile_tool.py legacy-compat --base /tmp/v2.4.2/resources/profiles
|
orca_profile_tool.py legacy-compat --base /tmp/v2.4.2/resources/profiles
|
||||||
rewrite resources/info/legacy_compatible_printers.json against 2.4.2's
|
rewrite resources/info/legacy_compatible_printers.json against 2.4.2's
|
||||||
profiles (git archive v2.4.2 resources/profiles | tar -x -C /tmp/v2.4.2)
|
profiles (git archive v2.4.2 resources/profiles | tar -x -C /tmp/v2.4.2)
|
||||||
orca_profile_tool.py gcode-diff > gcode.md
|
|
||||||
the changed custom G-code of the working tree since it left upstream/main,
|
|
||||||
as markdown with one collapsed diff per setting
|
|
||||||
|
|
||||||
after adding, renaming or deleting profile files, run in this order:
|
after adding, renaming or deleting profile files, run in this order:
|
||||||
normalize -> update-index -> generate-id -> check
|
normalize -> update-index -> generate-id -> check
|
||||||
@@ -3612,25 +3387,6 @@ def build_parser():
|
|||||||
legacy_cmd.add_argument("--check", action="store_true",
|
legacy_cmd.add_argument("--check", action="store_true",
|
||||||
help="fail if the map differs from what --base implies; write nothing")
|
help="fail if the map differs from what --base implies; write nothing")
|
||||||
|
|
||||||
gcode_cmd = add(
|
|
||||||
"gcode-diff", [vendor_opt, profiles_opt],
|
|
||||||
"print a readable markdown diff of changed custom G-code",
|
|
||||||
"Print, as markdown, a unified diff of every G-code or other multi-line\n"
|
|
||||||
"setting that differs between --base and --head in each changed profile\n"
|
|
||||||
"file, one collapsed section per setting. Files whose diff of a setting is\n"
|
|
||||||
"identical share a section. Trailing spaces are removed from every line, and\n"
|
|
||||||
"the output is cut to whole sections to fit a GitHub comment. Prints nothing\n"
|
|
||||||
"when no such setting changed. CI posts it on profile PRs.\n"
|
|
||||||
"\n"
|
|
||||||
"The settings are the *_gcode and multiline string options PrintConfig.cpp\n"
|
|
||||||
"defines, the options Tab.cpp edits as code (such as filename_format), and any\n"
|
|
||||||
"string value that spans several lines.")
|
|
||||||
gcode_cmd.add_argument("--base", metavar="REF",
|
|
||||||
help="compare against this git ref (default: the merge base "
|
|
||||||
"with upstream/main, else origin/main)")
|
|
||||||
gcode_cmd.add_argument("--head", metavar="REF",
|
|
||||||
help="compare this git ref (default: the working tree)")
|
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -3667,9 +3423,6 @@ def main(argv=None):
|
|||||||
if args.command == "legacy-compat":
|
if args.command == "legacy-compat":
|
||||||
return run_legacy_compat(args.base, profiles_dir, args.output, args.check)
|
return run_legacy_compat(args.base, profiles_dir, args.output, args.check)
|
||||||
|
|
||||||
if args.command == "gcode-diff":
|
|
||||||
return run_gcode_diff(profiles_dir, vendors, args.base, args.head)
|
|
||||||
|
|
||||||
if args.command == "fix-variant":
|
if args.command == "fix-variant":
|
||||||
_changed, errors = fix_variant_arrays(profiles_dir, vendors, dry_run=args.dry_run,
|
_changed, errors = fix_variant_arrays(profiles_dir, vendors, dry_run=args.dry_run,
|
||||||
strict=args.strict)
|
strict=args.strict)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Tests for the tree-maintenance half of scripts/orca_profile_tool.py: the
|
"""Tests for the tree-maintenance half of scripts/orca_profile_tool.py: the
|
||||||
normalize, trim, update-index, fix-variant, check and gcode-diff commands, and the
|
normalize, trim, update-index, fix-variant and check commands, and the subcommand
|
||||||
subcommand dispatch that reaches them (stdlib unittest, no external deps).
|
dispatch that reaches them (stdlib unittest, no external deps).
|
||||||
|
|
||||||
The id halves are covered by test_filament_id.py and test_setting_id.py.
|
The id halves are covered by test_filament_id.py and test_setting_id.py.
|
||||||
|
|
||||||
@@ -14,12 +14,9 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
@@ -44,24 +41,7 @@ class Tree:
|
|||||||
os.makedirs(self.profiles)
|
os.makedirs(self.profiles)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
root = os.path.abspath(self.dir)
|
shutil.rmtree(self.dir, ignore_errors=True)
|
||||||
|
|
||||||
def writable_retry(func, path, _exc):
|
|
||||||
# Windows refuses to delete read-only files such as git's objects, and POSIX
|
|
||||||
# refuses to delete the entries of a read-only directory.
|
|
||||||
if not os.path.lexists(path): # already removed, as on a second cleanup
|
|
||||||
return
|
|
||||||
parent = os.path.dirname(os.path.abspath(path))
|
|
||||||
try:
|
|
||||||
if os.path.commonpath([parent, root]) == root: # never the temp dir itself
|
|
||||||
os.chmod(parent, stat.S_IRWXU)
|
|
||||||
if not os.path.islink(path): # chmod would follow it out of the tree
|
|
||||||
os.chmod(path, stat.S_IRWXU)
|
|
||||||
func(path)
|
|
||||||
except (OSError, ValueError): # commonpath raises ValueError across drives
|
|
||||||
pass
|
|
||||||
hook = "onexc" if sys.version_info >= (3, 12) else "onerror"
|
|
||||||
shutil.rmtree(self.dir, **{hook: writable_retry})
|
|
||||||
|
|
||||||
def index_path(self, vendor):
|
def index_path(self, vendor):
|
||||||
return os.path.join(self.profiles, vendor + ".json")
|
return os.path.join(self.profiles, vendor + ".json")
|
||||||
@@ -1429,292 +1409,6 @@ class TestDispatch(TreeCase):
|
|||||||
self.assertEqual(self.t.read("V", "filament/A.json")["type"], "filament")
|
self.assertEqual(self.t.read("V", "filament/A.json")["type"], "filament")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# gcode-diff
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@unittest.skipUnless(shutil.which("git"), "git not installed")
|
|
||||||
class TestGcodeDiff(TreeCase):
|
|
||||||
START = "G28\nM104 S200\nG1 Z5"
|
|
||||||
|
|
||||||
def git(self, *args):
|
|
||||||
subprocess.run(["git", "-C", self.t.dir, "-c", "user.name=t", "-c", "user.email=t@t",
|
|
||||||
"-c", "commit.gpgsign=false", *args], check=True, capture_output=True)
|
|
||||||
|
|
||||||
def printer(self, name, **extra):
|
|
||||||
self.t.write("V", f"machine/{name}.json", {"type": "machine", "name": name, **extra})
|
|
||||||
|
|
||||||
def commit(self):
|
|
||||||
self.git("add", "-A")
|
|
||||||
self.git("commit", "-q", "--allow-empty", "-m", "x")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
# git's list of the variables that tie a process to a repository, asked with no
|
|
||||||
# GIT_* set since a stray GIT_DIR fails the query.
|
|
||||||
clean = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
|
|
||||||
cls.repo_env_vars = set(subprocess.run(
|
|
||||||
["git", "rev-parse", "--local-env-vars"], env=clean, check=True,
|
|
||||||
capture_output=True, text=True).stdout.split())
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
# A git hook's GIT_DIR or GIT_INDEX_FILE would point every git call here, the
|
|
||||||
# tool's included, at the real repository instead of the temp tree.
|
|
||||||
env = {k: v for k, v in os.environ.items() if k not in self.repo_env_vars}
|
|
||||||
patcher = mock.patch.dict(os.environ, env, clear=True)
|
|
||||||
patcher.start()
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
self.git("init", "-q")
|
|
||||||
for name in ("A", "B", "C"):
|
|
||||||
self.printer(name, machine_start_gcode=self.START, nozzle_diameter=["0.4"])
|
|
||||||
self.printer("Gone", machine_end_gcode="M84")
|
|
||||||
self.commit()
|
|
||||||
|
|
||||||
def diff(self):
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def test_identical_edits_share_one_section(self):
|
|
||||||
for name in ("A", "B"):
|
|
||||||
self.printer(name, machine_start_gcode="G28\nM104 S210 \nG1 Z5")
|
|
||||||
self.printer("C", machine_start_gcode="G28\nM104 S220\nG1 Z5")
|
|
||||||
out = self.diff()
|
|
||||||
self.assertEqual(out.count("<details>"), 2)
|
|
||||||
self.assertIn("<code>V/machine/A.json</code>, <code>V/machine/B.json</code> - "
|
|
||||||
"<code>machine_start_gcode</code>", out)
|
|
||||||
self.assertIn("<code>V/machine/C.json</code> - <code>machine_start_gcode</code>", out)
|
|
||||||
# Trailing spaces are removed.
|
|
||||||
self.assertIn("\n-M104 S200\n+M104 S210\n", out)
|
|
||||||
self.assertIn("\n\n```diff\n@@ ", out)
|
|
||||||
self.assertIn("\n```\n\n</details>", out)
|
|
||||||
|
|
||||||
def test_nothing_is_printed_without_a_text_change(self):
|
|
||||||
self.printer("A", machine_start_gcode=self.START, nozzle_diameter=["0.6"])
|
|
||||||
self.assertEqual(self.diff(), "")
|
|
||||||
|
|
||||||
def test_added_and_deleted_files_show_the_whole_value(self):
|
|
||||||
# An untracked file counts as added; the validator's user dir is not a profile.
|
|
||||||
self.printer("New", machine_start_gcode="G28\nG29")
|
|
||||||
self.t.write("user", "default/machine/U.json", {"machine_start_gcode": "G28"})
|
|
||||||
os.remove(self.t.path("V", "machine/Gone.json"))
|
|
||||||
out = self.diff()
|
|
||||||
self.assertIn("@@ -0,0 +1,2 @@\n+G28\n+G29\n", out)
|
|
||||||
self.assertIn("<code>V/machine/Gone.json</code> - <code>machine_end_gcode</code>", out)
|
|
||||||
self.assertIn("@@ -1 +0,0 @@\n-M84\n", out)
|
|
||||||
self.assertNotIn("U.json", out)
|
|
||||||
|
|
||||||
def test_a_multi_line_value_counts_whatever_its_key(self):
|
|
||||||
self.printer("A", machine_start_gcode=self.START, custom_note="one\ntwo")
|
|
||||||
self.assertIn("<code>V/machine/A.json</code> - <code>custom_note</code>", self.diff())
|
|
||||||
|
|
||||||
def test_a_file_name_cannot_mention_a_user(self):
|
|
||||||
self.t.write("V", "filament/PLA @BBL A1.json", {"filament_start_gcode": ["M106"]})
|
|
||||||
self.assertIn("<code>V/filament/PLA @BBL A1.json</code>", self.diff())
|
|
||||||
|
|
||||||
def test_head_compares_two_commits(self):
|
|
||||||
self.printer("A", machine_start_gcode="G28")
|
|
||||||
self.commit()
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD~1", "--head", "HEAD")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("<code>V/machine/A.json</code> - <code>machine_start_gcode</code>", out)
|
|
||||||
|
|
||||||
def test_a_renamed_file_diffs_against_its_old_self(self):
|
|
||||||
os.rename(self.t.path("V", "machine/A.json"), self.t.path("V", "machine/A2.json"))
|
|
||||||
self.printer("A2", machine_start_gcode="G28\nM104 S210\nG1 Z5", nozzle_diameter=["0.4"])
|
|
||||||
self.commit()
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD~1", "--head", "HEAD")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("<code>V/machine/A2.json</code> - <code>machine_start_gcode</code>", out)
|
|
||||||
self.assertIn("\n G28\n-M104 S200\n+M104 S210\n G1 Z5\n", out)
|
|
||||||
|
|
||||||
def test_a_file_renamed_off_json_loses_its_values(self):
|
|
||||||
os.rename(self.t.path("V", "machine/Gone.json"), self.t.path("V", "machine/Gone.json.bak"))
|
|
||||||
self.commit()
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD~1", "--head", "HEAD")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("<code>V/machine/Gone.json</code> - <code>machine_end_gcode</code>", out)
|
|
||||||
self.assertIn("@@ -1 +0,0 @@\n-M84\n", out)
|
|
||||||
|
|
||||||
def test_a_file_renamed_onto_json_is_new(self):
|
|
||||||
with open(self.t.path("V", "machine/Draft.txt"), "w", encoding="utf-8") as f:
|
|
||||||
json.dump({"type": "machine", "machine_start_gcode": self.START}, f)
|
|
||||||
self.commit()
|
|
||||||
os.rename(self.t.path("V", "machine/Draft.txt"), self.t.path("V", "machine/Draft.json"))
|
|
||||||
self.commit()
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD~1", "--head", "HEAD")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("@@ -0,0 +1,3 @@\n+G28\n+M104 S200\n+G1 Z5\n", out)
|
|
||||||
|
|
||||||
def test_a_batch_read_survives_paths_that_are_not_files(self):
|
|
||||||
blobs = apt._blobs_at(self.t.profiles, [("HEAD", "V/machine"), ("HEAD", "V/no such.json"),
|
|
||||||
("HEAD", "V/machine/Gone.json")])
|
|
||||||
self.assertIsNone(blobs[("HEAD", "V/machine")])
|
|
||||||
self.assertIsNone(blobs[("HEAD", "V/no such.json")])
|
|
||||||
self.assertEqual(json.loads(blobs[("HEAD", "V/machine/Gone.json")])["machine_end_gcode"],
|
|
||||||
"M84")
|
|
||||||
|
|
||||||
def test_each_value_of_a_vector_option_is_its_own_setting(self):
|
|
||||||
self.t.write("V", "filament/F.json", {"filament_start_gcode": ["M106", "M107"]})
|
|
||||||
self.commit()
|
|
||||||
self.t.write("V", "filament/F.json", {"filament_start_gcode": ["M106", "M106 S0"]})
|
|
||||||
out = self.diff()
|
|
||||||
self.assertEqual(out.count("<details>"), 1)
|
|
||||||
self.assertIn("<code>filament_start_gcode[1]</code>", out)
|
|
||||||
|
|
||||||
def test_vendor_limits_the_files(self):
|
|
||||||
self.printer("A", machine_start_gcode="G28")
|
|
||||||
self.t.write("W", "machine/X.json", {"machine_start_gcode": "G28"})
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD", "--vendor", "V")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("V/machine/A.json", out)
|
|
||||||
self.assertNotIn("W/machine/X.json", out)
|
|
||||||
|
|
||||||
def test_vendor_includes_files_moved_out_of_it(self):
|
|
||||||
self.t.add_vendor("W")
|
|
||||||
os.rename(self.t.path("V", "machine/A.json"), self.t.path("W", "machine/A.json"))
|
|
||||||
data = self.t.read("W", "machine/A.json")
|
|
||||||
self.t.write("W", "machine/A.json", {**data, "machine_start_gcode": "G28\nM104 S210\nG1 Z5"})
|
|
||||||
self.commit()
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD~1", "--head", "HEAD",
|
|
||||||
"--vendor", "V")
|
|
||||||
self.assertEqual(rc, 0, out)
|
|
||||||
self.assertIn("<code>W/machine/A.json</code> - <code>machine_start_gcode</code>", out)
|
|
||||||
|
|
||||||
def same_edit(self, names):
|
|
||||||
"""The diff after one start G-code edit to each of names; returns (summary, output)."""
|
|
||||||
for name in names:
|
|
||||||
self.printer(name, machine_start_gcode=self.START, nozzle_diameter=["0.4"])
|
|
||||||
self.commit()
|
|
||||||
for name in names:
|
|
||||||
self.printer(name, machine_start_gcode="G28")
|
|
||||||
out = self.diff()
|
|
||||||
return re.search(r"<summary>(.*)</summary>", out).group(1), out
|
|
||||||
|
|
||||||
def test_a_long_file_list_moves_into_the_section(self):
|
|
||||||
summary, out = self.same_edit("ABCDE")
|
|
||||||
self.assertIn("<code>V/machine/C.json</code> and 2 more - ", summary)
|
|
||||||
self.assertNotIn("D.json", summary)
|
|
||||||
self.assertIn("<code>V/machine/D.json</code>, <code>V/machine/E.json</code></p>\n\n```diff",
|
|
||||||
out)
|
|
||||||
|
|
||||||
def test_four_files_are_all_named_in_the_summary(self):
|
|
||||||
summary, _out = self.same_edit("ABCD")
|
|
||||||
self.assertIn("<code>V/machine/D.json</code> - ", summary)
|
|
||||||
self.assertNotIn("more", summary)
|
|
||||||
|
|
||||||
def test_an_error_goes_to_stderr_only(self):
|
|
||||||
err = io.StringIO()
|
|
||||||
with contextlib.redirect_stderr(err):
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "no-such-ref")
|
|
||||||
self.assertEqual(rc, 1)
|
|
||||||
self.assertEqual(out, "")
|
|
||||||
self.assertIn("no-such-ref", err.getvalue())
|
|
||||||
|
|
||||||
def test_a_file_name_that_is_not_utf8_is_an_error(self):
|
|
||||||
bad = UnicodeDecodeError("utf-8", b"\xe9.json", 0, 1, "invalid continuation byte")
|
|
||||||
err = io.StringIO()
|
|
||||||
with mock.patch.object(apt, "_changed_profiles", side_effect=bad), \
|
|
||||||
contextlib.redirect_stderr(err):
|
|
||||||
rc, out = self.run_command("gcode-diff", "--base", "HEAD")
|
|
||||||
self.assertEqual((rc, out), (1, ""))
|
|
||||||
self.assertIn("invalid continuation byte", err.getvalue())
|
|
||||||
|
|
||||||
def test_output_is_cut_to_whole_sections(self):
|
|
||||||
for name in ("A", "B", "C"):
|
|
||||||
self.printer(name, machine_start_gcode=name * 300)
|
|
||||||
size = len(apt.gcode_diff(self.t.profiles, "HEAD").encode("utf-8"))
|
|
||||||
with mock.patch.object(apt, "GCODE_DIFF_BUDGET", size - 1):
|
|
||||||
out = self.diff()
|
|
||||||
self.assertEqual(out.count("<details>"), 2)
|
|
||||||
self.assertEqual(out.count("</details>"), 2)
|
|
||||||
self.assertIn("\n1 of 3 sections is not shown", out)
|
|
||||||
with mock.patch.object(apt, "GCODE_DIFF_BUDGET", 1):
|
|
||||||
self.assertIn("\n3 of 3 sections are not shown", self.diff())
|
|
||||||
|
|
||||||
def test_cleanup_removes_the_repo(self):
|
|
||||||
# setUp's commit leaves read-only git objects, and a read-only directory blocks
|
|
||||||
# deletion on POSIX.
|
|
||||||
os.chmod(self.t.path("V", "machine"), stat.S_IRUSR | stat.S_IXUSR)
|
|
||||||
self.t.cleanup()
|
|
||||||
self.assertFalse(os.path.exists(self.t.dir))
|
|
||||||
with mock.patch.object(os, "chmod") as chmod:
|
|
||||||
self.t.cleanup() # a second cleanup, as addCleanup runs, must not touch the temp dir
|
|
||||||
chmod.assert_not_called()
|
|
||||||
|
|
||||||
def test_only_repository_variables_are_stripped(self):
|
|
||||||
self.assertTrue({"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"} <= self.repo_env_vars)
|
|
||||||
self.assertFalse({"GIT_SSH", "GIT_EXEC_PATH", "GIT_EDITOR"} & self.repo_env_vars)
|
|
||||||
|
|
||||||
def test_cleanup_leaves_symlink_targets_alone(self):
|
|
||||||
outside = tempfile.mkdtemp(prefix="profile_tool_outside_")
|
|
||||||
self.addCleanup(shutil.rmtree, outside, True)
|
|
||||||
target = os.path.join(outside, "f")
|
|
||||||
open(target, "w").close()
|
|
||||||
os.chmod(target, stat.S_IRUSR)
|
|
||||||
self.addCleanup(os.chmod, target, stat.S_IRWXU) # runs before the rmtree above
|
|
||||||
machine = self.t.path("V", "machine")
|
|
||||||
try:
|
|
||||||
os.symlink(target, os.path.join(machine, "link"))
|
|
||||||
except (OSError, NotImplementedError):
|
|
||||||
self.skipTest("cannot create symlinks here")
|
|
||||||
os.chmod(machine, stat.S_IRUSR | stat.S_IXUSR) # so removing the link fails at first
|
|
||||||
self.t.cleanup()
|
|
||||||
self.assertFalse(os.path.exists(self.t.dir))
|
|
||||||
self.assertEqual(stat.S_IMODE(os.stat(target).st_mode) & stat.S_IWUSR, 0)
|
|
||||||
|
|
||||||
def test_a_git_hook_environment_is_ignored(self):
|
|
||||||
scripts = os.path.join(REPO_ROOT, "scripts")
|
|
||||||
result = subprocess.run(
|
|
||||||
[sys.executable, "-m", "unittest",
|
|
||||||
"tests.test_profile_tool.TestGcodeDiff.test_head_compares_two_commits"],
|
|
||||||
cwd=scripts, capture_output=True, text=True,
|
|
||||||
env={**os.environ, "GIT_DIR": os.path.join(self.t.dir, "nowhere")})
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
|
|
||||||
def test_the_options_are_read_from_the_engine(self):
|
|
||||||
keys = apt._text_option_keys()
|
|
||||||
self.assertTrue({"machine_start_gcode", "filament_start_gcode", "printing_by_object_gcode",
|
|
||||||
"notes", "post_process"} <= keys)
|
|
||||||
self.assertNotIn("emit_machine_limits_to_gcode", keys) # a coBool
|
|
||||||
|
|
||||||
START_DEF = 'def = this->add("machine_start_gcode", coString);\n'
|
|
||||||
|
|
||||||
def keys_from(self, print_config, tab=None):
|
|
||||||
"""_text_option_keys read from these sources; no tab means no Tab.cpp."""
|
|
||||||
paths = [os.path.join(self.t.dir, "PrintConfig.cpp"), os.path.join(self.t.dir, "Tab.cpp")]
|
|
||||||
for path, text in zip(paths, (print_config, tab)):
|
|
||||||
if text is not None:
|
|
||||||
with open(path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(text)
|
|
||||||
apt._text_option_keys.cache_clear()
|
|
||||||
self.addCleanup(apt._text_option_keys.cache_clear)
|
|
||||||
with mock.patch.object(apt, "PRINT_CONFIG_CPP", paths[0]), \
|
|
||||||
mock.patch.object(apt, "TAB_CPP", paths[1]):
|
|
||||||
return apt._text_option_keys()
|
|
||||||
|
|
||||||
def test_multiline_is_read_whatever_its_spacing(self):
|
|
||||||
keys = self.keys_from(self.START_DEF + 'def = this->add("printer_notes", coString);\n'
|
|
||||||
' def->multiline = true;\n')
|
|
||||||
self.assertIn("printer_notes", keys)
|
|
||||||
|
|
||||||
def test_the_code_options_of_tab_cpp_are_read(self):
|
|
||||||
keys = self.keys_from(self.START_DEF, 'option = optgroup->get_option("filename_format");\n'
|
|
||||||
'option.opt.is_code = true;\n'
|
|
||||||
'option = optgroup->get_option("layer_height");\n')
|
|
||||||
self.assertIn("filename_format", keys)
|
|
||||||
self.assertNotIn("layer_height", keys)
|
|
||||||
|
|
||||||
def test_a_missing_tab_cpp_is_not_an_error(self):
|
|
||||||
self.assertIn("machine_start_gcode", self.keys_from(self.START_DEF))
|
|
||||||
|
|
||||||
def test_markdown_in_a_file_name_stays_literal(self):
|
|
||||||
section = apt._gcode_diff_section(["V/a *b*.json", "V/c.json", "V/d.json", "V/e.json",
|
|
||||||
"V/f.json"], "machine_start_gcode", "@@ -1 +1 @@")
|
|
||||||
self.assertIn("\n<p><code>V/a *b*.json</code>, ", section)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# the real tree
|
# the real tree
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1126,19 +1126,6 @@ static void load_downward_settings_list_from_config(std::string config_file, std
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __WXGTK__
|
|
||||||
// Virtual GPUs (VirtualBox, VMware, QEMU) cannot back WebKit's hardware DMABUF renderer.
|
|
||||||
static bool is_hypervisor_guest()
|
|
||||||
{
|
|
||||||
std::ifstream cpuinfo("/proc/cpuinfo");
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(cpuinfo, line))
|
|
||||||
if (boost::starts_with(line, "flags"))
|
|
||||||
return line.find(" hypervisor") != std::string::npos;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int CLI::run(int argc, char **argv)
|
int CLI::run(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// Mark the main thread for the debugger and for runtime checks.
|
// Mark the main thread for the debugger and for runtime checks.
|
||||||
@@ -1189,9 +1176,6 @@ int CLI::run(int argc, char **argv)
|
|||||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_hypervisor_guest() && WebView::SupportsForceSharedMemory())
|
|
||||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /* replace */ false);
|
|
||||||
|
|
||||||
if (x11_opt_in) {
|
if (x11_opt_in) {
|
||||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||||
|
|||||||
@@ -812,8 +812,7 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpa
|
|||||||
* Get the order constraints of the insets when printing walls per region / hole.
|
* Get the order constraints of the insets when printing walls per region / hole.
|
||||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||||
*
|
*
|
||||||
* Odd walls follow the same order as even ones, so that the centre line of a narrow wall is an inner wall
|
* Odd walls should always go after their enclosing wall polygons.
|
||||||
* like any other when printing inner to outer.
|
|
||||||
*
|
*
|
||||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||||
*/
|
*/
|
||||||
@@ -887,7 +886,12 @@ WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector<
|
|||||||
continue; // not directly adjacent
|
continue; // not directly adjacent
|
||||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||||
continue; // points are too far away from each other
|
continue; // points are too far away from each other
|
||||||
if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
if (here->is_odd || nearby->is_odd) {
|
||||||
|
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||||
|
order_requirements.emplace(std::make_pair(nearby, here));
|
||||||
|
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||||
|
order_requirements.emplace(std::make_pair(here, nearby));
|
||||||
|
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||||
order_requirements.emplace(std::make_pair(nearby, here));
|
order_requirements.emplace(std::make_pair(nearby, here));
|
||||||
} else {
|
} else {
|
||||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -988,7 +988,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
|
|
||||||
MetadataList metadata;
|
MetadataList metadata;
|
||||||
VolumeMetadataList volumes;
|
VolumeMetadataList volumes;
|
||||||
std::optional<bool> auto_drop;
|
bool auto_drop { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CutObjectInfo
|
struct CutObjectInfo
|
||||||
@@ -1208,7 +1208,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
//IdToGeometryMap m_orig_geometries; // backup & restore
|
//IdToGeometryMap m_orig_geometries; // backup & restore
|
||||||
CurrentConfig m_curr_config;
|
CurrentConfig m_curr_config;
|
||||||
IdToMetadataMap m_objects_metadata;
|
IdToMetadataMap m_objects_metadata;
|
||||||
std::set<int> m_items_auto_drop_off; // object ids with a build item auto_drop="0" (2.4.x layout)
|
|
||||||
IdToCutObjectInfoMap m_cut_object_infos;
|
IdToCutObjectInfoMap m_cut_object_infos;
|
||||||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||||
@@ -1464,7 +1463,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
m_objects.clear();
|
m_objects.clear();
|
||||||
m_instances.clear();
|
m_instances.clear();
|
||||||
m_objects_metadata.clear();
|
m_objects_metadata.clear();
|
||||||
m_items_auto_drop_off.clear();
|
|
||||||
m_curr_metadata_name.clear();
|
m_curr_metadata_name.clear();
|
||||||
m_curr_characters.clear();
|
m_curr_characters.clear();
|
||||||
|
|
||||||
@@ -1504,7 +1502,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
m_curr_config.object_id = -1;
|
m_curr_config.object_id = -1;
|
||||||
m_curr_config.volume_id = -1;
|
m_curr_config.volume_id = -1;
|
||||||
m_objects_metadata.clear();
|
m_objects_metadata.clear();
|
||||||
m_items_auto_drop_off.clear();
|
|
||||||
m_layer_heights_profiles.clear();
|
m_layer_heights_profiles.clear();
|
||||||
m_layer_config_ranges.clear();
|
m_layer_config_ranges.clear();
|
||||||
m_brim_ear_points.clear();
|
m_brim_ear_points.clear();
|
||||||
@@ -2260,14 +2257,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
ObjectMetadata::VolumeMetadataList volumes;
|
ObjectMetadata::VolumeMetadataList volumes;
|
||||||
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
||||||
|
|
||||||
// 2.4.x stored auto_drop on build items, shared by all instances of an object; any "off" wins
|
|
||||||
const bool legacy_auto_drop = !m_items_auto_drop_off.count(object.first.second);
|
|
||||||
|
|
||||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
|
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
|
||||||
if (obj_metadata != m_objects_metadata.end()) {
|
if (obj_metadata != m_objects_metadata.end()) {
|
||||||
// config data has been found, this model was saved using slic3r pe
|
// config data has been found, this model was saved using slic3r pe
|
||||||
|
|
||||||
model_object->auto_drop = obj_metadata->second.auto_drop.value_or(legacy_auto_drop);
|
model_object->auto_drop = obj_metadata->second.auto_drop;
|
||||||
|
|
||||||
// apply object's name and config data
|
// apply object's name and config data
|
||||||
for (const Metadata& metadata : obj_metadata->second.metadata) {
|
for (const Metadata& metadata : obj_metadata->second.metadata) {
|
||||||
@@ -2285,7 +2279,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// config data not found, this model was not saved using slic3r pe
|
// config data not found, this model was not saved using slic3r pe
|
||||||
model_object->auto_drop = legacy_auto_drop;
|
|
||||||
|
|
||||||
// add the entire geometry as the single volume to generate
|
// add the entire geometry as the single volume to generate
|
||||||
//volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
|
//volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
|
||||||
@@ -4128,8 +4121,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||||
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||||
int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
||||||
if (const char* auto_drop = bbs_get_attribute_value_charptr(attributes, num_attributes, AUTO_DROP_ATTR); auto_drop != nullptr && !::atoi(auto_drop))
|
|
||||||
m_items_auto_drop_off.insert(object_id);
|
|
||||||
return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1);
|
return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4498,8 +4489,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
// int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
|
// int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
|
||||||
|
|
||||||
ObjectMetadata metadata;
|
ObjectMetadata metadata;
|
||||||
if (bbs_get_attribute_value_charptr(attributes, num_attributes, AUTO_DROP_ATTR) != nullptr)
|
metadata.auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR);
|
||||||
metadata.auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR);
|
|
||||||
m_objects_metadata.insert({ object_id, std::move(metadata) });
|
m_objects_metadata.insert({ object_id, std::move(metadata) });
|
||||||
m_curr_config.object_id = object_id;
|
m_curr_config.object_id = object_id;
|
||||||
return true;
|
return true;
|
||||||
@@ -10120,7 +10110,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 +10126,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 << "\" ";
|
||||||
|
|||||||
@@ -3985,16 +3985,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
||||||
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||||
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||||
// The final print statistics are known only after the whole G-code is generated, so they are filled in during post-processing too.
|
|
||||||
// The running extruded_* totals are left alone; profiles use them as numbers.
|
|
||||||
this->placeholder_parser().set("used_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
|
||||||
this->placeholder_parser().set("print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("normal_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("silent_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_weight", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_wipe_tower_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_wipe_tower_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder)));
|
|
||||||
|
|
||||||
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
||||||
update_placeholder_parser_with_variant_params();
|
update_placeholder_parser_with_variant_params();
|
||||||
|
|||||||
@@ -120,13 +120,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
|||||||
"@PRINT_TIME_HOUR@",
|
"@PRINT_TIME_HOUR@",
|
||||||
"@PRINT_TIME_MINUTE@",
|
"@PRINT_TIME_MINUTE@",
|
||||||
"@PRINT_TIME_SEC@",
|
"@PRINT_TIME_SEC@",
|
||||||
"@USED_FILAMENT_LENGTH@",
|
"@USED_FILAMENT_LENGTH@"
|
||||||
"@NORMAL_PRINT_TIME@",
|
|
||||||
"@SILENT_PRINT_TIME@",
|
|
||||||
"@TOTAL_WEIGHT@",
|
|
||||||
"@TOTAL_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_FILAMENT@"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
||||||
@@ -153,13 +147,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
|||||||
"@PRINT_TIME_HOUR@",
|
"@PRINT_TIME_HOUR@",
|
||||||
"@PRINT_TIME_MINUTE@",
|
"@PRINT_TIME_MINUTE@",
|
||||||
"@PRINT_TIME_SEC@",
|
"@PRINT_TIME_SEC@",
|
||||||
"@USED_FILAMENT_LENGTH@",
|
"@USED_FILAMENT_LENGTH@"
|
||||||
"@NORMAL_PRINT_TIME@",
|
|
||||||
"@SILENT_PRINT_TIME@",
|
|
||||||
"@TOTAL_WEIGHT@",
|
|
||||||
"@TOTAL_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_FILAMENT@"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1276,22 +1264,19 @@ void GCodeProcessor::run_post_process()
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process inline placeholders (print_time_*, used_filament_length and the print statistics known only after export)
|
// Process inline placeholders (print_time_total_sec, print_time_day, print_time_hour, print_time_minute, print_time_sec and used_filament_length)
|
||||||
auto process_inline_placeholders = [&](std::string& gcode_line) {
|
auto process_inline_placeholders = [&](std::string& gcode_line) {
|
||||||
bool processed = false;
|
bool processed = false;
|
||||||
// Every inline placeholder contains '@', so a line without one has nothing to replace.
|
// Every inline placeholder contains '@', so a line without one has nothing to replace.
|
||||||
if (gcode_line.find('@') == std::string::npos)
|
if (gcode_line.find('@') == std::string::npos)
|
||||||
return processed;
|
return processed;
|
||||||
|
|
||||||
auto replace_tag = [&gcode_line, &processed](ETags tag, const std::string& value) {
|
const std::string& print_time_total_placeholder = reserved_tag(ETags::Print_Time_Total_Sec_Placeholder);
|
||||||
const std::string& placeholder = reserved_tag(tag);
|
const std::string& print_time_day_placeholder = reserved_tag(ETags::Print_Time_Day_Placeholder);
|
||||||
size_t pos = gcode_line.find(placeholder);
|
const std::string& print_time_hour_placeholder = reserved_tag(ETags::Print_Time_Hour_Placeholder);
|
||||||
while (pos != std::string::npos) {
|
const std::string& print_time_minute_placeholder = reserved_tag(ETags::Print_Time_Minute_Placeholder);
|
||||||
gcode_line.replace(pos, placeholder.length(), value);
|
const std::string& print_time_sec_placeholder = reserved_tag(ETags::Print_Time_Sec_Placeholder);
|
||||||
processed = true;
|
const std::string& used_filament_placeholder = reserved_tag(ETags::Used_Filament_Length_Placeholder);
|
||||||
pos = gcode_line.find(placeholder, pos + value.length());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
double print_time_total_sec = m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
|
double print_time_total_sec = m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
|
||||||
if (print_time_total_sec < 0.0)
|
if (print_time_total_sec < 0.0)
|
||||||
@@ -1304,31 +1289,70 @@ void GCodeProcessor::run_post_process()
|
|||||||
int print_time_minute = (day_remainder_seconds % 3600) / 60;
|
int print_time_minute = (day_remainder_seconds % 3600) / 60;
|
||||||
int print_time_sec = day_remainder_seconds % 60;
|
int print_time_sec = day_remainder_seconds % 60;
|
||||||
|
|
||||||
double total_filament_mm = 0.0;
|
// Replace print_time_total_sec
|
||||||
for (const auto& mm : filament_mm) {
|
size_t pos = gcode_line.find(print_time_total_placeholder);
|
||||||
total_filament_mm += mm;
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%.2f", print_time_total_sec);
|
||||||
|
gcode_line.replace(pos, print_time_total_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_total_placeholder, pos + strlen(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
replace_tag(ETags::Print_Time_Total_Sec_Placeholder, Slic3r::float_to_string_decimal_point(print_time_total_sec, 2));
|
// Replace print_time_day
|
||||||
replace_tag(ETags::Print_Time_Day_Placeholder, std::to_string(print_time_day));
|
pos = gcode_line.find(print_time_day_placeholder);
|
||||||
replace_tag(ETags::Print_Time_Hour_Placeholder, std::to_string(print_time_hour));
|
while (pos != std::string::npos) {
|
||||||
replace_tag(ETags::Print_Time_Minute_Placeholder, std::to_string(print_time_minute));
|
char buf[64];
|
||||||
replace_tag(ETags::Print_Time_Sec_Placeholder, std::to_string(print_time_sec));
|
sprintf(buf, "%d", print_time_day);
|
||||||
replace_tag(ETags::Used_Filament_Length_Placeholder,
|
gcode_line.replace(pos, print_time_day_placeholder.length(), buf);
|
||||||
Slic3r::float_to_string_decimal_point(total_filament_mm / 1000.0, 2)); // Convert mm to m
|
processed = true;
|
||||||
// Same values as the print statistics that filename_format gets (PrintStatistics::config(), filled in by
|
pos = gcode_line.find(print_time_day_placeholder, pos + strlen(buf));
|
||||||
// update_print_estimated_stats() in GCode.cpp), which runs only after this post-processing.
|
}
|
||||||
replace_tag(ETags::Normal_Print_Time_Placeholder, get_time_dhm(PrintEstimatedStatistics::ETimeMode::Normal));
|
|
||||||
replace_tag(ETags::Silent_Print_Time_Placeholder, is_stealth_time_estimator_enabled() ?
|
// Replace print_time_hour
|
||||||
get_time_dhm(PrintEstimatedStatistics::ETimeMode::Stealth) :
|
pos = gcode_line.find(print_time_hour_placeholder);
|
||||||
short_time("N/A"));
|
while (pos != std::string::npos) {
|
||||||
const double total_cost = filament_total_cost + m_print->config().time_cost.value * print_time_total_sec / 3600.0;
|
char buf[64];
|
||||||
replace_tag(ETags::Total_Weight_Placeholder, Slic3r::float_to_string_decimal_point(filament_total_g, 2));
|
sprintf(buf, "%d", print_time_hour);
|
||||||
replace_tag(ETags::Total_Cost_Placeholder, Slic3r::float_to_string_decimal_point(total_cost, 2));
|
gcode_line.replace(pos, print_time_hour_placeholder.length(), buf);
|
||||||
replace_tag(ETags::Total_Wipe_Tower_Cost_Placeholder,
|
processed = true;
|
||||||
Slic3r::float_to_string_decimal_point(m_print->print_statistics().total_wipe_tower_cost, 2));
|
pos = gcode_line.find(print_time_hour_placeholder, pos + strlen(buf));
|
||||||
replace_tag(ETags::Total_Wipe_Tower_Filament_Placeholder,
|
}
|
||||||
Slic3r::float_to_string_decimal_point(m_print->print_statistics().total_wipe_tower_filament, 2));
|
|
||||||
|
// Replace print_time_minute
|
||||||
|
pos = gcode_line.find(print_time_minute_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", print_time_minute);
|
||||||
|
gcode_line.replace(pos, print_time_minute_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_minute_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace print_time_sec
|
||||||
|
pos = gcode_line.find(print_time_sec_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", print_time_sec);
|
||||||
|
gcode_line.replace(pos, print_time_sec_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_sec_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace used_filament_length
|
||||||
|
pos = gcode_line.find(used_filament_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
double total_filament_mm = 0.0;
|
||||||
|
for (const auto& mm : filament_mm) {
|
||||||
|
total_filament_mm += mm;
|
||||||
|
}
|
||||||
|
double used_filament_length = total_filament_mm / 1000.0; // Convert mm to m
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%.2f", used_filament_length);
|
||||||
|
gcode_line.replace(pos, used_filament_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(used_filament_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -601,12 +601,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
Print_Time_Minute_Placeholder,
|
Print_Time_Minute_Placeholder,
|
||||||
Print_Time_Sec_Placeholder,
|
Print_Time_Sec_Placeholder,
|
||||||
Used_Filament_Length_Placeholder,
|
Used_Filament_Length_Placeholder,
|
||||||
Normal_Print_Time_Placeholder,
|
|
||||||
Silent_Print_Time_Placeholder,
|
|
||||||
Total_Weight_Placeholder,
|
|
||||||
Total_Cost_Placeholder,
|
|
||||||
Total_Wipe_Tower_Cost_Placeholder,
|
|
||||||
Total_Wipe_Tower_Filament_Placeholder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }
|
static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }
|
||||||
|
|||||||
@@ -62,12 +62,7 @@ public:
|
|||||||
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
||||||
unsigned short depth;
|
unsigned short depth;
|
||||||
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
|
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
|
||||||
// E.g. a 1mm tube wall with 0.5mm walls: 2 lines, the contour's outer wall and the hole's.
|
|
||||||
bool is_thin_wall_hole = false;
|
bool is_thin_wall_hole = false;
|
||||||
// ORCA: an external hole perimeter with no room for an inner wall beside it, at most gap fill. Every thin wall
|
|
||||||
// hole is one, and so is e.g. a 1.5mm tube wall with 0.5mm walls: 3 lines, the two outer walls and gap fill
|
|
||||||
// between them. A 2mm tube wall has room for an inner wall a side and is not. Only set for Inner/Outer/Inner.
|
|
||||||
bool is_narrow_wall_hole = false;
|
|
||||||
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
||||||
std::vector<PerimeterGeneratorLoop> children;
|
std::vector<PerimeterGeneratorLoop> children;
|
||||||
|
|
||||||
@@ -577,8 +572,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
|||||||
extrusion_coll.append(std::move(extrusion_loop));
|
extrusion_coll.append(std::move(extrusion_loop));
|
||||||
// Orca: Reverse the order of paths for thin wall holes. We define thin wall hole as a hole with only one perimeter.
|
// Orca: Reverse the order of paths for thin wall holes. We define thin wall hole as a hole with only one perimeter.
|
||||||
const bool thin_wall_hole = !pg_extrusion.is_contour && pg_extrusions.size() == 2;
|
const bool thin_wall_hole = !pg_extrusion.is_contour && pg_extrusions.size() == 2;
|
||||||
if (thin_wall_hole && perimeter_generator.config->wall_sequence != WallSequence::OuterInner &&
|
if (thin_wall_hole && perimeter_generator.config->wall_sequence != WallSequence::OuterInner)
|
||||||
!perimeter_generator.outer_walls_first_for_brim())
|
|
||||||
std::reverse(extrusion_coll.entities.begin(), extrusion_coll.entities.end());
|
std::reverse(extrusion_coll.entities.begin(), extrusion_coll.entities.end());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1696,24 +1690,13 @@ void PerimeterGenerator::process_classic()
|
|||||||
|
|
||||||
last = std::move(offsets);
|
last = std::move(offsets);
|
||||||
|
|
||||||
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall, a
|
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall.
|
||||||
// narrow wall hole none for an inner wall, the room the next loop is offset by below.
|
|
||||||
if (i == 0 && ! holes[0].empty()) {
|
if (i == 0 && ! holes[0].empty()) {
|
||||||
auto reach = [&last](double room) {
|
const float room = float(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
|
||||||
return to_polygons(offset2_ex(last, -float(room), float(room) + float(SCALED_EPSILON)));
|
const Polygons reach = to_polygons(offset2_ex(last, -room, room + float(SCALED_EPSILON)));
|
||||||
};
|
for (PerimeterGeneratorLoop &hole : holes[0])
|
||||||
auto out_of_reach = [](const Polygon &hole, const Polygons &reach) {
|
hole.is_thin_wall_hole = intersection_pl(Polylines{ hole.polygon.split_at_first_point() },
|
||||||
return intersection_pl(Polylines{ hole.split_at_first_point() },
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole.polygon).inflated(SCALED_EPSILON))).empty();
|
||||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole).inflated(SCALED_EPSILON))).empty();
|
|
||||||
};
|
|
||||||
// Only Inner/Outer/Inner orders narrow wall holes apart.
|
|
||||||
const bool narrow_holes_ordered = this->config->wall_sequence == WallSequence::InnerOuterInner;
|
|
||||||
const Polygons gap_fill_reach = reach(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
|
|
||||||
const Polygons inner_wall_reach = narrow_holes_ordered ? reach(ext_perimeter_spacing2 + min_spacing / 2.) : Polygons();
|
|
||||||
for (PerimeterGeneratorLoop &hole : holes[0]) {
|
|
||||||
hole.is_thin_wall_hole = out_of_reach(hole.polygon, gap_fill_reach);
|
|
||||||
hole.is_narrow_wall_hole = narrow_holes_ordered && out_of_reach(hole.polygon, inner_wall_reach);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: refer to superslicer
|
//BBS: refer to superslicer
|
||||||
@@ -1852,16 +1835,18 @@ void PerimeterGenerator::process_classic()
|
|||||||
|
|
||||||
// if brim will be printed, reverse the order of perimeters so that
|
// if brim will be printed, reverse the order of perimeters so that
|
||||||
// we continue inwards after having finished the brim
|
// we continue inwards after having finished the brim
|
||||||
|
// TODO: add test for perimeter order
|
||||||
bool is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner;
|
bool is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner;
|
||||||
// Orca: a contour with only a narrow wall hole beside it has nothing to sandwich, and prints the hole first,
|
if (is_outer_wall_first ||
|
||||||
// then the contour, then the gap fill between them, as Arachne does.
|
//BBS: always print outer wall first when there indeed has brim.
|
||||||
const PerimeterGeneratorLoops &island_loops = contours.front();
|
// btLeadingEdgeOnly is an outer brim too (a belt brim at the part's first contact).
|
||||||
const bool narrow_wall_hole_island = island_loops.size() == 1 && island_loops.front().children.size() == 1 &&
|
(this->layer_id == 0 &&
|
||||||
island_loops.front().children.front().is_narrow_wall_hole;
|
(this->object_config->brim_type == BrimType::btOuterOnly ||
|
||||||
if (is_outer_wall_first || this->outer_walls_first_for_brim())
|
this->object_config->brim_type == BrimType::btLeadingEdgeOnly) &&
|
||||||
|
this->object_config->brim_width.value > 0))
|
||||||
entities.reverse();
|
entities.reverse();
|
||||||
// Orca: sandwich mode. Apply after 1st layer.
|
// Orca: sandwich mode. Apply after 1st layer.
|
||||||
else if ((this->config->wall_sequence == WallSequence::InnerOuterInner) && layer_id > 0 && !narrow_wall_hole_island){
|
else if ((this->config->wall_sequence == WallSequence::InnerOuterInner) && layer_id > 0){
|
||||||
entities.reverse(); // reverse all entities - order them from external to internal
|
entities.reverse(); // reverse all entities - order them from external to internal
|
||||||
if(entities.entities.size()>2){ // 3 walls minimum needed to do inner outer inner ordering
|
if(entities.entities.size()>2){ // 3 walls minimum needed to do inner outer inner ordering
|
||||||
int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
|
int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
|
||||||
@@ -2427,25 +2412,11 @@ std::vector<PerimeterGeneratorArachneExtrusion> reorderPerimetersByProximity(std
|
|||||||
|
|
||||||
// Function to reorder perimeters starting from a given reference index
|
// Function to reorder perimeters starting from a given reference index
|
||||||
auto reorderFromReference = [&](int referenceIndex) {
|
auto reorderFromReference = [&](int referenceIndex) {
|
||||||
// Find first level touching perimeters. The odd centre line of a wall too narrow for two inner walls is shared
|
std::unordered_set<int> firstLevelIndices;
|
||||||
// by the outer walls on both sides, so those join this island and are printed before it too, holes first.
|
firstLevelIndices.insert(referenceIndex);
|
||||||
std::vector<int> outerIndices{referenceIndex};
|
|
||||||
std::vector<int> firstLevelTouchingIndices;
|
// Find first level touching perimeters
|
||||||
for (size_t next = 0; next < outerIndices.size(); ++next)
|
std::vector<int> firstLevelTouchingIndices = findAllTouchingPerimeters(entities, firstLevelIndices, threshold_external, threshold_internal, 1);
|
||||||
for (int idx : findAllTouchingPerimeters(entities, {outerIndices[next]}, threshold_external, threshold_internal, 1)) {
|
|
||||||
if (std::find(firstLevelTouchingIndices.begin(), firstLevelTouchingIndices.end(), idx) != firstLevelTouchingIndices.end())
|
|
||||||
continue;
|
|
||||||
firstLevelTouchingIndices.push_back(idx);
|
|
||||||
if (!entities[idx].extrusion->is_odd)
|
|
||||||
continue;
|
|
||||||
for (size_t i = 0; i < entities.size(); ++i)
|
|
||||||
if (entities[i].extrusion->inset_idx == 0 && includedIndices.count(int(i)) == 0 &&
|
|
||||||
std::find(outerIndices.begin(), outerIndices.end(), int(i)) == outerIndices.end() &&
|
|
||||||
arachne_lines_touch(*entities[i].extrusion, *entities[idx].extrusion, double(threshold_external)))
|
|
||||||
outerIndices.push_back(int(i));
|
|
||||||
}
|
|
||||||
std::stable_partition(outerIndices.begin(), outerIndices.end(),
|
|
||||||
[&entities](int idx) { return entities[idx].extrusion->is_closed && !entities[idx].is_contour; });
|
|
||||||
// Bring the largest first level perimeter to the front
|
// Bring the largest first level perimeter to the front
|
||||||
// The longest first neighbour is most likely the dominant proximate perimeter
|
// The longest first neighbour is most likely the dominant proximate perimeter
|
||||||
// hence printing it immediately after the external perimeter should speed things up
|
// hence printing it immediately after the external perimeter should speed things up
|
||||||
@@ -2456,10 +2427,8 @@ std::vector<PerimeterGeneratorArachneExtrusion> reorderPerimetersByProximity(std
|
|||||||
std::iter_swap(maxIt, firstLevelTouchingIndices.end() - 1);
|
std::iter_swap(maxIt, firstLevelTouchingIndices.end() - 1);
|
||||||
}
|
}
|
||||||
// Insert first level perimeters into reordered list
|
// Insert first level perimeters into reordered list
|
||||||
for (int idx : outerIndices) {
|
reordered.push_back(entities[referenceIndex]);
|
||||||
reordered.push_back(entities[idx]);
|
includedIndices.insert(referenceIndex);
|
||||||
includedIndices.insert(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int idx : firstLevelTouchingIndices) {
|
for (int idx : firstLevelTouchingIndices) {
|
||||||
if (includedIndices.count(idx) == 0) {
|
if (includedIndices.count(idx) == 0) {
|
||||||
@@ -2788,7 +2757,7 @@ void PerimeterGenerator::process_arachne()
|
|||||||
|
|
||||||
if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
|
if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
|
||||||
is_outer_wall_first =
|
is_outer_wall_first =
|
||||||
this->config->wall_sequence == WallSequence::OuterInner || this->outer_walls_first_for_brim();
|
this->config->wall_sequence == WallSequence::OuterInner;
|
||||||
}
|
}
|
||||||
if (is_outer_wall_first) {
|
if (is_outer_wall_first) {
|
||||||
start_perimeter = 0;
|
start_perimeter = 0;
|
||||||
@@ -3073,16 +3042,6 @@ ExPolygons PerimeterGenerator::printable_slices(const ExPolygons &slices) const
|
|||||||
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PerimeterGenerator::outer_walls_first_for_brim() const
|
|
||||||
{
|
|
||||||
return layer_id == 0 && brim_puts_outer_walls_first(object_config->brim_type.value, object_config->brim_width.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool brim_puts_outer_walls_first(BrimType brim_type, double brim_width)
|
|
||||||
{
|
|
||||||
return brim_width > 0 && (brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btLeadingEdgeOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||||
{
|
{
|
||||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||||
|
|||||||
@@ -164,8 +164,6 @@ public:
|
|||||||
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
||||||
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
||||||
ExPolygons printable_slices(const ExPolygons &slices) const;
|
ExPolygons printable_slices(const ExPolygons &slices) const;
|
||||||
// ORCA: whether this layer prints its outer walls first for the brim, see brim_puts_outer_walls_first().
|
|
||||||
bool outer_walls_first_for_brim() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Polygons> generate_lower_polygons_series(float width);
|
std::vector<Polygons> generate_lower_polygons_series(float width);
|
||||||
@@ -185,10 +183,6 @@ private:
|
|||||||
Polygons m_lower_slices_polygons;
|
Polygons m_lower_slices_polygons;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ORCA: a brim against the outer walls makes the first layer print them first, whatever the wall sequence, so the
|
|
||||||
// brim runs straight on into the outer wall. Auto, ear and painted brims may leave the outer walls bare.
|
|
||||||
bool brim_puts_outer_walls_first(BrimType brim_type, double brim_width);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -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",
|
||||||
@@ -2294,7 +2293,6 @@ void PresetCollection::load_project_embedded_presets(std::vector<Preset*>& proje
|
|||||||
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file;
|
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
extend_default_config_length(config, false, {});
|
|
||||||
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||||
//preset->config.apply(std::move(config));
|
//preset->config.apply(std::move(config));
|
||||||
Preset::normalize(preset->config);
|
Preset::normalize(preset->config);
|
||||||
|
|||||||
@@ -756,14 +756,7 @@ public:
|
|||||||
Preset& get_edited_preset() { return m_edited_preset; }
|
Preset& get_edited_preset() { return m_edited_preset; }
|
||||||
const Preset& get_edited_preset() const { return m_edited_preset; }
|
const Preset& get_edited_preset() const { return m_edited_preset; }
|
||||||
|
|
||||||
// The root of the selected preset's inherits chain, or the selected preset itself when
|
const Preset& get_selected_preset_base() const { return *get_preset_base(m_presets[m_idx_selected]); }
|
||||||
// the chain names a preset that is not loaded (e.g. a vendor that is not installed).
|
|
||||||
const Preset& get_selected_preset_base() const
|
|
||||||
{
|
|
||||||
const Preset &selected = m_presets[m_idx_selected];
|
|
||||||
const Preset *base = get_preset_base(selected);
|
|
||||||
return base ? *base : selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the last saved preset.
|
// Return the last saved preset.
|
||||||
// const Preset& get_saved_preset() const { return m_saved_preset; }
|
// const Preset& get_saved_preset() const { return m_saved_preset; }
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "MutablePolygon.hpp"
|
#include "MutablePolygon.hpp"
|
||||||
#include "PerimeterGenerator.hpp"
|
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "SLA/IndexedMesh.hpp"
|
#include "SLA/IndexedMesh.hpp"
|
||||||
@@ -1465,13 +1464,15 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
|| opt_key == "top_surface_speed") {
|
|| opt_key == "top_surface_speed") {
|
||||||
// Brim is printed below supports, support invalidates brim and skirt.
|
// Brim is printed below supports, support invalidates brim and skirt.
|
||||||
steps.emplace_back(posSupportMaterial);
|
steps.emplace_back(posSupportMaterial);
|
||||||
if (opt_key == "brim_type" || opt_key == "brim_width") {
|
if (opt_key == "brim_type") {
|
||||||
// Regenerate the walls when the brim starts or stops putting the first layer's outer walls first.
|
const auto* old_brim_type = old_config.option<ConfigOptionEnum<BrimType>>(opt_key);
|
||||||
auto outer_walls_first = [](const ConfigOptionResolver &config) {
|
const auto* new_brim_type = new_config.option<ConfigOptionEnum<BrimType>>(opt_key);
|
||||||
return brim_puts_outer_walls_first(config.option<ConfigOptionEnum<BrimType>>("brim_type")->value,
|
//BBS: When switch to manual brim, the object must have brim, then re-generate perimeter
|
||||||
config.option<ConfigOptionFloat>("brim_width")->value);
|
//to make the wall order of first layer to be outer-first
|
||||||
};
|
// btLeadingEdgeOnly is printed as an outer brim (Brim.cpp, BeltBrim.cpp), so it
|
||||||
if (outer_walls_first(old_config) != outer_walls_first(new_config))
|
// takes part in the same first-layer wall order rule.
|
||||||
|
if (old_brim_type->value == btOuterOnly || new_brim_type->value == btOuterOnly ||
|
||||||
|
old_brim_type->value == btLeadingEdgeOnly || new_brim_type->value == btLeadingEdgeOnly)
|
||||||
steps.emplace_back(posPerimeters);
|
steps.emplace_back(posPerimeters);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
|||||||
@@ -707,10 +707,8 @@ void tree_supports_generate_paths(
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
if (eec) {
|
if (eec) {
|
||||||
if (!eec->empty()) {
|
std::reverse(eec->entities.begin(), eec->entities.end());
|
||||||
std::reverse(eec->entities.begin(), eec->entities.end());
|
dst.emplace_back(eec.release());
|
||||||
dst.emplace_back(eec.release());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1827,7 +1825,12 @@ void generate_support_toolpaths(
|
|||||||
no_sort = true;
|
no_sort = true;
|
||||||
} else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic &&
|
} else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic &&
|
||||||
(config.support_base_pattern == smpNone || config.support_base_pattern == smpDefault)) {
|
(config.support_base_pattern == smpNone || config.support_base_pattern == smpDefault)) {
|
||||||
tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params);
|
// Orca: A special case for the hollow Organic supports
|
||||||
|
// Orca: If the tree supports are too tall, use a double wall to make it stronger
|
||||||
|
SupportParameters support_params2 = support_params;
|
||||||
|
if (support_layer.print_z > 100.0)
|
||||||
|
support_params2.tree_branch_diameter_double_wall_area_scaled = 0.1;
|
||||||
|
tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params2);
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
if (! done)
|
if (! done)
|
||||||
|
|||||||
@@ -2149,9 +2149,7 @@ void TreeSupport::draw_circles()
|
|||||||
const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning;
|
const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning;
|
||||||
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
|
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
|
||||||
const float tree_brim_width = config.tree_support_brim_width.value;
|
const float tree_brim_width = config.tree_support_brim_width.value;
|
||||||
// Reinforce branches moving more than half a support-line width between layers.
|
|
||||||
// Square the threshold for comparison with the squared XY movement below.
|
|
||||||
const double max_move_sq = sqr(double(scale_(m_object_config->support_line_width / 2)));
|
|
||||||
// Belt floor: the first object layer is not on a flat bed — it rests on the
|
// Belt floor: the first object layer is not on a flat bed — it rests on the
|
||||||
// tilted, moving belt. So the first-object-layer adhesion features (the tree
|
// tilted, moving belt. So the first-object-layer adhesion features (the tree
|
||||||
// support brim, the hybrid first-layer base expansion) must be suppressed:
|
// support brim, the hybrid first-layer base expansion) must be suppressed:
|
||||||
@@ -2203,7 +2201,7 @@ void TreeSupport::draw_circles()
|
|||||||
size_t first_base_roof_area = 0;
|
size_t first_base_roof_area = 0;
|
||||||
bool floor_interface_as_base = false;
|
bool floor_interface_as_base = false;
|
||||||
bool has_circle_node = false;
|
bool has_circle_node = false;
|
||||||
ExPolygons extra_wall_areas;
|
bool need_extra_wall = false;
|
||||||
ExPolygons collision_sharp_tails;
|
ExPolygons collision_sharp_tails;
|
||||||
ExPolygons collision_base;
|
ExPolygons collision_base;
|
||||||
auto get_collision = [&](bool sharp_tail) -> ExPolygons &{
|
auto get_collision = [&](bool sharp_tail) -> ExPolygons &{
|
||||||
@@ -2281,6 +2279,7 @@ void TreeSupport::draw_circles()
|
|||||||
// area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0));
|
// area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0));
|
||||||
|
|
||||||
if (!area.empty()) has_circle_node = true;
|
if (!area.empty()) has_circle_node = true;
|
||||||
|
if (node.need_extra_wall) need_extra_wall = true;
|
||||||
|
|
||||||
// merge overhang to get a smoother interface surface
|
// merge overhang to get a smoother interface surface
|
||||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||||
@@ -2295,10 +2294,6 @@ void TreeSupport::draw_circles()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the Pythagorean theorem to compare movement magnitude (dx² + dy²) without a square root.
|
|
||||||
if (node.is_processed && node.movement.cast<double>().squaredNorm() > max_move_sq)
|
|
||||||
append(extra_wall_areas, area);
|
|
||||||
|
|
||||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||||
append(roof_gap_areas, area);
|
append(roof_gap_areas, area);
|
||||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||||
@@ -2535,8 +2530,7 @@ void TreeSupport::draw_circles()
|
|||||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||||
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
|
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
|
||||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||||
area_groups.back().need_extra_wall = !area_groups.back().need_infill &&
|
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||||
!extra_wall_areas.empty() && overlaps({expoly}, extra_wall_areas);
|
|
||||||
}
|
}
|
||||||
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||||
auto &expoly = ts_layer->roof_areas[roof_idx];
|
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||||
@@ -3439,6 +3433,11 @@ void TreeSupport::smooth_nodes()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float max_move = scale_(m_object_config->support_line_width / 2);
|
||||||
|
// if the branch is very tall, the tip also needs extra wall
|
||||||
|
float thresh_tall_branch = 100;
|
||||||
|
float thresh_dist_to_top = 30;
|
||||||
|
|
||||||
for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) {
|
for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) {
|
||||||
std::vector<SupportNode *> &curr_layer_nodes = contact_nodes[layer_nr];
|
std::vector<SupportNode *> &curr_layer_nodes = contact_nodes[layer_nr];
|
||||||
if (curr_layer_nodes.empty()) continue;
|
if (curr_layer_nodes.empty()) continue;
|
||||||
@@ -3448,17 +3447,20 @@ void TreeSupport::smooth_nodes()
|
|||||||
std::vector<double> radii;
|
std::vector<double> radii;
|
||||||
std::vector<SupportNode *> branch;
|
std::vector<SupportNode *> branch;
|
||||||
SupportNode * p_node = node;
|
SupportNode * p_node = node;
|
||||||
|
float total_height = 0;
|
||||||
// add a fixed head if it's not a polygon node, see STUDIO-4403
|
// add a fixed head if it's not a polygon node, see STUDIO-4403
|
||||||
// Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling
|
// Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling
|
||||||
if (node->child && node->child->type!=ePolygon) {
|
if (node->child && node->child->type!=ePolygon) {
|
||||||
pts.push_back(p_node->child->position);
|
pts.push_back(p_node->child->position);
|
||||||
radii.push_back(p_node->child->radius);
|
radii.push_back(p_node->child->radius);
|
||||||
branch.push_back(p_node->child);
|
branch.push_back(p_node->child);
|
||||||
|
total_height += p_node->child->height;
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
pts.push_back(p_node->position);
|
pts.push_back(p_node->position);
|
||||||
radii.push_back(p_node->radius);
|
radii.push_back(p_node->radius);
|
||||||
branch.push_back(p_node);
|
branch.push_back(p_node);
|
||||||
|
total_height += p_node->height;
|
||||||
p_node = p_node->parent;
|
p_node = p_node->parent;
|
||||||
} while (p_node && !p_node->is_processed);
|
} while (p_node && !p_node->is_processed);
|
||||||
if (pts.size() < 3) continue;
|
if (pts.size() < 3) continue;
|
||||||
@@ -3477,6 +3479,9 @@ void TreeSupport::smooth_nodes()
|
|||||||
branch[i]->radius = radii1[i];
|
branch[i]->radius = radii1[i];
|
||||||
branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2;
|
branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2;
|
||||||
branch[i]->is_processed = true;
|
branch[i]->is_processed = true;
|
||||||
|
if (branch[i]->parents.size() > 1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move) ||
|
||||||
|
(total_height > thresh_tall_branch && branch[i]->dist_mm_to_top < thresh_dist_to_top))
|
||||||
|
branch[i]->need_extra_wall = true;
|
||||||
BOOST_LOG_TRIVIAL(info) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius;
|
BOOST_LOG_TRIVIAL(info) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3484,6 +3489,13 @@ void TreeSupport::smooth_nodes()
|
|||||||
std::swap(pts, pts1);
|
std::swap(pts, pts1);
|
||||||
std::swap(radii, radii1);
|
std::swap(radii, radii1);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// interpolate need_extra_wall in the end
|
||||||
|
for (size_t i = 1; i < branch.size() - 1; i++) {
|
||||||
|
if (branch[i - 1]->need_extra_wall && branch[i + 1]->need_extra_wall)
|
||||||
|
branch[i]->need_extra_wall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ struct SupportNode
|
|||||||
TreeNodeType type = eCircle;
|
TreeNodeType type = eCircle;
|
||||||
bool is_corner = false;
|
bool is_corner = false;
|
||||||
bool is_processed = false;
|
bool is_processed = false;
|
||||||
|
bool need_extra_wall = false;
|
||||||
bool is_sharp_tail = false;
|
bool is_sharp_tail = false;
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area
|
ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ std::vector<float> Layers::get_zs() const
|
|||||||
|
|
||||||
size_t Layers::get_layer_id_at(float z) const
|
size_t Layers::get_layer_id_at(float z) const
|
||||||
{
|
{
|
||||||
auto iter = std::upper_bound(m_items.begin(), m_items.end(), z, [](float z, const Item& item) { return item.z > z; });
|
auto iter = std::upper_bound(m_items.begin(), m_items.end(), z, [](float z, const Item& item) { return item.z < z; });
|
||||||
return std::distance(m_items.begin(), iter);
|
return std::distance(m_items.begin(), iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -3151,7 +3151,7 @@ void CreatePrinterPresetDialog::set_current_visible_printer()
|
|||||||
m_printer_name_to_preset.clear();
|
m_printer_name_to_preset.clear();
|
||||||
for (const Preset &printer_preset : printer_presets) {
|
for (const Preset &printer_preset : printer_presets) {
|
||||||
if (!printer_preset.is_visible) continue;
|
if (!printer_preset.is_visible) continue;
|
||||||
if (preset_bundle->printers.get_preset_base(printer_preset) != &printer_preset) continue;
|
if (preset_bundle->printers.get_preset_base(printer_preset)->name != printer_preset.name) continue;
|
||||||
if (auto printer_model = dynamic_cast<ConfigOptionString*>(const_cast<Preset&>(printer_preset).config.option("printer_model", false))) {
|
if (auto printer_model = dynamic_cast<ConfigOptionString*>(const_cast<Preset&>(printer_preset).config.option("printer_model", false))) {
|
||||||
if (m_printer_name_to_preset.find(printer_model->value) == m_printer_name_to_preset.end()) {
|
if (m_printer_name_to_preset.find(printer_model->value) == m_printer_name_to_preset.end()) {
|
||||||
printer_choice.push_back(from_u8(printer_model->value));
|
printer_choice.push_back(from_u8(printer_model->value));
|
||||||
@@ -4962,7 +4962,7 @@ void CreatePresetForPrinterDialog::get_visible_printer_and_compatible_filament_p
|
|||||||
if (filament_preset.is_default || !filament_preset.is_compatible || filament_preset.is_project_embedded) continue;
|
if (filament_preset.is_default || !filament_preset.is_compatible || filament_preset.is_project_embedded) continue;
|
||||||
ConfigOptionStrings *filament_types;
|
ConfigOptionStrings *filament_types;
|
||||||
const Preset * filament_preset_base = m_preset_bundle->filaments.get_preset_base(filament_preset);
|
const Preset * filament_preset_base = m_preset_bundle->filaments.get_preset_base(filament_preset);
|
||||||
if (filament_preset_base == &filament_preset || filament_preset_base == nullptr) {
|
if (filament_preset_base == &filament_preset) {
|
||||||
filament_types = dynamic_cast<ConfigOptionStrings *>(const_cast<Preset *>(&filament_preset)->config.option("filament_type"));
|
filament_types = dynamic_cast<ConfigOptionStrings *>(const_cast<Preset *>(&filament_preset)->config.option("filament_type"));
|
||||||
} else {
|
} else {
|
||||||
filament_types = dynamic_cast<ConfigOptionStrings *>(const_cast<Preset *>(filament_preset_base)->config.option("filament_type"));
|
filament_types = dynamic_cast<ConfigOptionStrings *>(const_cast<Preset *>(filament_preset_base)->config.option("filament_type"));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
#include "Search.hpp"
|
#include "Search.hpp"
|
||||||
#include "BitmapCache.hpp"
|
#include "BitmapCache.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "KeyChord.hpp"
|
|
||||||
|
|
||||||
#include <nanosvg/nanosvg.h>
|
#include <nanosvg/nanosvg.h>
|
||||||
#include <nanosvg/nanosvgrast.h>
|
#include <nanosvg/nanosvgrast.h>
|
||||||
@@ -2135,27 +2134,6 @@ bool ImGuiWrapper::want_any_input() const
|
|||||||
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
|
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImGuiWrapper::text_input_active() const
|
|
||||||
{
|
|
||||||
const ImGuiID id = ImGui::GetActiveID();
|
|
||||||
return id != 0 && ImGui::GetInputTextState(id) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGuiWrapper::modal_popup_open() const
|
|
||||||
{
|
|
||||||
// A modal stays on the popup stack while its canvas is hidden, so it counts only if the last finished
|
|
||||||
// frame drew it, which NewFrame() moves from Active to WasActive.
|
|
||||||
const ImGuiWindow* modal = ImGui::GetTopMostPopupModal();
|
|
||||||
if (modal == nullptr)
|
|
||||||
return false;
|
|
||||||
return ImGui::GetCurrentContext()->WithinFrameScope ? modal->WasActive : modal->Active;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImGuiWrapper::owns_key(const KeyChord& chord) const
|
|
||||||
{
|
|
||||||
return modal_popup_open() || (chord.edits_text() && text_input_active());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id)
|
ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id)
|
||||||
{
|
{
|
||||||
auto item = m_custom_glyph_rects_ids.find(tex_id);
|
auto item = m_custom_glyph_rects_ids.find(tex_id);
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ struct ImRect;
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
struct KeyChord;
|
|
||||||
|
|
||||||
bool get_data_from_svg(const std::string &filename, unsigned int max_size_px, ThumbnailData &thumbnail_data);
|
bool get_data_from_svg(const std::string &filename, unsigned int max_size_px, ThumbnailData &thumbnail_data);
|
||||||
|
|
||||||
@@ -220,12 +219,6 @@ public:
|
|||||||
bool want_keyboard() const;
|
bool want_keyboard() const;
|
||||||
bool want_text_input() const;
|
bool want_text_input() const;
|
||||||
bool want_any_input() const;
|
bool want_any_input() const;
|
||||||
// True while an ImGui InputText is active, without the one-frame lag of want_text_input().
|
|
||||||
bool text_input_active() const;
|
|
||||||
// True while an ImGui modal popup is on screen.
|
|
||||||
bool modal_popup_open() const;
|
|
||||||
// True for every key while a modal is open, and for text-editing keys while an InputText is active.
|
|
||||||
bool owns_key(const KeyChord& chord) const;
|
|
||||||
|
|
||||||
// Optional inputs are used for set up value inside of an optional, with default value
|
// Optional inputs are used for set up value inside of an optional, with default value
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ void KBShortcutsDialog::fill_pages()
|
|||||||
using Section = ShortcutSection;
|
using Section = ShortcutSection;
|
||||||
|
|
||||||
if (wxGetApp().is_editor()) {
|
if (wxGetApp().is_editor()) {
|
||||||
page(_L("Global"), _L("Available anywhere in the window."), ShortcutContext::Global, {
|
page(_L("Global"), _L("Available anywhere in the window, even while typing in a text field."), ShortcutContext::Global, {
|
||||||
fixed(Section::SpeedDial, { alt, "1-9, 0" }, L("Run favorite 1 to 10")),
|
fixed(Section::SpeedDial, { alt, "1-9, 0" }, L("Run favorite 1 to 10")),
|
||||||
fixed(Section::SpeedDial, { ctrl, "B" }, L("Pin or unpin the selected action")),
|
fixed(Section::SpeedDial, { ctrl, "B" }, L("Pin or unpin the selected action")),
|
||||||
// wx cycles notebook pages on Ctrl+Tab, which is Cmd+Tab on macOS and never arrives there.
|
// wx cycles notebook pages on Ctrl+Tab, which is Cmd+Tab on macOS and never arrives there.
|
||||||
@@ -438,16 +438,17 @@ ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut
|
|||||||
SetBackgroundColour(*wxWHITE);
|
SetBackgroundColour(*wxWHITE);
|
||||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
// A Global shortcut runs before the focused control gets the key, so its hint names the keys it can use.
|
// A Global shortcut also runs while a text field has the focus, so its hint names the keys it can use.
|
||||||
const bool global = (shortcut_info(shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
const bool global = (shortcut_info(shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
||||||
const wxString advice = global_key_advice();
|
const wxString advice = global_key_advice();
|
||||||
|
const wxString typing = _L("Global shortcuts also apply while typing.");
|
||||||
const wxString rule = _L("A key that types a character cannot be a global shortcut.");
|
const wxString rule = _L("A key that types a character cannot be a global shortcut.");
|
||||||
m_hint = global ? advice : _L("Esc cancels, Enter confirms.");
|
m_hint = global ? typing + "\n" + advice : _L("Esc cancels, Enter confirms.");
|
||||||
m_rejection = rule + "\n" + advice;
|
m_rejection = rule + "\n" + advice;
|
||||||
|
|
||||||
// Wide enough for each sentence on a line of its own where the translation allows, within limits.
|
// Wide enough for each sentence on a line of its own where the translation allows, within limits.
|
||||||
int width = FromDIP(450);
|
int width = FromDIP(450);
|
||||||
for (const wxString& sentence : { rule, advice }) {
|
for (const wxString& sentence : { typing, rule, advice }) {
|
||||||
int extent = 0;
|
int extent = 0;
|
||||||
GetTextExtent(sentence, &extent, nullptr, nullptr, nullptr, &wxGetApp().normal_font());
|
GetTextExtent(sentence, &extent, nullptr, nullptr, nullptr, &wxGetApp().normal_font());
|
||||||
width = std::max(width, extent);
|
width = std::max(width, extent);
|
||||||
|
|||||||
@@ -187,40 +187,6 @@ bool KeyChord::is_menu_accelerator() const
|
|||||||
return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE));
|
return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeyChord::edits_text() const
|
|
||||||
{
|
|
||||||
if (!valid())
|
|
||||||
return false;
|
|
||||||
if (!is_menu_accelerator())
|
|
||||||
return true;
|
|
||||||
const int mods = modifiers & ~wxMOD_SHIFT;
|
|
||||||
switch (key) {
|
|
||||||
case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
|
|
||||||
case WXK_HOME: case WXK_END: case WXK_BACK: case WXK_DELETE:
|
|
||||||
#ifdef __APPLE__
|
|
||||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL || mods == wxMOD_ALT; // Option moves by word
|
|
||||||
#else
|
|
||||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL;
|
|
||||||
#endif
|
|
||||||
case WXK_INSERT:
|
|
||||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL;
|
|
||||||
case WXK_RETURN: case WXK_TAB: case WXK_ESCAPE: case WXK_PAGEUP: case WXK_PAGEDOWN:
|
|
||||||
return mods == wxMOD_NONE;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!is_printable(key))
|
|
||||||
return false;
|
|
||||||
#ifdef __APPLE__
|
|
||||||
if (mods == wxMOD_ALT || mods == wxMOD_RAW_CONTROL)
|
|
||||||
return true; // Option types a character, Control runs the Cocoa text key bindings
|
|
||||||
#else
|
|
||||||
if (mods == (wxMOD_CONTROL | wxMOD_ALT))
|
|
||||||
return true; // AltGr types a character
|
|
||||||
#endif
|
|
||||||
return mods == wxMOD_CONTROL && (key == 'A' || key == 'C' || key == 'V' || key == 'X' || key == 'Y' || key == 'Z');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only a chord the desktop acts on while still delivering it to the app belongs here.
|
// Only a chord the desktop acts on while still delivering it to the app belongs here.
|
||||||
bool KeyChord::is_system_shortcut() const
|
bool KeyChord::is_system_shortcut() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ struct KeyChord
|
|||||||
// True when Ctrl or Alt is held or the key is non-printable, the chords a menu can own without
|
// True when Ctrl or Alt is held or the key is non-printable, the chords a menu can own without
|
||||||
// swallowing typing in text fields.
|
// swallowing typing in text fields.
|
||||||
bool is_menu_accelerator() const;
|
bool is_menu_accelerator() const;
|
||||||
// True for a chord a focused text field acts on: typing a character (AltGr and macOS Option
|
|
||||||
// characters included), moving the caret, selecting, deleting, or the clipboard and undo chords.
|
|
||||||
bool edits_text() const;
|
|
||||||
// True for a chord the desktop acts on although the app receives it, so a binding would
|
// True for a chord the desktop acts on although the app receives it, so a binding would
|
||||||
// take it from the system.
|
// take it from the system.
|
||||||
bool is_system_shortcut() const;
|
bool is_system_shortcut() const;
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
#include <wx/settings.h>
|
#include <wx/settings.h>
|
||||||
#include <wx/textentry.h>
|
#include <wx/textentry.h>
|
||||||
#include <wx/spinctrl.h>
|
|
||||||
#include <wx/notebook.h>
|
#include <wx/notebook.h>
|
||||||
#include <wx/listbook.h>
|
#include <wx/listbook.h>
|
||||||
#include <wx/simplebook.h>
|
#include <wx/simplebook.h>
|
||||||
@@ -108,8 +107,6 @@
|
|||||||
#include "../Utils/PrintHost.hpp"
|
#include "../Utils/PrintHost.hpp"
|
||||||
|
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "ImGuiWrapper.hpp"
|
|
||||||
#include "OpenGLManager.hpp"
|
|
||||||
#include "Shortcuts.hpp"
|
#include "Shortcuts.hpp"
|
||||||
#include "UnsavedChangesDialog.hpp"
|
#include "UnsavedChangesDialog.hpp"
|
||||||
#include "PublishSettingsDialog.hpp"
|
#include "PublishSettingsDialog.hpp"
|
||||||
@@ -187,31 +184,18 @@ enum class ERescaleTarget
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool focus_is_text_field(wxWindow* focus)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<wxTextEntryBase*>(focus) || dynamic_cast<wxWebView*>(focus))
|
|
||||||
return true; // a web page can have its own text fields
|
|
||||||
if (dynamic_cast<wxSpinCtrl*>(focus) || dynamic_cast<wxSpinCtrlDouble*>(focus))
|
|
||||||
return true; // the native Windows spin control is not a wxTextEntry
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only a scene canvas draws ImGui, and a hidden one can keep the wx focus after a tab switch.
|
|
||||||
const ImGuiWrapper* focused_canvas_imgui(wxWindow* focus)
|
|
||||||
{
|
|
||||||
if (!OpenGLManager::is_scene_canvas(focus) || !focus->IsShownOnScreen())
|
|
||||||
return nullptr;
|
|
||||||
return wxGetApp().imgui();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Space opens the speed dial, but it is the activation key for buttons, checkboxes and other
|
// Space opens the speed dial, but it is the activation key for buttons, checkboxes and other
|
||||||
// controls, so only take Space when the focused window has no keyboard-activation meaning of its
|
// controls. CHAR_HOOK runs before the focused child, so only take Space when the focused window has
|
||||||
// own. Canvases (GLCanvas3D) and panels are not controls and fall through to "open"; the Notebook
|
// no keyboard-activation meaning of its own. Canvases (GLCanvas3D) and panels are not controls and
|
||||||
// itself does too, so Space still opens the dial on any page.
|
// fall through to "open"; the Notebook itself does too, so Space still opens the dial on any page.
|
||||||
bool focus_keeps_space(wxWindow* focus)
|
bool focus_keeps_space(wxWindow* focus)
|
||||||
{
|
{
|
||||||
if (!focus)
|
if (!focus)
|
||||||
return false;
|
return false;
|
||||||
|
if (dynamic_cast<wxTextEntryBase*>(focus))
|
||||||
|
return true; // typing a space into a text field
|
||||||
|
if (dynamic_cast<wxWebView*>(focus))
|
||||||
|
return true; // web content scrolls and hosts its own text fields
|
||||||
if (dynamic_cast<::Button*>(focus))
|
if (dynamic_cast<::Button*>(focus))
|
||||||
return true; // custom button: Space clicks it (it is a wxWindow, not a wxControl)
|
return true; // custom button: Space clicks it (it is a wxWindow, not a wxControl)
|
||||||
if (dynamic_cast<StaticBox*>(focus))
|
if (dynamic_cast<StaticBox*>(focus))
|
||||||
@@ -832,12 +816,16 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
|||||||
|
|
||||||
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
||||||
{
|
{
|
||||||
// CHAR_HOOK runs before the focused window, so leave text-editing keys to a focused text field and
|
// ImGui draws its text fields (the text tool's box, a sketch value field) on the GL canvas, so
|
||||||
// the keys ImGui owns to the focused canvas.
|
// while one of them is typed into the focused wx window is that canvas or an in-canvas frame:
|
||||||
|
// neither is a wxControl, so focus_keeps_space() cannot see it and CHAR_HOOK takes the chord
|
||||||
|
// here first -- Space opened the Speed Dial, Ctrl+P the Preferences, and the field never saw
|
||||||
|
// the key. ImGui raises WantCaptureKeyboard while one of its items owns the keyboard, so leave
|
||||||
|
// the key to it then. A focused wxControl (text field, list, web view) is untouched: those
|
||||||
|
// keep their own keys, and ImGui's flag can outlive the focus moving to one of them.
|
||||||
wxWindow* const focus = wxWindow::FindFocus();
|
wxWindow* const focus = wxWindow::FindFocus();
|
||||||
if (chord.edits_text() && focus_is_text_field(focus))
|
ImGuiWrapper* const imgui = wxGetApp().imgui();
|
||||||
return false;
|
if (focus != nullptr && imgui != nullptr && imgui->want_keyboard() && dynamic_cast<wxControl*>(focus) == nullptr)
|
||||||
if (const ImGuiWrapper* imgui = focused_canvas_imgui(focus); imgui != nullptr && imgui->owns_key(chord))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
|
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair<i
|
|||||||
return m_context;
|
return m_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* const SCENE_CANVAS_NAME = "OrcaSceneCanvas";
|
|
||||||
|
|
||||||
wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
|
wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
|
||||||
{
|
{
|
||||||
int antialiasing_samples = 4;
|
int antialiasing_samples = 4;
|
||||||
@@ -472,17 +470,12 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
|
|||||||
attribList[samples_value_idx] = 0;
|
attribList[samples_value_idx] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS, SCENE_CANVAS_NAME);
|
wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
|
||||||
// The GL canvas paints its entire surface, so background erasing is unnecessary.
|
// The GL canvas paints its entire surface, so background erasing is unnecessary.
|
||||||
canvas->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
canvas->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLManager::is_scene_canvas(const wxWindow* window)
|
|
||||||
{
|
|
||||||
return window != nullptr && window->GetName() == SCENE_CANVAS_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLManager::detect_multisample(int* attribList)
|
void OpenGLManager::detect_multisample(int* attribList)
|
||||||
{
|
{
|
||||||
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
|
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
|
||||||
|
|||||||
@@ -101,8 +101,6 @@ public:
|
|||||||
static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }
|
static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }
|
||||||
static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
|
static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
|
||||||
static wxGLCanvas* create_wxglcanvas(wxWindow& parent);
|
static wxGLCanvas* create_wxglcanvas(wxWindow& parent);
|
||||||
// True for a canvas made by create_wxglcanvas(), the kind a GLCanvas3D draws its scene and ImGui into.
|
|
||||||
static bool is_scene_canvas(const wxWindow* window);
|
|
||||||
static const GLInfo& get_gl_info() { return s_gl_info; }
|
static const GLInfo& get_gl_info() { return s_gl_info; }
|
||||||
static bool force_power_of_two_textures() { return s_force_power_of_two_textures; }
|
static bool force_power_of_two_textures() { return s_force_power_of_two_textures; }
|
||||||
|
|
||||||
|
|||||||
@@ -21178,12 +21178,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
|||||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||||
if (!dev) return;
|
if (!dev) return;
|
||||||
|
|
||||||
const std::string dev_id = evt.GetString().ToStdString();
|
dev->set_selected_machine(evt.GetString().ToStdString());
|
||||||
MachineObject* selected = dev->get_selected_machine();
|
|
||||||
// After a LAN print, the completion event reselects the same printer.
|
|
||||||
// Keep the active session while asynchronous status traffic may still be in flight.
|
|
||||||
if (!selected || selected->get_dev_id() != dev_id || selected->connection_type() != "lan")
|
|
||||||
dev->set_selected_machine(dev_id);
|
|
||||||
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||||
// Selects the status page on a built Device tab; one built by the switch starts there.
|
// Selects the status page on a built Device tab; one built by the switch starts there.
|
||||||
MonitorPanel* curr_monitor = MonitorPanel::if_built();
|
MonitorPanel* curr_monitor = MonitorPanel::if_built();
|
||||||
|
|||||||
@@ -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_();
|
||||||
|
|||||||
@@ -4087,7 +4087,7 @@ void SelectMachineDialog::update_user_printer()
|
|||||||
{
|
{
|
||||||
m_printer_last_select = "";
|
m_printer_last_select = "";
|
||||||
update_select_layout(nullptr);
|
update_select_layout(nullptr);
|
||||||
m_printer_box->GetPrinterComboBox()->SetValue(_L("No available printers"));
|
m_printer_box->GetPrinterComboBox()->SetTextLabel("");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
||||||
|
|||||||
@@ -474,14 +474,17 @@ void Selection::add_curr_plate()
|
|||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_plate(wxGetApp().plater()->get_partplate_list().get_curr_plate_index());
|
|
||||||
if (volume_idxs.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
wxGetApp().plater()->take_snapshot(std::string("Selection-Add Curr Plate All!"));
|
wxGetApp().plater()->take_snapshot(std::string("Selection-Add Curr Plate All!"));
|
||||||
m_mode = Instance;
|
m_mode = Instance;
|
||||||
clear();
|
clear();
|
||||||
do_add_volumes(volume_idxs);
|
|
||||||
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||||
|
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
||||||
|
if (plate && plate->contain_instance_totally(obj_idx, 0)) {
|
||||||
|
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_object(obj_idx);
|
||||||
|
do_add_volumes(volume_idxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update_type();
|
update_type();
|
||||||
this->set_bounding_boxes_dirty();
|
this->set_bounding_boxes_dirty();
|
||||||
@@ -508,14 +511,20 @@ void Selection::remove_curr_plate()
|
|||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_plate(wxGetApp().plater()->get_partplate_list().get_curr_plate_index());
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||||
if (volume_idxs.empty())
|
if (plate->empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxGetApp().plater()->take_snapshot(std::string("Selection-Delete Curr Plate All"));
|
wxGetApp().plater()->take_snapshot(std::string("Selection-Delete Curr Plate All"));
|
||||||
m_mode = Instance;
|
m_mode = Instance;
|
||||||
clear();
|
clear();
|
||||||
do_add_volumes(volume_idxs);
|
|
||||||
|
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
||||||
|
if (plate && plate->contain_instance(obj_idx, 0)) {
|
||||||
|
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_object(obj_idx);
|
||||||
|
do_add_volumes(volume_idxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update_type();
|
update_type();
|
||||||
this->set_bounding_boxes_dirty();
|
this->set_bounding_boxes_dirty();
|
||||||
@@ -2261,27 +2270,6 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
|
|||||||
return idxs;
|
return idxs;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned int> Selection::get_volume_idxs_from_plate(const int plate_idx) const
|
|
||||||
{
|
|
||||||
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx);
|
|
||||||
std::vector<unsigned int> idxs;
|
|
||||||
if (!plate)
|
|
||||||
return idxs;
|
|
||||||
|
|
||||||
for (int obj_idx = 0; obj_idx < (int)m_model->objects.size(); obj_idx++) {
|
|
||||||
const ModelObject* obj = m_model->objects[obj_idx];
|
|
||||||
for (int inst_idx = 0; inst_idx < (int)obj->instances.size(); inst_idx++) {
|
|
||||||
// on this plate or overlapping it in some way (fully inside, oversized or partly outside)
|
|
||||||
if (plate->contain_instance(obj_idx, inst_idx) || plate->intersect_instance(obj_idx, inst_idx)) {
|
|
||||||
std::vector<unsigned int> inst_idxs = get_volume_idxs_from_instance(obj_idx, inst_idx);
|
|
||||||
idxs.insert(idxs.end(), inst_idxs.begin(), inst_idxs.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return idxs;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const
|
std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const
|
||||||
{
|
{
|
||||||
std::vector<unsigned int> idxs;
|
std::vector<unsigned int> idxs;
|
||||||
|
|||||||
@@ -409,8 +409,6 @@ public:
|
|||||||
std::vector<unsigned int> get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const;
|
std::vector<unsigned int> get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const;
|
||||||
// returns the idx of the volume corresponding to the volume with the given idxs
|
// returns the idx of the volume corresponding to the volume with the given idxs
|
||||||
std::vector<unsigned int> get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const;
|
std::vector<unsigned int> get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const;
|
||||||
// returns the list of idxs of the volumes of all instances on the given plate or overlapping it. (fully inside, oversized or partly outside)
|
|
||||||
std::vector<unsigned int> get_volume_idxs_from_plate(const int plate_idx) const;
|
|
||||||
// returns the list of idxs of the volumes contained in the selection but not in the given list
|
// returns the list of idxs of the volumes contained in the selection but not in the given list
|
||||||
std::vector<unsigned int> get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const;
|
std::vector<unsigned int> get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const;
|
||||||
// returns the list of idxs of the volumes contained in the given list but not in the selection
|
// returns the list of idxs of the volumes contained in the given list but not in the selection
|
||||||
|
|||||||
@@ -1229,7 +1229,7 @@ void SendToPrinterDialog::update_user_printer()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_printer_last_select = "";
|
m_printer_last_select = "";
|
||||||
m_comboBox_printer->SetValue(_L("No available printers"));
|
m_comboBox_printer->SetTextLabel("");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user