Compare commits

..

3 Commits

Author SHA1 Message Date
Phil Merricks
d8d2e26cd2 feat: 2-column dashboard layout toggle + local timelapse
2-column layout: settings toggle switches dashboard between 1-col
(current behaviour) and 2-col (Camera|Progress, Temps|AMS side by
side, motion cards in 2×2). Preference stored in localStorage only
(per-browser, not printer config). Mobile always stays 1-col via CSS.

Local timelapse: new TimelapseSpool class captures JPEG frames from
the camera stream during a print at a configurable interval (default
4s) and encodes them to MP4 with ffmpeg on print end. Stored in
data/timelapses/, indexed in SQLite. New /kx/timelapses API + browser
tab in the file store panel with download and delete. Also wires the
undocumented printer-native timelapse MQTT field as an experimental
opt-in setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:31:24 +01:00
Phil Merricks
1133af8de2 feat: add vibration_compensation and flow_calibration print settings
Expose resonance compensation and flow calibration as configurable
toggles in the settings UI, config.ini [print] section, and CLI args.
Both default to 0 (off). Previously hardcoded to 0 in all three
print-start MQTT payload paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:31:24 +01:00
Phil Merricks
29bd66106b fix: slot assignment dialog now matches "PLA Silk", "Matte PLA" etc. to PLA slots
_normalizeMaterialKey now scans all space-separated words in a slot label and
returns the first known base material type found. This handles both "modifier
first" ("Matte PLA") and "modifier last" ("PLA Silk", "PLA Matte") patterns,
which arise when users label slots with full product-style names while OrcaSlicer
writes only the base type (PLA, PETG, …) in GCode comments.

Dash-suffix composites ("PLA-CF", "PETG-CF") contain no space and are unchanged,
preserving correct incompatibility with their base types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:31:24 +01:00
47 changed files with 7408 additions and 6085 deletions

View File

@@ -1,31 +0,0 @@
---
name: KX-Bridge Changelog
description: Generiert einen CHANGELOG.md Eintrag aus Git-Commits seit dem letzten Tag.
tools:
- run_command
- read_file
- write_file
---
Du generierst CHANGELOG.md Einträge für KX-Bridge.
Vorgehen:
1. Führe aus: `git log $(git describe --tags --abbrev=0)..HEAD --oneline`
2. Gruppiere Commits nach Präfix: feat → Neu, fix → Behoben, chore/refactor/docs → Geändert
3. Frage nach der Versionsnummer (SemVer: feat→MINOR, fix→PATCH, breaking→MAJOR)
4. Schreibe den Abschnitt im Format:
```
## [VERSION] - DATUM
### Neu
- ...
### Behoben
- ...
### Geändert
- ...
```
5. Füge den Abschnitt am Anfang der bestehenden CHANGELOG.md ein, ohne vorhandene Einträge zu ändern.

View File

@@ -1,32 +0,0 @@
---
name: KX-Bridge Docker Check
description: Prüft Dockerfile, docker-compose und das gebaute Image auf häufige Probleme.
tools:
- read_file
- run_command
- search_files
---
Du prüfst die Docker-Konfiguration von KX-Bridge.
**Dockerfile:**
- Base-Image aktuell? (`python:3.11-slim` oder neuer)
- `.dockerignore` vorhanden und vollständig?
- Keine Secrets oder Zertifikate im Image (`anycubic_slicer.crt/.key` darf NICHT eingebettet sein)
- Healthcheck vorhanden?
- Kein `COPY . .` ohne `.dockerignore`
**docker-compose.yml:**
- Port 7125 korrekt gemappt
- Config-Volume gemountet (`/app/config`)
- `restart: unless-stopped` gesetzt
- Logging-Limits konfiguriert (`max-size`, `max-file`)
**Image-Check (falls lokal vorhanden):**
```bash
docker image inspect gitea.it-drui.de/viewit/kx-bridge:nightly
```
- Image-Größe sinnvoll (< 500MB)?
- Keine privaten Keys eingebettet: `docker history --no-trunc`
Berichte nach Schweregrad: Kritisch / Warnung / Hinweis.

View File

@@ -1,23 +0,0 @@
---
name: KX-Bridge Moonraker Debug
description: Analysiert Moonraker/Klipper Logs und KX-Bridge Ausgaben auf Fehlerursachen.
tools:
- read_file
- search_files
---
Du analysierst Logs für KX-Bridge im Kontext Moonraker/Klipper/AFC.
**Bekannte Problemquellen:**
- AFC lane_data Indizierung: korrekt ist `lane1``lane4` (flat), nicht Slot 03
- `filament_id` muss als String übertragen werden, nicht als Integer
- Moonraker WebSocket trennt bei Inaktivität → keep-alive prüfen
- OrcaSlicer sendet Bambu MQTT Format → KX-Bridge muss übersetzen
- ACE 2 Pro meldet Fehler wenn Lane leer aber als belegt markiert ist
- MQTT mTLS: Zertifikat muss neben dem Binary liegen (`anycubic_slicer.crt/.key`)
**Bei einem Log:**
1. Identifiziere den **ersten** Fehler (nicht den letzten Symptom)
2. Zeige den relevanten Log-Kontext (±10 Zeilen um den Fehler)
3. Nenne die wahrscheinliche Ursache
4. Schlage einen konkreten Fix vor (Datei + Funktion wenn möglich)

View File

@@ -1,25 +0,0 @@
---
name: KX-Bridge Nightly Prep
description: Bereitet den PR von nightly nach main vor. Prüft ob alle Voraussetzungen für ein Stable Release erfüllt sind.
tools:
- run_command
- read_file
---
Du bereitest einen nightly → main Merge für KX-Bridge vor.
Führe folgende Checks aus und berichte:
1. `git log main..nightly --oneline` → alle Commits die noch nicht in main sind
2. `git diff main..nightly -- CHANGELOG.md` → ist CHANGELOG.md für alle Änderungen aktualisiert?
3. Prüfe ob `tests/` alle geänderten Module abdeckt
4. Prüfe ob Dockerfile ein aktuelles Base-Image verwendet
5. Schlage eine SemVer-Versionsnummer vor:
- `feat:` Commits → MINOR erhöhen
- `fix:` Commits → PATCH erhöhen
- Breaking Change im Commit-Body → MAJOR erhöhen
Abschlussbericht:
- ✅ Bereit für Release
- ⚠️ Offen: [Liste]
- ❌ Blockiert durch: [Grund]

View File

@@ -1,24 +0,0 @@
---
name: KX-Bridge Reviewer
description: Reviewt geänderte Dateien vor einem PR auf nightly. Prüft Logik, Fehlerbehandlung, Moonraker-Kompatibilität und Stil.
tools:
- read_file
- list_directory
- search_files
---
Du bist Code-Reviewer für KX-Bridge — eine Python-Bridge zwischen OrcaSlicer und Moonraker/Klipper für den Anycubic Kobra X.
Beim Review prüfst du:
- Korrekte Fehlerbehandlung bei Moonraker HTTP/MQTT Calls (keine unbehandelten Exceptions)
- Keine hardcodierten IPs oder Ports (müssen aus config.ini kommen)
- Thread-Sicherheit bei parallelen Moonraker-Abfragen (asyncio korrekt verwendet)
- AFC lane_data Struktur: flache Indizierung lane1lane4, kein Slot-Mapping 03
- Kein `print()` statt `logging` (außer in CLI-Hilfsfunktionen)
- Typ-Annotationen vorhanden, Python 3.8+ kompatibel (kein `X | Y` Syntax)
- Tests für neue öffentliche Funktionen vorhanden
Ausgabeformat:
1. **Kritische Fehler** — blockieren den Merge
2. **Warnungen** — sollten vor Merge behoben werden
3. **Hinweise** — optional, für zukünftige Verbesserungen

View File

@@ -1,26 +0,0 @@
---
name: KX-Bridge Test Writer
description: Leitet pytest-Tests aus geänderten oder neuen Python-Dateien ab.
tools:
- read_file
- write_file
- list_directory
- search_files
---
Du schreibst pytest-Tests für KX-Bridge.
Kontext:
- Moonraker API läuft auf Port 7125 (HTTP + WebSocket)
- AFC lane_data: flache Indizierung lane1lane4
- Externe HTTP-Calls zu Moonraker werden mit `unittest.mock` gemockt
- Python 3.8+ Kompatibilität (kein `X | Y` Union-Syntax)
Für jede zu testende Funktion schreibst du:
1. Happy Path (Normalfall mit validen Eingaben)
2. Fehlerfall (Moonraker nicht erreichbar, Timeout, falsche Antwort)
3. Grenzwerte (leere lane_data, ungültige filament_id, None-Werte)
Dateiname: `tests/test_<modulname>.py`
Verwende pytest-Fixtures für Moonraker-Mock-Responses.
Keine echten Netzwerkaufrufe in Tests.

View File

@@ -1,12 +0,0 @@
{
"project": "KX-Bridge",
"language": "de",
"defaultAgent": "reviewer",
"context": {
"repoBase": "gitea.it-drui.de/viewit/KX-Bridge-Release",
"defaultBranch": "nightly",
"stableBranch": "main",
"registry": "gitea.it-drui.de/viewit/kx-bridge",
"moonrakerPort": 7125
}
}

View File

@@ -1,29 +0,0 @@
---
name: Bug Report
about: Report a bug in KX-Bridge
labels: bug
---
## Description
<!-- What is happening? -->
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
## Actual Behavior
## Environment
- KX-Bridge Version:
- OrcaSlicer Version:
- Moonraker/Klipper Version:
- Operating System:
- Installation: Docker / Binary
## Logs
```
<!-- docker logs kx-bridge --tail 50 -->
```

View File

@@ -1,14 +0,0 @@
---
name: Feature Request
about: Suggest a new feature or improvement
labels: enhancement
---
## Description
<!-- What should be added or improved? -->
## Motivation
<!-- Why is this useful? What problem does it solve? -->
## Proposed Implementation
<!-- Optional: How could this be implemented technically? -->

View File

@@ -1,21 +0,0 @@
## Description
<!-- What does this PR change? -->
## Related Issue
Closes #
## Type
- [ ] Bug fix
- [ ] Feature
- [ ] Documentation
- [ ] Refactoring
## Tested with
- OrcaSlicer Version:
- Printer:
- Moonraker/Klipper Version:
## Checklist
- [ ] Tests added/updated
- [ ] CHANGELOG.md updated
- [ ] No debug code included

View File

@@ -1,88 +0,0 @@
name: Nightly Build
on:
push:
branches:
- nightly
paths:
- '**.py'
- 'Dockerfile'
- 'requirements.txt'
- 'web/**'
- 'data/**'
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
build:
runs-on: server-runner
steps:
- name: Checkout
run: |
if [ -d .git ]; then
git fetch origin nightly
git reset --hard origin/nightly
git clean -fd
else
git clone --depth=1 --branch nightly https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
fi
- name: Install Docker CLI
run: |
if ! command -v docker >/dev/null 2>&1; then
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then
DARCH="x86_64"
BARCH="amd64"
else
DARCH="aarch64"
BARCH="arm64"
fi
wget -qO- "https://download.docker.com/linux/static/stable/${DARCH}/docker-27.5.1.tgz" \
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
chmod +x /usr/local/bin/docker
mkdir -p /usr/local/lib/docker/cli-plugins
wget -qO /usr/local/lib/docker/cli-plugins/docker-buildx \
"https://github.com/docker/buildx/releases/download/v0.23.0/buildx-v0.23.0.linux-${BARCH}"
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
fi
docker version --format '{{.Client.Version}}'
- name: Set up QEMU
run: |
docker run --rm --privileged tonistiigi/binfmt:latest --install all
- name: Set up buildx
run: |
docker buildx inspect kxbuilder 2>/dev/null || \
docker buildx create --name kxbuilder --use
docker buildx use kxbuilder
- name: Login to Gitea registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | \
docker login gitea.it-drui.de -u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build & push (amd64 + arm64)
run: |
DATE=$(date +%Y%m%d)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--provenance=false \
--no-cache \
-t "gitea.it-drui.de/viewit/kx-bridge:nightly" \
-t "gitea.it-drui.de/viewit/kx-bridge:nightly-$DATE" \
.
- name: Set nightly tag
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
DATE=$(date +%Y%m%d)
TAG="nightly-$DATE"
git config user.name "gitea-actions"
git config user.email "actions@it-drui.de"
git tag -f "$TAG"
git push https://gitea-actions:${GITEA_TOKEN}@gitea.it-drui.de/viewit/KX-Bridge-Release.git "$TAG" --force

View File

@@ -1,34 +0,0 @@
name: PR Check
on:
pull_request:
branches:
- nightly
jobs:
lint-and-test:
runs-on: server-runner
steps:
- name: Checkout
run: |
if [ -d .git ]; then
git fetch origin
git reset --hard origin/nightly
git clean -fd
else
git clone --depth=1 --branch nightly https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
fi
- name: Dependencies installieren
run: pip3 install -r requirements.txt
- name: Lint
run: |
pip3 install flake8
flake8 *.py --max-line-length=120 --extend-ignore=E501
- name: Tests
run: |
pip3 install pytest
pytest tests/ -v
if: ${{ hashFiles('tests/') != '' }}

View File

@@ -1,87 +0,0 @@
name: Stable Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
release:
runs-on: server-runner
steps:
- name: Checkout
run: |
TAG="${GITHUB_REF#refs/tags/}"
if [ -d .git ]; then
git fetch --tags origin
git checkout "$TAG"
git clean -fd
else
git clone --depth=1 --branch "$TAG" https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
fi
- name: Install Docker CLI
run: |
if ! command -v docker >/dev/null 2>&1; then
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then
DARCH="x86_64"
BARCH="amd64"
else
DARCH="aarch64"
BARCH="arm64"
fi
wget -qO- "https://download.docker.com/linux/static/stable/${DARCH}/docker-27.5.1.tgz" \
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
chmod +x /usr/local/bin/docker
mkdir -p /usr/local/lib/docker/cli-plugins
wget -qO /usr/local/lib/docker/cli-plugins/docker-buildx \
"https://github.com/docker/buildx/releases/download/v0.23.0/buildx-v0.23.0.linux-${BARCH}"
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
fi
docker version --format '{{.Client.Version}}'
- name: Set up QEMU
run: |
docker run --rm --privileged tonistiigi/binfmt:latest --install all
- name: Set up buildx
run: |
docker buildx inspect kxbuilder 2>/dev/null || \
docker buildx create --name kxbuilder --use
docker buildx use kxbuilder
- name: Login to Gitea registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | \
docker login gitea.it-drui.de -u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build & push (amd64 + arm64)
run: |
VERSION="${GITHUB_REF#refs/tags/}"
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--provenance=false \
--no-cache \
-t "gitea.it-drui.de/viewit/kx-bridge:latest" \
-t "gitea.it-drui.de/viewit/kx-bridge:${VERSION}" \
.
- name: Create Gitea Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
VERSION="${GITHUB_REF#refs/tags/}"
CHANGELOG=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md || echo "")
curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases" \
-d "{
\"tag_name\": \"${VERSION}\",
\"name\": \"KX-Bridge ${VERSION}\",
\"body\": $(echo "$CHANGELOG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),
\"draft\": false,
\"prerelease\": false
}"

5
.gitignore vendored
View File

