Compare commits
13 Commits
0217e6cdbf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3595cf839c | |||
| 2b39cc1a78 | |||
| d20308cf2c | |||
| 710c4831c2 | |||
| 5bff7adad0 | |||
| eea570052f | |||
| 303297bfbf | |||
| 6b9ad9d426 | |||
| ed30568092 | |||
| 1f300589d1 | |||
| 930e3774af | |||
| 636889bdbc | |||
| 3f6ea269e6 |
133
CHANGELOG.de.md
133
CHANGELOG.de.md
@@ -1,5 +1,138 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.26] – 2026-06-21
|
||||
|
||||
### Neu
|
||||
- **Italienische Sprachunterstützung** (PR #66, @Alex_M). Die Bridge-UI ist jetzt vollständig auf Italienisch verfügbar.
|
||||
|
||||
### Behoben
|
||||
- **Kamera startete immer beim Druckbeginn** (Issue #50). `camera_on_print` fehlte in der `/api/state`-Antwort — JavaScript las `undefined` und startete die Kamera unabhängig vom Setting. Jetzt korrekt im State enthalten.
|
||||
- **Auto-Leveling-Setting wurde im Moonraker-Druckpfad ignoriert** (Issue #57). `handle_print_start` las den Wert nur aus den Bridge-Args, nicht aus dem Request-Body — Dialog-Checkbox und Per-Print-Override hatten keine Wirkung. Verhält sich jetzt identisch zum direkten Druckpfad.
|
||||
- **Filament-Mapping: Freitext-Felder durch Dropdowns ersetzt** (Issue #57). Falsch getippte Vendor/Name-Kombination brach das Profil-Matching ohne Fehlermeldung; Felder sind jetzt Dropdowns (Vendor → Profil, vendor-gefiltert), sodass nur gültige Kombinationen gespeichert werden können.
|
||||
- **Dashboard zeigte generischen Materialtyp statt Profilname** (Issue #57). AMS-Slot-Karten zeigen jetzt den gemappten Profilnamen (z.B. „eSUN PLA-Basic") statt nur „PLA". Fallback auf generischen Typ wenn kein Profil gemappt ist.
|
||||
- **Ghost-Profil auf leerem Slot** (Issue #57). Verwaiste Mappings für leere Slots wurden weiterhin angezeigt; leere Slots zeigen jetzt korrekt „–".
|
||||
- **Skip-Objects-Panel fehlte im Orca-Upload-Flow** (Issue #57). Panel erscheint jetzt in allen Druckflows; bei frischem Upload fragt die Bridge `fileDetails` beim Drucker nach und pollt die Objektliste bis zu 6 Sekunden nach.
|
||||
- **Banner und Dialog erschienen gleichzeitig** (Issue #57). Settings-Save setzt jetzt den Dialog-Cancel-State zurück, sodass der Slot-Mapper nach Wechsel des Start-Print-Verhaltens zuverlässig öffnet.
|
||||
- **„Leeren" lud idle-Datei beim nächsten Poll nach** (Issue #57). Leeren setzt jetzt den lokalen State sofort zurück (`file_ready`, `filename`, `thumbnail`) und löscht alle Dialog-Sperren — Vorschaubild und Aktions-Buttons verschwinden sofort und kommen nicht zurück.
|
||||
- **Material-Matching für „PLA Silk", „Matte PLA" etc.** (PR #64, @p2l). Modifier+Basis-Muster in beliebiger Wortreihenfolge werden jetzt auf den Basis-Typ normalisiert; Dash-Varianten (PLA-CF) bleiben weiterhin korrekt inkompatibel mit ihrem Basis-Typ.
|
||||
|
||||
## [0.9.25] – 2026-06-17
|
||||
|
||||
### Behoben
|
||||
- **Zufällige Abstürze / Container-Restarts — Segfault in `libcrypto.so.3`
|
||||
(Issue #53).** Der MQTT-über-TLS-Client teilte einen einzelnen SSL-Socket
|
||||
zwischen dem Reader-Thread (`recv`) und den Sender-Threads (`sendall`), ohne sie
|
||||
zu serialisieren. CPythons `ssl`-Modul erlaubt kein gleichzeitiges Lesen und
|
||||
Schreiben auf demselben Socket — die Überlappung korrumpierte den internen
|
||||
OpenSSL-Zustand und löste eine Heap-Corruption + Segfault aus, die auf manchen
|
||||
Hosts timing-bedingt zuverlässig auftrat. Sämtliche Socket-Zugriffe (recv /
|
||||
sendall / close / reconnect) werden nun unter einem einzigen Lock serialisiert;
|
||||
der Reader prüft die Bereitschaft mit `select()` außerhalb des Locks, damit die
|
||||
Sender nie ausgehungert werden. Reconnect und Disconnect tauschen den Socket
|
||||
jetzt atomar. Dank an @BasK für den detaillierten Fault-Handler-Trace.
|
||||
- **File-Browser akzeptierte Nicht-GCode-Uploads (Issue #59).** Drag & Drop umging
|
||||
den `accept`-Filter des Dateidialogs, sodass z.B. ein JPG hochgeladen werden
|
||||
konnte. Uploads werden jetzt client- und serverseitig validiert; nur `.gcode`,
|
||||
`.gcode.3mf`, `.3mf` und `.bgcode` werden akzeptiert. Dank an @gangoke.
|
||||
|
||||
## [0.9.24] – 2026-06-16
|
||||
|
||||
### Neu
|
||||
- **Objekte überspringen in jedem Druck-Flow (Issue #57).** Der „Objekte
|
||||
überspringen"-Bereich im Slot-Mapper erschien bisher nur beim Druck aus dem
|
||||
Browser-Tab. Er ist jetzt in allen Flows verfügbar (inkl. Upload / Print-Leiste),
|
||||
standardmäßig eingeklappt hinter einem `✂ Objekte überspringen (N)`-Header, damit
|
||||
der Dialog kompakt bleibt — Klick klappt Vorschau + Checkliste auf.
|
||||
- **Slot-Mapper zeigt konkreten Profilnamen (Issue #57).** Jeder Slot zeigt nun das
|
||||
zugeordnete Filament-Profil (z.B. „PolyTerra PLA — Polymaker") in den Dropdown-
|
||||
Optionen und als Hover-Tooltip am Slot-Marker, statt nur des generischen Typs.
|
||||
Fällt auf den generischen Typ zurück, wenn kein Profil gemappt ist.
|
||||
|
||||
## [0.9.23] – 2026-06-16
|
||||
|
||||
### Neu
|
||||
- **Druckdialog nach Upload automatisch öffnen.** Eine neue Einstellung
|
||||
`print_start_dialog` (Einstellungen → Drucker → „Druckstart-Verhalten") steuert,
|
||||
was nach einem Upload bei leerlaufendem Drucker passiert: „Print-Dialog" öffnet
|
||||
den Slot-Zuordnungs-Dialog automatisch, „Print-Leiste" behält das bisherige
|
||||
Banner. Basiert auf PR #56 von @gangoke.
|
||||
- **Auto-Leveling-Schalter pro Druck.** Der Druckdialog hat jetzt eine eigene
|
||||
Auto-Leveling-Checkbox, die den globalen Standard für einen einzelnen Druck
|
||||
überschreibt.
|
||||
|
||||
### Behoben
|
||||
- **Objekt-Skip wurde beim Druckstart still ignoriert (PR #56, @gangoke).** Der
|
||||
Skip-Befehl wurde gesendet, *bevor* der Drucker im `printing`-Status war, und
|
||||
daher verworfen. Der Skip wird nun in einer Retry-Schleife erneut angewendet,
|
||||
sobald der Druck bestätigt läuft — mit einer Pending-Sperre, damit die UI den
|
||||
Skip-Status nicht vorzeitig zurücksetzt.
|
||||
- **Upload während eines laufenden Drucks überschrieb die Vorschau des laufenden
|
||||
Auftrags.** Ein neuer Upload während des Drucks ersetzt nicht mehr Thumbnail /
|
||||
file_ready des Auftrags auf dem Druckbett.
|
||||
|
||||
## [0.9.22] – 2026-06-16
|
||||
|
||||
### Neu
|
||||
- **Neu strukturiertes Einstellungs-Panel.** Das Einstellungs-Modal wurde durch
|
||||
ein dauerhaftes Master-Detail-Panel mit fünf Kategorien ersetzt: Verbindung,
|
||||
Drucker, Darstellung, Filament und System. Das Poll-Intervall ist nun live
|
||||
einstellbar.
|
||||
- **Vendor-Sichtbarkeitsfilter (Issue #41).** Eine neue Checkliste in den
|
||||
Filament-Einstellungen beschränkt das Slot-Profil-Dropdown auf bestimmte
|
||||
Hersteller. „Generic" und eigene importierte Profile sind immer sichtbar.
|
||||
- **Idle-Datei-Aktionen in der Fortschritts-Karte (Issue #55).** Nach einem
|
||||
Upload bei leerlaufendem Drucker erscheinen drei Schnellaktionen direkt in der
|
||||
Fortschritts-Karte: ▶ Drucken, ⚙ Slots zuordnen und ✕ Leeren.
|
||||
|
||||
### Behoben
|
||||
- **Mobileraker-Kompatibilität (Issue #48).** Absturz in `ConfigExtruder.fromJson`
|
||||
(leeres `configfile.config`), Hänger beim Refresh (Metadata-Endlosschleife) und
|
||||
fehlende ETA/Restzeit behoben.
|
||||
|
||||
## [0.9.21] – 2026-06-14
|
||||
|
||||
### Behoben
|
||||
- **Kamera-Stream auf Android (Chrome / Firefox) nicht sichtbar.** Android-Browser
|
||||
unterstützen `multipart/x-mixed-replace` (MJPEG) nicht. Die UI erkennt Android
|
||||
jetzt automatisch und fällt auf Snapshot-Polling mit 5 fps zurück
|
||||
(`/api/camera/snapshot` alle 200 ms) — keine Server-Änderung nötig.
|
||||
|
||||
### Geändert
|
||||
- Docker-Image auf **Debian 12 (Bookworm)** gepinnt (`python:3.11-slim-bookworm`),
|
||||
um Kompatibilitätsprobleme mit glibc 2.41 zu vermeiden, die das aktuell von
|
||||
`python:3.11-slim` gezogene Debian 13 Basis-Image mitbringt.
|
||||
- MQTT- und HTTP-Verbindungen erzwingen jetzt **IPv4** (`AF_INET`), um
|
||||
Verbindungsfehler auf Hosts zu verhindern, bei denen der Drucker nur über IPv4
|
||||
erreichbar ist, das OS aber IPv6 bevorzugt.
|
||||
- Extruder-Stub in der Moonraker-`configfile`-Antwort enthält jetzt `sensor_type`
|
||||
und `filament_diameter` — behebt einen Mobileraker-Absturz
|
||||
(`Null is not a subtype of Object`, Issue #48).
|
||||
|
||||
## [0.9.20] – 2026-06-08
|
||||
|
||||
### Neu
|
||||
- **Französische Sprachunterstützung (PR #45 von @Nathacks)**
|
||||
- **Z-Höhe in der Print-UI (PR #49 von @Nathacks).** Zeigt die aktuelle
|
||||
Z-Position in mm unterhalb des Layer-Zählers.
|
||||
|
||||
### Behoben
|
||||
- **Kamera-Autostart ignorierte das "Kamera bei Druckstart einschalten"-
|
||||
Setting nach einem Bridge-Restart (Issue #50).** Das Setting wurde in
|
||||
der Prozessumgebung gecacht — nach dem Speichern in der UI überlebte
|
||||
der alte Wert den Restart und der neue Wert aus `config.ini` wurde
|
||||
nicht gelesen.
|
||||
- **Kamera startete nach manuellem Stopp während eines Drucks automatisch
|
||||
neu (Issue #50).** Ein neues `_camera_user_stopped`-Flag unterdrückt
|
||||
den Autostart für die aktuelle Drucksitzung. Es wird beim Druckende
|
||||
zurückgesetzt.
|
||||
- **Falscher "Stream nicht verfügbar"-Fehler-Toast beim manuellen
|
||||
Kamera-Stopp.** Der Bild-Fehler-Handler war noch registriert als
|
||||
`img.src` geleert wurde.
|
||||
- **JS-Fehler (`ReferenceError: br is not defined`) beim Licht-Toggle.**
|
||||
Variable wurde aus dem falschen Scope referenziert.
|
||||
- Webcam-URLs sind jetzt absolut, damit Mobileraker/Obico-Clients sie
|
||||
erreichen können.
|
||||
|
||||
## [0.9.19.1] – 2026-06-04
|
||||
|
||||
### Behoben
|
||||
|
||||
199
CHANGELOG.md
199
CHANGELOG.md
@@ -1,24 +1,195 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.26] – 2026-06-21
|
||||
|
||||
### New
|
||||
- **Italian language support** (PR #66, @Alex_M). The bridge UI is now fully
|
||||
available in Italian.
|
||||
|
||||
### Fixed
|
||||
- **Camera always started at print begin** (issue #50). `camera_on_print` was
|
||||
missing from the `/api/state` response — JavaScript read `undefined` and started
|
||||
the camera regardless of the setting. Now correctly exposed in state.
|
||||
- **Auto-leveling setting ignored in Moonraker print path** (issue #57).
|
||||
`handle_print_start` read the value only from bridge args, not from the request
|
||||
body, so the dialog checkbox and the per-print override had no effect. Now
|
||||
behaves identically to the direct print path.
|
||||
- **Filament mapping free-text fields replaced by dropdowns** (issue #57). A
|
||||
mistyped vendor/name broke profile matching silently; fields are now dropdowns
|
||||
(vendor → profile, vendor-filtered) so only valid combinations can be saved.
|
||||
- **Dashboard showed generic material type instead of profile name** (issue #57).
|
||||
AMS slot cards now display the mapped profile name (e.g. "eSUN PLA-Basic")
|
||||
instead of just "PLA". Falls back to the generic type when no profile is mapped.
|
||||
- **Ghost profile shown on empty slot** (issue #57). Stale mappings for empty
|
||||
slots were still rendered; empty slots now correctly show "–".
|
||||
- **Skip-Objects panel missing in Orca upload flow** (issue #57). Panel now
|
||||
appears in all print flows; on fresh upload the bridge requests `fileDetails`
|
||||
from the printer and retries the object list for up to 6 s.
|
||||
- **Banner and dialog appeared simultaneously** (issue #57). Settings save now
|
||||
resets the dialog cancel state so the slot mapper reliably opens after toggling
|
||||
Start Print Behavior.
|
||||
- **"Clear" reloaded idle file on next poll** (issue #57). Clear now immediately
|
||||
resets local state (`file_ready`, `filename`, `thumbnail`) and clears all dialog
|
||||
locks — the preview and action buttons disappear instantly and do not return.
|
||||
- **Material matching for "PLA Silk", "Matte PLA" etc.** (PR #64, @p2l).
|
||||
Modifier+base patterns in any word order are now normalised to the base type;
|
||||
dash-suffix variants (PLA-CF) remain correctly incompatible with their base.
|
||||
|
||||
## [0.9.25] – 2026-06-17
|
||||
|
||||
### Fixed
|
||||
- **Random crashes / container restarts — segfault in `libcrypto.so.3` (issue #53).**
|
||||
The MQTT-over-TLS client shared a single SSL socket between the reader thread
|
||||
(`recv`) and the sender threads (`sendall`) without serializing them. CPython's
|
||||
`ssl` module does not allow concurrent read and write on the same socket — the
|
||||
overlap corrupted OpenSSL's internal state, causing a heap corruption and a
|
||||
segfault that manifested reliably on some hosts (timing-dependent). All socket
|
||||
access (recv / sendall / close / reconnect) is now serialized under a single
|
||||
lock; the reader probes readiness with `select()` outside the lock so senders
|
||||
are never starved. Reconnect and disconnect now swap the socket atomically.
|
||||
Thanks to @BasK for the detailed fault-handler trace that pinpointed this.
|
||||
- **File browser accepted non-GCode uploads (issue #59).** Drag & drop bypassed
|
||||
the file picker's `accept` filter, so e.g. a JPG could be uploaded. Uploads are
|
||||
now validated both client- and server-side; only `.gcode`, `.gcode.3mf`, `.3mf`
|
||||
and `.bgcode` are accepted. Thanks @gangoke.
|
||||
|
||||
## [0.9.24] – 2026-06-16
|
||||
|
||||
### New
|
||||
- **Skip Objects available in every print flow (issue #57).** The "Skip objects"
|
||||
panel in the Slot Mapper used to appear only when printing from the Browser tab.
|
||||
It now shows in all flows (upload / print bar included), collapsed by default
|
||||
behind a `✂ Skip objects (N)` header to keep the dialog compact, expanding on
|
||||
click with the object preview and checklist.
|
||||
- **Slot Mapper shows the specific profile name (issue #57).** Each slot now
|
||||
displays its mapped filament profile (e.g. "PolyTerra PLA — Polymaker") in the
|
||||
dropdown options and as a hover tooltip on the slot marker, instead of just the
|
||||
generic type. Falls back to the generic type when no profile is mapped.
|
||||
|
||||
## [0.9.23] – 2026-06-16
|
||||
|
||||
### New
|
||||
- **Auto-open print dialog after upload.** A new `print_start_dialog` setting
|
||||
(Settings → Printer → "Start Print Behavior") controls what happens after a
|
||||
file is uploaded while the printer is idle: `Print Dialog` opens the
|
||||
slot-assignment dialog automatically, `Print Bar` keeps the previous banner
|
||||
behaviour. Based on PR #56 by @gangoke.
|
||||
- **Per-print auto-leveling toggle.** The print dialog now has its own
|
||||
auto-leveling checkbox that overrides the global default for a single print.
|
||||
|
||||
### Fixed
|
||||
- **Object skip was silently ignored at print start (PR #56, @gangoke).** The
|
||||
skip command was sent *before* the printer entered the `printing` state, so it
|
||||
was dropped. The skip is now re-applied in a retry loop once the print is
|
||||
confirmed running, with a pending-lock so the UI doesn't reset the skip state
|
||||
prematurely.
|
||||
- **Upload during an active print overwrote the running job's preview.**
|
||||
Uploading a new file while printing no longer replaces the thumbnail /
|
||||
file_ready of the job currently on the bed.
|
||||
|
||||
## [0.9.22] – 2026-06-16
|
||||
|
||||
### New
|
||||
- **Restructured Settings panel.** The settings modal has been replaced by a
|
||||
persistent Master-Detail panel with five categories: Connection, Printer,
|
||||
Appearance, Filament, and System. Poll interval is now adjustable live.
|
||||
- **Vendor visibility filter (issue #41).** A new checklist in the Filament
|
||||
settings lets you restrict the slot profile dropdown to specific manufacturers.
|
||||
"Generic" and your own imported profiles are always visible. The list updates
|
||||
automatically after a profile import.
|
||||
- **Idle file actions in the progress card (issue #55).** After uploading a file
|
||||
while the printer is idle, three quick-action buttons appear directly in the
|
||||
progress card: ▶ Print, ⚙ Map Slots, and ✕ Clear — matching the file browser
|
||||
workflow without navigating away.
|
||||
|
||||
### Fixed
|
||||
- **Mobileraker: app crashed with `Null is not a subtype of Object` in
|
||||
`ConfigExtruder.fromJson` (issue #48).** `configfile.config` was returned as
|
||||
an empty object `{}`. Mobileraker parses both `configfile.settings` and
|
||||
`configfile.config` through the same strict Dart parser — both are now
|
||||
populated with the same extruder/bed/stepper stub.
|
||||
- **Mobileraker: app hung indefinitely on refresh (issue #48).** The WebSocket
|
||||
`server.files.metadata` handler called a non-existent store method
|
||||
(`get_file_by_filename`), always returning empty metadata. Mobileraker retried
|
||||
this thousands of times per second. Both the HTTP and WS paths now share a
|
||||
single `_build_file_metadata()` method.
|
||||
- **Mobileraker: ETA / remaining time not shown (issue #48).** A side effect of
|
||||
the metadata loop fix — once `currentFile` resolves, Mobileraker can calculate
|
||||
ETA from `estimated_time`.
|
||||
- **Mobileraker: `notify_status_update` triggered repeated `ConfigFile.parse`
|
||||
(issue #48).** Static objects (`configfile`, `webhooks`, `heaters`, `history`)
|
||||
were included in every live status push. They are now filtered out; only live
|
||||
telemetry is broadcast.
|
||||
- `motion_report` (`live_position`, `live_velocity`) added to printer objects
|
||||
for Mobileraker motion display.
|
||||
- Saving filament slot profiles no longer silently drops the `visible_vendors`
|
||||
setting from `config.ini`.
|
||||
|
||||
## [0.9.21] – 2026-06-14
|
||||
|
||||
### Fixed
|
||||
- **Camera stream not visible on Android (Chrome / Firefox).** Android
|
||||
browsers do not support `multipart/x-mixed-replace` (MJPEG). The UI
|
||||
now detects Android and falls back to snapshot-polling at 5 fps
|
||||
(`/api/camera/snapshot` every 200 ms) — no server-side change needed.
|
||||
|
||||
### Changed
|
||||
- Docker image now pinned to **Debian 12 (Bookworm)** (`python:3.11-slim-bookworm`)
|
||||
to avoid glibc 2.41 compatibility issues introduced by the Debian 13
|
||||
base image that `python:3.11-slim` recently started pulling.
|
||||
- MQTT and HTTP connections now **force IPv4** (`AF_INET`) to prevent
|
||||
connection failures on hosts where the printer is only reachable via
|
||||
IPv4 but the OS prefers IPv6.
|
||||
- Extruder stub in the Moonraker `configfile` response now includes
|
||||
`sensor_type` and `filament_diameter` — fixes a Mobileraker crash
|
||||
(`Null is not a subtype of Object`, issue #48).
|
||||
|
||||
## [0.9.20] – 2026-06-08
|
||||
|
||||
### New
|
||||
- **French language support (PR #45 by @Nathacks)**
|
||||
- **Z height display in the print UI (PR #49 by @Nathacks).** Shows
|
||||
current Z position in mm below the layer counter.
|
||||
|
||||
### Fixed
|
||||
- **Camera auto-start ignored "Enable camera on print start" setting
|
||||
after a bridge restart (issue #50).** The setting was cached in the
|
||||
process environment — after saving it in the UI, the old value
|
||||
survived the restart and the new value from `config.ini` was never
|
||||
read.
|
||||
- **Camera restarted automatically after manual stop during a print
|
||||
(issue #50).** A new `_camera_user_stopped` flag suppresses
|
||||
auto-restart for the current print session. It resets when the
|
||||
print ends.
|
||||
- **Spurious "stream unavailable" error toast when stopping the camera
|
||||
manually.** The image error handler was still registered when
|
||||
`img.src` was cleared.
|
||||
- **JS error (`ReferenceError: br is not defined`) when toggling the
|
||||
light.** Variable was referenced from the wrong scope.
|
||||
- Webcam URLs are now absolute so that Mobileraker/Obico clients can
|
||||
reach them.
|
||||
|
||||
## [0.9.19.1] – 2026-06-04
|
||||
|
||||
### Fixed
|
||||
- Standalone-Binaries (Linux/Windows) zeigten `vunknown` als Version.
|
||||
Die `VERSION`-Datei ist jetzt ins PyInstaller-Onefile eingebettet.
|
||||
- Bei fehlenden TLS-Zertifikaten (`anycubic_slicer.crt`/`.key`) gab
|
||||
es nur den rohen Fehler `[Errno 2] No such file or directory`. Die
|
||||
Bridge meldet jetzt klar, wo die Dateien hingelegt werden müssen
|
||||
und dass `anycubic-certs.zip` aus dem Gitea-Release stammt.
|
||||
- Standalone binaries (Linux/Windows) reported `vunknown` as their
|
||||
version. The `VERSION` file is now embedded into the PyInstaller
|
||||
onefile bundle.
|
||||
- When the TLS certificates (`anycubic_slicer.crt`/`.key`) were
|
||||
missing, the bridge only logged the raw `[Errno 2] No such file
|
||||
or directory`. It now states clearly where the files need to be
|
||||
placed and that `anycubic-certs.zip` from the Gitea release is the
|
||||
source.
|
||||
|
||||
### Changed
|
||||
- Filament-Profil-Liste neu kuratiert: 209 statt 399 Einträge.
|
||||
Profile die nur für drucker-spezifische Vendor-Bundles existieren
|
||||
(z.B. Eryone Thinker X400, Artillery M1 Pro, WonderMaker ZR,
|
||||
Tiertime, Cubicon, CoLiDo, Afinia, Snapmaker) sind rausgeflogen
|
||||
— OrcaSlicer hätte sie im Standard-Kobra-X-Setup beim Sync
|
||||
ohnehin nicht gefunden, weil die jeweiligen Vendor-Bundles nur
|
||||
bei aktivem Drucker-Vendor geladen werden. Für solche Filamente
|
||||
bleibt der Custom-Profile-Import (Issue #41) der Weg.
|
||||
- Filament profile list re-curated: 209 entries instead of 399.
|
||||
Profiles that only exist inside printer-specific vendor bundles
|
||||
(e.g. Eryone Thinker X400, Artillery M1 Pro, WonderMaker ZR,
|
||||
Tiertime, Cubicon, CoLiDo, Afinia, Snapmaker) were dropped —
|
||||
OrcaSlicer wouldn't have found them in a default Kobra X setup
|
||||
anyway, because the matching vendor bundle is only loaded when
|
||||
the corresponding printer vendor is active. For those filaments
|
||||
the custom profile import (issue #41) remains the way.
|
||||
|
||||
## [0.9.19] – 2026-06-02
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
FROM python:3.11-slim
|
||||
FROM python:3.11-slim-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
37
README.de.md
37
README.de.md
@@ -67,44 +67,17 @@ docker compose up -d
|
||||
|
||||
**Linux-Binary (kein Docker):**
|
||||
```bash
|
||||
chmod +x kx-bridge-linux-amd64 && ./kx-bridge-linux-amd64
|
||||
chmod +x kx-bridge && ./kx-bridge
|
||||
```
|
||||
|
||||
**Windows-EXE (kein Docker):**
|
||||
```
|
||||
kx-bridge.exe
|
||||
```
|
||||
> `config\` und `data\` werden neben der EXE angelegt — portabel.
|
||||
|
||||
> ⚠️ **TLS-Zertifikate für Standalone-Binary nötig**
|
||||
>
|
||||
> Die Bridge spricht per mTLS mit dem Drucker-MQTT und braucht zwei
|
||||
> Zertifikat-Dateien **direkt neben dem Binary**:
|
||||
>
|
||||
> - `anycubic_slicer.crt`
|
||||
> - `anycubic_slicer.key`
|
||||
>
|
||||
> Beide liegen im **`anycubic-certs.zip`** auf derselben Release-Seite.
|
||||
> Lade die ZIP herunter und entpacke die beiden Dateien in dasselbe
|
||||
> Verzeichnis wie `kx-bridge-linux-amd64` bzw. `kx-bridge.exe`. Ohne
|
||||
> die Zertifikate siehst du `Verbindung fehlgeschlagen: TLS-Zertifikate
|
||||
> fehlen …` (0.9.19.1+) oder `[Errno 2] No such file or directory`
|
||||
> (ältere Builds).
|
||||
>
|
||||
> So muss es aussehen:
|
||||
> ```
|
||||
> ~/kx-bridge/
|
||||
> ├── kx-bridge-linux-amd64 (oder kx-bridge.exe)
|
||||
> ├── anycubic_slicer.crt ← aus anycubic-certs.zip
|
||||
> ├── anycubic_slicer.key ← aus anycubic-certs.zip
|
||||
> └── config/ (wird beim ersten Start angelegt)
|
||||
> ```
|
||||
>
|
||||
> Docker-User müssen das nicht machen — die Zertifikate sind im Image
|
||||
> enthalten.
|
||||
|
||||
> Bei Linux- und Windows-Binary liegen `config/` und `data/` (Einstellungen,
|
||||
> SQLite, GCode-Store) jeweils neben dem Programm. Einfach den ganzen Ordner
|
||||
> kopieren = umziehen.
|
||||
> Bei Linux- und Windows-Binary liegen `config/` und `data/` (Einstellungen, SQLite,
|
||||
> GCode-Store) jeweils neben dem Programm. Einfach den ganzen Ordner kopieren = umziehen.
|
||||
|
||||
**Python direkt:**
|
||||
```bash
|
||||
@@ -157,7 +130,7 @@ Für sauberen AMS-Filament-Sync gibt es einen **gepatchten OrcaSlicer-Build**:
|
||||
- Vendor-Match wenn `tray_info_idx` gesetzt ist, das Preset aber inkompatibel
|
||||
- Zwei-Pass-Suche: erst kompatible Presets, dann alle sichtbaren
|
||||
|
||||
**Warum das zusammen wichtig ist:** ohne #13719 landen die AMS-Slots in OrcaSlicer alle auf `Generic PLA` / `Generic PETG`, obwohl die Bridge die konkrete Marke schon mitsendet (`name + vendor_name + gate_filament_name`). Mit dem KX-Build matched OrcaSlicer deine echten User-Presets — auch die, die du via [Eigene OrcaSlicer-Profile importieren](https://gitea.it-drui.de/viewit/KX-Bridge-Release/src/branch/master/docs/filament-preset-bridge-guide.md) in die Bridge gezogen hast.
|
||||
**Warum das zusammen wichtig ist:** ohne #13719 landen die AMS-Slots in OrcaSlicer alle auf `Generic PLA` / `Generic PETG`, obwohl die Bridge die konkrete Marke schon mitsendet (`name + vendor_name + gate_filament_name`). Mit dem KX-Build matched OrcaSlicer deine echten User-Presets — auch die, die du via [Eigene OrcaSlicer-Profile importieren](#-features) in die Bridge gezogen hast.
|
||||
|
||||
Stock-Upstream-OrcaSlicer funktioniert für Slicing und Drucken weiterhin — nur das Per-Slot-Vendor-Matching beim AMS-Sync fällt dann weg. Material und Farbe pro Slot kannst du auch ohne den KX-Build über die Bridge ans Drucker-Display schreiben (das läuft über MQTT, nicht über den Slicer).
|
||||
|
||||
|
||||
56
README.md
56
README.md
@@ -30,7 +30,7 @@ officially tested or supported. Feedback welcome.</sub>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## ✨ Features
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
@@ -50,7 +50,7 @@ officially tested or supported. Feedback welcome.</sub>
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Prepare the printer
|
||||
|
||||
@@ -66,43 +66,17 @@ docker compose up -d
|
||||
|
||||
**Linux binary (no Docker):**
|
||||
```bash
|
||||
chmod +x kx-bridge-linux-amd64 && ./kx-bridge-linux-amd64
|
||||
chmod +x kx-bridge && ./kx-bridge
|
||||
```
|
||||
|
||||
**Windows EXE (no Docker):**
|
||||
```
|
||||
kx-bridge.exe
|
||||
```
|
||||
> `config\` and `data\` are created next to the EXE — portable.
|
||||
|
||||
> ⚠️ **TLS certificates required for the standalone binary**
|
||||
>
|
||||
> The bridge talks to the printer's MQTT over mTLS and needs two
|
||||
> certificate files **right next to the binary**:
|
||||
>
|
||||
> - `anycubic_slicer.crt`
|
||||
> - `anycubic_slicer.key`
|
||||
>
|
||||
> Both ship inside **`anycubic-certs.zip`** on the same release page.
|
||||
> Download it and extract the two files into the same directory as
|
||||
> `kx-bridge-linux-amd64` / `kx-bridge.exe`. Without them you'll see
|
||||
> `Verbindung fehlgeschlagen: TLS-Zertifikate fehlen …` (0.9.19.1+)
|
||||
> or `[Errno 2] No such file or directory` (older builds).
|
||||
>
|
||||
> Working layout:
|
||||
> ```
|
||||
> ~/kx-bridge/
|
||||
> ├── kx-bridge-linux-amd64 (or kx-bridge.exe)
|
||||
> ├── anycubic_slicer.crt ← from anycubic-certs.zip
|
||||
> ├── anycubic_slicer.key ← from anycubic-certs.zip
|
||||
> └── config/ (auto-created on first run)
|
||||
> ```
|
||||
>
|
||||
> Docker users don't need to do this — the certs are baked into the
|
||||
> image.
|
||||
|
||||
> With the Linux and Windows binaries, `config/` and `data/` (settings,
|
||||
> SQLite, GCode store) live next to the program. Copy the whole folder
|
||||
> = move the installation.
|
||||
> With the Linux and Windows binaries, `config/` and `data/` (settings, SQLite, GCode store)
|
||||
> live next to the program. Copy the whole folder = move the installation.
|
||||
|
||||
**Python directly:**
|
||||
```bash
|
||||
@@ -130,13 +104,13 @@ Printer → Connection type **Moonraker** → Host: `http://BRIDGE-IP:7125`
|
||||
|
||||
---
|
||||
|
||||
## Video Tutorial
|
||||
## 📺 Video Tutorial
|
||||
|
||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||
|
||||
---
|
||||
|
||||
## Recommended Slicer
|
||||
## 🎨 Recommended Slicer
|
||||
|
||||
For proper AMS filament-sync we ship a **patched OrcaSlicer build**:
|
||||
|
||||
@@ -155,7 +129,7 @@ For proper AMS filament-sync we ship a **patched OrcaSlicer build**:
|
||||
- Vendor match when `tray_info_idx` is set but its preset is incompatible
|
||||
- Two-pass lookup: first compatible presets, then all visible ones
|
||||
|
||||
**Why this matters:** without #13719 the AMS slots in OrcaSlicer all fall back to `Generic PLA` / `Generic PETG` even though the bridge already sends the concrete brand (`name + vendor_name + gate_filament_name`). With the KX build OrcaSlicer matches your actual user presets — including profiles you imported into the bridge via the [Import your own OrcaSlicer profiles](https://gitea.it-drui.de/viewit/KX-Bridge-Release/src/branch/master/docs/filament-preset-bridge-guide.md) flow.
|
||||
**Why this matters:** without #13719 the AMS slots in OrcaSlicer all fall back to `Generic PLA` / `Generic PETG` even though the bridge already sends the concrete brand (`name + vendor_name + gate_filament_name`). With the KX build OrcaSlicer matches your actual user presets — including profiles you imported into the bridge via the [Import your own OrcaSlicer profiles](#-features) flow.
|
||||
|
||||
Stock upstream OrcaSlicer still works for slicing and printing — you just lose the per-slot brand matching on AMS sync. Slot material + colour can still be pushed bridge → printer either way (that goes over MQTT, not via the slicer).
|
||||
|
||||
@@ -163,7 +137,7 @@ OrcaSlicer-KX is a build of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer
|
||||
|
||||
---
|
||||
|
||||
## Community & Integrations
|
||||
## 🏠 Community & Integrations
|
||||
|
||||
- **[Home Assistant integration](https://github.com/gangoke/kobrax-lan-hass-component)**
|
||||
by [@gangoke](https://github.com/gangoke) — exposes sensors, print controls,
|
||||
@@ -180,7 +154,7 @@ OrcaSlicer-KX is a build of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer
|
||||
|
||||
---
|
||||
|
||||
## Getting credentials manually
|
||||
## 🔧 Getting credentials manually
|
||||
|
||||
Normally not needed — *"+ Add printer"* does this automatically. If you do need it:
|
||||
|
||||
@@ -197,7 +171,7 @@ Alternatively (if the IP is unknown): open AnycubicSlicerNext, connect the print
|
||||
|
||||
---
|
||||
|
||||
## Useful commands
|
||||
## ⚙️ Useful commands
|
||||
|
||||
```bash
|
||||
docker compose logs -f # show logs
|
||||
@@ -208,7 +182,7 @@ docker compose up -d --build # rebuild locally (instead of pulling)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
## 🩹 Troubleshooting
|
||||
|
||||
<details>
|
||||
<summary><b>"Wrong MQTT credentials" on start</b></summary>
|
||||
@@ -242,7 +216,7 @@ Migration runs automatically on first start after the upgrade — no action requ
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
## 🔒 Security
|
||||
|
||||
- The bridge is reachable on the local network at `http://<host-IP>:7125` — **do not** expose it to the internet
|
||||
- `config/config.ini` contains printer credentials — do not share publicly
|
||||
@@ -250,7 +224,7 @@ Migration runs automatically on first start after the upgrade — no action requ
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
## 📄 License
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ _BASE = pathlib.Path(sys.executable).parent if getattr(sys, "frozen", False) els
|
||||
CONFIG_SECTION_CONNECTION = "connection"
|
||||
CONFIG_SECTION_PRINT = "print"
|
||||
CONFIG_SECTION_BRIDGE = "bridge"
|
||||
CONFIG_SECTION_SPOOLMAN = "spoolman"
|
||||
|
||||
|
||||
def _find_config_file() -> pathlib.Path | None:
|
||||
@@ -61,7 +62,10 @@ def _load_config_file(path: pathlib.Path):
|
||||
"AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"),
|
||||
"CAMERA_ON_PRINT": (CONFIG_SECTION_PRINT, "camera_on_print"),
|
||||
"WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"),
|
||||
"PRINT_START_DIALOG": (CONFIG_SECTION_PRINT, "print_start_dialog"),
|
||||
"BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"),
|
||||
"SPOOLMAN_SERVER": (CONFIG_SECTION_SPOOLMAN, "server"),
|
||||
"SPOOLMAN_SYNC_RATE": (CONFIG_SECTION_SPOOLMAN, "sync_rate"),
|
||||
}
|
||||
for env_key, (section, option) in mapping.items():
|
||||
if env_key not in os.environ:
|
||||
@@ -73,6 +77,18 @@ def _load_config_file(path: pathlib.Path):
|
||||
pass
|
||||
|
||||
|
||||
# Backward compatibility: old key FILE_READY_DIALOG → PRINT_START_DIALOG
|
||||
if "PRINT_START_DIALOG" not in os.environ:
|
||||
try:
|
||||
legacy = cfg.get(CONFIG_SECTION_PRINT, "file_ready_dialog")
|
||||
if legacy:
|
||||
os.environ["PRINT_START_DIALOG"] = legacy
|
||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
pass
|
||||
if "PRINT_START_DIALOG" not in os.environ and "FILE_READY_DIALOG" in os.environ:
|
||||
os.environ["PRINT_START_DIALOG"] = os.environ["FILE_READY_DIALOG"]
|
||||
|
||||
|
||||
def migrate_env_to_config(env_path: pathlib.Path, config_path: pathlib.Path):
|
||||
"""Einmalige Migration: .env → config.ini anlegen."""
|
||||
env_vals: dict[str, str] = {}
|
||||
@@ -227,10 +243,17 @@ def save_filament_profiles(profiles: dict[int, dict]) -> bool:
|
||||
return False
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(path, encoding="utf-8")
|
||||
# visible_vendors (Issue #41) ist kein Slot-Mapping — beim Ersetzen der
|
||||
# Sektion erhalten, sonst geht der Vendor-Filter beim Slot-Save verloren.
|
||||
preserved_vendors = None
|
||||
if cfg.has_option("filament_profiles", "visible_vendors"):
|
||||
preserved_vendors = cfg.get("filament_profiles", "visible_vendors")
|
||||
if cfg.has_section("filament_profiles"):
|
||||
cfg.remove_section("filament_profiles")
|
||||
if profiles:
|
||||
if profiles or preserved_vendors:
|
||||
cfg["filament_profiles"] = {}
|
||||
if preserved_vendors:
|
||||
cfg["filament_profiles"]["visible_vendors"] = preserved_vendors
|
||||
for slot_idx in sorted(profiles.keys()):
|
||||
entry = profiles[slot_idx] or {}
|
||||
if entry.get("vendor"):
|
||||
@@ -244,6 +267,43 @@ def save_filament_profiles(profiles: dict[int, dict]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def list_visible_vendors() -> list[str]:
|
||||
"""Liest [filament_profiles] visible_vendors (komma-separiert) aus config.ini.
|
||||
|
||||
Vendor-Sichtbarkeitsfilter für das Slot-Profil-Dropdown (Issue #41 Option A).
|
||||
Leere Liste = keine Einschränkung (rückwärtskompatibel: alle Vendoren).
|
||||
"""
|
||||
path = _find_config_file()
|
||||
if not path:
|
||||
return []
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(path, encoding="utf-8")
|
||||
if not cfg.has_option("filament_profiles", "visible_vendors"):
|
||||
return []
|
||||
raw = cfg.get("filament_profiles", "visible_vendors")
|
||||
return [v.strip() for v in raw.split(",") if v.strip()]
|
||||
|
||||
|
||||
def save_visible_vendors(vendors: list[str]) -> bool:
|
||||
"""Schreibt visible_vendors in [filament_profiles], ohne die Slot-Mappings
|
||||
(slot_N_*) zu verlieren. Leere Liste entfernt den Key wieder."""
|
||||
path = _find_config_file()
|
||||
if not path:
|
||||
return False
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(path, encoding="utf-8")
|
||||
if not cfg.has_section("filament_profiles"):
|
||||
cfg.add_section("filament_profiles")
|
||||
clean = [v.strip() for v in (vendors or []) if v and v.strip()]
|
||||
if clean:
|
||||
cfg["filament_profiles"]["visible_vendors"] = ", ".join(clean)
|
||||
elif cfg.has_option("filament_profiles", "visible_vendors"):
|
||||
cfg.remove_option("filament_profiles", "visible_vendors")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
cfg.write(f)
|
||||
return True
|
||||
|
||||
|
||||
def get(key: str, default: str = "") -> str:
|
||||
return os.environ.get(key, default)
|
||||
|
||||
@@ -259,3 +319,6 @@ DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto")
|
||||
AUTO_LEVELING = int(get("AUTO_LEVELING","1"))
|
||||
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT","0"))
|
||||
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
|
||||
PRINT_START_DIALOG = int(get("PRINT_START_DIALOG", get("FILE_READY_DIALOG", "1")))
|
||||
SPOOLMAN_SERVER = get("SPOOLMAN_SERVER", "")
|
||||
SPOOLMAN_SYNC_RATE = int(get("SPOOLMAN_SYNC_RATE", "0"))
|
||||
|
||||
3
docker-compose.nightly.yml
Normal file
3
docker-compose.nightly.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
services:
|
||||
kx-bridge:
|
||||
image: gitea.it-drui.de/viewit/kx-bridge:nightly
|
||||
25
docker-compose.portainer-nightly.yml
Normal file
25
docker-compose.portainer-nightly.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
# KX-Bridge Nightly — Portainer Stack
|
||||
#
|
||||
# Paste this into Portainer → Stacks → Add stack → Web editor
|
||||
#
|
||||
# Uses the nightly build — may be unstable, for testing new features early.
|
||||
# For production use, see docker-compose.portainer.yml (:latest).
|
||||
|
||||
services:
|
||||
kx-bridge:
|
||||
image: gitea.it-drui.de/viewit/kx-bridge:nightly
|
||||
volumes:
|
||||
- /mnt/dockerdata/kx-nightly/config:/app/config
|
||||
- /mnt/dockerdata/kx-nightly/data:/app/data
|
||||
ports:
|
||||
# Port 7125 = first printer. Add 7126, 7127, … for each additional printer.
|
||||
- "7125:7125"
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# Verzeichnisse müssen auf dem Host existieren:
|
||||
# mkdir -p /mnt/dockerdata/kx-nightly/config /mnt/dockerdata/kx-nightly/data
|
||||
29
docker-compose.portainer.yml
Normal file
29
docker-compose.portainer.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# KX-Bridge — Portainer Stack
|
||||
#
|
||||
# Paste this into Portainer → Stacks → Add stack → Web editor
|
||||
#
|
||||
# No configuration needed upfront — just deploy, open http://HOST-IP:7125
|
||||
# and add your printer via the UI (IP only, credentials are fetched automatically).
|
||||
#
|
||||
# All data (config, GCode store, database) is stored in named Docker volumes
|
||||
# managed by Portainer.
|
||||
|
||||
services:
|
||||
kx-bridge:
|
||||
image: gitea.it-drui.de/viewit/kx-bridge:latest
|
||||
volumes:
|
||||
- kx-bridge-config:/app/config
|
||||
- kx-bridge-data:/app/data
|
||||
ports:
|
||||
# Port 7125 = first printer. Add 7126, 7127, … for each additional printer.
|
||||
- "7125:7125"
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
kx-bridge-config:
|
||||
kx-bridge-data:
|
||||
@@ -48,3 +48,6 @@ MODE_ID = get("MODE_ID", "")
|
||||
DEVICE_ID = get("DEVICE_ID", "")
|
||||
DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto")
|
||||
AUTO_LEVELING = int(get("AUTO_LEVELING", "1"))
|
||||
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT", "0"))
|
||||
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
|
||||
PRINT_START_DIALOG = int(get("PRINT_START_DIALOG", get("FILE_READY_DIALOG", "1")))
|
||||
|
||||
115
kobrax_client.py
115
kobrax_client.py
@@ -27,6 +27,7 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
@@ -120,6 +121,10 @@ class KobraXClient:
|
||||
self._buf = b""
|
||||
self._pid = 1
|
||||
self._lock = threading.Lock()
|
||||
# Generations-Marker: wird bei jedem Socket-Swap/Close erhöht, damit der
|
||||
# Reader-Thread erkennt wenn _reconnect/_do_connect den Socket unter ihm
|
||||
# ersetzt hat (Issue #53). Schützt gegen recv auf einem stale fd.
|
||||
self._sock_gen = 0
|
||||
self._running = False
|
||||
|
||||
# Pending requests by msgid (for response ACK)
|
||||
@@ -167,20 +172,31 @@ class KobraXClient:
|
||||
ctx.set_ciphers("DEFAULT:@SECLEVEL=0")
|
||||
ctx.load_cert_chain(CERT_FILE, KEY_FILE)
|
||||
|
||||
raw = socket.create_connection((self.host, self.port), timeout=5)
|
||||
self._sock = ctx.wrap_socket(raw)
|
||||
log.info("TLS connected cipher=%s", self._sock.cipher()[0])
|
||||
# Socket als lokale Variable aufbauen — der Handshake (Connect + CONNACK)
|
||||
# läuft OHNE gehaltenes Lock, damit ein langsamer Connect die Sender nicht
|
||||
# einfriert. Erst der fertige Socket wird unter Lock eingeschwenkt (#53).
|
||||
_ai = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM)
|
||||
raw = socket.create_connection(_ai[0][4], timeout=5)
|
||||
new_sock = ctx.wrap_socket(raw)
|
||||
log.info("TLS connected cipher=%s", new_sock.cipher()[0])
|
||||
|
||||
self._sock.sendall(_build_connect(self.client_id, self.username, self.password))
|
||||
self._sock.settimeout(3)
|
||||
r = self._sock.recv(64)
|
||||
new_sock.sendall(_build_connect(self.client_id, self.username, self.password))
|
||||
new_sock.settimeout(3)
|
||||
r = new_sock.recv(64)
|
||||
if len(r) < 4 or r[0] != 0x20 or r[3] != 0:
|
||||
try:
|
||||
new_sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
raise RuntimeError(f"CONNACK failed: {r.hex()}")
|
||||
log.info("CONNACK rc=0")
|
||||
|
||||
self._sock.settimeout(0.2)
|
||||
self._buf = b""
|
||||
self._subscribe(self._sub_topic())
|
||||
new_sock.settimeout(0.2)
|
||||
with self._lock:
|
||||
self._sock = new_sock
|
||||
self._sock_gen += 1
|
||||
self._buf = b""
|
||||
self._subscribe(self._sub_topic()) # nimmt das Lock selbst — nicht verschachteln
|
||||
log.debug("MQTT connected to %s:%s", self.host, self.port)
|
||||
|
||||
def connect(self):
|
||||
@@ -206,10 +222,14 @@ class KobraXClient:
|
||||
|
||||
def disconnect(self):
|
||||
self._running = False
|
||||
try:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
with self._lock:
|
||||
try:
|
||||
if self._sock is not None:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._sock = None
|
||||
self._sock_gen += 1
|
||||
|
||||
def _reconnect(self):
|
||||
"""Persistenter Reconnect: versucht endlos weiter bis der Drucker wieder
|
||||
@@ -218,10 +238,16 @@ class KobraXClient:
|
||||
nur DEBUG um Log-Spam bei langem Drucker-Ausfall (z.B. über Nacht
|
||||
ausgeschaltet) zu vermeiden."""
|
||||
log.warning("Verbindung verloren – reconnect…")
|
||||
try:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
# Close + Invalidierung unter Lock, damit kein Sender mitten im sendall
|
||||
# auf den gerade geschlossenen Socket trifft (Issue #53).
|
||||
with self._lock:
|
||||
try:
|
||||
if self._sock is not None:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._sock = None
|
||||
self._sock_gen += 1
|
||||
delays = [2, 4, 8, 15, 30, 60]
|
||||
attempt = 0
|
||||
while self._running:
|
||||
@@ -245,7 +271,8 @@ class KobraXClient:
|
||||
with self._lock:
|
||||
pid = self._pid
|
||||
self._pid += 1
|
||||
self._sock.sendall(_build_subscribe(topic, pid))
|
||||
if self._sock is not None:
|
||||
self._sock.sendall(_build_subscribe(topic, pid))
|
||||
log.info("SUB %s", topic)
|
||||
|
||||
# -- Read loop -----------------------------------------------------------
|
||||
@@ -255,17 +282,52 @@ class KobraXClient:
|
||||
_empty_count = 0
|
||||
while self._running:
|
||||
if time.time() - last_ping > 30:
|
||||
ping_ok = False
|
||||
with self._lock:
|
||||
try:
|
||||
self._sock.sendall(_build_pingreq())
|
||||
if self._sock is not None:
|
||||
self._sock.sendall(_build_pingreq())
|
||||
ping_ok = True
|
||||
except Exception:
|
||||
if self._running and not self._reconnect():
|
||||
break
|
||||
last_ping = time.time()
|
||||
continue
|
||||
ping_ok = False
|
||||
# _reconnect() AUSSERHALB des Locks aufrufen — es nimmt das Lock
|
||||
# selbst, und threading.Lock ist nicht reentrant (sonst Deadlock).
|
||||
if not ping_ok:
|
||||
if self._running and not self._reconnect():
|
||||
break
|
||||
last_ping = time.time()
|
||||
# Aktuellen Socket + Generation unter Lock greifen, damit ein
|
||||
# paralleler _reconnect/_do_connect-Swap uns nicht auf einem stale
|
||||
# fd pollen lässt (Issue #53).
|
||||
with self._lock:
|
||||
sock = self._sock
|
||||
gen = self._sock_gen
|
||||
if sock is None:
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
|
||||
# Idle-Wartezeit OHNE Lock — select probt nur die Bereitschaft, so
|
||||
# blockiert der Reader während Leerlauf nie das gemeinsame Lock.
|
||||
try:
|
||||
data = self._sock.recv(65536)
|
||||
ready, _, _ = select.select([sock], [], [], 0.2)
|
||||
except (OSError, ValueError):
|
||||
# fd geschlossen/ungültig (Reconnect oder Disconnect mitten im select)
|
||||
if not self._running:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
if not ready:
|
||||
continue # Leerlauf, kein Lock gehalten
|
||||
|
||||
# Daten liegen an: Lock kurz greifen für das eine recv, serialisiert
|
||||
# gegen alle sendall-Caller. recv blockiert nicht lange (select sagte
|
||||
# ready, Socket-Timeout ist 0.2s).
|
||||
try:
|
||||
with self._lock:
|
||||
# Socket könnte zwischen select und hier ersetzt worden sein.
|
||||
if self._sock_gen != gen or self._sock is not sock:
|
||||
continue
|
||||
data = sock.recv(65536)
|
||||
if not data:
|
||||
# Windows SSL kann kurzzeitig b"" liefern ohne echten EOF
|
||||
_empty_count += 1
|
||||
@@ -274,7 +336,7 @@ class KobraXClient:
|
||||
continue
|
||||
_empty_count = 0
|
||||
self._buf += data
|
||||
self._drain()
|
||||
self._drain() # außerhalb des Locks — Dispatch/event.set() bleibt prompt
|
||||
except ssl.SSLWantReadError:
|
||||
continue
|
||||
except socket.timeout:
|
||||
@@ -596,7 +658,8 @@ class KobraXClient:
|
||||
# langsamerem WLAN am Drucker dauert das Schieben sonst >30 s und
|
||||
# würde den Connect-Timeout fälschlich auslösen. Read-Timeout danach
|
||||
# generös (Drucker verarbeitet die Datei bevor er antwortet).
|
||||
sock = socket.create_connection((self.host, 18910), timeout=10)
|
||||
_ai = socket.getaddrinfo(self.host, 18910, socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock = socket.create_connection(_ai[0][4], timeout=10)
|
||||
sock.settimeout(None) # blocking während Send
|
||||
sock.sendall(headers + body)
|
||||
sock.settimeout(180)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -39,118 +39,16 @@
|
||||
<option value="en">English</option>
|
||||
<option value="es">Espanol</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="zh-cn">中文(简体)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="theme-btn" onclick="openSettings()" id="settings-btn" title="Einstellungen">⚙</button>
|
||||
<button class="theme-btn" onclick="showPanel('settings')" id="settings-btn" title="Einstellungen">⚙</button>
|
||||
<button class="conn-btn disconnected" id="conn-btn" onclick="toggleConnection()">⚡ Verbinden</button>
|
||||
</header>
|
||||
|
||||
<!-- ═══ SETTINGS MODAL ═══ -->
|
||||
<div class="modal-overlay" id="settings-modal" onclick="if(event.target===this)closeSettings()">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" id="modal-title-settings">Einstellungen</span>
|
||||
<button class="modal-close" onclick="closeSettings()">✕</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="modal-field" style="margin-bottom:12px">
|
||||
<label id="lbl-printer-name" style="font-weight:600">Drucker-Name</label>
|
||||
<input type="text" id="s-printer-name" placeholder="z.B. Kobra X Links">
|
||||
</div>
|
||||
<div class="modal-section" id="modal-sec-connection">Verbindung</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-printer-ip">Drucker-IP</label>
|
||||
<input type="text" id="s-printer-ip" placeholder="192.168.x.x">
|
||||
<small id="lbl-ip-hint" style="color:#f80;display:none"></small>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-mqtt-port">MQTT-Port</label>
|
||||
<input type="number" id="s-mqtt-port" placeholder="9883">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-username">MQTT-Benutzername</label>
|
||||
<input type="text" id="s-username" placeholder="userXXXXXXXX" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-password">MQTT-Passwort</label>
|
||||
<input type="password" id="s-password" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-device-id">Device-ID</label>
|
||||
<input type="text" id="s-device-id" placeholder="32 Hex-Zeichen">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-mode-id">Mode-ID</label>
|
||||
<input type="text" id="s-mode-id" placeholder="20030">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="modal-section" id="modal-sec-print">Druckeinstellungen</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-default-slot">Standard-Slot (Einfarbdruck)</label>
|
||||
<select id="s-default-slot">
|
||||
<option value="auto" id="opt-slot-auto">Auto (alle belegten Slots)</option>
|
||||
<option value="0" id="opt-slot-0">Slot 1</option>
|
||||
<option value="1" id="opt-slot-1">Slot 2</option>
|
||||
<option value="2" id="opt-slot-2">Slot 3</option>
|
||||
<option value="3" id="opt-slot-3">Slot 4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-auto-leveling" style="width:auto;margin:0">
|
||||
<label id="lbl-auto-leveling" style="margin:0;cursor:pointer" for="s-auto-leveling">Auto-Leveling vor Druck</label>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-camera-on-print" style="width:auto;margin:0">
|
||||
<label id="lbl-camera-on-print" style="margin:0;cursor:pointer" for="s-camera-on-print">Kamera bei Druckstart einschalten</label>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-web-upload-warning" style="width:auto;margin:0">
|
||||
<label id="lbl-web-upload-warning" style="margin:0;cursor:pointer" for="s-web-upload-warning">Warnung bei Web-Upload-Druck anzeigen</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="modal-section" id="modal-sec-poll">Poll-Intervall</div>
|
||||
<div class="poll-btns">
|
||||
<button class="poll-btn" onclick="setPoll(1000)" id="poll-1">1s</button>
|
||||
<button class="poll-btn active" onclick="setPoll(2000)" id="poll-2">2s</button>
|
||||
<button class="poll-btn" onclick="setPoll(5000)" id="poll-5">5s</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OrcaSlicer-Profile (Custom-Profile-Import, Issue #41) -->
|
||||
<div>
|
||||
<div class="modal-section" id="modal-sec-orca-profiles">OrcaSlicer-Profile</div>
|
||||
<div style="font-size:11px;color:var(--txt2);margin-bottom:8px" id="orca-profiles-hint">
|
||||
Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show Configuration Folder)
|
||||
</div>
|
||||
<div id="orca-profiles-list" style="margin-bottom:8px;font-size:12px;color:var(--txt2)"></div>
|
||||
<button class="btn btn-sm" id="btn-orca-profiles-import" onclick="openProfileImport()"
|
||||
style="background:var(--raised);color:var(--txt)">
|
||||
⬆ <span id="lbl-orca-profiles-import">Profile importieren</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="modal-section" id="modal-sec-version">Version</div>
|
||||
<div class="update-row">
|
||||
<span id="s-version-label" style="font-size:13px;color:var(--txt)">–</span>
|
||||
<button class="btn btn-sm" style="background:var(--raised);color:var(--txt)" onclick="checkUpdate()" id="btn-update-check">🔄 <span id="lbl-update-check">Auf Updates prüfen</span></button>
|
||||
</div>
|
||||
<div class="update-status" id="update-status" style="margin-top:6px"></div>
|
||||
<button class="btn btn-sm btn-accent" id="btn-update-apply" style="display:none;margin-top:8px" onclick="applyUpdate()">
|
||||
<span id="lbl-update-apply">Jetzt installieren</span>
|
||||
</button>
|
||||
<div id="update-changelog" style="display:none;margin-top:10px;background:var(--raised);border-radius:6px;padding:10px;font-size:11px;font-family:var(--mono);color:var(--txt2);white-space:pre-wrap;max-height:180px;overflow-y:auto;line-height:1.6"></div>
|
||||
</div>
|
||||
|
||||
<button class="modal-save" onclick="saveSettings()" id="btn-save-settings">Speichern & Neustart</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Settings-Modal entfernt — jetzt #panel-settings (Master-Detail im Main-Bereich) -->
|
||||
|
||||
<!-- ═══ AMS SLOT EDIT DIALOG ═══ -->
|
||||
<div class="modal-overlay" id="slot-edit-modal" onclick="if(event.target===this)closeSlotEdit()">
|
||||
@@ -230,6 +128,8 @@
|
||||
<span class="nav-icon">🗂</span><span class="nav-text">Browser</span></button>
|
||||
<button class="nav-btn" onclick="showPanel('console');clearLogBadge()" id="nb-console">
|
||||
<span class="nav-icon">≡</span><span class="nav-text">Konsole</span><span id="log-badge" style="display:none;margin-left:4px;background:var(--err);color:#fff;border-radius:10px;font-size:10px;padding:1px 5px;font-weight:700"></span></button>
|
||||
<button class="nav-btn" onclick="showPanel('settings')" id="nb-settings">
|
||||
<span class="nav-icon">⚙</span><span class="nav-text" id="nav-settings">Einstellungen</span></button>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
@@ -267,9 +167,15 @@
|
||||
<div class="pct-big"><span id="d-pct">0</span><small>%</small></div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin:8px 0">
|
||||
<div class="progress-bar" style="flex:1;margin:0"><div class="progress-fill" id="d-pbar" style="width:0%"></div></div>
|
||||
<div class="time-block" style="padding:6px 10px;min-width:72px;text-align:center;flex-shrink:0">
|
||||
<div class="time-label" id="d-lbl-layers"></div>
|
||||
<div class="time-val" style="font-size:16px" id="d-layers">–</div>
|
||||
<div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0">
|
||||
<div class="time-block" style="padding:6px 10px;min-width:72px;text-align:center">
|
||||
<div class="time-label" id="d-lbl-layers"></div>
|
||||
<div class="time-val" style="font-size:16px" id="d-layers">–</div>
|
||||
</div>
|
||||
<div class="time-block" style="padding:4px 10px;min-width:72px;text-align:center">
|
||||
<div class="time-label" id="d-lbl-zpos">Z</div>
|
||||
<div class="time-val" style="font-size:13px" id="d-zpos">–</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-grid">
|
||||
@@ -292,6 +198,12 @@
|
||||
<button class="btn btn-skip btn-sm" id="d-btn-skip" onclick="openSkipDialog()" style="display:none">✂ <span id="d-btn-skip-label">Objekte</span></button>
|
||||
<button class="btn btn-cancel btn-sm" id="d-btn-cancel" onclick="confirmCancel()">✕ Stopp</button>
|
||||
</div>
|
||||
<!-- Aktionen für eine geladene, aber nicht laufende Datei (Issue #55) -->
|
||||
<div class="ctrl-btns" id="d-idle-btns" style="margin-top:12px;display:none">
|
||||
<button class="btn btn-accent btn-sm" id="d-idle-print" onclick="startIdleFile()">▶ <span id="d-idle-print-lbl">Drucken</span></button>
|
||||
<button class="btn btn-sm" id="d-idle-slots" onclick="startIdleFileWithSlots()" style="background:var(--raised);color:var(--txt)">⚙ <span id="d-idle-slots-lbl">Slots zuordnen</span></button>
|
||||
<button class="btn btn-cancel btn-sm" id="d-idle-clear" onclick="clearIdleFile()">✕ <span id="d-idle-clear-lbl">Leeren</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temperatursteuerung + Verlauf -->
|
||||
@@ -514,6 +426,170 @@
|
||||
<div class="console" id="console-log" style="height:calc(100vh - 260px);min-height:160px" onscroll="onLogScroll()"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ EINSTELLUNGEN ═══ -->
|
||||
<div class="panel" id="panel-settings">
|
||||
<div class="settings-wrap">
|
||||
<div class="settings-cats">
|
||||
<button class="set-cat active" id="setcat-connection" onclick="showSettingsCat('connection')"><span>🔌</span> <span id="setcat-lbl-connection">Verbindung</span></button>
|
||||
<button class="set-cat" id="setcat-printer" onclick="showSettingsCat('printer')"><span>🖨</span> <span id="setcat-lbl-printer">Drucker</span></button>
|
||||
<button class="set-cat" id="setcat-display" onclick="showSettingsCat('display')"><span>🎨</span> <span id="setcat-lbl-display">Darstellung</span></button>
|
||||
<button class="set-cat" id="setcat-filament" onclick="showSettingsCat('filament')"><span>🧵</span> <span id="setcat-lbl-filament">Filament</span></button>
|
||||
<button class="set-cat" id="setcat-system" onclick="showSettingsCat('system')"><span>⚙</span> <span id="setcat-lbl-system">System</span></button>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<!-- Verbindung -->
|
||||
<div class="set-group active" id="setgrp-connection">
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🔌</span> <span id="modal-sec-connection">Verbindung</span></div>
|
||||
<div class="modal-field" style="margin-bottom:12px">
|
||||
<label id="lbl-printer-name" style="font-weight:600">Drucker-Name</label>
|
||||
<input type="text" id="s-printer-name" placeholder="z.B. Kobra X Links">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-printer-ip">Drucker-IP</label>
|
||||
<input type="text" id="s-printer-ip" placeholder="192.168.x.x">
|
||||
<small id="lbl-ip-hint" style="color:#f80;display:none"></small>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-mqtt-port">MQTT-Port</label>
|
||||
<input type="number" id="s-mqtt-port" placeholder="9883">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-username">MQTT-Benutzername</label>
|
||||
<input type="text" id="s-username" placeholder="userXXXXXXXX" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-password">MQTT-Passwort</label>
|
||||
<input type="password" id="s-password" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-device-id">Device-ID</label>
|
||||
<input type="text" id="s-device-id" placeholder="32 Hex-Zeichen">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-mode-id">Mode-ID</label>
|
||||
<input type="text" id="s-mode-id" placeholder="20030">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drucker -->
|
||||
<div class="set-group" id="setgrp-printer">
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🖨</span> <span id="modal-sec-print">Druckeinstellungen</span></div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-default-slot">Standard-Slot (Einfarbdruck)</label>
|
||||
<select id="s-default-slot">
|
||||
<option value="auto" id="opt-slot-auto">Auto (alle belegten Slots)</option>
|
||||
<option value="0" id="opt-slot-0">Slot 1</option>
|
||||
<option value="1" id="opt-slot-1">Slot 2</option>
|
||||
<option value="2" id="opt-slot-2">Slot 3</option>
|
||||
<option value="3" id="opt-slot-3">Slot 4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-auto-leveling" style="width:auto;margin:0">
|
||||
<label id="lbl-auto-leveling" style="margin:0;cursor:pointer" for="s-auto-leveling">Auto-Leveling vor Druck</label>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-file-ready-mode">Nach Upload: Druckstart-Verhalten</label>
|
||||
<select id="s-file-ready-mode">
|
||||
<option value="1" id="opt-file-ready-dialog">Print-Dialog</option>
|
||||
<option value="0" id="opt-file-ready-banner">Print-Leiste</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-camera-on-print" style="width:auto;margin:0">
|
||||
<label id="lbl-camera-on-print" style="margin:0;cursor:pointer" for="s-camera-on-print">Kamera bei Druckstart einschalten</label>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<input type="checkbox" id="s-web-upload-warning" style="width:auto;margin:0">
|
||||
<label id="lbl-web-upload-warning" style="margin:0;cursor:pointer" for="s-web-upload-warning">Warnung bei Web-Upload-Druck anzeigen</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Darstellung -->
|
||||
<div class="set-group" id="setgrp-display">
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🎨</span> <span id="setcat-lbl-display2">Darstellung</span></div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-set-lang">Sprache</label>
|
||||
<select id="s-lang-select" onchange="setLanguage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Espanol</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="zh-cn">中文(简体)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
|
||||
<button class="btn btn-sm" style="background:var(--raised);color:var(--txt)" onclick="toggleTheme()"><span id="lbl-set-theme">Hell / Dunkel umschalten</span></button>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-poll-interval">Poll-Intervall (Sekunden)</label>
|
||||
<input type="number" id="s-poll-interval" min="1" max="60" step="1" placeholder="3" oninput="onPollIntervalInput()">
|
||||
<small style="color:var(--txt2)" id="lbl-poll-hint">Wie oft die Bridge den Drucker-Status abfragt</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filament -->
|
||||
<div class="set-group" id="setgrp-filament">
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🧵</span> <span id="modal-sec-orca-profiles">OrcaSlicer-Profile</span></div>
|
||||
<div style="font-size:11px;color:var(--txt2);margin-bottom:8px" id="orca-profiles-hint">
|
||||
Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show Configuration Folder)
|
||||
</div>
|
||||
<div id="orca-profiles-list" style="margin-bottom:8px;font-size:12px;color:var(--txt2)"></div>
|
||||
<button class="btn btn-sm" id="btn-orca-profiles-import" onclick="openProfileImport()"
|
||||
style="background:var(--raised);color:var(--txt)">
|
||||
⬆ <span id="lbl-orca-profiles-import">Profile importieren</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🎯</span> <span id="lbl-filament-mapping">Filament-Profil-Mapping (pro Slot)</span></div>
|
||||
<div style="font-size:11px;color:var(--txt2);margin-bottom:8px" id="filament-mapping-hint">
|
||||
Festes Orca-Profil pro AMS-Slot. Beim Slicer-Sync sendet die Bridge dieses Profil statt „Generic".
|
||||
</div>
|
||||
<div id="filament-mapping-list"></div>
|
||||
<button class="btn btn-sm" style="background:var(--accent);color:#fff;margin-top:8px" onclick="saveFilamentMapping()"><span id="lbl-filament-mapping-save">Mapping speichern</span></button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title"><span>👁</span> <span id="lbl-visible-vendors">Sichtbare Hersteller (Profil-Dropdown)</span></div>
|
||||
<div style="font-size:11px;color:var(--txt2);margin-bottom:8px" id="visible-vendors-hint">
|
||||
Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic" und eigene Profile sind immer sichtbar.
|
||||
</div>
|
||||
<input type="text" id="vendor-filter-search" placeholder="Hersteller suchen…" oninput="renderVendorChecklist()"
|
||||
style="width:100%;padding:6px 10px;margin-bottom:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);font-size:12px">
|
||||
<div id="visible-vendors-list" style="max-height:260px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:8px"></div>
|
||||
<button class="btn btn-sm" style="background:var(--accent);color:#fff;margin-top:8px" onclick="saveVisibleVendors()"><span id="lbl-visible-vendors-save">Auswahl speichern</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System -->
|
||||
<div class="set-group" id="setgrp-system">
|
||||
<div class="card">
|
||||
<div class="card-title"><span>⚙</span> <span id="modal-sec-version">Version</span></div>
|
||||
<div class="update-row">
|
||||
<span id="s-version-label" style="font-size:13px;color:var(--txt)">–</span>
|
||||
<button class="btn btn-sm" style="background:var(--raised);color:var(--txt)" onclick="checkUpdate()" id="btn-update-check">🔄 <span id="lbl-update-check">Auf Updates prüfen</span></button>
|
||||
</div>
|
||||
<div class="update-status" id="update-status" style="margin-top:6px"></div>
|
||||
<button class="btn btn-sm btn-accent" id="btn-update-apply" style="display:none;margin-top:8px" onclick="applyUpdate()">
|
||||
<span id="lbl-update-apply">Jetzt installieren</span>
|
||||
</button>
|
||||
<div id="update-changelog" style="display:none;margin-top:10px;background:var(--raised);border-radius:6px;padding:10px;font-size:11px;font-family:var(--mono);color:var(--txt2);white-space:pre-wrap;max-height:180px;overflow-y:auto;line-height:1.6"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="modal-save" onclick="saveSettings()" id="btn-save-settings" style="margin-top:14px">Speichern & Neustart</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -522,6 +598,7 @@
|
||||
<button class="bnav-btn" onclick="showPanel('printers');loadPrinterTab()" id="bnb-printers"><span class="bnav-icon">🖨</span>Drucker</button>
|
||||
<button class="bnav-btn" onclick="showPanel('store');loadStore()" id="bnb-store"><span class="bnav-icon">🗂</span>Browser</button>
|
||||
<button class="bnav-btn" onclick="showPanel('console');clearLogBadge()" id="bnb-console"><span class="bnav-icon">≡</span>Log<span id="log-badge-bot" style="display:none;margin-left:3px;background:var(--err);color:#fff;border-radius:10px;font-size:10px;padding:1px 4px;font-weight:700"></span></button>
|
||||
<button class="bnav-btn" onclick="showPanel('settings')" id="bnb-settings"><span class="bnav-icon">⚙</span>Setup</button>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -551,9 +628,30 @@
|
||||
<p id="fd-slots-hint" style="font-size:12px;color:var(--txt2);margin-bottom:10px">GCode-Kanal → AMS-Slot zuweisen:</p>
|
||||
<div id="fd-slots" style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px"></div>
|
||||
<div id="fd-objects-section" style="display:none;margin-bottom:16px">
|
||||
<p id="fd-objects-hint" style="font-size:12px;color:var(--txt2);margin-bottom:8px">Objekte überspringen (optional):</p>
|
||||
<div id="fd-objects-svg" style="display:none;background:var(--raised);border:1px solid var(--border);border-radius:8px;padding:6px;margin-bottom:8px;text-align:center"></div>
|
||||
<div id="fd-objects" style="display:flex;flex-direction:column;gap:6px;max-height:140px;overflow-y:auto"></div>
|
||||
<button type="button" id="fd-objects-toggle" onclick="toggleFdObjects()"
|
||||
style="display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;background:var(--raised);border:1px solid var(--border);border-radius:8px;color:var(--txt);cursor:pointer;font-size:12px;text-align:left">
|
||||
<span id="fd-objects-arrow" style="font-size:10px;transition:transform .15s">▶</span>
|
||||
<span>✂ <span id="fd-objects-toggle-lbl">Objekte überspringen</span></span>
|
||||
<span id="fd-objects-count" style="margin-left:auto;color:var(--txt2);font-weight:600"></span>
|
||||
</button>
|
||||
<div id="fd-objects-body" style="display:none;margin-top:8px">
|
||||
<div id="fd-objects-svg" style="display:none;background:var(--raised);border:1px solid var(--border);border-radius:8px;padding:6px;margin-bottom:8px;text-align:center"></div>
|
||||
<div id="fd-objects" style="display:flex;flex-direction:column;gap:6px;max-height:140px;overflow-y:auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:14px;padding:10px 12px;background:var(--raised);border-radius:8px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;font-weight:600;color:var(--txt2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em" id="fd-options-title">Druckoptionen</div>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<input type="checkbox" id="fd-auto-leveling" style="width:auto;margin:0">
|
||||
<label for="fd-auto-leveling" style="margin:0;cursor:pointer;font-size:13px" id="fd-lbl-auto-leveling">Auto-Leveling</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="fd-spoolman-section" style="display:none;margin-bottom:16px;border-top:1px solid var(--border);padding-top:12px">
|
||||
<p style="font-size:12px;color:var(--txt2);margin-bottom:8px;display:flex;align-items:center;gap:6px">
|
||||
<span id="fd-spoolman-lbl">🧵 Spoolman</span>
|
||||
<span id="fd-spoolman-loading" style="display:none;font-size:10px">…</span>
|
||||
</p>
|
||||
<div id="fd-spoolman-rows" style="display:flex;flex-direction:column;gap:6px"></div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end">
|
||||
<button id="fd-cancel" onclick="closeFilamentDialog()" style="padding:8px 16px;background:var(--raised);border:1px solid var(--border);border-radius:8px;color:var(--txt);cursor:pointer">Abbrechen</button>
|
||||
|
||||
@@ -212,6 +212,24 @@ canvas.tchart{width:100%;height:60px;display:block;border-radius:6px;background:
|
||||
.panel{display:none}
|
||||
.panel.active{display:block}
|
||||
|
||||
/* ── SETTINGS (Master-Detail) ── */
|
||||
.settings-wrap{display:grid;grid-template-columns:200px 1fr;gap:16px;align-items:start}
|
||||
.settings-cats{display:flex;flex-direction:column;gap:4px;position:sticky;top:12px}
|
||||
.set-cat{display:flex;align-items:center;gap:8px;padding:10px 12px;border-radius:8px;
|
||||
border:1px solid transparent;background:var(--raised);color:var(--txt2);cursor:pointer;
|
||||
font-size:13px;text-align:left;transition:background .15s,color .15s}
|
||||
.set-cat:hover{color:var(--txt)}
|
||||
.set-cat.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||||
.set-group{display:none}
|
||||
.set-group.active{display:block}
|
||||
.set-group .card{margin-bottom:14px}
|
||||
@media(max-width:768px){
|
||||
.settings-wrap{grid-template-columns:1fr}
|
||||
.settings-cats{flex-direction:row;flex-wrap:wrap;position:static;overflow-x:auto}
|
||||
.set-cat{flex:1;min-width:auto;justify-content:center;padding:8px 6px;font-size:12px}
|
||||
.set-cat .nav-text{display:inline}
|
||||
}
|
||||
|
||||
/* ── FILE BROWSER UPLOAD ZONE ── */
|
||||
#store-upload-zone{
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"lbl_remaining": "Restzeit:",
|
||||
"lbl_slicer_time": "Slicer-Schätzung:",
|
||||
"lbl_layers": "Layer",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 Leise",
|
||||
"speed_normal": "⚡ Normal",
|
||||
"speed_sport": "🚀 Sport",
|
||||
@@ -126,8 +127,21 @@
|
||||
"settings_title": "Einstellungen",
|
||||
"settings_connection": "Verbindung",
|
||||
"settings_print": "Druckeinstellungen",
|
||||
"settings_poll": "Poll-Intervall",
|
||||
"settings_poll": "Poll-Intervall (Sekunden)",
|
||||
"settings_version": "Version",
|
||||
"nav_settings": "Einstellungen",
|
||||
"settings_cat_display": "Darstellung",
|
||||
"settings_cat_filament": "Filament",
|
||||
"settings_cat_language": "Sprache",
|
||||
"settings_cat_theme": "Hell / Dunkel umschalten",
|
||||
"settings_filament_mapping": "Filament-Profil-Mapping (pro Slot)",
|
||||
"settings_filament_mapping_save": "Mapping speichern",
|
||||
"settings_visible_vendors": "Sichtbare Hersteller (Profil-Dropdown)",
|
||||
"settings_visible_vendors_hint": "Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic\" und eigene Profile sind immer sichtbar.",
|
||||
"settings_visible_vendors_save": "Auswahl speichern",
|
||||
"progress_action_print": "Drucken",
|
||||
"progress_action_slots": "Slots zuordnen",
|
||||
"progress_action_clear": "Leeren",
|
||||
"settings_save": "Speichern & Neustart",
|
||||
"settings_printer_name": "Drucker-Name",
|
||||
"settings_printer_ip": "Drucker-IP",
|
||||
@@ -192,6 +206,7 @@
|
||||
"skip_sending": "Sende …",
|
||||
"skip_success": "Objekte werden übersprungen.",
|
||||
"fd_objects_hint": "Objekte überspringen (optional):",
|
||||
"fd_objects_toggle": "Objekte überspringen",
|
||||
"fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:",
|
||||
"fd_cancel": "Abbrechen",
|
||||
"fd_print": "▶ Drucken",
|
||||
@@ -237,11 +252,40 @@
|
||||
"store_upload_busy": "⏳ Hochladen…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "Alle",
|
||||
"sf_ok": "✓ Erfolgreich",
|
||||
"sf_err": "✗ Fehler",
|
||||
"sf_new": "Neu",
|
||||
"ss_date": "↓ Datum",
|
||||
"ss_name": "A–Z Name",
|
||||
"ss_dur": "⏱ Druckzeit"
|
||||
"ss_dur": "⏱ Druckzeit",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "Custom",
|
||||
"fd_options_title": "Optionen",
|
||||
"print_auto_leveling": "Auto-Leveling für diesen Druck",
|
||||
"settings_file_ready_mode": "Druckdialog starten",
|
||||
"settings_file_ready_banner": "Druckleiste",
|
||||
"settings_file_ready_dialog": "Druckdialog",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "Richtung:",
|
||||
"log_lvl_err": "⛔ Fehler",
|
||||
"log_lvl_warn": "⚠ Warnung",
|
||||
"log_topic_label": "Thema:",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "Druck",
|
||||
"log_topic_info": "Info",
|
||||
"log_topic_status": "Status",
|
||||
"log_download": "⬇ Download",
|
||||
"log_auto": "⬇ Auto",
|
||||
"log_clear": "✕ Leeren",
|
||||
"log_filter_placeholder": "Filtern…",
|
||||
"skip_cancel": "Abbrechen",
|
||||
"skip_confirm": "Überspringen"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"lbl_remaining": "Remaining:",
|
||||
"lbl_slicer_time": "Slicer estimate:",
|
||||
"lbl_layers": "Layer",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 Silent",
|
||||
"speed_normal": "⚡ Normal",
|
||||
"speed_sport": "🚀 Sport",
|
||||
@@ -68,6 +69,13 @@
|
||||
"ace_dry_dialog_save_restart": "Save & Restart",
|
||||
"ace_dry_dialog_custom_name": "Custom Name",
|
||||
"ace_dry_dialog_reset_default": "Reset to Default",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "Custom",
|
||||
"cam_placeholder": "📷 Camera not started",
|
||||
"cam_stream_unavailable": "Stream unavailable",
|
||||
"btn_cam_start": "▶ Camera",
|
||||
@@ -126,7 +134,20 @@
|
||||
"settings_title": "Settings",
|
||||
"settings_connection": "Connection",
|
||||
"settings_print": "Print Settings",
|
||||
"settings_poll": "Poll Interval",
|
||||
"settings_poll": "Poll Interval (seconds)",
|
||||
"nav_settings": "Settings",
|
||||
"settings_cat_display": "Appearance",
|
||||
"settings_cat_filament": "Filament",
|
||||
"settings_cat_language": "Language",
|
||||
"settings_cat_theme": "Toggle light / dark",
|
||||
"settings_filament_mapping": "Filament profile mapping (per slot)",
|
||||
"settings_filament_mapping_save": "Save mapping",
|
||||
"settings_visible_vendors": "Visible vendors (profile dropdown)",
|
||||
"settings_visible_vendors_hint": "Only these vendors appear in the slot profile dropdown. Nothing selected = show all. \"Generic\" and your own profiles are always visible.",
|
||||
"settings_visible_vendors_save": "Save selection",
|
||||
"progress_action_print": "Print",
|
||||
"progress_action_slots": "Map slots",
|
||||
"progress_action_clear": "Clear",
|
||||
"settings_version": "Version",
|
||||
"settings_save": "Save & Restart",
|
||||
"settings_printer_name": "Printer Name",
|
||||
@@ -139,7 +160,12 @@
|
||||
"hint_ip_no_port": "IP address only, no port (e.g. 192.168.1.102)",
|
||||
"settings_default_slot": "Default Slot (single color)",
|
||||
"settings_slot_auto": "Auto (all loaded slots)",
|
||||
"settings_auto_leveling": "Auto-Leveling before print",
|
||||
"settings_auto_leveling": "Auto-Leveling Default",
|
||||
"fd_options_title": "Print Options",
|
||||
"print_auto_leveling": "Auto-Leveling",
|
||||
"settings_file_ready_mode": "Start Print Behavior",
|
||||
"settings_file_ready_banner": "Print Bar",
|
||||
"settings_file_ready_dialog": "Print Dialog",
|
||||
"settings_camera_on_print": "Turn camera on at print start",
|
||||
"settings_web_upload_warning": "Show warning when printing web uploads",
|
||||
"update_check": "Check for Updates",
|
||||
@@ -178,7 +204,21 @@
|
||||
"orca_profile_done": "Imported",
|
||||
"orca_profile_skipped": "skipped",
|
||||
"log_dir_all": "All",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "Dir:",
|
||||
"log_lvl_label": "Level:",
|
||||
"log_lvl_err": "⛔ Errors",
|
||||
"log_lvl_warn": "⚠ Warn",
|
||||
"log_topic_label": "Topic:",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "Print",
|
||||
"log_topic_info": "Info",
|
||||
"log_topic_status": "Status",
|
||||
"log_download": "⬇ Download",
|
||||
"log_auto": "⬇ Auto",
|
||||
"log_clear": "✕ Clear",
|
||||
"log_filter_placeholder": "Filter…",
|
||||
"file_ready_btn": "▶ Start Print",
|
||||
"file_slots_btn": "🎨 Select Slots",
|
||||
"file_cancel_btn": "✕ Cancel",
|
||||
@@ -188,10 +228,13 @@
|
||||
"skip_btn_label": "Objects",
|
||||
"skip_no_objects": "No objects in this print.",
|
||||
"skip_already": "skipped",
|
||||
"skip_cancel": "Cancel",
|
||||
"skip_confirm": "Skip",
|
||||
"skip_select_at_least_one": "Please pick at least one object.",
|
||||
"skip_sending": "Sending …",
|
||||
"skip_success": "Objects will be skipped.",
|
||||
"fd_objects_hint": "Skip objects (optional):",
|
||||
"fd_objects_toggle": "Skip objects",
|
||||
"fd_slots_hint": "Assign GCode channel to AMS slot:",
|
||||
"fd_cancel": "Cancel",
|
||||
"fd_print": "▶ Print",
|
||||
@@ -237,6 +280,7 @@
|
||||
"store_upload_busy": "⏳ Uploading…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ Only GCode files allowed (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "All",
|
||||
"sf_ok": "✓ Completed",
|
||||
"sf_err": "✗ Failed",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"lbl_remaining": "Restante:",
|
||||
"lbl_slicer_time": "Estimación del slicer:",
|
||||
"lbl_layers": "Capa",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 Silencioso",
|
||||
"speed_normal": "⚡ Normal",
|
||||
"speed_sport": "🚀 Sport",
|
||||
@@ -126,7 +127,20 @@
|
||||
"settings_title": "Configuración",
|
||||
"settings_connection": "Conexión",
|
||||
"settings_print": "Ajustes de impresión",
|
||||
"settings_poll": "Intervalo de sondeo",
|
||||
"settings_poll": "Intervalo de sondeo (segundos)",
|
||||
"nav_settings": "Ajustes",
|
||||
"settings_cat_display": "Apariencia",
|
||||
"settings_cat_filament": "Filamento",
|
||||
"settings_cat_language": "Idioma",
|
||||
"settings_cat_theme": "Alternar claro / oscuro",
|
||||
"settings_filament_mapping": "Asignación de perfil de filamento (por ranura)",
|
||||
"settings_filament_mapping_save": "Guardar asignación",
|
||||
"settings_visible_vendors": "Fabricantes visibles (lista de perfiles)",
|
||||
"settings_visible_vendors_hint": "Solo estos fabricantes aparecen en la lista de perfiles de ranura. Nada seleccionado = mostrar todos. «Generic» y tus propios perfiles siempre son visibles.",
|
||||
"settings_visible_vendors_save": "Guardar selección",
|
||||
"progress_action_print": "Imprimir",
|
||||
"progress_action_slots": "Asignar ranuras",
|
||||
"progress_action_clear": "Vaciar",
|
||||
"settings_version": "Versión",
|
||||
"settings_save": "Guardar y reiniciar",
|
||||
"settings_printer_name": "Nombre de impresora",
|
||||
@@ -192,6 +206,7 @@
|
||||
"skip_sending": "Enviando …",
|
||||
"skip_success": "Se omitirán los objetos.",
|
||||
"fd_objects_hint": "Omitir objetos (opcional):",
|
||||
"fd_objects_toggle": "Omitir objetos",
|
||||
"fd_slots_hint": "Asignar canal GCode a la ranura AMS:",
|
||||
"fd_cancel": "Cancelar",
|
||||
"fd_print": "▶ Imprimir",
|
||||
@@ -237,11 +252,40 @@
|
||||
"store_upload_busy": "⏳ Subiendo…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "Todos",
|
||||
"sf_ok": "✓ Completado",
|
||||
"sf_err": "✗ Fallido",
|
||||
"sf_new": "Nuevo",
|
||||
"ss_date": "↓ Fecha",
|
||||
"ss_name": "A–Z Nombre",
|
||||
"ss_dur": "⏱ Tiempo de impresión"
|
||||
"ss_dur": "⏱ Tiempo de impresión",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "Personalizado",
|
||||
"fd_options_title": "Opciones",
|
||||
"print_auto_leveling": "Autonivelado para esta impresión",
|
||||
"settings_file_ready_mode": "Iniciar diálogo de impresión",
|
||||
"settings_file_ready_banner": "Barra de impresión",
|
||||
"settings_file_ready_dialog": "Diálogo de impresión",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "Dirección:",
|
||||
"log_lvl_err": "⛔ Errores",
|
||||
"log_lvl_warn": "⚠ Avisos",
|
||||
"log_topic_label": "Tema:",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "Impresión",
|
||||
"log_topic_info": "Info",
|
||||
"log_topic_status": "Estado",
|
||||
"log_download": "⬇ Descargar",
|
||||
"log_auto": "⬇ Auto",
|
||||
"log_clear": "✕ Limpiar",
|
||||
"log_filter_placeholder": "Filtrar…",
|
||||
"skip_cancel": "Cancelar",
|
||||
"skip_confirm": "Omitir"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"lbl_remaining": "Restant :",
|
||||
"lbl_slicer_time": "Estimation slicer :",
|
||||
"lbl_layers": "Couche",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 Silencieux",
|
||||
"speed_normal": "⚡ Normal",
|
||||
"speed_sport": "🚀 Sport",
|
||||
@@ -126,7 +127,20 @@
|
||||
"settings_title": "Paramètres",
|
||||
"settings_connection": "Connexion",
|
||||
"settings_print": "Paramètres d'impression",
|
||||
"settings_poll": "Intervalle de sondage",
|
||||
"settings_poll": "Intervalle de sondage (secondes)",
|
||||
"nav_settings": "Paramètres",
|
||||
"settings_cat_display": "Apparence",
|
||||
"settings_cat_filament": "Filament",
|
||||
"settings_cat_language": "Langue",
|
||||
"settings_cat_theme": "Basculer clair / sombre",
|
||||
"settings_filament_mapping": "Mappage du profil de filament (par emplacement)",
|
||||
"settings_filament_mapping_save": "Enregistrer le mappage",
|
||||
"settings_visible_vendors": "Fabricants visibles (liste des profils)",
|
||||
"settings_visible_vendors_hint": "Seuls ces fabricants apparaissent dans la liste des profils d'emplacement. Rien de sélectionné = tout afficher. « Generic » et vos propres profils sont toujours visibles.",
|
||||
"settings_visible_vendors_save": "Enregistrer la sélection",
|
||||
"progress_action_print": "Imprimer",
|
||||
"progress_action_slots": "Affecter les emplacements",
|
||||
"progress_action_clear": "Vider",
|
||||
"settings_version": "Version",
|
||||
"settings_save": "Enregistrer et redémarrer",
|
||||
"settings_printer_name": "Nom de l'imprimante",
|
||||
@@ -192,6 +206,7 @@
|
||||
"skip_sending": "Envoi …",
|
||||
"skip_success": "Les objets seront ignorés.",
|
||||
"fd_objects_hint": "Ignorer des objets (optionnel) :",
|
||||
"fd_objects_toggle": "Ignorer des objets",
|
||||
"fd_slots_hint": "Associer le canal GCode au slot AMS :",
|
||||
"fd_cancel": "Annuler",
|
||||
"fd_print": "▶ Imprimer",
|
||||
@@ -237,11 +252,40 @@
|
||||
"store_upload_busy": "⏳ Envoi en cours…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "Tout",
|
||||
"sf_ok": "✓ Terminés",
|
||||
"sf_err": "✗ Échoués",
|
||||
"sf_new": "Nouveau",
|
||||
"ss_date": "↓ Date",
|
||||
"ss_name": "A–Z Nom",
|
||||
"ss_dur": "⏱ Durée d'impression"
|
||||
"ss_dur": "⏱ Durée d'impression",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "Personnalisé",
|
||||
"fd_options_title": "Options",
|
||||
"print_auto_leveling": "Mise à niveau auto pour cette impression",
|
||||
"settings_file_ready_mode": "Démarrer le dialogue d'impression",
|
||||
"settings_file_ready_banner": "Barre d'impression",
|
||||
"settings_file_ready_dialog": "Dialogue d'impression",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "Sens :",
|
||||
"log_lvl_err": "⛔ Erreurs",
|
||||
"log_lvl_warn": "⚠ Avert.",
|
||||
"log_topic_label": "Sujet :",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "Impression",
|
||||
"log_topic_info": "Info",
|
||||
"log_topic_status": "Statut",
|
||||
"log_download": "⬇ Télécharger",
|
||||
"log_auto": "⬇ Auto",
|
||||
"log_clear": "✕ Effacer",
|
||||
"log_filter_placeholder": "Filtrer…",
|
||||
"skip_cancel": "Annuler",
|
||||
"skip_confirm": "Ignorer"
|
||||
}
|
||||
|
||||
291
web/translations/it.json
Normal file
291
web/translations/it.json
Normal file
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"header_status_standby": "Pronto",
|
||||
"header_status_printing": "In stampa",
|
||||
"header_status_complete": "Completato",
|
||||
"header_status_error": "Errore",
|
||||
"kobra_free": "Pronto",
|
||||
"kobra_busy": "Occupato",
|
||||
"kobra_printing": "In stampa",
|
||||
"kobra_preheating": "Preriscaldamento",
|
||||
"kobra_auto_leveling": "Livellamento automatico",
|
||||
"kobra_checking": "Verifica",
|
||||
"kobra_updated": "Aggiornamento",
|
||||
"kobra_init": "Inizializzazione",
|
||||
"kobra_pausing": "Pausa in corso...",
|
||||
"kobra_paused": "In pausa",
|
||||
"kobra_resuming": "Ripresa...",
|
||||
"kobra_resumed": "Ripreso",
|
||||
"kobra_stopping": "Arresto...",
|
||||
"kobra_stoped": "Arrestato",
|
||||
"kobra_finished": "Finito",
|
||||
"kobra_failed": "Errore",
|
||||
"kobra_canceled": "Annullato",
|
||||
"kobra_offline": "Offline",
|
||||
"nav_dashboard": "Dashboard",
|
||||
"nav_print": "Stampa",
|
||||
"nav_temps": "Temperature",
|
||||
"nav_motion": "Movimento",
|
||||
"nav_ams": "AMS",
|
||||
"nav_extras": "Luce / Ventola",
|
||||
"nav_console": "Console",
|
||||
"card_progress": "Avanzamento",
|
||||
"card_temps": "Temperature",
|
||||
"card_light_fan": "Ventola",
|
||||
"card_speed": "Velocità di stampa",
|
||||
"card_cam": "Camera",
|
||||
"lbl_elapsed": "Trascorso:",
|
||||
"lbl_remaining": "Rimanente:",
|
||||
"lbl_slicer_time": "Stima slicer:",
|
||||
"lbl_layers": "Layer",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 Silenzioso",
|
||||
"speed_normal": "⚡ Normale",
|
||||
"speed_sport": "🚀 Sport",
|
||||
"lbl_light": "💡 Luce",
|
||||
"lbl_feed": "Carica",
|
||||
"lbl_unload": "Rimuovi",
|
||||
"card_ace_dry": "Essiccazione ACE",
|
||||
"ace_dry_dryer": "Essiccatore",
|
||||
"ace_dry_status_off": "Stato: Spento",
|
||||
"ace_dry_status_on": "Stato: Attivo",
|
||||
"ace_dry_status_remaining": "Rimanente",
|
||||
"ace_dry_humidity": "Umidità",
|
||||
"ace_dry_current_temp": "Temperatura",
|
||||
"ace_dry_chart": "Cronologia (Temp/Umidità)",
|
||||
"ace_dry_temp": "Temperatura (°C)",
|
||||
"ace_dry_duration": "Durata (min)",
|
||||
"ace_dry_start": "▶ Avvia",
|
||||
"ace_dry_stop": "■ Ferma",
|
||||
"ace_dry_auto_refill": "Ricarica automatica",
|
||||
"ace_dry_enable": "Abilita essiccazione",
|
||||
"ace_dry_temp_line": "Temperatura di essiccazione",
|
||||
"ace_dry_time_line": "Tempo di essiccazione",
|
||||
"ace_dry_ui_pending": "(Solo interfaccia, backend a seguire)",
|
||||
"ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore",
|
||||
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
|
||||
"ace_dry_dialog_time": "Tempo rim. (h:m:s)",
|
||||
"ace_dry_dialog_confirm": "Conferma",
|
||||
"ace_dry_dialog_cancel": "Annulla",
|
||||
"ace_dry_dialog_save_restart": "Salva e riavvia",
|
||||
"ace_dry_dialog_custom_name": "Nome personalizzato",
|
||||
"ace_dry_dialog_reset_default": "Ripristina predefiniti",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "Personalizzato",
|
||||
"cam_placeholder": "📷 Camera non avviata",
|
||||
"cam_stream_unavailable": "Flusso video non disponibile",
|
||||
"btn_cam_start": "▶ Camera",
|
||||
"btn_cam_stop": "◼ Camera",
|
||||
"btn_pause": "⏸ Pausa",
|
||||
"btn_resume": "▶ Riprendi",
|
||||
"btn_cancel": "✕ Stop",
|
||||
"label_nozzle": "Ugello",
|
||||
"label_bed": "Piatto",
|
||||
"label_fan": "🌀 Ventola",
|
||||
"label_light": "💡 Luce",
|
||||
"label_on_off": "On / Off",
|
||||
"label_speed": "Velocità",
|
||||
"panel_print_title": "Controllo stampa",
|
||||
"panel_print_btn_pause": "⏸ Pausa",
|
||||
"panel_print_btn_resume": "▶ Riprendi",
|
||||
"panel_print_btn_cancel": "✕ Annulla",
|
||||
"panel_print_temps_live": "Temperature (In tempo reale)",
|
||||
"label_set": "Imposta",
|
||||
"label_off": "Off",
|
||||
"panel_temps_nozzle": "Ugello",
|
||||
"panel_temps_bed": "Piatto riscaldato",
|
||||
"panel_temps_chart": "Cronologia (ultime 60 letture)",
|
||||
"label_target_c": "Target:",
|
||||
"panel_motion_xy": "Assi XY",
|
||||
"panel_motion_z": "Asse Z",
|
||||
"label_step": "Ampiezza passo:",
|
||||
"btn_home_z": "Home Z",
|
||||
"btn_home_xy": "Home XY",
|
||||
"btn_home_all": "Home generale",
|
||||
"btn_disable_motors": "Spegni motori",
|
||||
"panel_ams_title": "Filamento",
|
||||
"card_ams": "Filamento",
|
||||
"ams_no_data": "Nessun dato ricevuto dall' AMS",
|
||||
"label_slot": "Slot",
|
||||
"ams_empty": "Vuoto",
|
||||
"panel_extras_light": "Luce",
|
||||
"panel_extras_fan": "Ventola",
|
||||
"panel_extras_camera": "Camera",
|
||||
"btn_cam_start2": "▶ Avvia",
|
||||
"btn_cam_stop2": "◼ Ferma",
|
||||
"panel_console_title": "Registro eventi",
|
||||
"log_light_on": "Luce accesa",
|
||||
"log_light_off": "Luce spenta",
|
||||
"log_fan": "Ventola →",
|
||||
"log_nozzle": "Ugello →",
|
||||
"log_bed": "Piatto →",
|
||||
"log_axis": "Asse",
|
||||
"log_home": "Home",
|
||||
"log_home_all": "Home generale",
|
||||
"log_cam_start": "Camera avviata:",
|
||||
"log_cam_stop": "Camera arrestata",
|
||||
"log_poll_error": "Errore di sincronizzazione:",
|
||||
"log_error": "Errore:",
|
||||
"confirm_cancel": "Annullare davvero la stampa?",
|
||||
"settings_title": "Impostazioni",
|
||||
"settings_connection": "Connessione",
|
||||
"settings_print": "Impostazioni di stampa",
|
||||
"settings_poll": "Intervallo di sincronizzazione (secondi)",
|
||||
"nav_settings": "Impostazioni",
|
||||
"settings_cat_display": "Aspetto",
|
||||
"settings_cat_filament": "Filamento",
|
||||
"settings_cat_language": "Lingua",
|
||||
"settings_cat_theme": "Alterna chiaro / scuro",
|
||||
"settings_filament_mapping": "Mappatura profilo filamento (per slot)",
|
||||
"settings_filament_mapping_save": "Salva mappatura",
|
||||
"settings_visible_vendors": "Produttori visibili (menu del profilo)",
|
||||
"settings_visible_vendors_hint": "Solo questi produttori appariranno nel menu del profilo dello slot. Se non selezioni nulla = mostra tutti. I profili \"Generici\" e i tuoi personali sono sempre visibili.",
|
||||
"settings_visible_vendors_save": "Salva selezione",
|
||||
"progress_action_print": "Stampa",
|
||||
"progress_action_slots": "Mappa slot",
|
||||
"progress_action_clear": "Cancella",
|
||||
"settings_version": "Versione",
|
||||
"settings_save": "Salva e riavvia",
|
||||
"settings_printer_name": "Nome stampante",
|
||||
"settings_printer_ip": "IP stampante",
|
||||
"settings_mqtt_port": "Porta MQTT",
|
||||
"settings_username": "Nome utente MQTT",
|
||||
"settings_password": "Password MQTT",
|
||||
"settings_device_id": "ID dispositivo",
|
||||
"settings_mode_id": "ID modalità",
|
||||
"hint_ip_no_port": "Solo indirizzo IP, senza porta (es. 192.168.1.102)",
|
||||
"settings_default_slot": "Slot predefinito (colore singolo)",
|
||||
"settings_slot_auto": "Auto (tutti gli slot caricati)",
|
||||
"settings_auto_leveling": "Livellamento automatico predefinito",
|
||||
"fd_options_title": "Opzioni di stampa",
|
||||
"print_auto_leveling": "Livellamento automatico",
|
||||
"settings_file_ready_mode": "Comportamento all'avvio stampa",
|
||||
"settings_file_ready_banner": "Barra di stampa",
|
||||
"settings_file_ready_dialog": "Finestra di dialogo di stampa",
|
||||
"settings_camera_on_print": "Attiva la camera all'avvio della stampa",
|
||||
"settings_web_upload_warning": "Mostra un avviso quando si stampano caricamenti web",
|
||||
"update_check": "Controlla aggiornamenti",
|
||||
"update_checking": "Verifica in corso...",
|
||||
"update_available": "disponibile",
|
||||
"update_none": "Già aggiornato",
|
||||
"update_apply": "Installa ora",
|
||||
"update_applying": "Download in corso...",
|
||||
"update_restarting": "Riavvio in corso...",
|
||||
"update_error": "Errore",
|
||||
"btn_connect": "⚡ Connetti",
|
||||
"btn_disconnect": "✕ Disconnetti",
|
||||
"lbl_conn_error": "Errore di connessione:",
|
||||
"slot_edit_title": "Modifica slot",
|
||||
"slot_edit_color": "Colore",
|
||||
"slot_edit_material": "Materiale",
|
||||
"slot_edit_load": "⬇ Carica",
|
||||
"slot_edit_unload": "⬆ Rimuovi",
|
||||
"slot_edit_save": "💾 Salva",
|
||||
"slot_edit_custom": "es. PLA, PETG, ABS…",
|
||||
"slot_edit_ok": "Slot AMS",
|
||||
"slot_edit_profile": "Profilo OrcaSlicer",
|
||||
"slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"",
|
||||
"slot_edit_profile_default": "— Generico (predefinito) —",
|
||||
"orca_profile_section": "Profili OrcaSlicer",
|
||||
"orca_profile_hint": "Importa i tuoi profili di filamento OrcaSlicer (apri la cartella utente tramite Aiuto → Mostra cartella di configurazione)",
|
||||
"orca_profile_import_btn": "Importa profili",
|
||||
"orca_profile_import_link": "★ Importa i tuoi profili…",
|
||||
"orca_profile_import_title": "Importa i tuoi profili OrcaSlicer",
|
||||
"orca_profile_help_html": "Carica un file <b>ZIP</b> della tua cartella filamenti di OrcaSlicer o file singoli <b>.json</b>.<br>In OrcaSlicer: <i>Aiuto → Mostra cartella di configurazione → user/<id>/filament/</i>",
|
||||
"orca_profile_dropmsg": "Trascina qui o fai clic",
|
||||
"orca_profile_list_label": "Attualmente importati",
|
||||
"orca_profile_user_label": "Profili personali",
|
||||
"orca_profile_user_empty": "– nessuno –",
|
||||
"orca_profile_uploading": "Caricamento in corso…",
|
||||
"orca_profile_done": "Importato",
|
||||
"orca_profile_skipped": "saltato",
|
||||
"log_dir_all": "Tutti",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "Dir:",
|
||||
"log_lvl_label": "Livello:",
|
||||
"log_lvl_err": "⛔ Errori",
|
||||
"log_lvl_warn": "⚠ Avvisi",
|
||||
"log_topic_label": "Argomento:",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "Stampa",
|
||||
"log_topic_info": "Info",
|
||||
"log_topic_status": "Stato",
|
||||
"log_download": "⬇ Scarica",
|
||||
"log_auto": "⬇ Auto",
|
||||
"log_clear": "✕ Cancella",
|
||||
"log_filter_placeholder": "Filtra…",
|
||||
"file_ready_btn": "▶ Avvia stampa",
|
||||
"file_slots_btn": "🎨 Seleziona slot",
|
||||
"file_cancel_btn": "✕ Annulla",
|
||||
"nav_printers": "Stampanti",
|
||||
"skip_title": "✂ Salta oggetti",
|
||||
"skip_hint": "Deseleziona gli oggetti che non vuoi più stampare:",
|
||||
"skip_btn_label": "Oggetti",
|
||||
"skip_no_objects": "Nessun oggetto in questa stampa.",
|
||||
"skip_already": "saltato",
|
||||
"skip_cancel": "Annulla",
|
||||
"skip_confirm": "Salta",
|
||||
"skip_select_at_least_one": "Seleziona almeno un oggetto.",
|
||||
"skip_sending": "Invio in corso …",
|
||||
"skip_success": "Gli oggetti verranno saltati.",
|
||||
"fd_objects_hint": "Salta oggetti (opzionale):",
|
||||
"fd_objects_toggle": "Salta oggetti",
|
||||
"fd_slots_hint": "Assegna il canale GCode allo slot AMS:",
|
||||
"fd_cancel": "Annulla",
|
||||
"fd_print": "▶ Stampa",
|
||||
"fd_no_slots_msg": "Nessuno slot AMS caricato.{br}Avviare comunque la stampa?",
|
||||
"fd_slot": "Slot",
|
||||
"fd_no_matching_material": "Nessun materiale corrispondente",
|
||||
"fd_used": "USATO",
|
||||
"add_printer": "Aggiungi stampante",
|
||||
"apd_lbl_ip": "IP stampante",
|
||||
"apd_lbl_name": "Nome (opzionale)",
|
||||
"apd_placeholder_name": "es. Kobra X Soggiorno",
|
||||
"apd_cancel": "Annulla",
|
||||
"apd_confirm": "Aggiungi",
|
||||
"apd_fetching": "Recupero dati dalla stampante…",
|
||||
"apd_success": "Stampante aggiunta, riavvio del bridge in corso…",
|
||||
"apd_err_ip": "Inserisci un indirizzo IP",
|
||||
"printers_remove": "Rimuovi stampante",
|
||||
"printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.",
|
||||
"printers_active": "● attiva",
|
||||
"printers_switch": "Cambia →",
|
||||
"printers_current": "Stampante corrente",
|
||||
"printers_loading": "Caricamento in corso…",
|
||||
"printers_none": "Nessuna stampante configurata.",
|
||||
"printers_empty_hint": "Nessuna stampante ancora configurata.",
|
||||
"nav_browser": "Browser",
|
||||
"panel_browser_title": "Browser dei file",
|
||||
"store_search_placeholder": "🔍 Cerca…",
|
||||
"store_empty": "Nessun file caricato.",
|
||||
"store_refresh": "↻ Aggiorna",
|
||||
"store_print": "▶ Stampa",
|
||||
"store_download": "⬇ Scarica",
|
||||
"store_delete_confirm": "Eliminare il file?",
|
||||
"store_print_confirm": "Stampare il file?",
|
||||
"store_web_verify_title": "Verifica file",
|
||||
"store_web_verify_msg": "Verifica che questo file sia stato creato per Anycubic Kobra X.",
|
||||
"store_web_verify_confirm": "Conferma",
|
||||
"store_web_verify_abort": "Interrompi",
|
||||
"store_no_results": "Nessun file trovato.",
|
||||
"store_never": "mai stampato",
|
||||
"store_estimate": "Stima",
|
||||
"store_upload_label_prefix": "Trascina il GCode qui o ",
|
||||
"store_upload_label_browse": "sfoglia",
|
||||
"store_upload_busy": "⏳ Caricamento in corso…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ Sono consentiti solo file GCode (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "Tutti",
|
||||
"sf_ok": "✓ Completato",
|
||||
"sf_err": "✗ Fallito",
|
||||
"sf_new": "Nuovo",
|
||||
"ss_date": "↓ Data",
|
||||
"ss_name": "Nome A–Z",
|
||||
"ss_dur": "⏱ Tempo di stampa"
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
"lbl_remaining": "剩余时间:",
|
||||
"lbl_slicer_time": "切片预估:",
|
||||
"lbl_layers": "层",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"speed_silent": "🐢 静音",
|
||||
"speed_normal": "⚡ 标准",
|
||||
"speed_sport": "🚀 运动",
|
||||
@@ -126,7 +127,20 @@
|
||||
"settings_title": "设置",
|
||||
"settings_connection": "连接",
|
||||
"settings_print": "打印设置",
|
||||
"settings_poll": "轮询间隔",
|
||||
"settings_poll": "轮询间隔(秒)",
|
||||
"nav_settings": "设置",
|
||||
"settings_cat_display": "外观",
|
||||
"settings_cat_filament": "耗材",
|
||||
"settings_cat_language": "语言",
|
||||
"settings_cat_theme": "切换浅色 / 深色",
|
||||
"settings_filament_mapping": "耗材配置映射(每槽位)",
|
||||
"settings_filament_mapping_save": "保存映射",
|
||||
"settings_visible_vendors": "可见厂商(配置下拉框)",
|
||||
"settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。",
|
||||
"settings_visible_vendors_save": "保存选择",
|
||||
"progress_action_print": "打印",
|
||||
"progress_action_slots": "分配槽位",
|
||||
"progress_action_clear": "清除",
|
||||
"settings_version": "版本",
|
||||
"settings_save": "保存并重启",
|
||||
"settings_printer_name": "打印机名称",
|
||||
@@ -192,6 +206,7 @@
|
||||
"skip_sending": "发送中 …",
|
||||
"skip_success": "对象将被跳过。",
|
||||
"fd_objects_hint": "跳过对象 (可选):",
|
||||
"fd_objects_toggle": "跳过对象",
|
||||
"fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:",
|
||||
"fd_cancel": "取消",
|
||||
"fd_print": "▶ 打印",
|
||||
@@ -237,11 +252,40 @@
|
||||
"store_upload_busy": "⏳ 上传中…",
|
||||
"store_upload_success": "✓ {file}",
|
||||
"store_upload_error": "✗ {error}",
|
||||
"store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)",
|
||||
"sf_all": "全部",
|
||||
"sf_ok": "✓ 已完成",
|
||||
"sf_err": "✗ 失败",
|
||||
"sf_new": "新",
|
||||
"ss_date": "↓ 日期",
|
||||
"ss_name": "A–Z 名称",
|
||||
"ss_dur": "⏱ 打印时间"
|
||||
"ss_dur": "⏱ 打印时间",
|
||||
"ace_dry_preset_pla": "PLA",
|
||||
"ace_dry_preset_pla_plus": "PLA+",
|
||||
"ace_dry_preset_petg": "PETG",
|
||||
"ace_dry_preset_tpu": "TPU",
|
||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
||||
"ace_dry_preset_pa_pc": "PA / PC",
|
||||
"ace_dry_preset_custom": "自定义",
|
||||
"fd_options_title": "选项",
|
||||
"print_auto_leveling": "本次打印自动调平",
|
||||
"settings_file_ready_mode": "开始打印对话框",
|
||||
"settings_file_ready_banner": "打印栏",
|
||||
"settings_file_ready_dialog": "打印对话框",
|
||||
"log_dir_rx": "RX",
|
||||
"log_dir_tx": "TX",
|
||||
"log_dir_label": "方向:",
|
||||
"log_lvl_err": "⛔ 错误",
|
||||
"log_lvl_warn": "⚠ 警告",
|
||||
"log_topic_label": "主题:",
|
||||
"log_topic_ams": "AMS",
|
||||
"log_topic_print": "打印",
|
||||
"log_topic_info": "信息",
|
||||
"log_topic_status": "状态",
|
||||
"log_download": "⬇ 下载",
|
||||
"log_auto": "⬇ 自动",
|
||||
"log_clear": "✕ 清空",
|
||||
"log_filter_placeholder": "筛选…",
|
||||
"skip_cancel": "取消",
|
||||
"skip_confirm": "跳过"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user