@@ -17,8 +17,3 @@ config/*.ini
data/
!data/orca_filaments.json
# Sensitive files — never commit
.runner-token
secrets/
*.token

View File

@@ -1,217 +1,5 @@
# 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
- 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.
### Geändert
- 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.
## [0.9.19] 2026-06-02
### Neu
- **🎯 Filament-Sync mit OrcaSlicer matched jetzt das richtige Preset**
statt immer auf „Generic PLA" zu landen. Voraussetzung: ein
OrcaSlicer-Build mit dem
[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)
Empfangs-Patch (im OrcaSlicer-KX-Build dabei). Die Bridge sendet pro
AMS-Slot jetzt `name` + `vendor_name` im Lane-Pfad UND
`gate_filament_name` im Happy-Hare-MMU-Pfad (OrcaSlicer wechselt bei
AMS-Setups automatisch auf den HH-Pfad).
- **Eigene OrcaSlicer-Profile in die Bridge importieren (Issue #41).**
Settings-Tab → „OrcaSlicer-Profile" oder direkt im Slot-Edit-Dialog
(„★ Eigene Profile importieren…") lädst du deine `.json`-Files aus
`~/.config/OrcaSlicer/user/<id>/filament/` hoch — einzeln oder als
ZIP. Erscheint dann im Slot-Dropdown unter „★ Eigene Profile" und
wird beim Sync an Orca als User-Match weitergegeben. Funktioniert
über HTTP, also auch wenn die Bridge im Docker auf Raspi/NAS läuft
und OrcaSlicer auf dem Desktop. Auch reine Override-Profile mit nur
`inherits: "Generic PLA @System"` + ein paar Tweaks werden korrekt
erkannt — die Bridge resolved die vererbten Felder aus dem
System-Parent.
### Fixes
- **AMS-Sync landete hartnäckig auf „Generic PLA":** das Orca-
Datenmodell hat 68 duplikate `filament_id`-Werte (`OGFL99` allein
136 mal), und die Bridge wählte oft eine ID die für den Kobra X
nicht `is_compatible` war (z.B. `GFL92` aus dem Kobra-2-Profil →
Orca verwarf es). Generator priorisiert jetzt Kobra-X-Varianten und
filtert Phantom-Profile (Cross-Vendor-Overrides) raus —
`orca_filaments.json` von 1035 → 400 saubere Profile.
- **Slot ohne expliziten Override sendet jetzt `Generic <Typ>`** statt
einer impliziten Vendor-Annahme. Library-Generic-Profile haben
`compatible_printers: []` (= alle Drucker), sind also immer sichtbar
und matchen verlässlich.
- **Slot-Karte zeigt den Hersteller direkt nach dem Speichern** —
ohne Browser-Reload. `poll()` war async, das Re-Render kam erst
beim nächsten Tick.
- **ACE-Trockner-Toggle warf 502-Fehler obwohl der Trockner ein-/aus-
ging (PR #42 von @gangoke):** `setDry` jetzt fire-and-forget wie
`setAutoFeed`. Der Drucker antwortet auf diesem Push-Topic mit
`code: 0` statt `code: 200`, das hat die Bridge fälschlich als
Fehler interpretiert.
### Datenmodell / API
- `orca_filaments.json` regeneriert: nur echte Vendor-Profile und
OrcaFilamentLibrary-Profile bleiben drin. Bambu/Polymaker/SUNLU-
Library-Profile dabei, Qidi-Cross-Bundles raus.
- Neue Endpoints: `POST /kx/filament/profiles/user` (ZIP/JSON-Upload),
`GET /kx/filament/profiles/user` (Liste der User-Imports),
`DELETE /kx/filament/profiles/user[?vendor=…&name=…]`. Persistenz
in `<KX_DATA_DIR>/orca_filaments.user.json` (Volume — überlebt
Image-Updates).
### Build
- Neues Modul `bridge/orca_filaments.py` (gemeinsame Parser-Helfer
für Generator und Import-Endpoint).
- Dockerfile + release.sh um `orca_filaments.py` erweitert.
## [0.9.18] 2026-05-31
### Neu
@@ -223,8 +11,8 @@
(`slicer/printer/…`) gesendet, der Drucker hat ihn stillschweigend
ignoriert. Jetzt geht er über `web/printer/…` wie der Anycubic
Slicer Next es macht (verifiziert via Live-MQTT-Sniff +
Workbench-Vue-Source). **Achtung:** der Drucker muss im Idle-Zustand
sein, und leere Slots lassen sich nicht beschriften.
Workbench-Vue-Source). **Achtung:** der aktiv geladene Slot kann
während des Drucks nicht umgeschrieben werden — vorher ausziehen.
- **Mehrsprachiges UI spanische Übersetzung von Muttersprachler
überarbeitet (PR #40 von @pezfisk):** fehlende Akzente
(impresión, cámara, después, animación, …), Begriffe vereinheitlicht

View File

@@ -1,255 +1,5 @@
# 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) 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 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
### New
- **🎯 Filament sync with OrcaSlicer now picks the right preset**
instead of always falling back to "Generic PLA". Requires an
OrcaSlicer build with the
[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)
receive-side patch (included in the OrcaSlicer-KX build). The bridge
sends `name` + `vendor_name` per AMS slot on the lane path AND
`gate_filament_name` per gate on the Happy-Hare MMU path (OrcaSlicer
switches to the HH path automatically for AMS setups).
- **Import your own OrcaSlicer profiles into the bridge (issue #41).**
Settings → "OrcaSlicer Profiles" or directly in the slot-edit dialog
("★ Import own profiles…") lets you upload `.json` files from
`~/.config/OrcaSlicer/user/<id>/filament/` — single files or as a
ZIP. They show up in the slot dropdown under "★ Own profiles" and
are passed through to Orca on sync as user matches. Works over HTTP
so the bridge can run in Docker on a Raspi/NAS while OrcaSlicer
lives on a desktop. Override-only profiles with just
`inherits: "Generic PLA @System"` + a few tweaks are detected
correctly — the bridge resolves the inherited fields from the
system parent.
### Fixes
- **AMS sync stuck on "Generic PLA":** the Orca data model has 68
duplicate `filament_id` values (`OGFL99` alone shared by 136
profiles), and the bridge often picked an ID that was not
`is_compatible` with the Kobra X (e.g. `GFL92` from the Kobra-2
profile → Orca rejected it). The generator now prioritises Kobra-X
variants and filters out phantom profiles (cross-vendor overrides) —
`orca_filaments.json` dropped from 1035 to 400 clean profiles.
- **Slot without an explicit override now sends `Generic <type>`**
instead of an implicit vendor guess. Library generic profiles have
`compatible_printers: []` (= all printers), so they are always
visible and match reliably.
- **Slot card shows the vendor right after save** without a browser
reload. `poll()` was async, the re-render only happened on the next
tick.
- **ACE dryer toggle threw a 502 even though the dryer worked
(PR #42 by @gangoke):** `setDry` is now fire-and-forget like
`setAutoFeed`. The printer answers on that push topic with
`code: 0` instead of `code: 200`, which the bridge wrongly treated
as an error.
### Data model / API
- `orca_filaments.json` regenerated: only real vendor profiles and
OrcaFilamentLibrary profiles. Bambu/Polymaker/SUNLU library profiles
in, Qidi cross-bundles out.
- New endpoints: `POST /kx/filament/profiles/user` (ZIP/JSON upload),
`GET /kx/filament/profiles/user` (list user imports),
`DELETE /kx/filament/profiles/user[?vendor=…&name=…]`. Persisted in
`<KX_DATA_DIR>/orca_filaments.user.json` (volume — survives image
updates).
### Build
- New module `bridge/orca_filaments.py` (shared parser helpers used by
the generator and the import endpoint).
- Dockerfile + release.sh updated to include `orca_filaments.py`.
## [0.9.18] 2026-05-31
### New
@@ -260,8 +10,8 @@
sent over the wrong MQTT topic (`slicer/printer/…`) and the printer
silently dropped it. It now goes via `web/printer/…` like the
Anycubic Slicer Next does (verified by live MQTT sniff +
Workbench-Vue source). **Note:** the printer must be idle, and
empty slots can not be labelled.
Workbench-Vue source). **Note:** the currently loaded slot can not
be overwritten during a print — unload it first.
- **Spanish translation reviewed by a native speaker (PR #40 by
@pezfisk):** missing accents (impresión, cámara, después,
animación, …) and term consistency (Pause → Pausa, Start →

View File

@@ -1,102 +0,0 @@
# Contributing to KX-Bridge
Thanks for taking the time to contribute! Here's everything you need to know.
---
## How to report a bug or request a feature
Use the issue tracker:
- **Bug:** [New Bug Report](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues/new?template=bug_report.md)
- **Feature:** [New Feature Request](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues/new?template=feature_request.md)
Please fill in the template — especially the **KX-Bridge version** and **logs**.
Issues without version info are hard to debug.
---
## How to submit a Pull Request
### 1. Fork the repository
Click **Fork** at the top of this page.
You now have your own copy at `gitea.it-drui.de/your-username/KX-Bridge-Release`.
### 2. Clone your fork
```bash
git clone https://gitea.it-drui.de/your-username/KX-Bridge-Release.git
cd KX-Bridge-Release
```
### 3. Create a branch
Always branch off `nightly`:
```bash
git checkout nightly
git checkout -b feature/my-feature # or fix/my-fix
```
### 4. Make your changes
- Test your changes locally with Docker:
```bash
docker build -t kx-bridge:dev .
docker run -p 7125:7125 -v ./config:/app/config kx-bridge:dev
```
- No debug `print()` statements — use `logging`
- Keep commits focused; one thing per commit
### 5. Push and open a PR
```bash
git push origin feature/my-feature
```
Gitea will show a banner — click **"Create Pull Request"**.
The PR template will be pre-filled. Set the target branch to **`nightly`**.
---
## Branch model
```
main ← stable releases only (merged by maintainer)
nightly ← integration branch — PRs go here
feature/* ← your feature branch (in your fork)
fix/* ← your bugfix branch (in your fork)
```
Your PR always targets `nightly`. The maintainer periodically merges `nightly → main` for a new stable release.
---
## Commit style
Use conventional commit prefixes:
| Prefix | When |
|---|---|
| `feat:` | new feature |
| `fix:` | bug fix |
| `docs:` | documentation only |
| `chore:` | maintenance, dependencies |
| `refactor:` | code change without new feature or fix |
Example: `fix: prevent crash when printer is offline during startup`
---
## Language
- **Code and comments:** English
- **Issue comments:** match the language of the issue (if someone writes in German, reply in German)
- **Commit messages:** English
---
## Questions?
Open a [Discussion](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues) or leave a comment on the relevant issue.

View File

@@ -1,9 +1,7 @@
FROM python:3.11-slim-bookworm
FROM python:3.11-slim
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
@@ -15,7 +13,6 @@ COPY data/ ./static/
COPY config_loader.py .
COPY env_loader.py .
COPY kobrax_client.py .
COPY orca_filaments.py .
COPY VERSION .
COPY anycubic_slicer.crt .
COPY anycubic_slicer.key .

View File

@@ -8,11 +8,6 @@
Eine Moonraker-kompatible Bridge, die direkt mit dem Drucker spricht.
<sub>🧪 Ein Community-Bericht auf Reddit deutet darauf hin, dass die Bridge auch
mit dem **Kobra S1** und **Kobra S1 Max** funktioniert — die Protokolle wirken
kompatibel, beides ist aber weder offiziell getestet noch unterstützt.
Feedback willkommen.</sub>
<sub>🇬🇧 <a href="README.md">English version</a> · 🇪🇸 <a href="README.es.md">Versión española</a></sub>
<br>
@@ -37,17 +32,12 @@ Feedback willkommen.</sub>
|---|---|
| 🖨️ | **Druckersteuerung** — Start, Pause, Resume, Abbruch, Temperaturen, Druckgeschwindigkeit |
| 📊 | **Live-Status** — Temperatur, Fortschritt, Layer, Restzeit, Kamera-Stream |
| 🎨 | **AMS / Multicolor**Slots mit **Profil-Picker pro Slot** (eigene Marke aus OrcaSlicer-Profilen pro Slot zuweisen); Bridge schreibt Material und Farbe ans Drucker-Display zurück |
| 📦 | **Eigene OrcaSlicer-Profile importieren** — ZIP aus `~/.config/OrcaSlicer/user/<id>/filament/` in die Bridge ziehen; tauchen im Slot-Dropdown unter ★ Eigene Profile auf |
| 📷 | **Obico-Integration (experimentell)** — Time-Lapse und WebRTC-Livestream gegen einen selbst gehosteten [Obico-Server](https://github.com/TheSpaghettiDetective/obico-server) via moonraker-obico |
| 📐 | **H.264-Direkt-Stream + Z-Höhe** — sparsamer Kamera-Pfad für Obico, aktuelle Z aus der Layer-Höhe abgeleitet (Mm-Progress-Widget) |
| 🎨 | **AMS / Multicolor**Filament-Slots, Per-Kanal-Remapping, MMU-Emulation für OrcaSlicer Filament-Sync |
| 🗂️ | **GCode-Browser** — hochgeladene Dateien mit Thumbnail, Druckhistorie, Suche & Filter |
| 🧩 | **Multi-Printer** — mehrere Drucker in **einer** Bridge-Instanz, Umschalten per Dropdown |
| | **Drucker hinzufügen per Klick** — nur die IP eingeben, Zugangsdaten werden automatisch importiert |
| 🔁 | **Robuster MQTT-Reconnect** — Bridge überlebt nächtlichen Drucker-Reboot ohne manuellen Neustart |
| 🌐 | **Mehrsprachiges UI** — DE / EN / ES / 中文, Browser-Sprache automatisch erkannt |
| 🔄 | **Self-Update** — neue Versionen direkt im Browser installieren |
| 🧠 | **OrcaSlicer** — volles Moonraker-Protokoll (HTTP + WebSocket); für korrekten Vendor-Match pro Slot den [OrcaSlicer-KX-Build](#-empfohlener-slicer) nutzen |
| 🌐 | **OrcaSlicer** — volles Moonraker-Protokoll (HTTP + WebSocket), DE/EN UI |
---
@@ -113,28 +103,18 @@ Drucker → Verbindungstyp **Moonraker** → Host: `http://BRIDGE-IP:7125`
## 🎨 Empfohlener Slicer
Für sauberen AMS-Filament-Sync gibt es einen **gepatchten OrcaSlicer-Build**:
Für die beste Erfahrung mit der KX-Bridge bieten wir einen **gepatchten
OrcaSlicer-Build**, der drei offene SoftFever/OrcaSlicer-PRs bündelt: das
Anycubic-Kobra-X-Druckerprofil, einen Multicolor-G-Code-Fix und — am wichtigsten
— einen Moonraker/Happy-Hare-Filament-Sync-Fix, der die AMS-Slot-Positionen auch
bei einem leeren Slot in der Mitte korrekt beibehält.
**[OrcaSlicer-KX Releases](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases/latest)** (Linux AppImage + Windows ZIP)
**Upstream-PRs im KX-Build:**
- **[PR #13372](https://github.com/SoftFever/OrcaSlicer/pull/13372)** — Moonraker / Happy-Hare AMS-Sync-Fix (Slot-Positionen bleiben auch bei leerem Slot in der Mitte korrekt)
- **[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)** — Vendor- + Name-Matching für Moonraker (liest `name` + `vendor_name` pro Slot und matched gegen deine Filament-Presets), von [@LordGuenni](https://github.com/LordGuenni)
- **[PR #13315](https://github.com/SoftFever/OrcaSlicer/pull/13315)** — Eindeutige `filament_id` für User-Presets (neu erstellte eigene Profile bekommen eine frische ID statt das `OGFL99` vom Generic-Parent zu erben), von [@mrnoisytiger](https://github.com/mrnoisytiger)
**Plus vier KX-eigene Verbesserungen on top:**
- Bridge-Filament-Hint (`tray_info_idx` + Vendor) respektieren
- Vendor-Match auch wenn das gewählte Base-Preset **nicht is_compatible** mit dem aktiven Drucker ist (so matchen Profile aus anderen Drucker-Setups trotzdem über die Marke)
- 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](#-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).
OrcaSlicer-KX ist ein Build von [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0); der Quellcode der Upstream-PRs ist auf GitHub, die KX-spezifischen Patches im OrcaSlicer-KX-Repo.
Standard-OrcaSlicer funktioniert auch; der gepatchte Build verbessert
hauptsächlich das AMS-Handling. Es ist ein Build von
[OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0); der Quellcode
ist über die verlinkten PRs verfügbar.
---
@@ -143,13 +123,6 @@ OrcaSlicer-KX ist ein Build von [OrcaSlicer](https://github.com/SoftFever/OrcaSl
- **[Home-Assistant-Integration](https://github.com/gangoke/kobrax-lan-hass-component)**
von [@gangoke](https://github.com/gangoke) — bindet Sensoren, Drucksteuerung,
Licht, Kamera und das GCode-Vorschaubild als native Home-Assistant-Entitäten ein.
- **[Obico (selbst gehostet)](https://github.com/TheSpaghettiDetective/obico-server)** —
die Bridge bietet eine Moonraker-kompatible API, die
[moonraker-obico](https://github.com/TheSpaghettiDetective/moonraker-obico)
akzeptiert; damit hast du Time-Lapse und WebRTC-Live-Stream gegen deinen
eigenen Obico-Server. Die KI-Spaghetti-Erkennung ist beim Kobra X
experimentell — der Top-Down-Kamerawinkel weicht von dem ab, auf den
das Modell trainiert wurde.
> Dies sind **Community-Projekte**, die nicht von KX-Bridge betreut oder
> supportet werden. Bei Fragen oder Problemen bitte das verlinkte Repository nutzen.

View File

@@ -8,21 +8,8 @@
Un puente compatible con Moonraker que se comunica directamente con la impresora.
<sub>🧪 Un usuario en Reddit ha reportado que el puente también funciona con la
**Kobra S1** y la **Kobra S1 Max** — los protocolos parecen compatibles, pero
ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
<sub>🇬🇧 <a href="README.md">English version</a> · 🇩🇪 <a href="README.de.md">Deutsche Version</a></sub>
</div>
> [!CAUTION]
> **Trabajos de mantenimiento en curso esta semana** — Estamos reestructurando el repositorio (modelo de ramas, flujos CI, proceso de contribución). Es posible que notes cambios en los nombres de ramas, plantillas de PR y la forma en que se publican las versiones. Pedimos disculpas por los inconvenientes. El manejo, el flujo de trabajo y la mantenibilidad a largo plazo mejorarán significativamente como resultado.
>
> 👉 ¿Quieres contribuir? Por favor lee primero [CONTRIBUTING.md](CONTRIBUTING.md).
<div align="center">
<br>
[![Ko-fi](https://img.shields.io/badge/Ko--fi-Apoya%20este%20proyecto-FF5E5B?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/viewitde)
@@ -45,17 +32,12 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|---|---|
| 🖨️ | **Control de impresora** — iniciar, pausar, reanudar, cancelar, temperaturas, velocidad de impresión |
| 📊 | **Estado en tiempo real** — temperatura, progreso, capas, tiempo restante, transmisión de cámara |
| 🎨 | **AMS / multicolor** — ranuras con **selector de perfil por ranura** (asigna tu propia marca de los perfiles de OrcaSlicer a cada ranura); el puente escribe material y color al display de la impresora |
| 📦 | **Importa tus propios perfiles de OrcaSlicer** — arrastra un ZIP de `~/.config/OrcaSlicer/user/<id>/filament/` al puente; aparecen en el desplegable de la ranura bajo ★ Perfiles propios |
| 📷 | **Integración con Obico (experimental)** — Time-Lapse y stream en vivo WebRTC contra un [servidor Obico](https://github.com/TheSpaghettiDetective/obico-server) autoalojado vía moonraker-obico |
| 📐 | **Stream H.264 directo + altura Z** — ruta de cámara de bajo consumo de CPU para Obico, Z actual derivada de la altura de capa (widget de progreso) |
| 🎨 | **AMS / multicolor** — ranuras de filamento, reasignación por canal, emulación MMU para sincronización de filamento en OrcaSlicer |
| 🗂️ | **Explorador de GCode** — archivos subidos con vistas previas, historial de impresión, búsqueda y filtros |
| 🧩 | **Multi-impresora** — múltiples impresoras en **una** instancia del puente, cambia mediante un menú desplegable |
| | **Añade una impresora con un clic** — solo introduce la IP, las credenciales se importan automáticamente |
| 🔁 | **Reconexión MQTT robusta** — el puente sobrevive a reinicios nocturnos de la impresora sin reinicio manual |
| 🌐 | **Interfaz multilingüe** — DE / EN / ES / 中文, detecta automáticamente el idioma del navegador |
| 🔄 | **Actualización automática** — instala nuevas versiones directamente desde el navegador |
| 🧠 | **OrcaSlicer** — protocolo Moonraker completo (HTTP + WebSocket); usa el [build OrcaSlicer-KX](#-slicer-recomendado) para emparejamiento correcto de vendor por ranura |
| 🌐 | **OrcaSlicer** — protocolo Moonraker completo (HTTP + WebSocket), interfaz EN/ES |
---
@@ -75,43 +57,17 @@ docker compose up -d
**Binario Linux (sin Docker):**
```bash
chmod +x kx-bridge-linux-amd64 && ./kx-bridge-linux-amd64
chmod +x kx-bridge && ./kx-bridge
```
**EXE Windows (sin Docker):**
```
kx-bridge.exe
```
> `config\` y `data\` se crean junto al EXE — instalación portátil.
> ⚠️ **Certificados TLS necesarios para el binario standalone**
>
> El bridge habla con el MQTT de la impresora vía mTLS y necesita dos
> ficheros de certificado **junto al binario**:
>
> - `anycubic_slicer.crt`
> - `anycubic_slicer.key`
>
> Ambos vienen en **`anycubic-certs.zip`** en la misma página de release.
> Descárgalo y extrae los dos ficheros en el mismo directorio que
> `kx-bridge-linux-amd64` o `kx-bridge.exe`. Sin ellos verás
> `Verbindung fehlgeschlagen: TLS-Zertifikate fehlen …` (0.9.19.1+) o
> `[Errno 2] No such file or directory` (versiones anteriores).
>
> Estructura correcta:
> ```
> ~/kx-bridge/
> ├── kx-bridge-linux-amd64 (o kx-bridge.exe)
> ├── anycubic_slicer.crt ← de anycubic-certs.zip
> ├── anycubic_slicer.key ← de anycubic-certs.zip
> └── config/ (se crea en el primer arranque)
> ```
>
> Los usuarios de Docker no necesitan hacer esto — los certificados
> están incluidos en la imagen.
> Con los binarios de Linux y Windows, `config/` y `data/` (configuración,
> SQLite, almacén de GCode) viven junto al programa. Copia toda la carpeta
> para mover la instalación.
> Con los binarios de Linux y Windows, `config/` y `data/` (configuración, SQLite, almacén de GCode)
> viven junto al programa. Copia toda la carpeta para mover la instalación.
**Python directamente:**
```bash
@@ -139,31 +95,24 @@ Impresora → Tipo de conexión **Moonraker** → Host: `http://IP-DEL-PUENTE:71
---
## 📺 Vídeo tutorial
[![Configuración y uso de KX-Bridge](https://img.youtube.com/vi/1Ql4wfH27fM/hqdefault.jpg)](https://www.youtube.com/watch?v=1Ql4wfH27fM)
---
## 🎨 Slicer recomendado
Para una sincronización de filamento AMS correcta ofrecemos una **versión modificada de OrcaSlicer**:
Para la mejor experiencia con KX-Bridge ofrecemos una **versión modificada de OrcaSlicer** que
incluye tres PRs abiertos de SoftFever/OrcaSlicer: el perfil de la impresora Anycubic Kobra
X, una corrección de GCode multicolor y — lo más importante — una corrección de sincronización de
filamento Moonraker/Happy-Hare que mantiene las posiciones de las ranuras AMS intactas incluso con una ranura vacía.
**[Lanzamientos de OrcaSlicer-KX](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases/latest)** (Linux AppImage + Windows ZIP)
**PRs upstream incluidos en el build KX:**
- **[PR #13372](https://github.com/SoftFever/OrcaSlicer/pull/13372)** — Corrección de sincronización Moonraker / Happy-Hare AMS (las posiciones de las ranuras se mantienen correctas incluso con ranuras vacías)
- **[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)** — Coincidencia de Vendor + Nombre para Moonraker (lee `name` + `vendor_name` por ranura y los empareja con los presets de filamento del usuario), por [@LordGuenni](https://github.com/LordGuenni)
- **[PR #13315](https://github.com/SoftFever/OrcaSlicer/pull/13315)** — `filament_id` único para presets de usuario (los perfiles nuevos reciben un ID nuevo en vez de heredar `OGFL99` del padre genérico), por [@mrnoisytiger](https://github.com/mrnoisytiger)
**Más cuatro mejoras específicas de KX encima:**
- Respetar el hint de filamento del puente (`tray_info_idx` + vendor)
- Coincidencia por vendor incluso cuando el preset base no es **is_compatible** con la impresora activa (así un perfil copiado de otra máquina sigue coincidiendo por vendor)
- Coincidencia por vendor cuando `tray_info_idx` está definido pero su preset es incompatible
- Búsqueda de dos pasadas: primero presets compatibles, luego todos los visibles
**Por qué importa:** sin #13719 todas las ranuras AMS caen en `Generic PLA` / `Generic PETG` aunque el puente ya envíe la marca concreta (`name + vendor_name + gate_filament_name`). Con el build KX, OrcaSlicer coincide con tus presets de usuario reales — incluyendo los perfiles que importaste al puente vía [Importa tus propios perfiles de OrcaSlicer](#-características).
OrcaSlicer upstream también funciona para rebanar e imprimir — solo pierdes la coincidencia de vendor por ranura en la sincronización AMS. El material y color por ranura se pueden empujar puente → impresora con cualquier slicer (eso va por MQTT, no por el slicer).
OrcaSlicer-KX es un build de [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0); el código fuente de cada PR upstream está en GitHub, los parches específicos de KX en el repo OrcaSlicer-KX.
OrcaSlicer estándar también funciona; la versión modificada mejora principalmente el manejo de AMS.
Es una versión basada en [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0);
el código fuente está disponible a través de los PRs enlazados.
---
@@ -172,12 +121,6 @@ OrcaSlicer-KX es un build de [OrcaSlicer](https://github.com/SoftFever/OrcaSlice
- **[Integración con Home Assistant](https://github.com/gangoke/kobrax-lan-hass-component)**
por [@gangoke](https://github.com/gangoke) — expone sensores, controles de impresión,
luz, cámara y la vista previa del GCode como entidades nativas de Home Assistant.
- **[Obico (autoalojado)](https://github.com/TheSpaghettiDetective/obico-server)** —
el puente expone una API compatible con Moonraker que
[moonraker-obico](https://github.com/TheSpaghettiDetective/moonraker-obico)
acepta, así obtienes Time-Lapse y streaming en vivo WebRTC contra tu propio
servidor Obico. La detección de fallos por IA es experimental en la Kobra X
(el ángulo cenital de la cámara difiere del que el modelo fue entrenado).
> Estos son **proyectos de la comunidad**, no mantenidos ni soportados por KX-Bridge.
> Para preguntas o problemas, utiliza el repositorio enlazado.

View File

@@ -8,10 +8,6 @@
A Moonraker-compatible bridge that talks directly to the printer.
<sub>🧪 A community report on Reddit suggests the bridge also works with the
**Kobra S1** and **Kobra S1 Max** — protocols look compatible, but neither is
officially tested or supported. Feedback welcome.</sub>
<sub>🇩🇪 <a href="README.de.md">Deutsche Version</a> · 🇪🇸 <a href="README.es.md">Versión española</a></sub>
<br>
@@ -36,17 +32,12 @@ officially tested or supported. Feedback welcome.</sub>
|---|---|
| 🖨️ | **Printer control** — start, pause, resume, cancel, temperatures, print speed |
| 📊 | **Live status** — temperature, progress, layers, remaining time, camera stream |
| 🎨 | **AMS / multicolor**slots with per-slot **profile picker** (assign your own brand from OrcaSlicer profiles per slot); bridge writes material & colour back to the printer display |
| 📦 | **Import your own OrcaSlicer profiles** — drag a ZIP from `~/.config/OrcaSlicer/user/<id>/filament/` into the bridge; they show up in the slot dropdown under ★ Own profiles |
| 📷 | **Obico integration (experimental)** — Time-Lapse and WebRTC live stream against a self-hosted [Obico server](https://github.com/TheSpaghettiDetective/obico-server) via moonraker-obico |
| 📐 | **Direct H.264 stream + Z-height** — low-CPU camera path for Obico, current Z derived from layer-height for the print-progress widget |
| 🎨 | **AMS / multicolor**filament slots, per-channel remapping, MMU emulation for OrcaSlicer filament sync |
| 🗂️ | **GCode browser** — uploaded files with thumbnails, print history, search & filter |
| 🧩 | **Multi-printer** — multiple printers in **one** bridge instance, switch via dropdown |
| | **Add a printer with one click** — just enter the IP, credentials are imported automatically |
| 🔁 | **Robust MQTT reconnect** — bridge survives overnight printer reboots without manual restart |
| 🌐 | **Multi-language UI** — DE / EN / ES / 中文, auto-detect browser locale |
| 🔄 | **Self-update** — install new versions directly in the browser |
| 🧠 | **OrcaSlicer** — full Moonraker protocol (HTTP + WebSocket); pair with the [OrcaSlicer-KX build](#-recommended-slicer) for proper per-slot vendor matching |
| 🌐 | **OrcaSlicer** — full Moonraker protocol (HTTP + WebSocket), EN/DE UI |
---
@@ -112,28 +103,16 @@ Printer → Connection type **Moonraker** → Host: `http://BRIDGE-IP:7125`
## 🎨 Recommended Slicer
For proper AMS filament-sync we ship a **patched OrcaSlicer build**:
For the best KX-Bridge experience we offer a **patched OrcaSlicer build** that
bundles three open SoftFever/OrcaSlicer PRs: the Anycubic Kobra X printer
profile, a multicolor G-code fix and — most importantly — a Moonraker/Happy-Hare
filament-sync fix that keeps AMS slot positions intact even with an empty slot.
**[OrcaSlicer-KX releases](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases/latest)** (Linux AppImage + Windows ZIP)
**Upstream PRs bundled in the KX build:**
- **[PR #13372](https://github.com/SoftFever/OrcaSlicer/pull/13372)** — Moonraker / Happy-Hare AMS sync fix (slot positions stay correct even with empty slots)
- **[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)** — Vendor + Name matching for Moonraker (reads `name` + `vendor_name` per slot and matches against the user's filament presets), by [@LordGuenni](https://github.com/LordGuenni)
- **[PR #13315](https://github.com/SoftFever/OrcaSlicer/pull/13315)** — Unique `filament_id` for user presets (so newly created custom profiles get a fresh ID instead of inheriting `OGFL99` from the generic parent), by [@mrnoisytiger](https://github.com/mrnoisytiger)
**Plus four KX-specific matching improvements on top:**
- Respect the bridge filament hint (`tray_info_idx` + vendor)
- Vendor match also when the chosen base preset is **not is_compatible** with the active printer (so a profile copied from a different machine still matches by vendor)
- 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](#-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).
OrcaSlicer-KX is a build of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0); source for each upstream PR is on GitHub, KX-specific patches live in the OrcaSlicer-KX repo.
Standard OrcaSlicer also works; the patched build mainly improves AMS handling.
It's a build of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) (AGPL-3.0);
source is available via the linked PRs.
---
@@ -142,12 +121,6 @@ OrcaSlicer-KX is a build of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer
- **[Home Assistant integration](https://github.com/gangoke/kobrax-lan-hass-component)**
by [@gangoke](https://github.com/gangoke) — exposes sensors, print controls,
light, camera and the GCode thumbnail as native Home Assistant entities.
- **[Obico (self-hosted)](https://github.com/TheSpaghettiDetective/obico-server)** —
the bridge exposes a Moonraker-compatible surface that
[moonraker-obico](https://github.com/TheSpaghettiDetective/moonraker-obico)
accepts, so you get Time-Lapse and WebRTC live streaming against your own
Obico server. The AI failure-detection side is experimental on the Kobra X
(top-down camera angle differs from what the model was trained on).
> These are **community projects**, not maintained or supported by KX-Bridge.
> For questions or issues, please use the linked repository.

View File

@@ -1 +1 @@
0.9.27-nightly2
0.9.18

View File

@@ -1,31 +0,0 @@
# KX-Bridge Claude Agents
## Available Agents
| Agent | File | When to use |
|---|---|---|
| Reviewer | `.claude/agents/reviewer.md` | Before every PR — checks logic, error handling, Moonraker compatibility |
| Changelog | `.claude/agents/changelog.md` | After merge to nightly — generates CHANGELOG.md entry from commits |
| Test Writer | `.claude/agents/test-writer.md` | When adding new functions — derives pytest tests |
| Nightly Prep | `.claude/agents/nightly-prep.md` | Before a release — checks readiness of nightly → main merge |
| Docker Check | `.claude/agents/docker-check.md` | Before image push — validates Dockerfile and compose config |
| Moonraker Debug | `.claude/agents/moonraker-debug.md` | On runtime errors — analyzes Moonraker/Klipper logs |
## Usage
In VS Code with Claude Code extension:
```
@reviewer → code review of current changes
@changelog → generate CHANGELOG entry
@test-writer → write tests for changed files
@nightly-prep → check release readiness
@docker-check → validate Docker config
@moonraker-debug → analyze logs
```
## Context
- Moonraker API: Port 7125
- AFC lane_data: flat indexing lane1lane4
- Registry: `gitea.it-drui.de/viewit/kx-bridge`
- Default PR target: `nightly`

View File

@@ -31,88 +31,6 @@ default_ams_slot = auto
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
auto_leveling = 1
# Kamera-Stream bei Druckstart automatisch einschalten (1 = an, 0 = aus)
camera_on_print = 0
# Warnung vor Druck von Web-Uploads (1 = an, 0 = aus)
web_upload_warning = 1
# Nach Upload: Filament/Color-Selector automatisch öffnen (1 = an, 0 = aus)
print_start_dialog = 1
# ─── Filament-Profile pro AMS-Slot (optional) ────────────────────────────────
# Beim Slicer-Sync nimmt OrcaSlicer per Default immer "Generic PLA/PETG/...".
# Mit diesen Mappings sendet die Bridge die konkrete Orca-Filament-ID +
# Vendor mit (Anzeige im Slicer dann z.B. "PolyTerra PLA — Polymaker" statt
# nur "Generic PLA"). Mapping wird über die Web-UI gepflegt.
# Beispiel:
# [filament_profiles]
# slot_0_id = OGFL01
# slot_0_vendor = Polymaker
# slot_1_id = OGFG23
# slot_1_vendor = Polymaker
[bridge]
# Poll-Intervall in Sekunden
poll_interval = 3
# ─── Multi-Printer (optional) ──────────────────────────────────────────────────
# Mehrere Drucker können als [printer_1], [printer_2], … definiert werden.
# Jede Bridge-Instanz verbindet sich mit einem Drucker (je eigener Port).
# bridge_url zeigt auf die jeweilige Bridge-Instanz (für den /kx/printers-Endpunkt).
# Die [connection]-Sektion wird weiterhin als Fallback für diese Instanz verwendet.
#
# Beispiel:
# [printer_1]
# name = Kobra X Links
# bridge_url = http://192.168.178.95:7125
# printer_ip = 192.168.178.95
# mqtt_port = 9883
# username = userXXXXXXXXXX
# password = XXXXXXXXXXXXXXX
# device_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# mode_id = 20030
#
# [printer_2]
# name = Kobra X Rechts
# bridge_url = http://192.168.178.96:7125
# printer_ip = 192.168.178.96
# mqtt_port = 9883
# username = userYYYYYYYYYY
# password = YYYYYYYYYYYYYYY
# device_id = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
# mode_id = 20030
[ace_dry_presets]
# Vordefinierte Dry-Set Presets (Temp in °C, Dauer in Sekunden)
pla_temp = 45
pla_duration_sec = 14400
pla_plus_temp = 45
pla_plus_duration_sec = 14400
petg_temp = 50
petg_duration_sec = 14400
tpu_temp = 55
tpu_duration_sec = 14400
abs_asa_temp = 45
abs_asa_duration_sec = 28800
pa_pc_temp = 55
pa_pc_duration_sec = 43200
# Custom Presets (Name + Temp + Dauer)
custom_1_name = Custom 1
custom_1_temp = 45
custom_1_duration_sec = 14400
custom_2_name = Custom 2
custom_2_temp = 45
custom_2_duration_sec = 14400
custom_3_name = Custom 3
custom_3_temp = 45
custom_3_duration_sec = 14400
[spoolman]
# URL der Spoolman-Instanz (leer lassen um Spoolman zu deaktivieren)
# server = http://192.168.x.x:7912
# Wie oft (Sekunden) der Filamentverbrauch während des Drucks gemeldet wird
# (0 = nur beim Druckende)
# sync_rate = 0

View File

@@ -13,7 +13,6 @@ _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:
@@ -58,14 +57,16 @@ def _load_config_file(path: pathlib.Path):
"MQTT_PASSWORD": (CONFIG_SECTION_CONNECTION, "password"),
"MODE_ID": (CONFIG_SECTION_CONNECTION, "mode_id"),
"DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"),
"DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"),
"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"),
"DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"),
"AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"),
"VIBRATION_COMPENSATION": (CONFIG_SECTION_PRINT, "vibration_compensation"),
"FLOW_CALIBRATION": (CONFIG_SECTION_PRINT, "flow_calibration"),
"CAMERA_ON_PRINT": (CONFIG_SECTION_PRINT, "camera_on_print"),
"WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"),
"TIMELAPSE_LOCAL": (CONFIG_SECTION_PRINT, "timelapse_local"),
"TIMELAPSE_INTERVAL_SEC": (CONFIG_SECTION_PRINT, "timelapse_interval_sec"),
"TIMELAPSE_PRINTER": (CONFIG_SECTION_PRINT, "timelapse_printer"),
"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:
@@ -77,18 +78,6 @@ 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] = {}
@@ -111,10 +100,15 @@ def migrate_env_to_config(env_path: pathlib.Path, config_path: pathlib.Path):
"device_id": env_vals.get("DEVICE_ID", ""),
}
cfg[CONFIG_SECTION_PRINT] = {
"default_ams_slot": env_vals.get("DEFAULT_AMS_SLOT", "auto"),
"auto_leveling": env_vals.get("AUTO_LEVELING", "1"),
"camera_on_print": env_vals.get("CAMERA_ON_PRINT", "0"),
"web_upload_warning": env_vals.get("WEB_UPLOAD_WARNING", "1"),
"default_ams_slot": env_vals.get("DEFAULT_AMS_SLOT", "auto"),
"auto_leveling": env_vals.get("AUTO_LEVELING", "1"),
"vibration_compensation": env_vals.get("VIBRATION_COMPENSATION", "0"),
"flow_calibration": env_vals.get("FLOW_CALIBRATION", "0"),
"camera_on_print": env_vals.get("CAMERA_ON_PRINT", "0"),
"web_upload_warning": env_vals.get("WEB_UPLOAD_WARNING", "1"),
"timelapse_local": env_vals.get("TIMELAPSE_LOCAL", "0"),
"timelapse_interval_sec": env_vals.get("TIMELAPSE_INTERVAL_SEC", "4"),
"timelapse_printer": env_vals.get("TIMELAPSE_PRINTER", "0"),
}
cfg[CONFIG_SECTION_BRIDGE] = {
"poll_interval": "3",
@@ -243,17 +237,10 @@ 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 or preserved_vendors:
if profiles:
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"):
@@ -267,43 +254,6 @@ 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)
@@ -316,9 +266,11 @@ PASSWORD = get("MQTT_PASSWORD", "")
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")))
SPOOLMAN_SERVER = get("SPOOLMAN_SERVER", "")
SPOOLMAN_SYNC_RATE = int(get("SPOOLMAN_SYNC_RATE", "0"))
AUTO_LEVELING = int(get("AUTO_LEVELING", "1"))
VIBRATION_COMPENSATION = int(get("VIBRATION_COMPENSATION", "0"))
FLOW_CALIBRATION = int(get("FLOW_CALIBRATION", "0"))
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT", "0"))
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
TIMELAPSE_LOCAL = int(get("TIMELAPSE_LOCAL", "0"))
TIMELAPSE_INTERVAL_SEC = int(get("TIMELAPSE_INTERVAL_SEC", "4"))
TIMELAPSE_PRINTER = int(get("TIMELAPSE_PRINTER", "0"))

File diff suppressed because it is too large Load Diff

View File

@@ -1,210 +0,0 @@
# KobraX Full Stack — KX-Bridge + Obico Self-Hosted + Spoolman
#
# Für Portainer: Stack → Add Stack → Upload → diese Datei wählen
#
# Voraussetzung: Obico-Images einmalig in Gitea-Registry pushen:
# docker tag obico-server-web:latest gitea.it-drui.de/viewit/obico-web:latest
# docker tag obico-server-ml_api:latest gitea.it-drui.de/viewit/obico-ml:latest
# docker tag obico-server-tasks:latest gitea.it-drui.de/viewit/obico-tasks:latest
# docker push gitea.it-drui.de/viewit/obico-web:latest
# docker push gitea.it-drui.de/viewit/obico-ml:latest
# docker push gitea.it-drui.de/viewit/obico-tasks:latest
#
# Persistente Daten: /mnt/dockerdata/KobraXStack/<service>/
#
# Ports:
# 7125 — KX-Bridge (Moonraker-API)
# 3334 — Obico (Web-UI)
# 7912 — Spoolman (Web-UI)
#
# Obico Admin-Account nach dem ersten Start:
# docker exec obico-web python manage.py createsuperuser
x-obico-base: &obico-base
restart: unless-stopped
volumes:
- /mnt/dockerdata/KobraXStack/obico/data:/data
- /mnt/dockerdata/KobraXStack/obico/frontend:/frontend
depends_on:
- obico-redis
environment:
DEBUG: "False"
REDIS_URL: "redis://obico-redis:6379"
DATABASE_URL: "sqlite:////data/db.sqlite3"
INTERNAL_MEDIA_HOST: "http://obico-web:3334"
ML_API_HOST: "http://obico-ml:3333"
ACCOUNT_ALLOW_SIGN_UP: "False"
SITE_USES_HTTPS: "False"
SITE_IS_PUBLIC: "False"
DJANGO_SECRET_KEY: "change-me-to-a-random-secret-key-before-use"
WEBPACK_LOADER_ENABLED: "False"
networks:
- kobrax-stack
services:
# ── KX-Bridge ───────────────────────────────────────────────
kx-bridge:
image: gitea.it-drui.de/viewit/kx-bridge:latest
container_name: kx-bridge
restart: unless-stopped
ports:
- "7125:7125"
volumes:
- /mnt/dockerdata/KobraXStack/kx-bridge/config:/app/config
- /mnt/dockerdata/KobraXStack/kx-bridge/data:/app/data
networks:
- kobrax-stack
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Spoolman ────────────────────────────────────────────────
spoolman:
image: ghcr.io/donkie/spoolman:latest
container_name: spoolman
restart: unless-stopped
ports:
- "7912:8000"
volumes:
- /mnt/dockerdata/KobraXStack/spoolman:/home/app/.local/share/spoolman
networks:
- kobrax-stack
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Obico Redis ─────────────────────────────────────────────
obico-redis:
image: redis:7.2-alpine
container_name: obico-redis
restart: unless-stopped
volumes:
- /mnt/dockerdata/KobraXStack/obico/redis:/data
networks:
- kobrax-stack
healthcheck:
test: ["CMD", "redis-cli", "ping"]
start_period: 10s
interval: 15s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
# ── Obico ML API ────────────────────────────────────────────
obico-ml:
image: gitea.it-drui.de/viewit/obico-ml:latest
container_name: obico-ml
restart: unless-stopped
command: bash -c "gunicorn --bind 0.0.0.0:3333 --workers 1 wsgi"
working_dir: /app
environment:
DEBUG: "False"
FLASK_APP: "server.py"
networks:
- kobrax-stack
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3333/hc/ || exit 1"]
start_period: 30s
interval: 30s
timeout: 10s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Obico Web ───────────────────────────────────────────────
obico-web:
<<: *obico-base
image: gitea.it-drui.de/viewit/obico-web:latest
container_name: obico-web
ports:
- "3334:3334"
depends_on:
- obico-ml
- obico-redis
command: >
sh -c 'python manage.py migrate &&
python manage.py shell -c "from django.contrib.sites.models import Site; s=Site.objects.first(); s.domain=\"192.168.178.204:3334\"; s.name=\"Obico\"; s.save()" &&
python manage.py collectstatic --noinput &&
daphne -b 0.0.0.0 -p 3334 config.routing:application'
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3334/hc/ || exit 1"]
start_period: 60s
interval: 90s
timeout: 20s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── Obico Tasks (Celery) ────────────────────────────────────
obico-tasks:
<<: *obico-base
image: gitea.it-drui.de/viewit/obico-tasks:latest
container_name: obico-tasks
command: sh -c "celery -A config worker --beat -l info -c 2 -Q realtime,celery"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── moonraker-obico Plugin ──────────────────────────────────
# Verbindet KX-Bridge mit dem Obico-Server (Spaghetti-Detektion, Remote-UI)
# Voraussetzung: /mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg
# muss existieren und einen gültigen auth_token enthalten.
#
# Token holen (nach erstem obico-web Start):
# docker exec obico-web python manage.py shell -c "
# from app.models import OneTimeVerificationCode, User
# from django.utils import timezone; from datetime import timedelta; import random
# u = User.objects.first()
# c = OneTimeVerificationCode.objects.create(user=u, code='%06d' % random.randint(100000,999999), expired_at=timezone.now()+timedelta(hours=2))
# print('CODE:', c.code)"
# curl -X POST 'http://localhost:3334/api/v1/octo/verify/?code=<CODE>'
# → printer.auth_token aus der Antwort in die cfg eintragen
moonraker-obico:
image: gitea.it-drui.de/viewit/moonraker-obico:latest
container_name: moonraker-obico
restart: unless-stopped
network_mode: host
volumes:
- /mnt/dockerdata/KobraXStack/moonraker-obico:/opt/printer_data/config
- /mnt/dockerdata/KobraXStack/moonraker-obico/logs:/opt/printer_data/logs
command: ["-c", "/opt/printer_data/config/moonraker-obico.cfg"]
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
kobrax-stack:
driver: bridge
# Verzeichnisse müssen auf dem Host existieren:
# mkdir -p /mnt/dockerdata/KobraXStack/kx-bridge/config \
# /mnt/dockerdata/KobraXStack/kx-bridge/data \
# /mnt/dockerdata/KobraXStack/spoolman \
# /mnt/dockerdata/KobraXStack/obico/data \
# /mnt/dockerdata/KobraXStack/obico/frontend \
# /mnt/dockerdata/KobraXStack/obico/redis \
# /mnt/dockerdata/KobraXStack/moonraker-obico/logs
# Spoolman benötigt UID/GID 1000:
# sudo chown -R 1000:1000 /mnt/dockerdata/KobraXStack/spoolman
#
# moonraker-obico Config anlegen (auth_token nach Obico-Setup eintragen):
# cp /path/to/moonraker-obico.cfg.example /mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg

View File

@@ -1,3 +0,0 @@
services:
kx-bridge:
image: gitea.it-drui.de/viewit/kx-bridge:nightly

View File

@@ -1,25 +0,0 @@
# 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

View File

@@ -1,29 +0,0 @@
# 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:

View File

@@ -1,344 +0,0 @@
# Eigene Filament-Presets anlegen, prüfen und mit KX-Bridge verknüpfen
> **Gilt für:** OrcaSlicer-KX v2.4.0-alpha-kx2 oder neuer
---
## Was ist die `filament_id` und warum ist sie wichtig?
Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wird von der KX-Bridge genutzt, um beim AMS-Sync das richtige Preset zuzuordnen.
- System-Presets (z.B. "Polymaker PolyTerra PLA") haben eine feste ID wie `GFL99` oder `OGFL04`.
- **Eigene (User-)Presets** bekommen in OrcaSlicer-KX automatisch eine eindeutige ID, die mit `P` beginnt (z.B. `P3a7f2c1`).
Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert.
---
## 1. Eigenes Filament-Preset anlegen
1. OrcaSlicer-KX starten
2. Rechts oben im **Filament-Dropdown** ein passendes Basis-Preset wählen (z.B. "Generic PLA" oder ein Hersteller-Preset)
3. Einstellungen nach Wunsch anpassen (Temperaturen, Kühlung, etc.)
4. Auf das **Speichern-Symbol** (Diskette) klicken → **"Save as new preset"**
5. Namen eingeben — z.B. `SUNLU PLA+ 2.0`
> Der Name muss später exakt so in der Bridge eingetragen werden.
6. Drucker auswählen: **Anycubic Kobra X 0.4 nozzle** — wichtig für die Kompatibilität!
7. **Speichern** klicken
8. OrcaSlicer **einmal neu starten** — erst dann wird die `filament_id` dauerhaft gespeichert.
---
## 2. Eindeutige ID prüfen
Nach dem Neustart prüfen, ob die ID korrekt gesetzt wurde:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Die Datei öffnen und nach `filament_id` suchen:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Korrekt: ID beginnt mit `P` gefolgt von 7 Hex-Zeichen
❌ Fehlt oder leer: OrcaSlicer-KX zu alt — Update auf v2.4.0-alpha-kx2 oder neuer
---
## 3. Preset auf einen anderen PC übertragen (Import)
### Exportieren (Quell-PC)
Die Preset-Datei einfach kopieren:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importieren (Ziel-PC)
**Methode A — Datei direkt kopieren:**
1. Die `.json`-Datei in das gleiche Verzeichnis auf dem Ziel-PC kopieren
2. OrcaSlicer neu starten → Preset erscheint im Dropdown
**Methode B — OrcaSlicer Import-Funktion:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Die `.json`-Datei auswählen
3. OrcaSlicer neu starten
> **Wichtig:** Die `filament_id` in der Datei bleibt erhalten — das Preset wird auf dem Ziel-PC genauso erkannt wie auf dem Quell-PC.
---
## 4. Preset in KX-Bridge verknüpfen
1. KX-Bridge UI öffnen
2. **Filament-Verwaltung** → AMS-Slot auswählen
3. Im Feld **Filament-Name** exakt den OrcaSlicer-Preset-Namen eintragen:
```
SUNLU PLA+ 2.0
```
4. Speichern
Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer findet das Preset anhand von Name und `filament_id` → zeigt es korrekt an.
---
## Wichtige Hinweise
| Was | Warum |
|-----|-------|
| Name in OrcaSlicer und Bridge müssen **exakt** übereinstimmen | Groß-/Kleinschreibung und Sonderzeichen werden verglichen |
| Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen |
| Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben |
| **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets |
---
---
# How to Create, Verify and Import Custom Filament Presets for KX-Bridge
> **Requires:** OrcaSlicer-KX v2.4.0-alpha-kx2 or newer
---
## What is the `filament_id` and why does it matter?
Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge uses this ID to match the correct preset during AMS sync.
- System presets (e.g. "Polymaker PolyTerra PLA") have a fixed ID like `GFL99` or `OGFL04`.
- **Custom (user) presets** automatically receive a unique ID starting with `P` (e.g. `P3a7f2c1`) in OrcaSlicer-KX.
Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists.
---
## 1. Create a Custom Filament Preset
1. Launch OrcaSlicer-KX
2. Select a suitable base preset from the **filament dropdown** (e.g. "Generic PLA" or a vendor preset)
3. Adjust settings as needed (temperatures, cooling, etc.)
4. Click the **save icon** (floppy disk) → **"Save as new preset"**
5. Enter a name — e.g. `SUNLU PLA+ 2.0`
> This name must be entered in the bridge exactly as typed here.
6. Select printer: **Anycubic Kobra X 0.4 nozzle** — required for compatibility!
7. Click **Save**
8. **Restart OrcaSlicer once** — the `filament_id` is only written permanently after a restart.
---
## 2. Verify the Unique ID
After restarting, check that the ID was set correctly:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Open the file and look for `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correct: ID starts with `P` followed by 7 hex characters
❌ Missing or empty: Your OrcaSlicer-KX version is too old — update to v2.4.0-alpha-kx2 or newer
---
## 3. Transfer a Preset to Another PC (Import)
### Export (source PC)
Simply copy the preset file:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Import (target PC)
**Method A — Copy file directly:**
1. Copy the `.json` file to the same directory on the target PC
2. Restart OrcaSlicer → preset appears in the dropdown
**Method B — OrcaSlicer import function:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Select the `.json` file
3. Restart OrcaSlicer
> **Note:** The `filament_id` inside the file is preserved — the preset will be recognized on the target PC exactly as on the source PC.
---
## 4. Link the Preset in KX-Bridge
1. Open the KX-Bridge UI
2. Go to **Filament Management** → select the AMS slot
3. In the **Filament Name** field, enter the OrcaSlicer preset name exactly:
```
SUNLU PLA+ 2.0
```
4. Save
The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer matches by name and `filament_id` → displays the preset correctly.
---
## Quick Reference
| What | Why |
|------|-----|
| Name in OrcaSlicer and Bridge must match **exactly** | Case and special characters are compared |
| Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving |
| **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart |
| Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets |
---
---
# Cómo crear, verificar e importar perfiles de filamento personalizados para KX-Bridge
> **Requiere:** OrcaSlicer-KX v2.4.0-alpha-kx2 o superior
---
## ¿Qué es el `filament_id` y por qué es importante?
Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge usa este ID para asignar el perfil correcto durante la sincronización AMS.
- Los perfiles del sistema (p. ej. "Polymaker PolyTerra PLA") tienen un ID fijo como `GFL99` o `OGFL04`.
- Los **perfiles personalizados (usuario)** reciben automáticamente un ID único que empieza por `P` (p. ej. `P3a7f2c1`) en OrcaSlicer-KX.
Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista.
---
## 1. Crear un perfil de filamento personalizado
1. Iniciar OrcaSlicer-KX
2. Seleccionar un perfil base adecuado en el **menú desplegable de filamento** (p. ej. "Generic PLA" o un perfil de fabricante)
3. Ajustar la configuración según sea necesario (temperaturas, refrigeración, etc.)
4. Hacer clic en el **icono de guardar** (disquete) → **"Save as new preset"**
5. Introducir un nombre — p. ej. `SUNLU PLA+ 2.0`
> Este nombre debe introducirse en la bridge exactamente igual.
6. Seleccionar impresora: **Anycubic Kobra X 0.4 nozzle** — ¡necesario para la compatibilidad!
7. Hacer clic en **Guardar**
8. **Reiniciar OrcaSlicer una vez** — el `filament_id` solo se escribe de forma permanente tras un reinicio.
---
## 2. Verificar el ID único
Tras reiniciar, comprobar que el ID se ha establecido correctamente:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Abrir el archivo y buscar `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correcto: el ID empieza por `P` seguido de 7 caracteres hexadecimales
❌ Falta o está vacío: la versión de OrcaSlicer-KX es demasiado antigua — actualizar a v2.4.0-alpha-kx2 o superior
---
## 3. Transferir un perfil a otro PC (importar)
### Exportar (PC de origen)
Simplemente copiar el archivo del perfil:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importar (PC de destino)
**Método A — Copiar el archivo directamente:**
1. Copiar el archivo `.json` al mismo directorio en el PC de destino
2. Reiniciar OrcaSlicer → el perfil aparece en el menú desplegable
**Método B — Función de importación de OrcaSlicer:**
1. En OrcaSlicer: **File → Import → Import Configs...**
2. Seleccionar el archivo `.json`
3. Reiniciar OrcaSlicer
> **Nota:** El `filament_id` dentro del archivo se conserva — el perfil se reconocerá en el PC de destino exactamente igual que en el de origen.
---
## 4. Vincular el perfil en KX-Bridge
1. Abrir la interfaz de KX-Bridge
2. Ir a **Gestión de filamentos** → seleccionar la ranura AMS
3. En el campo **Nombre de filamento**, introducir el nombre exacto del perfil de OrcaSlicer:
```
SUNLU PLA+ 2.0
```
4. Guardar
La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización → OrcaSlicer busca por nombre y `filament_id` → muestra el perfil correctamente.
---
## Referencia rápida
| Qué | Por qué |
|-----|---------|
| El nombre en OrcaSlicer y en Bridge debe coincidir **exactamente** | Se comparan mayúsculas, minúsculas y caracteres especiales |
| El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar |
| **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio |
| Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario |

View File

@@ -48,6 +48,3 @@ 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")))

View File

@@ -27,7 +27,6 @@ import hashlib
import json
import logging
import os
import select
import socket
import ssl
import sys
@@ -121,10 +120,6 @@ 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)
@@ -159,44 +154,26 @@ class KobraXClient:
# -- Connection ----------------------------------------------------------
def _do_connect(self):
if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE):
raise FileNotFoundError(
f"TLS-Zertifikate fehlen: anycubic_slicer.crt + anycubic_slicer.key "
f"müssen neben der kx-bridge Binary liegen ({_SCRIPT_DIR}/). "
f"Lade anycubic-certs.zip vom Gitea-Release herunter und entpacke "
f"die Dateien dorthin."
)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.set_ciphers("DEFAULT:@SECLEVEL=0")
ctx.load_cert_chain(CERT_FILE, KEY_FILE)
# 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])
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])
new_sock.sendall(_build_connect(self.client_id, self.username, self.password))
new_sock.settimeout(3)
r = new_sock.recv(64)
self._sock.sendall(_build_connect(self.client_id, self.username, self.password))
self._sock.settimeout(3)
r = self._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")
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
self._sock.settimeout(0.2)
self._buf = b""
self._subscribe(self._sub_topic())
log.debug("MQTT connected to %s:%s", self.host, self.port)
def connect(self):
@@ -222,14 +199,10 @@ class KobraXClient:
def disconnect(self):
self._running = False
with self._lock:
try:
if self._sock is not None:
self._sock.close()
except Exception:
pass
self._sock = None
self._sock_gen += 1
try:
self._sock.close()
except Exception:
pass
def _reconnect(self):
"""Persistenter Reconnect: versucht endlos weiter bis der Drucker wieder
@@ -238,16 +211,10 @@ class KobraXClient:
nur DEBUG um Log-Spam bei langem Drucker-Ausfall (z.B. über Nacht
ausgeschaltet) zu vermeiden."""
log.warning("Verbindung verloren reconnect…")
# 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
try:
self._sock.close()
except Exception:
pass
delays = [2, 4, 8, 15, 30, 60]
attempt = 0
while self._running:
@@ -271,8 +238,7 @@ class KobraXClient:
with self._lock:
pid = self._pid
self._pid += 1
if self._sock is not None:
self._sock.sendall(_build_subscribe(topic, pid))
self._sock.sendall(_build_subscribe(topic, pid))
log.info("SUB %s", topic)
# -- Read loop -----------------------------------------------------------
@@ -282,52 +248,17 @@ class KobraXClient:
_empty_count = 0
while self._running:
if time.time() - last_ping > 30:
ping_ok = False
with self._lock:
try:
if self._sock is not None:
self._sock.sendall(_build_pingreq())
ping_ok = True
self._sock.sendall(_build_pingreq())
except Exception:
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:
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:
if self._running and not self._reconnect():
break
last_ping = time.time()
continue
data = sock.recv(65536)
last_ping = time.time()
try:
data = self._sock.recv(65536)
if not data:
# Windows SSL kann kurzzeitig b"" liefern ohne echten EOF
_empty_count += 1
@@ -336,7 +267,7 @@ class KobraXClient:
continue
_empty_count = 0
self._buf += data
self._drain() # außerhalb des Locks — Dispatch/event.set() bleibt prompt
self._drain()
except ssl.SSLWantReadError:
continue
except socket.timeout:
@@ -658,8 +589,7 @@ 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).
_ai = socket.getaddrinfo(self.host, 18910, socket.AF_INET, socket.SOCK_STREAM)
sock = socket.create_connection(_ai[0][4], timeout=10)
sock = socket.create_connection((self.host, 18910), 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

View File

@@ -6,7 +6,7 @@
# ein → zur Laufzeit über sys._MEIPASS lesbar (_WEB_BASE in der Bridge).
from PyInstaller.utils.hooks import collect_all
datas = [("web", "web"), ("data", "static"), ("VERSION", ".")] # bridge/data/ → static/ im _MEIPASS
datas = [("web", "web"), ("data", "static")] # bridge/data/ → static/ im _MEIPASS
binaries = []
hiddenimports = []

View File

@@ -1,18 +0,0 @@
[server]
url = http://127.0.0.1:3334
auth_token = REPLACE_ME
sentry_opt = out
[moonraker]
host = 127.0.0.1
port = 7125
[webcam]
disable_video_streaming = False
snapshot_url = http://127.0.0.1:7125/api/camera/snapshot
stream_url = http://127.0.0.1:7125/api/camera/stream
target_fps = 5
[logging]
path = /opt/printer_data/logs/moonraker-obico.log
level = INFO

View File

@@ -1,147 +0,0 @@
"""OrcaSlicer Filament-Profil Parser.
Geteilt zwischen dem Generator (tools/gen_orca_filament_list.py) und dem
Custom-Profile-Import-Endpoint (bridge/kobrax_moonraker_bridge.py).
Liest Orca-Filament-JSON-Dateien (System- oder User-Profile) und gibt
sie als normalisierte Liste mit (id, name, vendor, type, color) zurück.
"""
from __future__ import annotations
import json
import re
def first_str(value, default: str = "") -> str:
"""Orca-Profile speichern manche Felder als ['wert']. Liefert erstes
Element als String."""
if isinstance(value, list):
return str(value[0]) if value else default
if isinstance(value, str):
return value
return default
def clean_name(raw: str) -> str:
"""Strippt printer-/varianten-spezifische Suffixe:
'PolyTerra PLA @base''PolyTerra PLA'
'Anycubic PLA @Anycubic Kobra X 0.4 nozzle''Anycubic PLA'
'Anker Generic PLA 0.4 nozzle''Anker Generic PLA'
"""
name = re.sub(r"\s*@.*$", "", raw).strip()
name = re.sub(r"\s+\d+(\.\d+)?\s*nozzle\s*$", "", name, flags=re.IGNORECASE).strip()
return name or raw
def parse_profile(data: dict, by_name: dict | None = None,
path_vendor: str | None = None,
source_path: str = "",
system_index: list | None = None) -> dict | None:
"""Parsed ein einzelnes Orca-Filament-Profil zum Bridge-Schema.
`by_name` ist optional ein {name: [profile, …]}-Index für Inherits-Resolve
aus dem rohen Source-Tree (Generator). Bei Single-File-Import (User-Datei
aus OrcaSlicer-User-Dir) reichen wir stattdessen `system_index` rein —
die fertige System-Profile-Liste aus orca_filaments.json. Damit können
wir filament_id/vendor/type/color über die `inherits`-Kette aus dem
System-Parent ableiten, auch wenn das User-Profil diese Felder nicht
selbst setzt (typisch: User-Override-Profile haben nur Tweaks).
Liefert {id, name, vendor, type, color} oder None wenn das Profil
keine filament_id hat (z.B. abstrakte @base-Templates).
"""
if not isinstance(data, dict):
return None
# User-Profile aus dem OrcaSlicer-User-Dir setzen oft KEIN "type"-Feld —
# das kommt vom System-Parent. Wir akzeptieren das wenn entweder "type"
# explizit "filament" ist ODER ein "inherits" auf ein anderes Profil zeigt.
if data.get("type") not in (None, "filament") and not data.get("inherits"):
return None
if data.get("type") == "filament" and data.get("inherits") is None and not data.get("filament_id"):
# type=filament aber kein parent + keine ID → wertloses Stub
return None
inst = data.get("instantiation", "true")
if isinstance(inst, str) and inst.lower() == "false":
return None
# Build system-name-Index für den fallback-Lookup wenn system_index gesetzt.
sys_by_name: dict[str, dict] = {}
if system_index:
for p in system_index:
if isinstance(p, dict) and p.get("name"):
sys_by_name[p["name"]] = p
def _resolve(key: str, depth: int = 5):
cur_list = [data]
for _ in range(depth):
for cur in cur_list:
v = cur.get(key)
if v not in ("", [], None, [""]) and v is not None:
return v
# Erst raw-Inherits via by_name (Generator-Pfad)
if by_name:
next_list: list[dict] = []
for cur in cur_list:
parent_name = cur.get("inherits")
if parent_name and parent_name in by_name:
next_list.extend(by_name[parent_name])
if next_list:
cur_list = next_list
continue
break
return None
def _resolve_via_system_index(key: str):
"""Inherits-Kette über system_index (clean_name-Match)."""
parent_raw = data.get("inherits")
if not parent_raw or not sys_by_name:
return None
parent_clean = clean_name(parent_raw)
sys_p = sys_by_name.get(parent_clean)
if not sys_p:
return None
# System-JSON benutzt schon das normalisierte Schema
mapping = {
"filament_id": "id",
"filament_vendor": "vendor",
"filament_type": "type",
"default_filament_colour": "color",
}
return sys_p.get(mapping.get(key, key))
def _resolve_full(key: str):
v = _resolve(key)
if v not in ("", [], None, [""]) and v is not None:
return v
return _resolve_via_system_index(key)
fid = _resolve_full("filament_id")
if not fid or not isinstance(fid, str):
return None
name_raw = data.get("name", fid)
name = clean_name(name_raw)
vendor = first_str(_resolve_full("filament_vendor")) or (path_vendor or "Generic")
ftype = first_str(_resolve_full("filament_type"), "")
color = first_str(_resolve_full("default_filament_colour"), "")
return {
"id": fid,
"name": name,
"vendor": vendor,
"type": ftype,
"color": color,
}
def parse_profile_bytes(blob: bytes, source_name: str = "",
system_index: list | None = None) -> dict | None:
"""Liest ein einzelnes Profil aus JSON-Bytes. Für File-Upload-Pfad.
`system_index` ist optional die fertige Liste aus orca_filaments.json —
wird für die Inherits-Resolve von User-Profilen genutzt die das volle
Schema vom System-Parent erben."""
try:
data = json.loads(blob.decode("utf-8", errors="replace"))
except Exception:
return None
return parse_profile(data, source_path=source_name, system_index=system_index)

View File

@@ -1,21 +0,0 @@
## Description
<!-- What does this PR change? -->
## Related Issue
Closes #
## Type
- [ ] Bug fix
- [ ] Feature
- [ ] Documentation
- [ ] Refactoring
## Tested with
- OrcaSlicer Version:
- Printer:
- Moonraker/Klipper Version:
## Checklist
- [ ] Tests added/updated
- [ ] CHANGELOG.md updated
- [ ] No debug code included

File diff suppressed because it is too large Load Diff

View File

@@ -38,17 +38,136 @@
<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>
<button class="theme-btn" onclick="showPanel('settings')" id="settings-btn" title="Einstellungen"></button>
<button class="theme-btn" onclick="openSettings()" id="settings-btn" title="Einstellungen"></button>
<button class="conn-btn disconnected" id="conn-btn" onclick="toggleConnection()">⚡ Verbinden</button>
</header>
<!-- ═══ SETTINGS MODAL ═══ -->
<!-- Settings-Modal entfernt — jetzt #panel-settings (Master-Detail im Main-Bereich) -->
<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-vibration-compensation" style="width:auto;margin:0">
<label id="lbl-vibration-compensation" style="margin:0;cursor:pointer" for="s-vibration-compensation">Resonance Compensation</label>
</div>
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
<input type="checkbox" id="s-flow-calibration" style="width:auto;margin:0">
<label id="lbl-flow-calibration" style="margin:0;cursor:pointer" for="s-flow-calibration">Flow Calibration</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 class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
<input type="checkbox" id="s-timelapse-local" style="width:auto;margin:0" onchange="_toggleTlInterval()">
<label id="lbl-timelapse-local" style="margin:0;cursor:pointer" for="s-timelapse-local">Lokales Timelapse während Druck</label>
</div>
<div class="modal-field" id="timelapse-interval-row" style="display:none">
<label id="lbl-timelapse-interval" style="font-size:12px;color:var(--txt2)">Aufnahme-Intervall (Sekunden)</label>
<input type="number" id="s-timelapse-interval" min="1" max="60" value="4" style="width:80px">
</div>
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px">
<input type="checkbox" id="s-timelapse-printer" style="width:auto;margin:0">
<label id="lbl-timelapse-printer" style="margin:0;cursor:pointer" for="s-timelapse-printer">Drucker-Timelapse aktivieren (experimentell)</label>
</div>
</div>
<div>
<div class="modal-section" id="modal-sec-layout">Layout</div>
<div class="modal-field" style="flex-direction:row;align-items:center;gap:10px;flex-wrap:wrap">
<label id="lbl-layout" style="margin:0;font-size:12px;color:var(--txt2)">Dashboard-Layout</label>
<select id="s-layout" style="width:auto;margin:0">
<option value="1col" id="opt-layout-1col">1 Spalte</option>
<option value="2col" id="opt-layout-2col">2 Spalten</option>
</select>
</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>
<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 &amp; Neustart</button>
</div>
</div>
<!-- ═══ AMS SLOT EDIT DIALOG ═══ -->
<div class="modal-overlay" id="slot-edit-modal" onclick="if(event.target===this)closeSlotEdit()">
@@ -82,42 +201,12 @@
<option value="" id="slot-profile-default-opt"></option>
</select>
<div style="font-size:11px;color:var(--txt2);margin-top:4px" id="slot-profile-hint"></div>
<a href="#" onclick="event.preventDefault();openProfileImport()"
style="display:inline-block;margin-top:6px;font-size:11px;color:var(--accent);text-decoration:none"
id="lbl-slot-profile-import">★ Eigene Profile importieren…</a>
</div>
<button class="btn" id="btn-slot-edit-feed" style="width:100%;margin-bottom:8px" onclick="slotEditFeed()"></button>
<button class="modal-save" id="btn-slot-edit-save" onclick="saveSlotEdit()"></button>
</div>
</div>
<!-- ═══ ORCA-PROFILE-IMPORT-DIALOG (Issue #41) ═══ -->
<div class="modal-overlay" id="profile-import-modal" onclick="if(event.target===this)closeProfileImport()">
<div class="modal-box" style="max-width:480px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
<div style="font-size:16px;font-weight:600" id="profile-import-title">Eigene OrcaSlicer-Profile importieren</div>
<button onclick="closeProfileImport()" style="background:none;border:none;color:var(--txt2);font-size:20px;cursor:pointer">×</button>
</div>
<div style="font-size:12px;color:var(--txt2);margin-bottom:12px;line-height:1.5" id="profile-import-help">
Lade ein <b>ZIP</b> deines OrcaSlicer-Filament-Ordners oder einzelne <b>.json</b>-Files hoch.<br>
In OrcaSlicer: <i>Help → Show Configuration Folder → user/&lt;id&gt;/filament/</i>
</div>
<div id="profile-import-drop" style="border:2px dashed var(--border);border-radius:8px;padding:24px;text-align:center;cursor:pointer;margin-bottom:12px"
ondragover="event.preventDefault();this.style.borderColor='var(--accent)'"
ondragleave="this.style.borderColor='var(--border)'"
ondrop="event.preventDefault();this.style.borderColor='var(--border)';doProfileImportUpload(event.dataTransfer.files)"
onclick="document.getElementById('profile-import-file').click()">
<div style="font-size:32px;margin-bottom:8px"></div>
<div style="font-size:13px;color:var(--txt2)" id="profile-import-dropmsg">Hierher ziehen oder klicken</div>
<input type="file" id="profile-import-file" accept=".zip,.json" multiple
style="display:none" onchange="doProfileImportUpload(this.files);this.value=''">
</div>
<div id="profile-import-status" style="font-size:12px;margin-bottom:12px;min-height:18px"></div>
<div style="font-size:11px;color:var(--txt2);margin-bottom:6px" id="profile-import-list-label">Aktuell importiert</div>
<div id="profile-import-list" style="max-height:240px;overflow-y:auto;font-size:12px"></div>
</div>
</div>
<div class="layout">
<nav class="sidebar">
<button class="nav-btn active" onclick="showPanel('dashboard')" id="nb-dashboard">
@@ -128,8 +217,6 @@
<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>
@@ -137,7 +224,7 @@
<div class="panel active" id="panel-dashboard">
<div class="grid">
<!-- Kamera -->
<div class="card" style="grid-column:1/-1">
<div class="card" id="d-cam-card" style="grid-column:1/-1">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
<div class="card-title" style="margin-bottom:0"><span>📷</span> <span id="d-card-cam">Kamera</span></div>
<div style="display:flex;align-items:center;gap:10px">
@@ -161,21 +248,15 @@
</div>
<!-- Fortschritt -->
<div class="card" style="grid-column:1/-1">
<div class="card" id="d-progress-card" style="grid-column:1/-1">
<div class="card-title"><span></span> <span id="d-card-progress">Fortschritt</span></div>
<img id="d-thumbnail" src="" alt="" style="display:none;width:100%;max-height:160px;object-fit:contain;border-radius:8px;background:#111;margin-bottom:10px">
<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 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 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>
</div>
<div class="time-grid">
@@ -198,16 +279,10 @@
<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 -->
<div class="card" style="grid-column:1/-1">
<div class="card" id="d-temps-card" style="grid-column:1/-1">
<div class="card-title"><span></span> <span id="d-card-temps">Temperaturen</span></div>
<div class="temp-card-inner">
<div class="temp-block">
@@ -357,8 +432,15 @@
<div class="card">
<div class="card-title" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
<span id="store-panel-title">🗂 Datei-Browser</span>
<button id="store-refresh-btn" onclick="loadStore()" style="font-size:12px;padding:4px 12px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt2);cursor:pointer">↻ Aktualisieren</button>
<div style="display:flex;gap:6px;align-items:center">
<button id="store-tab-files" class="store-tab-btn active" onclick="switchStoreTab('files')">📁 <span id="store-tab-files-label">Dateien</span></button>
<button id="store-tab-timelapses" class="store-tab-btn" onclick="switchStoreTab('timelapses')">🎬 <span id="store-tab-tl-label">Timelapses</span></button>
<button id="store-refresh-btn" onclick="refreshStoreTab()" style="font-size:12px;padding:4px 12px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt2);cursor:pointer"></button>
</div>
</div>
<!-- Dateien-Bereich -->
<div id="store-files-section">
<div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap">
<input id="store-search" type="text" placeholder="🔍 Suche…" oninput="renderStore()"
style="flex:1;min-width:140px;padding:6px 10px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);font-size:13px">
@@ -389,6 +471,12 @@
<div id="store-empty" style="display:none;color:var(--txt2);text-align:center;padding:40px 0;font-size:14px">
</div>
<div id="store-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px"></div>
</div><!-- /store-files-section -->
<!-- Timelapse-Bereich -->
<div id="store-timelapses-section" style="display:none">
<div id="timelapse-list"><div style="text-align:center;padding:40px;color:var(--txt2)" id="timelapse-empty-msg">Keine Timelapses vorhanden</div></div>
</div>
</div>
</div>
@@ -426,198 +514,6 @@
<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-integrations" onclick="showSettingsCat('integrations')"><span></span> <span id="setcat-lbl-integrations">Integrationen</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>
<!-- Integrationen -->
<div class="set-group" id="setgrp-integrations">
<!-- Spoolman -->
<div class="card">
<div class="card-title"><span>🧵</span> <span id="modal-sec-spoolman">Spoolman</span></div>
<div class="set-row">
<label id="lbl-spoolman-url">Server-URL</label>
<input type="text" id="s-spoolman-url" placeholder="http://spoolman:7912" style="width:200px">
</div>
<div class="set-row">
<label id="lbl-spoolman-sync-rate">Sync-Rate (s, 0=aus)</label>
<input type="number" id="s-spoolman-sync-rate" min="0" max="3600" value="30" style="width:80px">
</div>
<div id="spoolman-status-row" style="margin-top:6px;font-size:12px;color:var(--txt2)">
<span id="spoolman-status-dot"></span> <span id="spoolman-status-lbl"></span>
</div>
</div>
<!-- Obico -->
<div class="card" style="margin-top:10px">
<div class="card-title"><span>🕵</span> <span id="modal-sec-obico">Obico</span></div>
<div style="font-size:12px;color:var(--txt2);line-height:1.6" id="obico-info-box">
Obico wird über den <code>moonraker-obico</code>-Container konfiguriert.<br>
Config-Datei: <code id="obico-cfg-path">/mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg</code>
</div>
</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 &amp; Neustart</button>
</div>
</div>
</div>
</main>
</div>
@@ -626,7 +522,6 @@
<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>
@@ -656,30 +551,9 @@
<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">
<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>
<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>
</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>

View File

@@ -212,24 +212,6 @@ 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;
@@ -331,3 +313,25 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
.modal-box{padding:16px;border-radius:10px}
.poll-btns{gap:6px}
}
/* ── 2-Column Dashboard Layout ── */
#panel-dashboard.layout-2col .grid{grid-template-columns:1fr 1fr}
@media(max-width:768px){
#panel-dashboard.layout-2col .grid{grid-template-columns:1fr}
}
/* ── Store tabs ── */
.store-tab-btn{padding:6px 16px;border-radius:6px;border:1px solid var(--border);
background:var(--raised);color:var(--txt2);cursor:pointer;font-size:13px;font-weight:600;transition:.12s}
.store-tab-btn.active{background:var(--accent);border-color:var(--accent);color:#000}
/* ── Timelapse list ── */
.tl-row{display:flex;align-items:center;gap:12px;padding:10px;
border:1px solid var(--border);border-radius:8px;margin-bottom:8px}
.tl-icon{font-size:28px;color:var(--txt2);flex-shrink:0}
.tl-info{flex:1;min-width:0}
.tl-name{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tl-meta{font-size:11px;color:var(--txt2);margin-top:2px}
.tl-status-ok{color:var(--ok)}
.tl-status-err{color:var(--err)}
.tl-status-busy{color:var(--warn)}

View File

@@ -1,327 +1,248 @@
{
"ace_dry_auto_refill": "Auto-Nachschub",
"ace_dry_chart": "Verlauf (Temp/Feuchte)",
"ace_dry_current_temp": "Temperatur",
"ace_dry_dialog_cancel": "Abbrechen",
"ace_dry_dialog_confirm": "Bestätigen",
"ace_dry_dialog_custom_name": "Eigener Name",
"ace_dry_dialog_reset_default": "Auf Standard zurücksetzen",
"ace_dry_dialog_save_restart": "Speichern & Neustart",
"ace_dry_dialog_temp": "Temperatur (30-80°C)",
"ace_dry_dialog_time": "Restzeit (h:m:s)",
"ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen",
"header_status_standby": "Bereit",
"header_status_printing": "Druckt",
"header_status_complete": "Fertig",
"header_status_error": "Fehler",
"kobra_free": "Bereit",
"kobra_busy": "Beschäftigt",
"kobra_printing": "Druckt",
"kobra_preheating": "Aufheizen",
"kobra_auto_leveling": "Nivellierung",
"kobra_checking": "Prüfung",
"kobra_updated": "Aktualisierung",
"kobra_init": "Initialisierung",
"kobra_pausing": "Pausiert...",
"kobra_paused": "Pausiert",
"kobra_resuming": "Fortsetzen...",
"kobra_resumed": "Fortgesetzt",
"kobra_stopping": "Stoppt...",
"kobra_stoped": "Gestoppt",
"kobra_finished": "Abgeschlossen",
"kobra_failed": "Fehler",
"kobra_canceled": "Abgebrochen",
"kobra_offline": "Offline",
"nav_dashboard": "Dashboard",
"nav_print": "Druck",
"nav_temps": "Temperaturen",
"nav_motion": "Achsen",
"nav_ams": "AMS",
"nav_extras": "Licht / Lüfter",
"nav_console": "Konsole",
"card_progress": "Fortschritt",
"card_temps": "Temperaturen",
"card_light_fan": "Lüfter",
"card_speed": "Druckgeschwindigkeit",
"card_cam": "Kamera",
"lbl_elapsed": "Verstrichen:",
"lbl_remaining": "Restzeit:",
"lbl_slicer_time": "Slicer-Schätzung:",
"lbl_layers": "Layer",
"speed_silent": "🐢 Leise",
"speed_normal": "⚡ Normal",
"speed_sport": "🚀 Sport",
"lbl_light": "💡 Licht",
"lbl_feed": "Einziehen",
"lbl_unload": "Ausziehen",
"card_ace_dry": "ACE Trocknung",
"ace_dry_dryer": "Trockner",
"ace_dry_duration": "Dauer (Min)",
"ace_dry_enable": "Trocknung aktivieren",
"ace_dry_humidity": "Luftfeuchte",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "Custom",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ Start",
"ace_dry_status_off": "Status: Aus",
"ace_dry_status_on": "Status: Aktiv",
"ace_dry_status_remaining": "Rest",
"ace_dry_stop": "■ Stop",
"ace_dry_humidity": "Luftfeuchte",
"ace_dry_current_temp": "Temperatur",
"ace_dry_chart": "Verlauf (Temp/Feuchte)",
"ace_dry_temp": "Temperatur (°C)",
"ace_dry_duration": "Dauer (Min)",
"ace_dry_start": "▶ Start",
"ace_dry_stop": "■ Stop",
"ace_dry_auto_refill": "Auto-Nachschub",
"ace_dry_enable": "Trocknung aktivieren",
"ace_dry_temp_line": "Trocknungstemperatur",
"ace_dry_time_line": "Trocknungszeit",
"ace_dry_ui_pending": "(nur UI, Backend folgt)",
"add_printer": "Drucker hinzufügen",
"ams_empty": "Leer",
"ams_no_data": "Keine AMS-Daten empfangen",
"apd_cancel": "Abbrechen",
"apd_confirm": "Hinzufügen",
"apd_err_ip": "Bitte IP-Adresse eingeben",
"apd_fetching": "Hole Daten vom Drucker…",
"apd_lbl_ip": "Drucker-IP",
"apd_lbl_name": "Name (optional)",
"apd_placeholder_name": "z.B. Kobra X Wohnzimmer",
"apd_success": "Drucker hinzugefügt, Bridge startet neu…",
"apd_title": "Drucker hinzufügen",
"btn_cam_start": "▶ Kamera",
"btn_cam_start2": "▶ Start",
"btn_cam_stop": "◼ Kamera",
"btn_cam_stop2": "◼ Stop",
"btn_cancel": "✕ Stopp",
"btn_cancel_generic": "Abbrechen",
"btn_confirm_generic": "Bestätigen",
"btn_connect": "⚡ Verbinden",
"btn_delete": "Löschen",
"btn_disable_motors": "Motoren aus",
"btn_disconnect": "✕ Trennen",
"btn_home_all": "Home All",
"btn_home_xy": "Home XY",
"btn_home_z": "Home Z",
"btn_pause": "⏸ Pause",
"btn_resume": "▶ Weiter",
"ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen",
"ace_dry_dialog_temp": "Temperatur (30-80°C)",
"ace_dry_dialog_time": "Restzeit (h:m:s)",
"ace_dry_dialog_confirm": "Bestätigen",
"ace_dry_dialog_cancel": "Abbrechen",
"ace_dry_dialog_save_restart": "Speichern & Neustart",
"ace_dry_dialog_custom_name": "Eigener Name",
"ace_dry_dialog_reset_default": "Auf Standard zurücksetzen",
"cam_placeholder": "📷 Kamera nicht gestartet",
"cam_stream_unavailable": "Stream nicht verfügbar",
"card_ace_dry": "ACE Trocknung",
"card_ams": "Filament",
"card_cam": "Kamera",
"card_light_fan": "Lüfter",
"card_progress": "Fortschritt",
"card_speed": "Druckgeschwindigkeit",
"card_temps": "Temperaturen",
"confirm_cancel": "Druck wirklich abbrechen?",
"fd_cancel": "Abbrechen",
"fd_no_matching_material": "Kein passendes Material",
"fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?",
"fd_objects_hint": "Objekte überspringen (optional):",
"fd_objects_toggle": "Objekte überspringen",
"fd_options_title": "Optionen",
"fd_print": "▶ Drucken",
"fd_slot": "Slot",
"fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:",
"fd_title": "Slot-Zuweisung",
"fd_used": "BELEGT",
"file_cancel_btn": "✕ Abbrechen",
"file_ready_btn": "▶ Druck starten",
"file_slots_btn": "🎨 Slots wählen",
"header_status_complete": "Fertig",
"header_status_error": "Fehler",
"header_status_printing": "Druckt",
"header_status_standby": "Bereit",
"hint_ip_no_port": "Nur IP-Adresse, kein Port (z.B. 192.168.1.102)",
"kobra_auto_leveling": "Nivellierung",
"kobra_busy": "Beschäftigt",
"kobra_canceled": "Abgebrochen",
"kobra_checking": "Prüfung",
"kobra_failed": "Fehler",
"kobra_finished": "Abgeschlossen",
"kobra_free": "Bereit",
"kobra_init": "Initialisierung",
"kobra_offline": "Offline",
"kobra_paused": "Pausiert",
"kobra_pausing": "Pausiert...",
"kobra_preheating": "Aufheizen",
"kobra_printing": "Druckt",
"kobra_resumed": "Fortgesetzt",
"kobra_resuming": "Fortsetzen...",
"kobra_stoped": "Gestoppt",
"kobra_stopping": "Stoppt...",
"kobra_updated": "Aktualisierung",
"btn_cam_start": "▶ Kamera",
"btn_cam_stop": "◼ Kamera",
"btn_pause": "⏸ Pause",
"btn_resume": "▶ Weiter",
"btn_cancel": "✕ Stopp",
"label_nozzle": "Düse",
"label_bed": "Bett",
"label_fan": "🌀 Lüfter",
"label_light": "💡 Licht",
"label_nozzle": "Düse",
"label_off": "Aus",
"label_on_off": "Ein / Aus",
"label_set": "Setzen",
"label_slot": "Slot",
"label_speed": "Geschwindigkeit",
"label_step": "Schrittweite:",
"label_target_c": "Ziel:",
"lbl_conn_error": "Verbindungsfehler:",
"lbl_elapsed": "Verstrichen:",
"lbl_feed": "Einziehen",
"lbl_layers": "Layer",
"lbl_light": "💡 Licht",
"lbl_remaining": "Restzeit:",
"lbl_slicer_time": "Slicer-Schätzung:",
"lbl_spoolman_sync_rate": "Sync-Rate (s, 0=aus)",
"lbl_spoolman_url": "Server-URL",
"lbl_unload": "Ausziehen",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ Auto",
"log_axis": "Achse",
"log_bed": "Bett →",
"log_cam_start": "Kamera gestartet:",
"log_cam_stop": "Kamera gestoppt",
"log_clear": "✕ Leeren",
"log_delete_failed": "Löschung fehlgeschlagen",
"log_dir_all": "Alle",
"log_dir_label": "Richtung:",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ Download",
"log_error": "Fehler:",
"log_fan": "Lüfter →",
"log_filter_placeholder": "Filtern…",
"log_home": "Home",
"log_home_all": "Home All",
"log_light_off": "Licht aus",
"log_light_on": "Licht an",
"log_lvl_err": "⛔ Fehler",
"log_lvl_label": "Level:",
"log_lvl_warn": "⚠ Warnung",
"log_nozzle": "Düse →",
"log_poll_error": "Poll-Fehler:",
"log_print_action": "Druck:",
"log_print_start": "Druckstart:",
"log_topic_ams": "AMS",
"log_topic_info": "Info",
"log_topic_label": "Thema:",
"log_topic_print": "Druck",
"log_topic_status": "Status",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "Browser",
"nav_console": "Konsole",
"nav_dashboard": "Dashboard",
"nav_extras": "Licht / Lüfter",
"nav_motion": "Achsen",
"nav_print": "Druck",
"nav_printers": "Drucker",
"nav_settings": "Einstellungen",
"nav_temps": "Temperaturen",
"orca_profile_done": "Importiert",
"orca_profile_dropmsg": "Hierher ziehen oder klicken",
"orca_profile_help_html": "Lade ein <b>ZIP</b> deines OrcaSlicer-Filament-Ordners oder einzelne <b>.json</b>-Files hoch.<br>In OrcaSlicer: <i>Help → Show Configuration Folder → user/&lt;id&gt;/filament/</i>",
"orca_profile_hint": "Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show Configuration Folder)",
"orca_profile_import_btn": "Profile importieren",
"orca_profile_import_link": "★ Eigene Profile importieren…",
"orca_profile_import_title": "Eigene OrcaSlicer-Profile importieren",
"orca_profile_list_label": "Aktuell importiert",
"orca_profile_section": "OrcaSlicer-Profile",
"orca_profile_skipped": "übersprungen",
"orca_profile_uploading": "Lade hoch…",
"orca_profile_user_empty": " keine ",
"orca_profile_user_label": "Eigene Profile",
"panel_ams_title": "Filament",
"panel_browser_title": "Datei-Browser",
"panel_console_title": "Ereignis-Log",
"panel_extras_camera": "Kamera",
"panel_extras_fan": "Lüfter",
"panel_extras_light": "Licht",
"panel_motion_xy": "XY-Achsen",
"panel_motion_z": "Z-Achse",
"panel_print_btn_cancel": "✕ Abbrechen",
"panel_print_title": "Drucksteuerung",
"panel_print_btn_pause": "⏸ Pause",
"panel_print_btn_resume": "▶ Fortsetzen",
"panel_print_btn_cancel": "✕ Abbrechen",
"panel_print_temps_live": "Temperaturen (Live)",
"panel_print_title": "Drucksteuerung",
"label_set": "Setzen",
"label_off": "Aus",
"panel_temps_nozzle": "Düse",
"panel_temps_bed": "Heizbett",
"panel_temps_chart": "Verlauf (letzte 60 Messungen)",
"panel_temps_nozzle": "Düse",
"print_auto_leveling": "Auto-Leveling für diesen Druck",
"printers_active": "● aktiv",
"printers_current": "Aktueller Drucker",
"printers_empty_hint": "Noch kein Drucker eingerichtet.",
"printers_loading": "Lade…",
"printers_none": "Keine Drucker konfiguriert.",
"printers_remove": "Drucker entfernen",
"printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.",
"printers_switch": "Wechseln →",
"progress_action_clear": "Leeren",
"progress_action_print": "Drucken",
"progress_action_slots": "Slots zuordnen",
"settings_auto_leveling": "Auto-Leveling vor Druck",
"settings_auto_leveling_label": "Auto-Leveling vor dem Druck",
"settings_btn_tooltip": "Einstellungen",
"settings_camera_on_print": "Kamera bei Druckstart einschalten",
"settings_cat_connection": "Verbindung",
"settings_cat_display": "Darstellung",
"settings_cat_filament": "Filament",
"settings_cat_language": "Sprache",
"settings_cat_printer": "Drucker",
"settings_cat_system": "System",
"settings_cat_theme": "Hell / Dunkel umschalten",
"settings_connection": "Verbindung",
"settings_default_slot": "Standard-Slot (Einfarbdruck)",
"settings_device_id": "Device-ID",
"settings_device_id_hint": "32 Hexzeichen",
"settings_device_id_placeholder": "32 Hexzeichen",
"settings_filament_mapping": "Filament-Profil-Mapping (pro Slot)",
"settings_filament_mapping_hint": "Festes Orca-Profil pro AMS-Slot. Bei der Slicer-Synchronisierung sendet die Bridge dieses Profil statt \"Generic\".",
"settings_filament_mapping_label": "Filament-Profil-Mapping (pro Slot)",
"settings_filament_mapping_save": "Mapping speichern",
"settings_filament_mapping_save_label": "Mapping speichern",
"settings_file_ready_banner": "Druckleiste",
"settings_file_ready_dialog": "Druckdialog",
"settings_file_ready_mode": "Nach Upload: Druckstart-Verhalten",
"settings_integrations": "Integrationen",
"settings_language": "Sprache",
"settings_mode_id": "Mode-ID",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "MQTT-Port",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "Profile importieren",
"settings_orca_profiles_label": "OrcaSlicer-Profile",
"settings_password": "MQTT-Passwort",
"settings_poll": "Poll-Intervall (Sekunden)",
"settings_poll_interval_hint": "Wie oft die Bridge den Druckerstatus abfragt",
"settings_poll_interval_label": "Poll-Intervall (Sekunden)",
"settings_print": "Druckeinstellungen",
"settings_printer_ip": "Drucker-IP",
"settings_printer_name": "Drucker-Name",
"settings_printer_name_placeholder": "z.B. Kobra X Links",
"settings_save": "Speichern & Neustart",
"settings_slot_auto": "Auto (alle belegten Slots)",
"settings_theme_toggle": "Wechsel Hell / Dunkel",
"label_target_c": "Ziel:",
"panel_motion_xy": "XY-Achsen",
"panel_motion_z": "Z-Achse",
"label_step": "Schrittweite:",
"btn_home_z": "Home Z",
"btn_home_xy": "Home XY",
"btn_home_all": "Home All",
"btn_disable_motors": "Motoren aus",
"panel_ams_title": "Filament",
"card_ams": "Filament",
"ams_no_data": "Keine AMS-Daten empfangen",
"label_slot": "Slot",
"ams_empty": "Leer",
"panel_extras_light": "Licht",
"panel_extras_fan": "Lüfter",
"panel_extras_camera": "Kamera",
"btn_cam_start2": "▶ Start",
"btn_cam_stop2": "◼ Stop",
"panel_console_title": "Ereignis-Log",
"log_light_on": "Licht an",
"log_light_off": "Licht aus",
"log_fan": "Lüfter →",
"log_nozzle": "Düse →",
"log_bed": "Bett →",
"log_axis": "Achse",
"log_home": "Home",
"log_home_all": "Home All",
"log_cam_start": "Kamera gestartet:",
"log_cam_stop": "Kamera gestoppt",
"log_poll_error": "Poll-Fehler:",
"log_error": "Fehler:",
"confirm_cancel": "Druck wirklich abbrechen?",
"settings_title": "Einstellungen",
"settings_username": "MQTT-Benutzername",
"settings_vendor_filter_placeholder": "Hersteller suchen",
"settings_connection": "Verbindung",
"settings_print": "Druckeinstellungen",
"settings_poll": "Poll-Intervall",
"settings_version": "Version",
"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_label": "Sichtbare Hersteller (Profil-Dropdown)",
"settings_visible_vendors_save": "Auswahl speichern",
"settings_visible_vendors_save_label": "Auswahl speichern",
"settings_save": "Speichern & Neustart",
"settings_printer_name": "Drucker-Name",
"settings_printer_ip": "Drucker-IP",
"settings_mqtt_port": "MQTT-Port",
"settings_username": "MQTT-Benutzername",
"settings_password": "MQTT-Passwort",
"settings_device_id": "Device-ID",
"settings_mode_id": "Mode-ID",
"hint_ip_no_port": "Nur IP-Adresse, kein Port (z.B. 192.168.1.102)",
"settings_default_slot": "Standard-Slot (Einfarbdruck)",
"settings_slot_auto": "Auto (alle belegten Slots)",
"settings_auto_leveling": "Auto-Leveling vor Druck",
"settings_vibration_compensation": "Resonanzkompensation vor Druck",
"settings_flow_calibration": "Flow-Kalibrierung vor Druck",
"settings_camera_on_print": "Kamera bei Druckstart einschalten",
"settings_web_upload_warning": "Warnung bei Web-Upload-Druck anzeigen",
"sf_all": "Alle",
"sf_err": "✗ Fehler",
"sf_new": "Neu",
"sf_ok": "✓ Erfolgreich",
"skip_already": "übersprungen",
"skip_btn_label": "Objekte",
"skip_cancel": "Abbrechen",
"skip_confirm": "Überspringen",
"skip_confirm_btn": "Überspringen",
"settings_timelapse_local": "Lokales Timelapse während Druck",
"settings_timelapse_interval": "Aufnahme-Intervall (Sekunden)",
"settings_timelapse_printer": "Drucker-Timelapse aktivieren (experimentell)",
"settings_layout": "Dashboard-Layout",
"settings_layout_1col": "1 Spalte",
"settings_layout_2col": "2 Spalten",
"store_tab_files": "Dateien",
"timelapse_tab": "Timelapses",
"timelapse_empty": "Noch keine Timelapses vorhanden",
"timelapse_recording": "Aufnahme läuft…",
"timelapse_processing": "Kodierung läuft…",
"confirm_delete_timelapse": "Dieses Timelapse löschen?",
"update_check": "Auf Updates prüfen",
"update_checking": "Prüfe...",
"update_available": "verfügbar",
"update_none": "Bereits aktuell",
"update_apply": "Jetzt installieren",
"update_applying": "Lade herunter...",
"update_restarting": "Starte neu...",
"update_error": "Fehler",
"btn_connect": "⚡ Verbinden",
"btn_disconnect": "✕ Trennen",
"lbl_conn_error": "Verbindungsfehler:",
"slot_edit_title": "Slot bearbeiten",
"slot_edit_color": "Farbe",
"slot_edit_material": "Material",
"slot_edit_load": "⬇ Einziehen",
"slot_edit_unload": "⬆ Ausziehen",
"slot_edit_save": "💾 Speichern",
"slot_edit_custom": "z.B. PLA, PETG, ABS…",
"slot_edit_ok": "AMS Slot",
"slot_edit_profile": "OrcaSlicer-Profil",
"slot_edit_profile_hint": "Sendet beim OrcaSlicer-Sync die konkrete Marke statt nur „Generic\"",
"slot_edit_profile_default": "— Generic (Default) —",
"log_dir_all": "Alle",
"log_lvl_label": "Level:",
"file_ready_btn": "▶ Druck starten",
"file_slots_btn": "🎨 Slots wählen",
"file_cancel_btn": "✕ Abbrechen",
"nav_printers": "Drucker",
"skip_title": "✂ Objekte überspringen",
"skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:",
"skip_btn_label": "Objekte",
"skip_no_objects": "Keine Objekte in diesem Druck.",
"skip_already": "übersprungen",
"skip_select_at_least_one": "Bitte mindestens ein Objekt wählen.",
"skip_sending": "Sende …",
"skip_success": "Objekte werden übersprungen.",
"skip_title": "Objekte überspringen",
"slot_edit_color": "Farbe",
"slot_edit_custom": "z.B. PLA, PETG, ABS…",
"slot_edit_load": "⬇ Einziehen",
"slot_edit_material": "Material",
"slot_edit_ok": "AMS Slot",
"slot_edit_profile": "OrcaSlicer-Profil",
"slot_edit_profile_default": "— Generic (Default) —",
"slot_edit_profile_hint": "Sendet beim OrcaSlicer-Sync die konkrete Marke statt nur „Generic\"",
"slot_edit_save": "💾 Speichern",
"slot_edit_title": "Slot bearbeiten",
"slot_edit_unload": "⬆ Ausziehen",
"speed_normal": "⚡ Normal",
"speed_silent": "🐢 Leise",
"speed_sport": "🚀 Sport",
"ss_date": "↓ Datum",
"ss_dur": "⏱ Druckzeit",
"ss_name": "AZ Name",
"store_delete_confirm": "Datei löschen?",
"store_download": "⬇ Download",
"store_empty": "Noch keine Dateien hochgeladen.",
"store_estimate": "Schätzung",
"store_never": "noch nicht gedruckt",
"store_no_results": "Keine Dateien gefunden.",
"store_print": "▶ Drucken",
"store_print_confirm": "Datei drucken?",
"store_refresh": "↻ Aktualisieren",
"fd_objects_hint": "Objekte überspringen (optional):",
"fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:",
"fd_cancel": "Abbrechen",
"fd_print": "▶ Drucken",
"fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?",
"fd_slot": "Slot",
"fd_no_matching_material": "Kein passendes Material",
"fd_used": "BELEGT",
"add_printer": "Drucker hinzufügen",
"apd_lbl_ip": "Drucker-IP",
"apd_lbl_name": "Name (optional)",
"apd_placeholder_name": "z.B. Kobra X Wohnzimmer",
"apd_cancel": "Abbrechen",
"apd_confirm": "Hinzufügen",
"apd_fetching": "Hole Daten vom Drucker…",
"apd_success": "Drucker hinzugefügt, Bridge startet neu…",
"apd_err_ip": "Bitte IP-Adresse eingeben",
"printers_remove": "Drucker entfernen",
"printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.",
"printers_active": "● aktiv",
"printers_switch": "Wechseln →",
"printers_current": "Aktueller Drucker",
"printers_loading": "Lade…",
"printers_none": "Keine Drucker konfiguriert.",
"printers_empty_hint": "Noch kein Drucker eingerichtet.",
"nav_browser": "Browser",
"panel_browser_title": "Datei-Browser",
"store_search_placeholder": "🔍 Suche…",
"store_upload_busy": "⏳ Hochladen",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "durchsuchen",
"store_upload_label_prefix": "GCode hierher ziehen oder ",
"store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "Abbrechen",
"store_web_verify_confirm": "Bestätigen",
"store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.",
"store_empty": "Noch keine Dateien hochgeladen.",
"store_refresh": "↻ Aktualisieren",
"store_print": "▶ Drucken",
"store_download": "⬇ Download",
"store_delete_confirm": "Datei löschen?",
"store_print_confirm": "Datei drucken?",
"store_web_verify_title": "Datei verifizieren",
"update_apply": "Jetzt installieren",
"update_applying": "Lade herunter...",
"update_available": "verfügbar",
"update_check": "Auf Updates prüfen",
"update_checking": "Prüfe...",
"update_error": "Fehler",
"update_none": "Bereits aktuell",
"update_restarting": "Starte neu..."
"store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.",
"store_web_verify_confirm": "Bestätigen",
"store_web_verify_abort": "Abbrechen",
"store_no_results": "Keine Dateien gefunden.",
"store_never": "noch nicht gedruckt",
"store_estimate": "Schätzung",
"store_upload_label_prefix": "GCode hierher ziehen oder ",
"store_upload_label_browse": "durchsuchen",
"store_upload_busy": "⏳ Hochladen…",
"store_upload_success": "✓ {file}",
"store_upload_error": "✗ {error}",
"sf_all": "Alle",
"sf_ok": "✓ Erfolgreich",
"sf_err": "✗ Fehler",
"sf_new": "Neu",
"ss_date": "↓ Datum",
"ss_name": "AZ Name",
"ss_dur": "⏱ Druckzeit"
}

View File

@@ -1,327 +1,248 @@
{
"ace_dry_auto_refill": "Auto Refill",
"ace_dry_chart": "History (Temp/Humidity)",
"ace_dry_current_temp": "Temperature",
"ace_dry_dialog_cancel": "Cancel",
"ace_dry_dialog_confirm": "Confirm",
"ace_dry_dialog_custom_name": "Custom Name",
"ace_dry_dialog_reset_default": "Reset to Default",
"ace_dry_dialog_save_restart": "Save & Restart",
"ace_dry_dialog_temp": "Temperature (30-80°C)",
"ace_dry_dialog_time": "Rem. Time (h:m:s)",
"ace_dry_dialog_title": "Dryer Temp/Time Settings",
"header_status_standby": "Ready",
"header_status_printing": "Printing",
"header_status_complete": "Complete",
"header_status_error": "Error",
"kobra_free": "Ready",
"kobra_busy": "Busy",
"kobra_printing": "Printing",
"kobra_preheating": "Preheating",
"kobra_auto_leveling": "Auto Leveling",
"kobra_checking": "Checking",
"kobra_updated": "Updating",
"kobra_init": "Initializing",
"kobra_pausing": "Pausing...",
"kobra_paused": "Paused",
"kobra_resuming": "Resuming...",
"kobra_resumed": "Resumed",
"kobra_stopping": "Stopping...",
"kobra_stoped": "Stopped",
"kobra_finished": "Finished",
"kobra_failed": "Error",
"kobra_canceled": "Cancelled",
"kobra_offline": "Offline",
"nav_dashboard": "Dashboard",
"nav_print": "Print",
"nav_temps": "Temperatures",
"nav_motion": "Motion",
"nav_ams": "AMS",
"nav_extras": "Light / Fan",
"nav_console": "Console",
"card_progress": "Progress",
"card_temps": "Temperatures",
"card_light_fan": "Fan",
"card_speed": "Print Speed",
"card_cam": "Camera",
"lbl_elapsed": "Elapsed:",
"lbl_remaining": "Remaining:",
"lbl_slicer_time": "Slicer estimate:",
"lbl_layers": "Layer",
"speed_silent": "🐢 Silent",
"speed_normal": "⚡ Normal",
"speed_sport": "🚀 Sport",
"lbl_light": "💡 Light",
"lbl_feed": "Load",
"lbl_unload": "Unload",
"card_ace_dry": "ACE Drying",
"ace_dry_dryer": "Dryer",
"ace_dry_duration": "Duration (min)",
"ace_dry_enable": "Enable Drying",
"ace_dry_humidity": "Humidity",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "Custom",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ Start",
"ace_dry_status_off": "Status: Off",
"ace_dry_status_on": "Status: Active",
"ace_dry_status_remaining": "Remaining",
"ace_dry_stop": "■ Stop",
"ace_dry_humidity": "Humidity",
"ace_dry_current_temp": "Temperature",
"ace_dry_chart": "History (Temp/Humidity)",
"ace_dry_temp": "Temperature (°C)",
"ace_dry_duration": "Duration (min)",
"ace_dry_start": "▶ Start",
"ace_dry_stop": "■ Stop",
"ace_dry_auto_refill": "Auto Refill",
"ace_dry_enable": "Enable Drying",
"ace_dry_temp_line": "Drying Temperature",
"ace_dry_time_line": "Drying Time",
"ace_dry_ui_pending": "(UI only, backend next)",
"add_printer": "Add printer",
"ams_empty": "Empty",
"ams_no_data": "No AMS data received",
"apd_cancel": "Cancel",
"apd_confirm": "Add",
"apd_err_ip": "Please enter an IP address",
"apd_fetching": "Fetching data from printer…",
"apd_lbl_ip": "Printer IP",
"apd_lbl_name": "Name (optional)",
"apd_placeholder_name": "e.g. Kobra X Living Room",
"apd_success": "Printer added, bridge restarting…",
"apd_title": "Add printer",
"btn_cam_start": "▶ Camera",
"btn_cam_start2": "▶ Start",
"btn_cam_stop": "◼ Camera",
"btn_cam_stop2": "◼ Stop",
"btn_cancel": "✕ Stop",
"btn_cancel_generic": "Cancel",
"btn_confirm_generic": "Confirm",
"btn_connect": "⚡ Connect",
"btn_delete": "Delete",
"btn_disable_motors": "Motors Off",
"btn_disconnect": "✕ Disconnect",
"btn_home_all": "Home All",
"btn_home_xy": "Home XY",
"btn_home_z": "Home Z",
"btn_pause": "⏸ Pause",
"btn_resume": "▶ Resume",
"ace_dry_dialog_title": "Dryer Temp/Time Settings",
"ace_dry_dialog_temp": "Temperature (30-80°C)",
"ace_dry_dialog_time": "Rem. Time (h:m:s)",
"ace_dry_dialog_confirm": "Confirm",
"ace_dry_dialog_cancel": "Cancel",
"ace_dry_dialog_save_restart": "Save & Restart",
"ace_dry_dialog_custom_name": "Custom Name",
"ace_dry_dialog_reset_default": "Reset to Default",
"cam_placeholder": "📷 Camera not started",
"cam_stream_unavailable": "Stream unavailable",
"card_ace_dry": "ACE Drying",
"card_ams": "Filament",
"card_cam": "Camera",
"card_light_fan": "Fan",
"card_progress": "Progress",
"card_speed": "Print Speed",
"card_temps": "Temperatures",
"confirm_cancel": "Really cancel the print?",
"fd_cancel": "Cancel",
"fd_no_matching_material": "No matching material",
"fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?",
"fd_objects_hint": "Skip objects (optional):",
"fd_objects_toggle": "Skip objects",
"fd_options_title": "Print Options",
"fd_print": "▶ Print",
"fd_slot": "Slot",
"fd_slots_hint": "Assign GCode channel to AMS slot:",
"fd_title": "Slot Assignment",
"fd_used": "USED",
"file_cancel_btn": "✕ Cancel",
"file_ready_btn": "▶ Start Print",
"file_slots_btn": "🎨 Select Slots",
"header_status_complete": "Complete",
"header_status_error": "Error",
"header_status_printing": "Printing",
"header_status_standby": "Ready",
"hint_ip_no_port": "IP address only, no port (e.g. 192.168.1.102)",
"kobra_auto_leveling": "Auto Leveling",
"kobra_busy": "Busy",
"kobra_canceled": "Cancelled",
"kobra_checking": "Checking",
"kobra_failed": "Error",
"kobra_finished": "Finished",
"kobra_free": "Ready",
"kobra_init": "Initializing",
"kobra_offline": "Offline",
"kobra_paused": "Paused",
"kobra_pausing": "Pausing...",
"kobra_preheating": "Preheating",
"kobra_printing": "Printing",
"kobra_resumed": "Resumed",
"kobra_resuming": "Resuming...",
"kobra_stoped": "Stopped",
"kobra_stopping": "Stopping...",
"kobra_updated": "Updating",
"btn_cam_start": "▶ Camera",
"btn_cam_stop": "◼ Camera",
"btn_pause": "⏸ Pause",
"btn_resume": "▶ Resume",
"btn_cancel": "✕ Stop",
"label_nozzle": "Nozzle",
"label_bed": "Bed",
"label_fan": "🌀 Fan",
"label_light": "💡 Light",
"label_nozzle": "Nozzle",
"label_off": "Off",
"label_on_off": "On / Off",
"label_set": "Set",
"label_slot": "Slot",
"label_speed": "Speed",
"label_step": "Step size:",
"label_target_c": "Target:",
"lbl_conn_error": "Connection error:",
"lbl_elapsed": "Elapsed:",
"lbl_feed": "Load",
"lbl_layers": "Layer",
"lbl_light": "💡 Light",
"lbl_remaining": "Remaining:",
"lbl_slicer_time": "Slicer estimate:",
"lbl_spoolman_sync_rate": "Sync rate (s, 0=off)",
"lbl_spoolman_url": "Server URL",
"lbl_unload": "Unload",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ Auto",
"log_axis": "Axis",
"log_bed": "Bed →",
"log_cam_start": "Camera started:",
"log_cam_stop": "Camera stopped",
"log_clear": "✕ Clear",
"log_delete_failed": "Delete failed",
"log_dir_all": "All",
"log_dir_label": "Dir:",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ Download",
"log_error": "Error:",
"log_fan": "Fan →",
"log_filter_placeholder": "Filter…",
"log_home": "Home",
"log_home_all": "Home All",
"log_light_off": "Light off",
"log_light_on": "Light on",
"log_lvl_err": "⛔ Errors",
"log_lvl_label": "Level:",
"log_lvl_warn": "⚠ Warn",
"log_nozzle": "Nozzle →",
"log_poll_error": "Poll error:",
"log_print_action": "Print:",
"log_print_start": "Print start:",
"log_topic_ams": "AMS",
"log_topic_info": "Info",
"log_topic_label": "Topic:",
"log_topic_print": "Print",
"log_topic_status": "Status",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "Browser",
"nav_console": "Console",
"nav_dashboard": "Dashboard",
"nav_extras": "Light / Fan",
"nav_motion": "Motion",
"nav_print": "Print",
"nav_printers": "Printers",
"nav_settings": "Settings",
"nav_temps": "Temperatures",
"orca_profile_done": "Imported",
"orca_profile_dropmsg": "Drop here or click",
"orca_profile_help_html": "Upload a <b>ZIP</b> of your OrcaSlicer filament folder or single <b>.json</b> files.<br>In OrcaSlicer: <i>Help → Show Configuration Folder → user/&lt;id&gt;/filament/</i>",
"orca_profile_hint": "Import your own OrcaSlicer filament profiles (open the user dir via Help → Show Configuration Folder)",
"orca_profile_import_btn": "Import profiles",
"orca_profile_import_link": "★ Import own profiles…",
"orca_profile_import_title": "Import your OrcaSlicer profiles",
"orca_profile_list_label": "Currently imported",
"orca_profile_section": "OrcaSlicer Profiles",
"orca_profile_skipped": "skipped",
"orca_profile_uploading": "Uploading…",
"orca_profile_user_empty": " none ",
"orca_profile_user_label": "Own profiles",
"panel_ams_title": "Filament",
"panel_browser_title": "File Browser",
"panel_console_title": "Event Log",
"panel_extras_camera": "Camera",
"panel_extras_fan": "Fan",
"panel_extras_light": "Light",
"panel_motion_xy": "XY Axes",
"panel_motion_z": "Z Axis",
"panel_print_btn_cancel": "✕ Cancel",
"panel_print_title": "Print Control",
"panel_print_btn_pause": "⏸ Pause",
"panel_print_btn_resume": "▶ Resume",
"panel_print_btn_cancel": "✕ Cancel",
"panel_print_temps_live": "Temperatures (Live)",
"panel_print_title": "Print Control",
"label_set": "Set",
"label_off": "Off",
"panel_temps_nozzle": "Nozzle",
"panel_temps_bed": "Heated Bed",
"panel_temps_chart": "History (last 60 readings)",
"panel_temps_nozzle": "Nozzle",
"print_auto_leveling": "Auto-Leveling",
"printers_active": "● active",
"printers_current": "Current printer",
"printers_empty_hint": "No printer set up yet.",
"printers_loading": "Loading…",
"printers_none": "No printers configured.",
"printers_remove": "Remove printer",
"printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.",
"printers_switch": "Switch →",
"progress_action_clear": "Clear",
"progress_action_print": "Print",
"progress_action_slots": "Map slots",
"settings_auto_leveling": "Auto-Leveling Default",
"settings_auto_leveling_label": "Auto-Leveling before print",
"settings_btn_tooltip": "Settings",
"settings_camera_on_print": "Turn camera on at print start",
"settings_cat_connection": "Connection",
"settings_cat_display": "Appearance",
"settings_cat_filament": "Filament",
"settings_cat_language": "Language",
"settings_cat_printer": "Printer",
"settings_cat_system": "System",
"settings_cat_theme": "Toggle light / dark",
"settings_connection": "Connection",
"settings_default_slot": "Default Slot (single color)",
"settings_device_id": "Device ID",
"settings_device_id_hint": "32 hex characters",
"settings_device_id_placeholder": "32 hex characters",
"settings_filament_mapping": "Filament profile mapping (per slot)",
"settings_filament_mapping_hint": "Fixed Orca profile per AMS slot. On slicer sync, the bridge sends this profile instead of \"Generic\".",
"settings_filament_mapping_label": "Filament profile mapping (per slot)",
"settings_filament_mapping_save": "Save mapping",
"settings_filament_mapping_save_label": "Save mapping",
"settings_file_ready_banner": "Print bar",
"settings_file_ready_dialog": "Print dialog",
"settings_file_ready_mode": "After upload: Start print behavior",
"settings_integrations": "Integrations",
"settings_language": "Language",
"settings_mode_id": "Mode ID",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "MQTT Port",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "Import profiles",
"settings_orca_profiles_label": "OrcaSlicer Profiles",
"settings_password": "MQTT Password",
"settings_poll": "Poll Interval (seconds)",
"settings_poll_interval_hint": "How often the bridge queries printer status",
"settings_poll_interval_label": "Poll Interval (seconds)",
"settings_print": "Print Settings",
"settings_printer_ip": "Printer IP",
"settings_printer_name": "Printer Name",
"settings_printer_name_placeholder": "e.g. Kobra X Left",
"settings_save": "Save & Restart",
"settings_slot_auto": "Auto (all loaded slots)",
"settings_theme_toggle": "Toggle light / dark",
"label_target_c": "Target:",
"panel_motion_xy": "XY Axes",
"panel_motion_z": "Z Axis",
"label_step": "Step size:",
"btn_home_z": "Home Z",
"btn_home_xy": "Home XY",
"btn_home_all": "Home All",
"btn_disable_motors": "Motors Off",
"panel_ams_title": "Filament",
"card_ams": "Filament",
"ams_no_data": "No AMS data received",
"label_slot": "Slot",
"ams_empty": "Empty",
"panel_extras_light": "Light",
"panel_extras_fan": "Fan",
"panel_extras_camera": "Camera",
"btn_cam_start2": "▶ Start",
"btn_cam_stop2": "◼ Stop",
"panel_console_title": "Event Log",
"log_light_on": "Light on",
"log_light_off": "Light off",
"log_fan": "Fan →",
"log_nozzle": "Nozzle →",
"log_bed": "Bed →",
"log_axis": "Axis",
"log_home": "Home",
"log_home_all": "Home All",
"log_cam_start": "Camera started:",
"log_cam_stop": "Camera stopped",
"log_poll_error": "Poll error:",
"log_error": "Error:",
"confirm_cancel": "Really cancel the print?",
"settings_title": "Settings",
"settings_username": "MQTT Username",
"settings_vendor_filter_placeholder": "Search vendors…",
"settings_connection": "Connection",
"settings_print": "Print Settings",
"settings_poll": "Poll Interval",
"settings_version": "Version",
"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_label": "Visible vendors (profile dropdown)",
"settings_visible_vendors_save": "Save selection",
"settings_visible_vendors_save_label": "Save selection",
"settings_save": "Save & Restart",
"settings_printer_name": "Printer Name",
"settings_printer_ip": "Printer IP",
"settings_mqtt_port": "MQTT Port",
"settings_username": "MQTT Username",
"settings_password": "MQTT Password",
"settings_device_id": "Device ID",
"settings_mode_id": "Mode ID",
"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_vibration_compensation": "Resonance Compensation before print",
"settings_flow_calibration": "Flow Calibration before print",
"settings_camera_on_print": "Turn camera on at print start",
"settings_web_upload_warning": "Show warning when printing web uploads",
"sf_all": "All",
"sf_err": "✗ Failed",
"sf_new": "New",
"sf_ok": "✓ Completed",
"skip_already": "skipped",
"skip_btn_label": "Objects",
"skip_cancel": "Cancel",
"skip_confirm": "Skip",
"skip_confirm_btn": "Skip",
"settings_timelapse_local": "Local timelapse during print",
"settings_timelapse_interval": "Capture interval (seconds)",
"settings_timelapse_printer": "Enable printer timelapse (experimental)",
"settings_layout": "Dashboard layout",
"settings_layout_1col": "1 column",
"settings_layout_2col": "2 columns",
"store_tab_files": "Files",
"timelapse_tab": "Timelapses",
"timelapse_empty": "No timelapses yet",
"timelapse_recording": "Recording…",
"timelapse_processing": "Encoding…",
"confirm_delete_timelapse": "Delete this timelapse?",
"update_check": "Check for Updates",
"update_checking": "Checking...",
"update_available": "available",
"update_none": "Already up to date",
"update_apply": "Install Now",
"update_applying": "Downloading...",
"update_restarting": "Restarting...",
"update_error": "Error",
"btn_connect": "⚡ Connect",
"btn_disconnect": "✕ Disconnect",
"lbl_conn_error": "Connection error:",
"slot_edit_title": "Edit Slot",
"slot_edit_color": "Color",
"slot_edit_material": "Material",
"slot_edit_load": "⬇ Load",
"slot_edit_unload": "⬆ Unload",
"slot_edit_save": "💾 Save",
"slot_edit_custom": "e.g. PLA, PETG, ABS…",
"slot_edit_ok": "AMS Slot",
"slot_edit_profile": "OrcaSlicer profile",
"slot_edit_profile_hint": "Sent on OrcaSlicer sync as the specific brand instead of just \"Generic\"",
"slot_edit_profile_default": "— Generic (default) —",
"log_dir_all": "All",
"log_lvl_label": "Level:",
"file_ready_btn": "▶ Start Print",
"file_slots_btn": "🎨 Select Slots",
"file_cancel_btn": "✕ Cancel",
"nav_printers": "Printers",
"skip_title": "✂ Skip objects",
"skip_hint": "Uncheck objects you no longer want to print:",
"skip_btn_label": "Objects",
"skip_no_objects": "No objects in this print.",
"skip_already": "skipped",
"skip_select_at_least_one": "Please pick at least one object.",
"skip_sending": "Sending …",
"skip_success": "Objects will be skipped.",
"skip_title": "Skip objects",
"slot_edit_color": "Color",
"slot_edit_custom": "e.g. PLA, PETG, ABS…",
"slot_edit_load": "⬇ Load",
"slot_edit_material": "Material",
"slot_edit_ok": "AMS Slot",
"slot_edit_profile": "OrcaSlicer profile",
"slot_edit_profile_default": "— Generic (default) —",
"slot_edit_profile_hint": "Sent on OrcaSlicer sync as the specific brand instead of just \"Generic\"",
"slot_edit_save": "💾 Save",
"slot_edit_title": "Edit Slot",
"slot_edit_unload": "⬆ Unload",
"speed_normal": "⚡ Normal",
"speed_silent": "🐢 Silent",
"speed_sport": "🚀 Sport",
"ss_date": "↓ Date",
"ss_dur": "⏱ Print time",
"ss_name": "AZ Name",
"store_delete_confirm": "Delete file?",
"store_download": "⬇ Download",
"store_empty": "No files uploaded yet.",
"store_estimate": "Estimate",
"store_never": "never printed",
"store_no_results": "No files found.",
"store_print": "▶ Print",
"store_print_confirm": "Print file?",
"store_refresh": "↻ Refresh",
"fd_objects_hint": "Skip objects (optional):",
"fd_slots_hint": "Assign GCode channel to AMS slot:",
"fd_cancel": "Cancel",
"fd_print": "▶ Print",
"fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?",
"fd_slot": "Slot",
"fd_no_matching_material": "No matching material",
"fd_used": "USED",
"add_printer": "Add printer",
"apd_lbl_ip": "Printer IP",
"apd_lbl_name": "Name (optional)",
"apd_placeholder_name": "e.g. Kobra X Living Room",
"apd_cancel": "Cancel",
"apd_confirm": "Add",
"apd_fetching": "Fetching data from printer…",
"apd_success": "Printer added, bridge restarting…",
"apd_err_ip": "Please enter an IP address",
"printers_remove": "Remove printer",
"printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.",
"printers_active": "● active",
"printers_switch": "Switch →",
"printers_current": "Current printer",
"printers_loading": "Loading…",
"printers_none": "No printers configured.",
"printers_empty_hint": "No printer set up yet.",
"nav_browser": "Browser",
"panel_browser_title": "File Browser",
"store_search_placeholder": "🔍 Search…",
"store_upload_busy": "⏳ Uploading…",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "browse",
"store_upload_label_prefix": "Drag GCode here or ",
"store_upload_only_gcode": "✗ Only GCode files allowed (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "Cancel",
"store_web_verify_confirm": "Confirm",
"store_web_verify_msg": "Please verify this file was made for Anycubic Kobra X.",
"store_empty": "No files uploaded yet.",
"store_refresh": "↻ Refresh",
"store_print": "▶ Print",
"store_download": "⬇ Download",
"store_delete_confirm": "Delete file?",
"store_print_confirm": "Print file?",
"store_web_verify_title": "Verify file",
"update_apply": "Install Now",
"update_applying": "Downloading...",
"update_available": "available",
"update_check": "Check for Updates",
"update_checking": "Checking...",
"update_error": "Error",
"update_none": "Already up to date",
"update_restarting": "Restarting..."
"store_web_verify_msg": "Please verify this file was made for Anycubic Kobra X.",
"store_web_verify_confirm": "Confirm",
"store_web_verify_abort": "Abort",
"store_no_results": "No files found.",
"store_never": "never printed",
"store_estimate": "Estimate",
"store_upload_label_prefix": "Drag GCode here or ",
"store_upload_label_browse": "browse",
"store_upload_busy": "⏳ Uploading…",
"store_upload_success": "✓ {file}",
"store_upload_error": "✗ {error}",
"sf_all": "All",
"sf_ok": "✓ Completed",
"sf_err": "✗ Failed",
"sf_new": "New",
"ss_date": "↓ Date",
"ss_name": "AZ Name",
"ss_dur": "⏱ Print time"
}

View File

@@ -1,327 +1,248 @@
{
"ace_dry_auto_refill": "Relleno automático",
"ace_dry_chart": "Historial (Temp/Humedad)",
"ace_dry_current_temp": "Temperatura",
"ace_dry_dialog_cancel": "Cancelar",
"ace_dry_dialog_confirm": "Confirmar",
"ace_dry_dialog_custom_name": "Nombre personalizado",
"ace_dry_dialog_reset_default": "Restablecer valores predeterminados",
"ace_dry_dialog_save_restart": "Guardar y reiniciar",
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
"ace_dry_dialog_time": "Tiempo restante (h:m:s)",
"ace_dry_dialog_title": "Ajustes de temp/tiempo del secador",
"header_status_standby": "Listo",
"header_status_printing": "Imprimiendo",
"header_status_complete": "Completado",
"header_status_error": "Error",
"kobra_free": "Listo",
"kobra_busy": "Ocupado",
"kobra_printing": "Imprimiendo",
"kobra_preheating": "Precalentando",
"kobra_auto_leveling": "Autonivelado",
"kobra_checking": "Comprobando",
"kobra_updated": "Actualizando",
"kobra_init": "Inicializando",
"kobra_pausing": "Pausando...",
"kobra_paused": "Pausado",
"kobra_resuming": "Reanudando...",
"kobra_resumed": "Reanudado",
"kobra_stopping": "Deteniendo...",
"kobra_stoped": "Detenido",
"kobra_finished": "Finalizado",
"kobra_failed": "Error",
"kobra_canceled": "Cancelado",
"kobra_offline": "Desconectada",
"nav_dashboard": "Panel",
"nav_print": "Impresión",
"nav_temps": "Temperaturas",
"nav_motion": "Movimiento",
"nav_ams": "AMS",
"nav_extras": "Luz / Ventilador",
"nav_console": "Consola",
"card_progress": "Progreso",
"card_temps": "Temperaturas",
"card_light_fan": "Ventilador",
"card_speed": "Velocidad de impresión",
"card_cam": "Cámara",
"lbl_elapsed": "Transcurrido:",
"lbl_remaining": "Restante:",
"lbl_slicer_time": "Estimación del slicer:",
"lbl_layers": "Capa",
"speed_silent": "🐢 Silencioso",
"speed_normal": "⚡ Normal",
"speed_sport": "🚀 Sport",
"lbl_light": "💡 Luz",
"lbl_feed": "Cargar",
"lbl_unload": "Descargar",
"card_ace_dry": "Secado ACE",
"ace_dry_dryer": "Secador",
"ace_dry_duration": "Duración (min)",
"ace_dry_enable": "Activar secado",
"ace_dry_humidity": "Humedad",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "Personalizado",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ Iniciar",
"ace_dry_status_off": "Estado: Apagado",
"ace_dry_status_on": "Estado: Activo",
"ace_dry_status_remaining": "Restante",
"ace_dry_stop": "■ Parar",
"ace_dry_humidity": "Humedad",
"ace_dry_current_temp": "Temperatura",
"ace_dry_chart": "Historial (Temp/Humedad)",
"ace_dry_temp": "Temperatura (°C)",
"ace_dry_duration": "Duración (min)",
"ace_dry_start": "▶ Iniciar",
"ace_dry_stop": "■ Parar",
"ace_dry_auto_refill": "Relleno automático",
"ace_dry_enable": "Activar secado",
"ace_dry_temp_line": "Temperatura de secado",
"ace_dry_time_line": "Tiempo de secado",
"ace_dry_ui_pending": "(solo UI, backend después)",
"add_printer": "Añadir impresora",
"ams_empty": "Vacío",
"ams_no_data": "No se recibieron datos de AMS",
"apd_cancel": "Cancelar",
"apd_confirm": "Agregar",
"apd_err_ip": "Introduce una dirección IP",
"apd_fetching": "Obteniendo datos de la impresora…",
"apd_lbl_ip": "IP de impresora",
"apd_lbl_name": "Nombre (opcional)",
"apd_placeholder_name": "p. ej. Kobra X Sala",
"apd_success": "Impresora añadida, reiniciando bridge…",
"apd_title": "Agregar impresora",
"btn_cam_start": "▶ Cámara",
"btn_cam_start2": "▶ Iniciar",
"btn_cam_stop": "◼ Cámara",
"btn_cam_stop2": "◼ Detener",
"btn_cancel": "✕ Detener",
"btn_cancel_generic": "Cancelar",
"btn_confirm_generic": "Confirmar",
"btn_connect": "⚡ Conectar",
"btn_delete": "Eliminar",
"btn_disable_motors": "Motores apagados",
"btn_disconnect": "✕ Desconectar",
"btn_home_all": "Home All",
"btn_home_xy": "Home XY",
"btn_home_z": "Home Z",
"btn_pause": "⏸ Pausa",
"btn_resume": "▶ Reanudar",
"ace_dry_dialog_title": "Ajustes de temp/tiempo del secador",
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
"ace_dry_dialog_time": "Tiempo restante (h:m:s)",
"ace_dry_dialog_confirm": "Confirmar",
"ace_dry_dialog_cancel": "Cancelar",
"ace_dry_dialog_save_restart": "Guardar y reiniciar",
"ace_dry_dialog_custom_name": "Nombre personalizado",
"ace_dry_dialog_reset_default": "Restablecer valores predeterminados",
"cam_placeholder": "📷 Cámara no iniciada",
"cam_stream_unavailable": "Stream no disponible",
"card_ace_dry": "Secado ACE",
"card_ams": "Filamento",
"card_cam": "Cámara",
"card_light_fan": "Ventilador",
"card_progress": "Progreso",
"card_speed": "Velocidad de impresión",
"card_temps": "Temperaturas",
"confirm_cancel": "¿Realmente cancelar la impresión?",
"fd_cancel": "Cancelar",
"fd_no_matching_material": "No hay material compatible",
"fd_no_slots_msg": "No hay slots AMS cargados.{br}¿Iniciar impresión de todos modos?",
"fd_objects_hint": "Omitir objetos (opcional):",
"fd_objects_toggle": "Omitir objetos",
"fd_options_title": "Opciones",
"fd_print": "▶ Imprimir",
"fd_slot": "Ranura",
"fd_slots_hint": "Asignar canal GCode a la ranura AMS:",
"fd_title": "Asignación de ranura",
"fd_used": "USADO",
"file_cancel_btn": "✕ Cancelar",
"file_ready_btn": "▶ Iniciar impresión",
"file_slots_btn": "🎨 Seleccionar ranuras",
"header_status_complete": "Completado",
"header_status_error": "Error",
"header_status_printing": "Imprimiendo",
"header_status_standby": "Listo",
"hint_ip_no_port": "Solo dirección IP, sin puerto (p. ej. 192.168.1.102)",
"kobra_auto_leveling": "Autonivelado",
"kobra_busy": "Ocupado",
"kobra_canceled": "Cancelado",
"kobra_checking": "Comprobando",
"kobra_failed": "Error",
"kobra_finished": "Finalizado",
"kobra_free": "Listo",
"kobra_init": "Inicializando",
"kobra_offline": "Desconectada",
"kobra_paused": "Pausado",
"kobra_pausing": "Pausando...",
"kobra_preheating": "Precalentando",
"kobra_printing": "Imprimiendo",
"kobra_resumed": "Reanudado",
"kobra_resuming": "Reanudando...",
"kobra_stoped": "Detenido",
"kobra_stopping": "Deteniendo...",
"kobra_updated": "Actualizando",
"btn_cam_start": "▶ Cámara",
"btn_cam_stop": "◼ Cámara",
"btn_pause": "⏸ Pausa",
"btn_resume": "▶ Reanudar",
"btn_cancel": "✕ Detener",
"label_nozzle": "Boquilla",
"label_bed": "Cama",
"label_fan": "🌀 Ventilador",
"label_light": "💡 Luz",
"label_nozzle": "Boquilla",
"label_off": "Apagado",
"label_on_off": "Encendido / Apagado",
"label_set": "Set",
"label_slot": "Ranura",
"label_speed": "Velocidad",
"label_step": "Tamaño del paso:",
"label_target_c": "Objetivo:",
"lbl_conn_error": "Error de conexión:",
"lbl_elapsed": "Transcurrido:",
"lbl_feed": "Cargar",
"lbl_layers": "Capa",
"lbl_light": "💡 Luz",
"lbl_remaining": "Restante:",
"lbl_slicer_time": "Estimación del slicer:",
"lbl_spoolman_sync_rate": "Tasa de sincronización (s, 0=desact.)",
"lbl_spoolman_url": "URL del servidor",
"lbl_unload": "Descargar",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ Auto",
"log_axis": "Eje",
"log_bed": "Cama →",
"log_cam_start": "Cámara iniciada:",
"log_cam_stop": "Cámara detenida",
"log_clear": "✕ Limpiar",
"log_delete_failed": "Error al eliminar",
"log_dir_all": "Todos",
"log_dir_label": "Dirección:",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ Descargar",
"log_error": "Error:",
"log_fan": "Ventilador →",
"log_filter_placeholder": "Filtrar…",
"log_home": "Home",
"log_home_all": "Home All",
"log_light_off": "Luz apagada",
"log_light_on": "Luz encendida",
"log_lvl_err": "⛔ Errores",
"log_lvl_label": "Nivel:",
"log_lvl_warn": "⚠ Avisos",
"log_nozzle": "Boquilla →",
"log_poll_error": "Error de sondeo:",
"log_print_action": "Impresión:",
"log_print_start": "Inicio de impresión:",
"log_topic_ams": "AMS",
"log_topic_info": "Info",
"log_topic_label": "Tema:",
"log_topic_print": "Impresión",
"log_topic_status": "Estado",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "Explorador",
"nav_console": "Consola",
"nav_dashboard": "Panel",
"nav_extras": "Luz / Ventilador",
"nav_motion": "Movimiento",
"nav_print": "Impresión",
"nav_printers": "Impresoras",
"nav_settings": "Ajustes",
"nav_temps": "Temperaturas",
"orca_profile_done": "Importado",
"orca_profile_dropmsg": "Suelta aquí o haz clic",
"orca_profile_help_html": "Sube un <b>ZIP</b> de tu carpeta de filamentos de OrcaSlicer o archivos <b>.json</b> sueltos.<br>En OrcaSlicer: <i>Help → Show Configuration Folder → user/&lt;id&gt;/filament/</i>",
"orca_profile_hint": "Importa tus propios perfiles de filamento de OrcaSlicer (abre el directorio del usuario vía Help → Show Configuration Folder)",
"orca_profile_import_btn": "Importar perfiles",
"orca_profile_import_link": "★ Importar perfiles propios…",
"orca_profile_import_title": "Importar tus perfiles de OrcaSlicer",
"orca_profile_list_label": "Actualmente importados",
"orca_profile_section": "Perfiles de OrcaSlicer",
"orca_profile_skipped": "omitido",
"orca_profile_uploading": "Subiendo…",
"orca_profile_user_empty": " ninguno ",
"orca_profile_user_label": "Perfiles propios",
"panel_ams_title": "Filamento",
"panel_browser_title": "Explorador de archivos",
"panel_console_title": "Registro de eventos",
"panel_extras_camera": "Cámara",
"panel_extras_fan": "Ventilador",
"panel_extras_light": "Luz",
"panel_motion_xy": "Ejes XY",
"panel_motion_z": "Eje Z",
"panel_print_btn_cancel": "✕ Cancelar",
"panel_print_title": "Control de impresión",
"panel_print_btn_pause": "⏸ Pausa",
"panel_print_btn_resume": "▶ Reanudar",
"panel_print_btn_cancel": "✕ Cancelar",
"panel_print_temps_live": "Temperaturas (en vivo)",
"panel_print_title": "Control de impresión",
"label_set": "Set",
"label_off": "Apagado",
"panel_temps_nozzle": "Boquilla",
"panel_temps_bed": "Cama caliente",
"panel_temps_chart": "Historial (últimas 60 lecturas)",
"panel_temps_nozzle": "Boquilla",
"print_auto_leveling": "Autonivelado para esta impresión",
"printers_active": "● activa",
"printers_current": "Impresora actual",
"printers_empty_hint": "Aún no hay impresora configurada.",
"printers_loading": "Cargando…",
"printers_none": "No hay impresoras configuradas.",
"printers_remove": "Eliminar impresora",
"printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.",
"printers_switch": "Cambiar →",
"progress_action_clear": "Vaciar",
"progress_action_print": "Imprimir",
"progress_action_slots": "Asignar ranuras",
"settings_auto_leveling": "Autonivelado antes de imprimir",
"settings_auto_leveling_label": "Autonivelado antes de imprimir",
"settings_btn_tooltip": "Ajustes",
"settings_camera_on_print": "Encender cámara al iniciar impresión",
"settings_cat_connection": "Conexión",
"settings_cat_display": "Apariencia",
"settings_cat_filament": "Filamento",
"settings_cat_language": "Idioma",
"settings_cat_printer": "Impresora",
"settings_cat_system": "Sistema",
"settings_cat_theme": "Alternar claro / oscuro",
"settings_connection": "Conexión",
"settings_default_slot": "Ranura predeterminada (un color)",
"settings_device_id": "ID del dispositivo",
"settings_device_id_hint": "32 caracteres hexadecimales",
"settings_device_id_placeholder": "32 caracteres hexadecimales",
"settings_filament_mapping": "Asignación de perfil de filamento (por ranura)",
"settings_filament_mapping_hint": "Perfil Orca fijo por ranura AMS. Al sincronizar con el slicer, el bridge envía este perfil en lugar de \"Generic\".",
"settings_filament_mapping_label": "Asignación de perfil de filamento (por ranura)",
"settings_filament_mapping_save": "Guardar asignación",
"settings_filament_mapping_save_label": "Guardar asignación",
"settings_file_ready_banner": "Barra de impresión",
"settings_file_ready_dialog": "Diálogo de impresión",
"settings_file_ready_mode": "Después de carga: Comportamiento de inicio de impresión",
"settings_integrations": "Integraciones",
"settings_language": "Idioma",
"settings_mode_id": "ID de modo",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "MQTT Port",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "Importar perfiles",
"settings_orca_profiles_label": "Perfiles de OrcaSlicer",
"settings_password": "Contraseña MQTT",
"settings_poll": "Intervalo de sondeo (segundos)",
"settings_poll_interval_hint": "Con qué frecuencia el bridge consulta el estado de la impresora",
"settings_poll_interval_label": "Intervalo de sondeo (segundos)",
"settings_print": "Ajustes de impresión",
"settings_printer_ip": "IP de impresora",
"settings_printer_name": "Nombre de impresora",
"settings_printer_name_placeholder": "p. ej. Kobra X Sala",
"settings_save": "Guardar y reiniciar",
"settings_slot_auto": "Auto (todos los slots cargados)",
"settings_theme_toggle": "Alternar claro / oscuro",
"label_target_c": "Objetivo:",
"panel_motion_xy": "Ejes XY",
"panel_motion_z": "Eje Z",
"label_step": "Tamaño del paso:",
"btn_home_z": "Home Z",
"btn_home_xy": "Home XY",
"btn_home_all": "Home All",
"btn_disable_motors": "Motores apagados",
"panel_ams_title": "Filamento",
"card_ams": "Filamento",
"ams_no_data": "No se recibieron datos de AMS",
"label_slot": "Ranura",
"ams_empty": "Vacío",
"panel_extras_light": "Luz",
"panel_extras_fan": "Ventilador",
"panel_extras_camera": "Cámara",
"btn_cam_start2": "▶ Iniciar",
"btn_cam_stop2": "◼ Detener",
"panel_console_title": "Registro de eventos",
"log_light_on": "Luz encendida",
"log_light_off": "Luz apagada",
"log_fan": "Ventilador →",
"log_nozzle": "Boquilla →",
"log_bed": "Cama →",
"log_axis": "Eje",
"log_home": "Home",
"log_home_all": "Home All",
"log_cam_start": "Cámara iniciada:",
"log_cam_stop": "Cámara detenida",
"log_poll_error": "Error de sondeo:",
"log_error": "Error:",
"confirm_cancel": "¿Realmente cancelar la impresión?",
"settings_title": "Configuración",
"settings_username": "Usuario MQTT",
"settings_vendor_filter_placeholder": "Buscar fabricantes…",
"settings_connection": "Conexión",
"settings_print": "Ajustes de impresión",
"settings_poll": "Intervalo de sondeo",
"settings_version": "Versió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_label": "Fabricantes visibles (lista de perfiles)",
"settings_visible_vendors_save": "Guardar selección",
"settings_visible_vendors_save_label": "Guardar selección",
"settings_save": "Guardar y reiniciar",
"settings_printer_name": "Nombre de impresora",
"settings_printer_ip": "IP de impresora",
"settings_mqtt_port": "MQTT Port",
"settings_username": "Usuario MQTT",
"settings_password": "Contraseña MQTT",
"settings_device_id": "ID del dispositivo",
"settings_mode_id": "ID de modo",
"hint_ip_no_port": "Solo dirección IP, sin puerto (p. ej. 192.168.1.102)",
"settings_default_slot": "Ranura predeterminada (un color)",
"settings_slot_auto": "Auto (todos los slots cargados)",
"settings_auto_leveling": "Autonivelado antes de imprimir",
"settings_vibration_compensation": "Compensación de resonancia antes de imprimir",
"settings_flow_calibration": "Calibración de flujo antes de imprimir",
"settings_camera_on_print": "Encender cámara al iniciar impresión",
"settings_web_upload_warning": "Mostrar advertencia al imprimir subidas web",
"sf_all": "Todos",
"sf_err": "✗ Fallido",
"sf_new": "Nuevo",
"sf_ok": "✓ Completado",
"skip_already": "omitido",
"skip_btn_label": "Objetos",
"skip_cancel": "Cancelar",
"skip_confirm": "Omitir",
"skip_confirm_btn": "Omitir",
"settings_timelapse_local": "Timelapse local durante la impresión",
"settings_timelapse_interval": "Intervalo de captura (segundos)",
"settings_timelapse_printer": "Activar timelapse de impresora (experimental)",
"settings_layout": "Diseño del panel",
"settings_layout_1col": "1 columna",
"settings_layout_2col": "2 columnas",
"store_tab_files": "Archivos",
"timelapse_tab": "Timelapses",
"timelapse_empty": "Aún no hay timelapses",
"timelapse_recording": "Grabando…",
"timelapse_processing": "Codificando…",
"confirm_delete_timelapse": "¿Eliminar este timelapse?",
"update_check": "Buscar actualizaciones",
"update_checking": "Comprobando...",
"update_available": "disponible",
"update_none": "Ya actualizado",
"update_apply": "Instalar ahora",
"update_applying": "Descargando...",
"update_restarting": "Reiniciando...",
"update_error": "Error",
"btn_connect": "⚡ Conectar",
"btn_disconnect": "✕ Desconectar",
"lbl_conn_error": "Error de conexión:",
"slot_edit_title": "Editar slot",
"slot_edit_color": "Color",
"slot_edit_material": "Material",
"slot_edit_load": "⬇ Cargar",
"slot_edit_unload": "⬆ Descargar",
"slot_edit_save": "💾 Guardar",
"slot_edit_custom": "p. ej. PLA, PETG, ABS…",
"slot_edit_ok": "Ranura AMS",
"slot_edit_profile": "Perfil de OrcaSlicer",
"slot_edit_profile_hint": "Envía al sincronizar con OrcaSlicer la marca concreta en lugar de solo \"Generic\"",
"slot_edit_profile_default": "— Genérico (Predeterminado) —",
"log_dir_all": "Todos",
"log_lvl_label": "Nivel:",
"file_ready_btn": "▶ Iniciar impresión",
"file_slots_btn": "🎨 Seleccionar ranuras",
"file_cancel_btn": "✕ Cancelar",
"nav_printers": "Impresoras",
"skip_title": "✂ Omitir objetos",
"skip_hint": "Deselecciona los objetos que ya no quieras imprimir:",
"skip_btn_label": "Objetos",
"skip_no_objects": "No hay objetos en esta impresión.",
"skip_already": "omitido",
"skip_select_at_least_one": "Selecciona al menos un objeto.",
"skip_sending": "Enviando …",
"skip_success": "Se omitirán los objetos.",
"skip_title": "Omitir objetos",
"slot_edit_color": "Color",
"slot_edit_custom": "p. ej. PLA, PETG, ABS…",
"slot_edit_load": "⬇ Cargar",
"slot_edit_material": "Material",
"slot_edit_ok": "Ranura AMS",
"slot_edit_profile": "Perfil de OrcaSlicer",
"slot_edit_profile_default": "— Genérico (Predeterminado) —",
"slot_edit_profile_hint": "Envía al sincronizar con OrcaSlicer la marca concreta en lugar de solo \"Generic\"",
"slot_edit_save": "💾 Guardar",
"slot_edit_title": "Editar slot",
"slot_edit_unload": "⬆ Descargar",
"speed_normal": "⚡ Normal",
"speed_silent": "🐢 Silencioso",
"speed_sport": "🚀 Sport",
"ss_date": "↓ Fecha",
"ss_dur": "⏱ Tiempo de impresión",
"ss_name": "AZ Nombre",
"store_delete_confirm": "¿Eliminar archivo?",
"store_download": "⬇ Descargar",
"store_empty": "Aún no hay archivos subidos.",
"store_estimate": "Estimación",
"store_never": "nunca impreso",
"store_no_results": "No se encontraron archivos.",
"store_print": "▶ Imprimir",
"store_print_confirm": "¿Imprimir archivo?",
"store_refresh": "↻ Actualizar",
"fd_objects_hint": "Omitir objetos (opcional):",
"fd_slots_hint": "Asignar canal GCode a la ranura AMS:",
"fd_cancel": "Cancelar",
"fd_print": "▶ Imprimir",
"fd_no_slots_msg": "No hay slots AMS cargados.{br}¿Iniciar impresión de todos modos?",
"fd_slot": "Ranura",
"fd_no_matching_material": "No hay material compatible",
"fd_used": "USADO",
"add_printer": "Añadir impresora",
"apd_lbl_ip": "IP de impresora",
"apd_lbl_name": "Nombre (opcional)",
"apd_placeholder_name": "p. ej. Kobra X Sala",
"apd_cancel": "Cancelar",
"apd_confirm": "Añadir",
"apd_fetching": "Obteniendo datos de la impresora…",
"apd_success": "Impresora añadida, reiniciando bridge…",
"apd_err_ip": "Introduce una dirección IP",
"printers_remove": "Eliminar impresora",
"printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.",
"printers_active": "● activa",
"printers_switch": "Cambiar →",
"printers_current": "Impresora actual",
"printers_loading": "Cargando…",
"printers_none": "No hay impresoras configuradas.",
"printers_empty_hint": "Aún no hay impresora configurada.",
"nav_browser": "Explorador",
"panel_browser_title": "Explorador de archivos",
"store_search_placeholder": "🔍 Buscar…",
"store_upload_busy": "⏳ Subiendo…",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "buscar",
"store_upload_label_prefix": "Arrastra el GCode aquí o ",
"store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "Cancelar",
"store_web_verify_confirm": "Confirmar",
"store_web_verify_msg": "Verifica que este archivo fue hecho para Anycubic Kobra X.",
"store_empty": "Aún no hay archivos subidos.",
"store_refresh": "↻ Actualizar",
"store_print": "▶ Imprimir",
"store_download": "⬇ Descargar",
"store_delete_confirm": "¿Eliminar archivo?",
"store_print_confirm": "¿Imprimir archivo?",
"store_web_verify_title": "Verificar archivo",
"update_apply": "Instalar ahora",
"update_applying": "Descargando...",
"update_available": "disponible",
"update_check": "Buscar actualizaciones",
"update_checking": "Comprobando...",
"update_error": "Error",
"update_none": "Ya actualizado",
"update_restarting": "Reiniciando..."
"store_web_verify_msg": "Verifica que este archivo fue creado para Anycubic Kobra X.",
"store_web_verify_confirm": "Confirmar",
"store_web_verify_abort": "Abortar",
"store_no_results": "No se encontraron archivos.",
"store_never": "nunca impreso",
"store_estimate": "Estimación",
"store_upload_label_prefix": "Arrastra el GCode aquí o ",
"store_upload_label_browse": "buscar",
"store_upload_busy": "⏳ Subiendo…",
"store_upload_success": "✓ {file}",
"store_upload_error": "✗ {error}",
"sf_all": "Todos",
"sf_ok": "✓ Completado",
"sf_err": "✗ Fallido",
"sf_new": "Nuevo",
"ss_date": "↓ Fecha",
"ss_name": "AZ Nombre",
"ss_dur": "⏱ Tiempo de impresión"
}

View File

@@ -1,327 +0,0 @@
{
"ace_dry_auto_refill": "Remplissage auto",
"ace_dry_chart": "Historique (Temp/Humidité)",
"ace_dry_current_temp": "Température",
"ace_dry_dialog_cancel": "Annuler",
"ace_dry_dialog_confirm": "Confirmer",
"ace_dry_dialog_custom_name": "Nom personnalisé",
"ace_dry_dialog_reset_default": "Réinitialiser",
"ace_dry_dialog_save_restart": "Enregistrer et redémarrer",
"ace_dry_dialog_temp": "Température (30-80°C)",
"ace_dry_dialog_time": "Temps restant (h:m:s)",
"ace_dry_dialog_title": "Réglages Temp/Durée du séchoir",
"ace_dry_dryer": "Séchoir",
"ace_dry_duration": "Durée (min)",
"ace_dry_enable": "Activer le séchage",
"ace_dry_humidity": "Humidité",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "Personnalisé",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ Démarrer",
"ace_dry_status_off": "Statut : Arrêté",
"ace_dry_status_on": "Statut : Actif",
"ace_dry_status_remaining": "Restant",
"ace_dry_stop": "■ Arrêter",
"ace_dry_temp": "Température (°C)",
"ace_dry_temp_line": "Température de séchage",
"ace_dry_time_line": "Durée de séchage",
"ace_dry_ui_pending": "(Interface seule, backend suivant)",
"add_printer": "Ajouter une imprimante",
"ams_empty": "Vide",
"ams_no_data": "Aucune donnée AMS reçue",
"apd_cancel": "Annuler",
"apd_confirm": "Ajouter",
"apd_err_ip": "Veuillez saisir une adresse IP",
"apd_fetching": "Récupération des données de l'imprimante…",
"apd_lbl_ip": "IP de l'imprimante",
"apd_lbl_name": "Nom (optionnel)",
"apd_placeholder_name": "ex. Kobra X Salon",
"apd_success": "Imprimante ajoutée, redémarrage du bridge…",
"apd_title": "Ajouter une imprimante",
"btn_cam_start": "▶ Caméra",
"btn_cam_start2": "▶ Démarrer",
"btn_cam_stop": "◼ Caméra",
"btn_cam_stop2": "◼ Arrêter",
"btn_cancel": "✕ Arrêter",
"btn_cancel_generic": "Annuler",
"btn_confirm_generic": "Confirmer",
"btn_connect": "⚡ Connecter",
"btn_delete": "Supprimer",
"btn_disable_motors": "Moteurs Off",
"btn_disconnect": "✕ Déconnecter",
"btn_home_all": "Origine Tout",
"btn_home_xy": "Origine XY",
"btn_home_z": "Origine Z",
"btn_pause": "⏸ Pause",
"btn_resume": "▶ Reprendre",
"cam_placeholder": "📷 Caméra non démarrée",
"cam_stream_unavailable": "Flux indisponible",
"card_ace_dry": "Séchage ACE",
"card_ams": "Filament",
"card_cam": "Caméra",
"card_light_fan": "Ventilateur",
"card_progress": "Progression",
"card_speed": "Vitesse d'impression",
"card_temps": "Températures",
"confirm_cancel": "Vraiment annuler l'impression ?",
"fd_cancel": "Annuler",
"fd_no_matching_material": "Aucun matériau correspondant",
"fd_no_slots_msg": "Aucun slot AMS chargé.{br}Lancer l'impression quand même ?",
"fd_objects_hint": "Ignorer des objets (optionnel) :",
"fd_objects_toggle": "Ignorer des objets",
"fd_options_title": "Options",
"fd_print": "▶ Imprimer",
"fd_slot": "Slot",
"fd_slots_hint": "Associer le canal GCode au slot AMS :",
"fd_title": "Attribution de fente",
"fd_used": "UTILISÉ",
"file_cancel_btn": "✕ Annuler",
"file_ready_btn": "▶ Lancer l'impression",
"file_slots_btn": "🎨 Choisir les slots",
"header_status_complete": "Terminé",
"header_status_error": "Erreur",
"header_status_printing": "Impression",
"header_status_standby": "Prêt",
"hint_ip_no_port": "Adresse IP uniquement, sans port (ex. 192.168.1.102)",
"kobra_auto_leveling": "Mise à niveau auto",
"kobra_busy": "Occupé",
"kobra_canceled": "Annulé",
"kobra_checking": "Vérification",
"kobra_failed": "Erreur",
"kobra_finished": "Terminé",
"kobra_free": "Disponible",
"kobra_init": "Initialisation",
"kobra_offline": "Hors ligne",
"kobra_paused": "En pause",
"kobra_pausing": "Pause en cours…",
"kobra_preheating": "Préchauffage",
"kobra_printing": "Impression",
"kobra_resumed": "Repris",
"kobra_resuming": "Reprise en cours…",
"kobra_stoped": "Arrêté",
"kobra_stopping": "Arrêt en cours…",
"kobra_updated": "Mise à jour",
"label_bed": "Plateau",
"label_fan": "🌀 Ventilateur",
"label_light": "💡 Lumière",
"label_nozzle": "Buse",
"label_off": "Éteint",
"label_on_off": "On / Off",
"label_set": "Définir",
"label_slot": "Slot",
"label_speed": "Vitesse",
"label_step": "Pas :",
"label_target_c": "Cible :",
"lbl_conn_error": "Erreur de connexion :",
"lbl_elapsed": "Écoulé :",
"lbl_feed": "Charger",
"lbl_layers": "Couche",
"lbl_light": "💡 Lumière",
"lbl_remaining": "Restant :",
"lbl_slicer_time": "Estimation slicer :",
"lbl_spoolman_sync_rate": "Taux de sync. (s, 0=désact.)",
"lbl_spoolman_url": "URL du serveur",
"lbl_unload": "Décharger",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ Auto",
"log_axis": "Axe",
"log_bed": "Plateau →",
"log_cam_start": "Caméra démarrée :",
"log_cam_stop": "Caméra arrêtée",
"log_clear": "✕ Effacer",
"log_delete_failed": "Échec de la suppression",
"log_dir_all": "Tout",
"log_dir_label": "Sens :",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ Télécharger",
"log_error": "Erreur :",
"log_fan": "Ventilateur →",
"log_filter_placeholder": "Filtrer…",
"log_home": "Origine",
"log_home_all": "Origine Tout",
"log_light_off": "Lumière éteinte",
"log_light_on": "Lumière allumée",
"log_lvl_err": "⛔ Erreurs",
"log_lvl_label": "Niveau :",
"log_lvl_warn": "⚠ Avert.",
"log_nozzle": "Buse →",
"log_poll_error": "Erreur de sondage :",
"log_print_action": "Impression :",
"log_print_start": "Début de l'impression :",
"log_topic_ams": "AMS",
"log_topic_info": "Info",
"log_topic_label": "Sujet :",
"log_topic_print": "Impression",
"log_topic_status": "Statut",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "Navigateur",
"nav_console": "Console",
"nav_dashboard": "Tableau de bord",
"nav_extras": "Lumière / Ventilateur",
"nav_motion": "Mouvement",
"nav_print": "Impression",
"nav_printers": "Imprimantes",
"nav_settings": "Paramètres",
"nav_temps": "Températures",
"orca_profile_done": "Importé",
"orca_profile_dropmsg": "Déposez ici ou cliquez",
"orca_profile_help_html": "Déposez un <b>ZIP</b> de votre dossier filament OrcaSlicer ou des fichiers <b>.json</b> individuels.<br>Dans OrcaSlicer : <i>Aide → Afficher le dossier de configuration → user/&lt;id&gt;/filament/</i>",
"orca_profile_hint": "Importez vos propres profils de filament OrcaSlicer (ouvrez le dossier utilisateur via Aide → Afficher le dossier de configuration)",
"orca_profile_import_btn": "Importer des profils",
"orca_profile_import_link": "★ Importer mes profils…",
"orca_profile_import_title": "Importer vos profils OrcaSlicer",
"orca_profile_list_label": "Profils importés",
"orca_profile_section": "Profils OrcaSlicer",
"orca_profile_skipped": "ignoré",
"orca_profile_uploading": "Envoi en cours…",
"orca_profile_user_empty": " aucun ",
"orca_profile_user_label": "Mes profils",
"panel_ams_title": "Filament",
"panel_browser_title": "Explorateur de fichiers",
"panel_console_title": "Journal d'événements",
"panel_extras_camera": "Caméra",
"panel_extras_fan": "Ventilateur",
"panel_extras_light": "Lumière",
"panel_motion_xy": "Axes XY",
"panel_motion_z": "Axe Z",
"panel_print_btn_cancel": "✕ Annuler",
"panel_print_btn_pause": "⏸ Pause",
"panel_print_btn_resume": "▶ Reprendre",
"panel_print_temps_live": "Températures (en direct)",
"panel_print_title": "Contrôle impression",
"panel_temps_bed": "Plateau chauffant",
"panel_temps_chart": "Historique (60 dernières valeurs)",
"panel_temps_nozzle": "Buse",
"print_auto_leveling": "Mise à niveau auto pour cette impression",
"printers_active": "● actif",
"printers_current": "Imprimante actuelle",
"printers_empty_hint": "Aucune imprimante configurée.",
"printers_loading": "Chargement…",
"printers_none": "Aucune imprimante configurée.",
"printers_remove": "Supprimer l'imprimante",
"printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.",
"printers_switch": "Changer →",
"progress_action_clear": "Vider",
"progress_action_print": "Imprimer",
"progress_action_slots": "Affecter les emplacements",
"settings_auto_leveling": "Mise à niveau auto avant impression",
"settings_auto_leveling_label": "Mise à niveau auto avant impression",
"settings_btn_tooltip": "Paramètres",
"settings_camera_on_print": "Activer la caméra au démarrage de l'impression",
"settings_cat_connection": "Connexion",
"settings_cat_display": "Apparence",
"settings_cat_filament": "Filament",
"settings_cat_language": "Langue",
"settings_cat_printer": "Imprimante",
"settings_cat_system": "Système",
"settings_cat_theme": "Basculer clair / sombre",
"settings_connection": "Connexion",
"settings_default_slot": "Slot par défaut (couleur unique)",
"settings_device_id": "ID de l'appareil",
"settings_device_id_hint": "32 caractères hexadécimaux",
"settings_device_id_placeholder": "32 caractères hexadécimaux",
"settings_filament_mapping": "Mappage du profil de filament (par emplacement)",
"settings_filament_mapping_hint": "Profil Orca fixe par emplacement AMS. Lors de la synchronisation du slicer, le bridge envoie ce profil au lieu de « Generic ».",
"settings_filament_mapping_label": "Mappage du profil de filament (par emplacement)",
"settings_filament_mapping_save": "Enregistrer le mappage",
"settings_filament_mapping_save_label": "Enregistrer le mappage",
"settings_file_ready_banner": "Barre d'impression",
"settings_file_ready_dialog": "Dialogue d'impression",
"settings_file_ready_mode": "Après téléchargement : Comportement de démarrage d'impression",
"settings_integrations": "Intégrations",
"settings_language": "Langue",
"settings_mode_id": "ID du mode",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "Port MQTT",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "Importer des profils",
"settings_orca_profiles_label": "Profils OrcaSlicer",
"settings_password": "Mot de passe MQTT",
"settings_poll": "Intervalle de sondage (secondes)",
"settings_poll_interval_hint": "Fréquence à laquelle le bridge interroge l'état de l'imprimante",
"settings_poll_interval_label": "Intervalle de sondage (secondes)",
"settings_print": "Paramètres d'impression",
"settings_printer_ip": "IP de l'imprimante",
"settings_printer_name": "Nom de l'imprimante",
"settings_printer_name_placeholder": "p. ex. Kobra X Salon",
"settings_save": "Enregistrer et redémarrer",
"settings_slot_auto": "Auto (tous les slots chargés)",
"settings_theme_toggle": "Basculer clair / sombre",
"settings_title": "Paramètres",
"settings_username": "Nom d'utilisateur MQTT",
"settings_vendor_filter_placeholder": "Rechercher des fabricants…",
"settings_version": "Version",
"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_label": "Fabricants visibles (liste des profils)",
"settings_visible_vendors_save": "Enregistrer la sélection",
"settings_visible_vendors_save_label": "Enregistrer la sélection",
"settings_web_upload_warning": "Afficher un avertissement lors de l'impression de fichiers web",
"sf_all": "Tout",
"sf_err": "✗ Échoués",
"sf_new": "Nouveau",
"sf_ok": "✓ Terminés",
"skip_already": "ignoré",
"skip_btn_label": "Objets",
"skip_cancel": "Annuler",
"skip_confirm": "Ignorer",
"skip_confirm_btn": "Ignorer",
"skip_hint": "Décochez les objets que vous ne souhaitez plus imprimer :",
"skip_no_objects": "Aucun objet dans cette impression.",
"skip_select_at_least_one": "Veuillez sélectionner au moins un objet.",
"skip_sending": "Envoi …",
"skip_success": "Les objets seront ignorés.",
"skip_title": "✂ Ignorer des objets",
"slot_edit_color": "Couleur",
"slot_edit_custom": "ex. PLA, PETG, ABS…",
"slot_edit_load": "⬇ Charger",
"slot_edit_material": "Matériau",
"slot_edit_ok": "Slot AMS",
"slot_edit_profile": "Profil OrcaSlicer",
"slot_edit_profile_default": "— Générique (défaut) —",
"slot_edit_profile_hint": "Envoyé lors de la synchronisation OrcaSlicer comme marque spécifique au lieu de \"Générique\"",
"slot_edit_save": "💾 Enregistrer",
"slot_edit_title": "Modifier le slot",
"slot_edit_unload": "⬆ Décharger",
"speed_normal": "⚡ Normal",
"speed_silent": "🐢 Silencieux",
"speed_sport": "🚀 Sport",
"ss_date": "↓ Date",
"ss_dur": "⏱ Durée d'impression",
"ss_name": "AZ Nom",
"store_delete_confirm": "Supprimer le fichier ?",
"store_download": "⬇ Télécharger",
"store_empty": "Aucun fichier uploadé.",
"store_estimate": "Estimation",
"store_never": "jamais imprimé",
"store_no_results": "Aucun fichier trouvé.",
"store_print": "▶ Imprimer",
"store_print_confirm": "Imprimer le fichier ?",
"store_refresh": "↻ Actualiser",
"store_search_placeholder": "🔍 Rechercher…",
"store_upload_busy": "⏳ Envoi en cours…",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "parcourir",
"store_upload_label_prefix": "Déposez un GCode ici ou ",
"store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "Annuler",
"store_web_verify_confirm": "Confirmer",
"store_web_verify_msg": "Veuillez vérifier que ce fichier a été créé pour Anycubic Kobra X.",
"store_web_verify_title": "Vérifier le fichier",
"update_apply": "Installer maintenant",
"update_applying": "Téléchargement…",
"update_available": "disponible",
"update_check": "Vérifier les mises à jour",
"update_checking": "Vérification…",
"update_error": "Erreur",
"update_none": "Déjà à jour",
"update_restarting": "Redémarrage…"
}

View File

@@ -1,327 +0,0 @@
{
"ace_dry_auto_refill": "Ricarica automatica",
"ace_dry_chart": "Cronologia (Temp/Umidità)",
"ace_dry_current_temp": "Temperatura",
"ace_dry_dialog_cancel": "Annulla",
"ace_dry_dialog_confirm": "Conferma",
"ace_dry_dialog_custom_name": "Nome personalizzato",
"ace_dry_dialog_reset_default": "Ripristina predefiniti",
"ace_dry_dialog_save_restart": "Salva e riavvia",
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
"ace_dry_dialog_time": "Tempo rim. (h:m:s)",
"ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore",
"ace_dry_dryer": "Essiccatore",
"ace_dry_duration": "Durata (min)",
"ace_dry_enable": "Abilita essiccazione",
"ace_dry_humidity": "Umidità",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "Personalizzato",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ Avvia",
"ace_dry_status_off": "Stato: Spento",
"ace_dry_status_on": "Stato: Attivo",
"ace_dry_status_remaining": "Rimanente",
"ace_dry_stop": "■ Ferma",
"ace_dry_temp": "Temperatura (°C)",
"ace_dry_temp_line": "Temperatura di essiccazione",
"ace_dry_time_line": "Tempo di essiccazione",
"ace_dry_ui_pending": "(Solo interfaccia, backend a seguire)",
"add_printer": "Aggiungi stampante",
"ams_empty": "Vuoto",
"ams_no_data": "Nessun dato ricevuto dall' AMS",
"apd_cancel": "Annulla",
"apd_confirm": "Aggiungi",
"apd_err_ip": "Inserisci un indirizzo IP",
"apd_fetching": "Recupero dati dalla stampante…",
"apd_lbl_ip": "IP stampante",
"apd_lbl_name": "Nome (opzionale)",
"apd_placeholder_name": "es. Kobra X Soggiorno",
"apd_success": "Stampante aggiunta, riavvio del bridge in corso…",
"apd_title": "Aggiungi stampante",
"btn_cam_start": "▶ Camera",
"btn_cam_start2": "▶ Avvia",
"btn_cam_stop": "◼ Camera",
"btn_cam_stop2": "◼ Ferma",
"btn_cancel": "✕ Stop",
"btn_cancel_generic": "Annulla",
"btn_confirm_generic": "Conferma",
"btn_connect": "⚡ Connetti",
"btn_delete": "Elimina",
"btn_disable_motors": "Spegni motori",
"btn_disconnect": "✕ Disconnetti",
"btn_home_all": "Home generale",
"btn_home_xy": "Home XY",
"btn_home_z": "Home Z",
"btn_pause": "⏸ Pausa",
"btn_resume": "▶ Riprendi",
"cam_placeholder": "📷 Camera non avviata",
"cam_stream_unavailable": "Flusso video non disponibile",
"card_ace_dry": "Essiccazione ACE",
"card_ams": "Filamento",
"card_cam": "Camera",
"card_light_fan": "Ventola",
"card_progress": "Avanzamento",
"card_speed": "Velocità di stampa",
"card_temps": "Temperature",
"confirm_cancel": "Annullare davvero la stampa?",
"fd_cancel": "Annulla",
"fd_no_matching_material": "Nessun materiale corrispondente",
"fd_no_slots_msg": "Nessuno slot AMS caricato.{br}Avviare comunque la stampa?",
"fd_objects_hint": "Salta oggetti (opzionale):",
"fd_objects_toggle": "Salta oggetti",
"fd_options_title": "Opzioni di stampa",
"fd_print": "▶ Stampa",
"fd_slot": "Slot",
"fd_slots_hint": "Assegna il canale GCode allo slot AMS:",
"fd_title": "Assegnazione slot",
"fd_used": "USATO",
"file_cancel_btn": "✕ Annulla",
"file_ready_btn": "▶ Avvia stampa",
"file_slots_btn": "🎨 Seleziona slot",
"header_status_complete": "Completato",
"header_status_error": "Errore",
"header_status_printing": "In stampa",
"header_status_standby": "Pronto",
"hint_ip_no_port": "Solo indirizzo IP, senza porta (es. 192.168.1.102)",
"kobra_auto_leveling": "Livellamento automatico",
"kobra_busy": "Occupato",
"kobra_canceled": "Annullato",
"kobra_checking": "Verifica",
"kobra_failed": "Errore",
"kobra_finished": "Finito",
"kobra_free": "Pronto",
"kobra_init": "Inizializzazione",
"kobra_offline": "Offline",
"kobra_paused": "In pausa",
"kobra_pausing": "Pausa in corso...",
"kobra_preheating": "Preriscaldamento",
"kobra_printing": "In stampa",
"kobra_resumed": "Ripreso",
"kobra_resuming": "Ripresa...",
"kobra_stoped": "Arrestato",
"kobra_stopping": "Arresto...",
"kobra_updated": "Aggiornamento",
"label_bed": "Piatto",
"label_fan": "🌀 Ventola",
"label_light": "💡 Luce",
"label_nozzle": "Ugello",
"label_off": "Off",
"label_on_off": "On / Off",
"label_set": "Imposta",
"label_slot": "Slot",
"label_speed": "Velocità",
"label_step": "Ampiezza passo:",
"label_target_c": "Target:",
"lbl_conn_error": "Errore di connessione:",
"lbl_elapsed": "Trascorso:",
"lbl_feed": "Carica",
"lbl_layers": "Layer",
"lbl_light": "💡 Luce",
"lbl_remaining": "Rimanente:",
"lbl_slicer_time": "Stima slicer:",
"lbl_spoolman_sync_rate": "Frequenza sync (s, 0=disatt.)",
"lbl_spoolman_url": "URL server",
"lbl_unload": "Rimuovi",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ Auto",
"log_axis": "Asse",
"log_bed": "Piatto →",
"log_cam_start": "Camera avviata:",
"log_cam_stop": "Camera arrestata",
"log_clear": "✕ Cancella",
"log_delete_failed": "Eliminazione non riuscita",
"log_dir_all": "Tutti",
"log_dir_label": "Dir:",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ Scarica",
"log_error": "Errore:",
"log_fan": "Ventola →",
"log_filter_placeholder": "Filtra…",
"log_home": "Home",
"log_home_all": "Home generale",
"log_light_off": "Luce spenta",
"log_light_on": "Luce accesa",
"log_lvl_err": "⛔ Errori",
"log_lvl_label": "Livello:",
"log_lvl_warn": "⚠ Avvisi",
"log_nozzle": "Ugello →",
"log_poll_error": "Errore di sincronizzazione:",
"log_print_action": "Stampa:",
"log_print_start": "Inizio stampa:",
"log_topic_ams": "AMS",
"log_topic_info": "Info",
"log_topic_label": "Argomento:",
"log_topic_print": "Stampa",
"log_topic_status": "Stato",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "Browser",
"nav_console": "Console",
"nav_dashboard": "Dashboard",
"nav_extras": "Luce / Ventola",
"nav_motion": "Movimento",
"nav_print": "Stampa",
"nav_printers": "Stampanti",
"nav_settings": "Impostazioni",
"nav_temps": "Temperature",
"orca_profile_done": "Importato",
"orca_profile_dropmsg": "Trascina qui o fai clic",
"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/&lt;id&gt;/filament/</i>",
"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_list_label": "Attualmente importati",
"orca_profile_section": "Profili OrcaSlicer",
"orca_profile_skipped": "saltato",
"orca_profile_uploading": "Caricamento in corso…",
"orca_profile_user_empty": " nessuno ",
"orca_profile_user_label": "Profili personali",
"panel_ams_title": "Filamento",
"panel_browser_title": "Browser dei file",
"panel_console_title": "Registro eventi",
"panel_extras_camera": "Camera",
"panel_extras_fan": "Ventola",
"panel_extras_light": "Luce",
"panel_motion_xy": "Assi XY",
"panel_motion_z": "Asse Z",
"panel_print_btn_cancel": "✕ Annulla",
"panel_print_btn_pause": "⏸ Pausa",
"panel_print_btn_resume": "▶ Riprendi",
"panel_print_temps_live": "Temperature (In tempo reale)",
"panel_print_title": "Controllo stampa",
"panel_temps_bed": "Piatto riscaldato",
"panel_temps_chart": "Cronologia (ultime 60 letture)",
"panel_temps_nozzle": "Ugello",
"print_auto_leveling": "Livellamento automatico",
"printers_active": "● attiva",
"printers_current": "Stampante corrente",
"printers_empty_hint": "Nessuna stampante ancora configurata.",
"printers_loading": "Caricamento in corso…",
"printers_none": "Nessuna stampante configurata.",
"printers_remove": "Rimuovi stampante",
"printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.",
"printers_switch": "Cambia →",
"progress_action_clear": "Cancella",
"progress_action_print": "Stampa",
"progress_action_slots": "Mappa slot",
"settings_auto_leveling": "Livellamento automatico predefinito",
"settings_auto_leveling_label": "Livellamento automatico prima della stampa",
"settings_btn_tooltip": "Impostazioni",
"settings_camera_on_print": "Attiva la camera all'avvio della stampa",
"settings_cat_connection": "Connessione",
"settings_cat_display": "Aspetto",
"settings_cat_filament": "Filamento",
"settings_cat_language": "Lingua",
"settings_cat_printer": "Stampante",
"settings_cat_system": "Sistema",
"settings_cat_theme": "Alterna chiaro / scuro",
"settings_connection": "Connessione",
"settings_default_slot": "Slot predefinito (colore singolo)",
"settings_device_id": "ID dispositivo",
"settings_device_id_hint": "32 caratteri esadecimali",
"settings_device_id_placeholder": "32 caratteri esadecimali",
"settings_filament_mapping": "Mappatura profilo filamento (per slot)",
"settings_filament_mapping_hint": "Profilo Orca fisso per slot AMS. Durante la sincronizzazione dello slicer, il bridge invia questo profilo al posto di \"Generic\".",
"settings_filament_mapping_label": "Mappatura profilo filamento (per slot)",
"settings_filament_mapping_save": "Salva mappatura",
"settings_filament_mapping_save_label": "Salva mappatura",
"settings_file_ready_banner": "Barra di stampa",
"settings_file_ready_dialog": "Finestra di dialogo stampa",
"settings_file_ready_mode": "Dopo il caricamento: Comportamento di avvio stampa",
"settings_integrations": "Integrazioni",
"settings_language": "Lingua",
"settings_mode_id": "ID modalità",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "Porta MQTT",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "Importa profili",
"settings_orca_profiles_label": "Profili OrcaSlicer",
"settings_password": "Password MQTT",
"settings_poll": "Intervallo di sincronizzazione (secondi)",
"settings_poll_interval_hint": "Con che frequenza il bridge interroga lo stato della stampante",
"settings_poll_interval_label": "Intervallo di sincronizzazione (secondi)",
"settings_print": "Impostazioni di stampa",
"settings_printer_ip": "IP stampante",
"settings_printer_name": "Nome stampante",
"settings_printer_name_placeholder": "p. es. Kobra X Sala",
"settings_save": "Salva e riavvia",
"settings_slot_auto": "Auto (tutti gli slot caricati)",
"settings_theme_toggle": "Attiva/disattiva chiaro / scuro",
"settings_title": "Impostazioni",
"settings_username": "Nome utente MQTT",
"settings_vendor_filter_placeholder": "Cerca produttori…",
"settings_version": "Versione",
"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_label": "Produttori visibili (menu del profilo)",
"settings_visible_vendors_save": "Salva selezione",
"settings_visible_vendors_save_label": "Salva selezione",
"settings_web_upload_warning": "Mostra un avviso quando si stampano caricamenti web",
"sf_all": "Tutti",
"sf_err": "✗ Fallito",
"sf_new": "Nuovo",
"sf_ok": "✓ Completato",
"skip_already": "saltato",
"skip_btn_label": "Oggetti",
"skip_cancel": "Annulla",
"skip_confirm": "Salta",
"skip_confirm_btn": "Salta",
"skip_hint": "Deseleziona gli oggetti che non vuoi più stampare:",
"skip_no_objects": "Nessun oggetto in questa stampa.",
"skip_select_at_least_one": "Seleziona almeno un oggetto.",
"skip_sending": "Invio in corso …",
"skip_success": "Gli oggetti verranno saltati.",
"skip_title": "✂ Salta oggetti",
"slot_edit_color": "Colore",
"slot_edit_custom": "es. PLA, PETG, ABS…",
"slot_edit_load": "⬇ Carica",
"slot_edit_material": "Materiale",
"slot_edit_ok": "Slot AMS",
"slot_edit_profile": "Profilo OrcaSlicer",
"slot_edit_profile_default": "— Generico (predefinito) —",
"slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"",
"slot_edit_save": "💾 Salva",
"slot_edit_title": "Modifica slot",
"slot_edit_unload": "⬆ Rimuovi",
"speed_normal": "⚡ Normale",
"speed_silent": "🐢 Silenzioso",
"speed_sport": "🚀 Sport",
"ss_date": "↓ Data",
"ss_dur": "⏱ Tempo di stampa",
"ss_name": "Nome AZ",
"store_delete_confirm": "Eliminare il file?",
"store_download": "⬇ Scarica",
"store_empty": "Nessun file caricato.",
"store_estimate": "Stima",
"store_never": "mai stampato",
"store_no_results": "Nessun file trovato.",
"store_print": "▶ Stampa",
"store_print_confirm": "Stampare il file?",
"store_refresh": "↻ Aggiorna",
"store_search_placeholder": "🔍 Cerca…",
"store_upload_busy": "⏳ Caricamento in corso…",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "sfoglia",
"store_upload_label_prefix": "Trascina il GCode qui o ",
"store_upload_only_gcode": "✗ Sono consentiti solo file GCode (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "Annulla",
"store_web_verify_confirm": "Conferma",
"store_web_verify_msg": "Verifica che questo file sia stato creato per Anycubic Kobra X.",
"store_web_verify_title": "Verifica file",
"update_apply": "Installa ora",
"update_applying": "Download in corso...",
"update_available": "disponibile",
"update_check": "Controlla aggiornamenti",
"update_checking": "Verifica in corso...",
"update_error": "Errore",
"update_none": "Già aggiornato",
"update_restarting": "Riavvio in corso..."
}

View File

@@ -1,327 +1,248 @@
{
"ace_dry_auto_refill": "自动补料",
"ace_dry_chart": "历史 (温度/湿度)",
"ace_dry_current_temp": "温度",
"ace_dry_dialog_cancel": "取消",
"ace_dry_dialog_confirm": "确认",
"ace_dry_dialog_custom_name": "自定义名称",
"ace_dry_dialog_reset_default": "恢复默认",
"ace_dry_dialog_save_restart": "保存并重启",
"ace_dry_dialog_temp": "温度 (30-80°C)",
"ace_dry_dialog_time": "剩余时间 (h:m:s)",
"ace_dry_dialog_title": "烘干温度/时间设置",
"header_status_standby": "就绪",
"header_status_printing": "打印中",
"header_status_complete": "完成",
"header_status_error": "错误",
"kobra_free": "就绪",
"kobra_busy": "忙碌",
"kobra_printing": "打印中",
"kobra_preheating": "预热中",
"kobra_auto_leveling": "自动调平",
"kobra_checking": "检查中",
"kobra_updated": "更新中",
"kobra_init": "初始化中",
"kobra_pausing": "暂停中...",
"kobra_paused": "已暂停",
"kobra_resuming": "恢复中...",
"kobra_resumed": "已恢复",
"kobra_stopping": "停止中...",
"kobra_stoped": "已停止",
"kobra_finished": "已完成",
"kobra_failed": "错误",
"kobra_canceled": "已取消",
"kobra_offline": "离线",
"nav_dashboard": "仪表盘",
"nav_print": "打印",
"nav_temps": "温度",
"nav_motion": "运动",
"nav_ams": "AMS",
"nav_extras": "灯光 / 风扇",
"nav_console": "控制台",
"card_progress": "进度",
"card_temps": "温度",
"card_light_fan": "风扇",
"card_speed": "打印速度",
"card_cam": "相机",
"lbl_elapsed": "已用时间:",
"lbl_remaining": "剩余时间:",
"lbl_slicer_time": "切片预估:",
"lbl_layers": "层",
"speed_silent": "🐢 静音",
"speed_normal": "⚡ 标准",
"speed_sport": "🚀 运动",
"lbl_light": "💡 灯光",
"lbl_feed": "进料",
"lbl_unload": "退料",
"card_ace_dry": "ACE 烘干",
"ace_dry_dryer": "烘干机",
"ace_dry_duration": "时长 (分钟)",
"ace_dry_enable": "启用烘干",
"ace_dry_humidity": "湿度",
"ace_dry_preset_abs_asa": "ABS / ASA",
"ace_dry_preset_custom": "自定义",
"ace_dry_preset_pa_pc": "PA / PC",
"ace_dry_preset_petg": "PETG",
"ace_dry_preset_pla": "PLA",
"ace_dry_preset_pla_plus": "PLA+",
"ace_dry_preset_tpu": "TPU",
"ace_dry_start": "▶ 启动",
"ace_dry_status_off": "状态: 关闭",
"ace_dry_status_on": "状态: 运行中",
"ace_dry_status_remaining": "剩余",
"ace_dry_stop": "■ 停止",
"ace_dry_humidity": "湿度",
"ace_dry_current_temp": "温度",
"ace_dry_chart": "历史 (温度/湿度)",
"ace_dry_temp": "温度 (°C)",
"ace_dry_duration": "时长 (分钟)",
"ace_dry_start": "▶ 启动",
"ace_dry_stop": "■ 停止",
"ace_dry_auto_refill": "自动补料",
"ace_dry_enable": "启用烘干",
"ace_dry_temp_line": "烘干温度",
"ace_dry_time_line": "烘干时间",
"ace_dry_ui_pending": "(仅 UI后端稍后支持)",
"add_printer": "添加打印机",
"ams_empty": "",
"ams_no_data": "未收到 AMS 数据",
"apd_cancel": "取消",
"apd_confirm": "添加",
"apd_err_ip": "请输入 IP 地址",
"apd_fetching": "正在从打印机获取数据…",
"apd_lbl_ip": "打印机 IP",
"apd_lbl_name": "名称 (可选)",
"apd_placeholder_name": "例如 Kobra X 客厅",
"apd_success": "打印机已添加Bridge 正在重启…",
"apd_title": "添加打印机",
"btn_cam_start": "▶ 相机",
"btn_cam_start2": "▶ 启动",
"btn_cam_stop": "◼ 相机",
"btn_cam_stop2": "◼ 停止",
"btn_cancel": "✕ 停止",
"btn_cancel_generic": "取消",
"btn_confirm_generic": "确认",
"btn_connect": "⚡ 连接",
"btn_delete": "删除",
"btn_disable_motors": "关闭电机",
"btn_disconnect": "✕ 断开",
"btn_home_all": "全部回零",
"btn_home_xy": "回零 XY",
"btn_home_z": "回零 Z",
"btn_pause": "⏸ 暂停",
"btn_resume": "▶ 继续",
"ace_dry_dialog_title": "烘干温度/时间设置",
"ace_dry_dialog_temp": "温度 (30-80°C)",
"ace_dry_dialog_time": "剩余时间 (h:m:s)",
"ace_dry_dialog_confirm": "确认",
"ace_dry_dialog_cancel": "取消",
"ace_dry_dialog_save_restart": "保存并重启",
"ace_dry_dialog_custom_name": "自定义名称",
"ace_dry_dialog_reset_default": "恢复默认",
"cam_placeholder": "📷 相机未启动",
"cam_stream_unavailable": "视频流不可用",
"card_ace_dry": "ACE 烘干",
"card_ams": "耗材",
"card_cam": "相机",
"card_light_fan": "风扇",
"card_progress": "进度",
"card_speed": "打印速度",
"card_temps": "温度",
"confirm_cancel": "确定要取消打印吗?",
"fd_cancel": "取消",
"fd_no_matching_material": "无匹配材料",
"fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?",
"fd_objects_hint": "跳过对象 (可选):",
"fd_objects_toggle": "跳过对象",
"fd_options_title": "选项",
"fd_print": "▶ 打印",
"fd_slot": "槽位",
"fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:",
"fd_title": "槽位分配",
"fd_used": "已用",
"file_cancel_btn": "✕ 取消",
"file_ready_btn": "▶ 开始打印",
"file_slots_btn": "🎨 选择槽位",
"header_status_complete": "完成",
"header_status_error": "错误",
"header_status_printing": "打印中",
"header_status_standby": "就绪",
"hint_ip_no_port": "仅填写 IP不要端口 (例如 192.168.1.102)",
"kobra_auto_leveling": "自动调平",
"kobra_busy": "忙碌",
"kobra_canceled": "已取消",
"kobra_checking": "检查中",
"kobra_failed": "错误",
"kobra_finished": "已完成",
"kobra_free": "就绪",
"kobra_init": "初始化中",
"kobra_offline": "离线",
"kobra_paused": "已暂停",
"kobra_pausing": "暂停中...",
"kobra_preheating": "预热中",
"kobra_printing": "打印中",
"kobra_resumed": "已恢复",
"kobra_resuming": "恢复中...",
"kobra_stoped": "已停止",
"kobra_stopping": "停止中...",
"kobra_updated": "更新中",
"btn_cam_start": "▶ 相机",
"btn_cam_stop": "◼ 相机",
"btn_pause": "⏸ 暂停",
"btn_resume": "▶ 继续",
"btn_cancel": "✕ 停止",
"label_nozzle": "喷嘴",
"label_bed": "热床",
"label_fan": "🌀 风扇",
"label_light": "💡 灯光",
"label_nozzle": "喷嘴",
"label_off": "关闭",
"label_on_off": "开 / 关",
"label_set": "设置",
"label_slot": "槽位",
"label_speed": "速度",
"label_step": "步进:",
"label_target_c": "目标:",
"lbl_conn_error": "连接错误:",
"lbl_elapsed": "已用时间:",
"lbl_feed": "进料",
"lbl_layers": "层",
"lbl_light": "💡 灯光",
"lbl_remaining": "剩余时间:",
"lbl_slicer_time": "切片预估:",
"lbl_spoolman_sync_rate": "同步频率0=关闭)",
"lbl_spoolman_url": "服务器地址",
"lbl_unload": "退料",
"lbl_zpos": "Z (mm)",
"log_auto": "⬇ 自动",
"log_axis": "轴",
"log_bed": "热床 →",
"log_cam_start": "相机已启动:",
"log_cam_stop": "相机已停止",
"log_clear": "✕ 清空",
"log_delete_failed": "删除失败",
"log_dir_all": "全部",
"log_dir_label": "方向:",
"log_dir_rx": "RX",
"log_dir_tx": "TX",
"log_download": "⬇ 下载",
"log_error": "错误:",
"log_fan": "风扇 →",
"log_filter_placeholder": "筛选…",
"log_home": "回零",
"log_home_all": "全部回零",
"log_light_off": "灯光已关",
"log_light_on": "灯光已开",
"log_lvl_err": "⛔ 错误",
"log_lvl_label": "级别:",
"log_lvl_warn": "⚠ 警告",
"log_nozzle": "喷嘴 →",
"log_poll_error": "轮询错误:",
"log_print_action": "打印:",
"log_print_start": "打印开始:",
"log_topic_ams": "AMS",
"log_topic_info": "信息",
"log_topic_label": "主题:",
"log_topic_print": "打印",
"log_topic_status": "状态",
"modal_sec_obico": "Obico",
"modal_sec_spoolman": "Spoolman",
"nav_ams": "AMS",
"nav_browser": "浏览器",
"nav_console": "控制台",
"nav_dashboard": "仪表盘",
"nav_extras": "灯光 / 风扇",
"nav_motion": "运动",
"nav_print": "打印",
"nav_printers": "打印机",
"nav_settings": "设置",
"nav_temps": "温度",
"orca_profile_done": "已导入",
"orca_profile_dropmsg": "拖到此处或点击",
"orca_profile_help_html": "上传 OrcaSlicer 耗材文件夹的 <b>ZIP</b> 或单个 <b>.json</b> 文件。<br>在 OrcaSlicer 中: <i>Help → Show Configuration Folder → user/&lt;id&gt;/filament/</i>",
"orca_profile_hint": "导入你自己的 OrcaSlicer 耗材配置(在 Help → Show Configuration Folder 打开用户目录)",
"orca_profile_import_btn": "导入配置",
"orca_profile_import_link": "★ 导入自己的配置…",
"orca_profile_import_title": "导入你的 OrcaSlicer 配置",
"orca_profile_list_label": "已导入",
"orca_profile_section": "OrcaSlicer 配置",
"orca_profile_skipped": "跳过",
"orca_profile_uploading": "上传中…",
"orca_profile_user_empty": "",
"orca_profile_user_label": "自己的配置",
"panel_ams_title": "耗材",
"panel_browser_title": "文件浏览器",
"panel_console_title": "事件日志",
"panel_extras_camera": "相机",
"panel_extras_fan": "风扇",
"panel_extras_light": "灯光",
"panel_motion_xy": "XY 轴",
"panel_motion_z": "Z 轴",
"panel_print_btn_cancel": "✕ 取消",
"panel_print_title": "打印控制",
"panel_print_btn_pause": "⏸ 暂停",
"panel_print_btn_resume": "▶ 继续",
"panel_print_btn_cancel": "✕ 取消",
"panel_print_temps_live": "温度 (实时)",
"panel_print_title": "打印控制",
"label_set": "设置",
"label_off": "关闭",
"panel_temps_nozzle": "喷嘴",
"panel_temps_bed": "热床",
"panel_temps_chart": "历史 (最近 60 次读数)",
"panel_temps_nozzle": "喷嘴",
"print_auto_leveling": "本次打印自动调平",
"printers_active": "● 活动",
"printers_current": "当前打印机",
"printers_empty_hint": "尚未设置打印机。",
"printers_loading": "加载中…",
"printers_none": "未配置打印机。",
"printers_remove": "移除打印机",
"printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。",
"printers_switch": "切换 →",
"progress_action_clear": "清除",
"progress_action_print": "打印",
"progress_action_slots": "分配槽位",
"settings_auto_leveling": "打印前自动调平",
"settings_auto_leveling_label": "打印前自动调平",
"settings_btn_tooltip": "设置",
"settings_camera_on_print": "打印开始时开启相机",
"settings_cat_connection": "连接",
"settings_cat_display": "外观",
"settings_cat_filament": "耗材",
"settings_cat_language": "语言",
"settings_cat_printer": "打印机",
"settings_cat_system": "系统",
"settings_cat_theme": "切换浅色 / 深色",
"settings_connection": "连接",
"settings_default_slot": "默认槽位 (单色)",
"settings_device_id": "设备 ID",
"settings_device_id_hint": "32 个十六进制字符",
"settings_device_id_placeholder": "32 个十六进制字符",
"settings_filament_mapping": "耗材配置映射(每槽位)",
"settings_filament_mapping_hint": "每个 AMS 槽位的固定 Orca 配置。在切片器同步时Bridge 会发送此配置而不是“Generic”。",
"settings_filament_mapping_label": "耗材配置映射(每槽位)",
"settings_filament_mapping_save": "保存映射",
"settings_filament_mapping_save_label": "保存映射",
"settings_file_ready_banner": "打印栏",
"settings_file_ready_dialog": "打印对话框",
"settings_file_ready_mode": "上传后:开始打印行为",
"settings_integrations": "集成",
"settings_language": "语言",
"settings_mode_id": "模式 ID",
"settings_mode_id_placeholder": "20030",
"settings_mqtt_port": "MQTT 端口",
"settings_mqtt_username_placeholder": "userXXXXXXXX",
"settings_orca_profiles_import": "导入配置文件",
"settings_orca_profiles_label": "OrcaSlicer 配置文件",
"settings_password": "MQTT 密码",
"settings_poll": "轮询间隔(秒)",
"settings_poll_interval_hint": "Bridge 查询打印机状态的频率",
"settings_poll_interval_label": "轮询间隔(秒)",
"settings_print": "打印设置",
"settings_printer_ip": "打印机 IP",
"settings_printer_name": "打印机名称",
"settings_printer_name_placeholder": "例如 Kobra X 左",
"settings_save": "保存并重启",
"settings_slot_auto": "自动 (所有已装载槽位)",
"settings_theme_toggle": "切换浅色 / 深色",
"label_target_c": "目标:",
"panel_motion_xy": "XY 轴",
"panel_motion_z": "Z 轴",
"label_step": "步进:",
"btn_home_z": "回零 Z",
"btn_home_xy": "回零 XY",
"btn_home_all": "全部回零",
"btn_disable_motors": "关闭电机",
"panel_ams_title": "耗材",
"card_ams": "耗材",
"ams_no_data": "未收到 AMS 数据",
"label_slot": "槽位",
"ams_empty": "",
"panel_extras_light": "灯光",
"panel_extras_fan": "风扇",
"panel_extras_camera": "相机",
"btn_cam_start2": "▶ 启动",
"btn_cam_stop2": "◼ 停止",
"panel_console_title": "事件日志",
"log_light_on": "灯光已开",
"log_light_off": "灯光已关",
"log_fan": "风扇 →",
"log_nozzle": "喷嘴 →",
"log_bed": "热床 →",
"log_axis": "",
"log_home": "回零",
"log_home_all": "全部回零",
"log_cam_start": "相机已启动:",
"log_cam_stop": "相机已停止",
"log_poll_error": "轮询错误:",
"log_error": "错误:",
"confirm_cancel": "确定要取消打印吗?",
"settings_title": "设置",
"settings_username": "MQTT 用户名",
"settings_vendor_filter_placeholder": "搜索厂商…",
"settings_connection": "连接",
"settings_print": "打印设置",
"settings_poll": "轮询间隔",
"settings_version": "版本",
"settings_visible_vendors": "可见厂商(配置下拉框)",
"settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。",
"settings_visible_vendors_label": "可见厂商(配置下拉框)",
"settings_visible_vendors_save": "保存选择",
"settings_visible_vendors_save_label": "保存选择",
"settings_save": "保存并重启",
"settings_printer_name": "打印机名称",
"settings_printer_ip": "打印机 IP",
"settings_mqtt_port": "MQTT 端口",
"settings_username": "MQTT 用户名",
"settings_password": "MQTT 密码",
"settings_device_id": "设备 ID",
"settings_mode_id": "模式 ID",
"hint_ip_no_port": "仅填写 IP不要端口 (例如 192.168.1.102)",
"settings_default_slot": "默认槽位 (单色)",
"settings_slot_auto": "自动 (所有已装载槽位)",
"settings_auto_leveling": "打印前自动调平",
"settings_vibration_compensation": "打印前共振补偿",
"settings_flow_calibration": "打印前流量校准",
"settings_camera_on_print": "打印开始时开启相机",
"settings_web_upload_warning": "打印网页上传文件时显示警告",
"sf_all": "全部",
"sf_err": "✗ 失败",
"sf_new": "新",
"sf_ok": "✓ 已完成",
"skip_already": "已跳过",
"skip_btn_label": "对象",
"skip_cancel": "取消",
"skip_confirm": "跳过",
"skip_confirm_btn": "跳过",
"settings_timelapse_local": "打印期间本地延时摄影",
"settings_timelapse_interval": "拍摄间隔(秒)",
"settings_timelapse_printer": "启用打印机延时摄影(实验性)",
"settings_layout": "仪表板布局",
"settings_layout_1col": "单列",
"settings_layout_2col": "双列",
"store_tab_files": "文件",
"timelapse_tab": "延时视频",
"timelapse_empty": "暂无延时视频",
"timelapse_recording": "录制中…",
"timelapse_processing": "编码中…",
"confirm_delete_timelapse": "删除此延时视频?",
"update_check": "检查更新",
"update_checking": "检查中...",
"update_available": "可用",
"update_none": "已是最新版本",
"update_apply": "立即安装",
"update_applying": "下载中...",
"update_restarting": "重启中...",
"update_error": "错误",
"btn_connect": "⚡ 连接",
"btn_disconnect": "✕ 断开",
"lbl_conn_error": "连接错误:",
"slot_edit_title": "编辑槽位",
"slot_edit_color": "颜色",
"slot_edit_material": "材料",
"slot_edit_load": "⬇ 进料",
"slot_edit_unload": "⬆ 退料",
"slot_edit_save": "💾 保存",
"slot_edit_custom": "例如 PLA, PETG, ABS…",
"slot_edit_ok": "AMS 槽位",
"slot_edit_profile": "OrcaSlicer 配置",
"slot_edit_profile_hint": "在 OrcaSlicer 同步时发送具体品牌而不仅仅是“Generic”",
"slot_edit_profile_default": "— 通用 (默认) —",
"log_dir_all": "全部",
"log_lvl_label": "级别:",
"file_ready_btn": "▶ 开始打印",
"file_slots_btn": "🎨 选择槽位",
"file_cancel_btn": "✕ 取消",
"nav_printers": "打印机",
"skip_title": "✂ 跳过对象",
"skip_hint": "取消勾选不想继续打印的对象:",
"skip_btn_label": "对象",
"skip_no_objects": "此打印任务没有对象。",
"skip_already": "已跳过",
"skip_select_at_least_one": "请至少选择一个对象。",
"skip_sending": "发送中 …",
"skip_success": "对象将被跳过。",
"skip_title": "跳过对象",
"slot_edit_color": "颜色",
"slot_edit_custom": "例如 PLA, PETG, ABS…",
"slot_edit_load": "⬇ 进料",
"slot_edit_material": "材料",
"slot_edit_ok": "AMS 槽位",
"slot_edit_profile": "OrcaSlicer 配置",
"slot_edit_profile_default": "— 通用 (默认) —",
"slot_edit_profile_hint": "在 OrcaSlicer 同步时发送具体品牌而不仅仅是“Generic”",
"slot_edit_save": "💾 保存",
"slot_edit_title": "编辑槽位",
"slot_edit_unload": "⬆ 退料",
"speed_normal": "⚡ 标准",
"speed_silent": "🐢 静音",
"speed_sport": "🚀 运动",
"ss_date": "↓ 日期",
"ss_dur": "⏱ 打印时间",
"ss_name": "AZ 名称",
"store_delete_confirm": "删除文件?",
"store_download": "⬇ 下载",
"store_empty": "尚未上传文件。",
"store_estimate": "估算",
"store_never": "从未打印",
"store_no_results": "未找到文件。",
"store_print": "▶ 打印",
"store_print_confirm": "打印文件?",
"store_refresh": "↻ 刷新",
"fd_objects_hint": "跳过对象 (可选):",
"fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:",
"fd_cancel": "取消",
"fd_print": "▶ 打印",
"fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?",
"fd_slot": "槽位",
"fd_no_matching_material": "无匹配材料",
"fd_used": "已用",
"add_printer": "添加打印机",
"apd_lbl_ip": "打印机 IP",
"apd_lbl_name": "名称 (可选)",
"apd_placeholder_name": "例如 Kobra X 客厅",
"apd_cancel": "取消",
"apd_confirm": "添加",
"apd_fetching": "正在从打印机获取数据…",
"apd_success": "打印机已添加Bridge 正在重启…",
"apd_err_ip": "请输入 IP 地址",
"printers_remove": "移除打印机",
"printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。",
"printers_active": "● 活动",
"printers_switch": "切换 →",
"printers_current": "当前打印机",
"printers_loading": "加载中…",
"printers_none": "未配置打印机。",
"printers_empty_hint": "尚未设置打印机。",
"nav_browser": "浏览器",
"panel_browser_title": "文件浏览器",
"store_search_placeholder": "🔍 搜索…",
"store_upload_busy": "⏳ 上传中…",
"store_upload_error": "✗ {error}",
"store_upload_label_browse": "浏览",
"store_upload_label_prefix": "将 GCode 拖到这里或 ",
"store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)",
"store_upload_success": "✓ {file}",
"store_web_verify_abort": "取消",
"store_web_verify_confirm": "确认",
"store_web_verify_msg": "请验证此文件是为Anycubic Kobra X创建的。",
"store_empty": "尚未上传文件。",
"store_refresh": "↻ 刷新",
"store_print": "▶ 打印",
"store_download": "⬇ 下载",
"store_delete_confirm": "删除文件?",
"store_print_confirm": "打印文件?",
"store_web_verify_title": "验证文件",
"update_apply": "立即安装",
"update_applying": "下载中...",
"update_available": "可用",
"update_check": "检查更新",
"update_checking": "检查中...",
"update_error": "错误",
"update_none": "已是最新版本",
"update_restarting": "重启中..."
"store_web_verify_msg": "请确认此文件是为 Anycubic Kobra X 创建的。",
"store_web_verify_confirm": "确认",
"store_web_verify_abort": "取消",
"store_no_results": "未找到文件。",
"store_never": "从未打印",
"store_estimate": "估算",
"store_upload_label_prefix": "将 GCode 拖到这里或 ",
"store_upload_label_browse": "浏览",
"store_upload_busy": "⏳ 上传中…",
"store_upload_success": "✓ {file}",
"store_upload_error": "✗ {error}",
"sf_all": "全部",
"sf_ok": "✓ 已完成",
"sf_err": "✗ 失败",
"sf_new": "新",
"ss_date": "↓ 日期",
"ss_name": "AZ 名称",
"ss_dur": "⏱ 打印时间"
}