forked from viewit/KX-Bridge-Release
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efde35130b | |||
| a31e01d28c | |||
| 7a43698ecc | |||
| 6b12bfb321 | |||
| 823cbfe1a9 | |||
| ce416f3b9a | |||
| 67c013f4ff | |||
| 40f85b1eb6 | |||
| 54ce101f99 | |||
| 3531cad0ef | |||
| f192a9943d | |||
| eb7fd44f68 | |||
| e5b2a19192 | |||
| 2f59a2b02b | |||
| bc9bfb58ea | |||
| ac309d5d3d | |||
| 38d98666c4 | |||
| e7c978a067 | |||
| d7c2dccef5 | |||
| e59550b5a0 | |||
| 5871e851da | |||
| e70e9c82d7 | |||
| be110fd766 | |||
| 77fce988d7 | |||
| fe1815c76f | |||
| 29a4262a2a | |||
| e753bcdb03 | |||
| 73ecb1e618 | |||
| 08d9a7ab0f | |||
| 93f72f0be9 | |||
| 812936d90d | |||
| 326453e2fd | |||
| ffd8ed09d5 | |||
| dc7e92688b | |||
| 3e1ba9df4b | |||
| 41f4700b24 | |||
| 216b2de2c0 | |||
| 394b0e69ab | |||
| 877cddb1ba | |||
| c9043e9630 | |||
| 6165a7f62a | |||
| fa8e0c1491 | |||
| 282c02ae0a | |||
| 72f77d92af | |||
| 3595cf839c | |||
| 2b39cc1a78 | |||
| d20308cf2c | |||
| 710c4831c2 |
31
.claude/agents/changelog.md
Normal file
31
.claude/agents/changelog.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
32
.claude/agents/docker-check.md
Normal file
32
.claude/agents/docker-check.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
23
.claude/agents/moonraker-debug.md
Normal file
23
.claude/agents/moonraker-debug.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
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 0–3
|
||||||
|
- `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)
|
||||||
25
.claude/agents/nightly-prep.md
Normal file
25
.claude/agents/nightly-prep.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
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]
|
||||||
24
.claude/agents/reviewer.md
Normal file
24
.claude/agents/reviewer.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
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 lane1–lane4, kein Slot-Mapping 0–3
|
||||||
|
- 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
|
||||||
26
.claude/agents/test-writer.md
Normal file
26
.claude/agents/test-writer.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
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 lane1–lane4
|
||||||
|
- 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.
|
||||||
12
.claude/settings.json
Normal file
12
.claude/settings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
29
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
29
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
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 -->
|
||||||
|
```
|
||||||
14
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
14
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
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? -->
|
||||||
21
.gitea/pull_request_template.md
Normal file
21
.gitea/pull_request_template.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## 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
|
||||||
132
.gitea/workflows/nightly.yml
Normal file
132
.gitea/workflows/nightly.yml
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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: |
|
||||||
|
VERSION=$(cat VERSION)
|
||||||
|
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-${VERSION}" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Create Gitea Nightly Release
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
VERSION=$(cat VERSION)
|
||||||
|
TAG="nightly-${VERSION}"
|
||||||
|
git config user.name "gitea-actions"
|
||||||
|
git config user.email "actions@it-drui.de"
|
||||||
|
|
||||||
|
# Changelog aus CHANGES.md lesen (wird von release.sh aus dem Dev-Repo generiert)
|
||||||
|
BODY_FILE=$(mktemp)
|
||||||
|
if [ -f CHANGES.md ]; then
|
||||||
|
cat CHANGES.md > "$BODY_FILE"
|
||||||
|
else
|
||||||
|
# Fallback falls CHANGES.md fehlt
|
||||||
|
printf '## KX-Bridge %s -- Nightly Build\n\n' "$VERSION" > "$BODY_FILE"
|
||||||
|
printf '[experimentell] Ungetestete Features, nur fuer Tester geeignet.\n\n' >> "$BODY_FILE"
|
||||||
|
printf '- Automatischer Nightly-Build\n\n---\n\n' >> "$BODY_FILE"
|
||||||
|
printf '### Docker-Image aktualisieren\n\n```bash\ndocker compose pull && docker compose up -d\n```\n\n' >> "$BODY_FILE"
|
||||||
|
printf 'Image-Tag: `gitea.it-drui.de/viewit/kx-bridge:nightly`\n' >> "$BODY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tag setzen
|
||||||
|
git tag -f "$TAG"
|
||||||
|
git push https://gitea-actions:${GITEA_TOKEN}@gitea.it-drui.de/viewit/KX-Bridge-Release.git "$TAG" --force
|
||||||
|
|
||||||
|
# curl installieren (BusyBox wget kann kein DELETE/POST mit Headers)
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
if ! apk add --no-cache curl 2>/dev/null; then
|
||||||
|
wget -qO /usr/local/bin/curl \
|
||||||
|
"https://github.com/moparisthebest/static-curl/releases/download/v8.6.0/curl-amd64"
|
||||||
|
chmod +x /usr/local/bin/curl
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Altes Release loeschen falls vorhanden
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases/tags/${TAG}" \
|
||||||
|
2>/dev/null || true
|
||||||
|
|
||||||
|
# Release erstellen (JSON-Body via awk escapen)
|
||||||
|
BODY_JSON=$(awk '{
|
||||||
|
gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t");
|
||||||
|
printf "%s\\n", $0
|
||||||
|
}' "$BODY_FILE" | awk 'BEGIN{printf "\""} {printf "%s", $0} END{printf "\""}')
|
||||||
|
JSON_PAYLOAD="{\"tag_name\":\"${TAG}\",\"name\":\"KX-Bridge ${VERSION} Nightly\",\"body\":${BODY_JSON},\"draft\":false,\"prerelease\":true}"
|
||||||
|
printf '%s' "$JSON_PAYLOAD" > /tmp/release_body.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data-binary @/tmp/release_body.json \
|
||||||
|
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases"
|
||||||
|
rm -f "$BODY_FILE" /tmp/release_body.json
|
||||||
34
.gitea/workflows/pr-check.yml
Normal file
34
.gitea/workflows/pr-check.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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/') != '' }}
|
||||||
74
.gitea/workflows/release.yml
Normal file
74
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
name: Stable Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Strip fuehrendes 'v' fuer den Image-Tag (VERSION-Datei hat kein 'v').
|
||||||
|
- name: Build & push (amd64 + arm64)
|
||||||
|
run: |
|
||||||
|
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||||
|
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}" \
|
||||||
|
.
|
||||||
|
|
||||||
|
# Hinweis: Das Gitea-Release (inkl. englischem Auto-Changelog + Binaries als
|
||||||
|
# Assets) erstellt release.sh synchron, da es die lokal via CodeBuilder
|
||||||
|
# gebauten Binaries direkt hochlaedt. Dieser Workflow baut nur das Docker-Image.
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -17,3 +17,8 @@ config/*.ini
|
|||||||
data/
|
data/
|
||||||
|
|
||||||
!data/orca_filaments.json
|
!data/orca_filaments.json
|
||||||
|
.runner-token
|
||||||
|
|
||||||
|
# Dev-only Dateien — nicht ins öffentliche Repo
|
||||||
|
CLAUDE.md
|
||||||
|
release.sh
|
||||||
|
|||||||
33
CHANGES.md
Normal file
33
CHANGES.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## KX-Bridge 0.9.27-nightly9 — Nightly Build
|
||||||
|
|
||||||
|
[experimentell] Ungetestete Features, nur für Tester geeignet.
|
||||||
|
|
||||||
|
### Änderungen seit `v0.9.26`
|
||||||
|
|
||||||
|
- fix(spoolman): Status-Dot beim Seitenload initialisieren
|
||||||
|
- chore: CHANGES.md mit echtem Changelog aus Dev-Repo ins Release-Repo schreiben
|
||||||
|
- fix(spoolman): SPOOLMAN_* env-Cache bei Restart leeren, Status-Dot nach Save aktualisieren
|
||||||
|
- chore: README.es.md + CONTRIBUTING.md in release.sh-Sync aufnehmen
|
||||||
|
- docs: Nightly-Sektion, Wartungshinweis + CONTRIBUTING.md, FR/IT-Sprachen, Downloads-Badge
|
||||||
|
- fix(update): Nightly-Vergleich auf Versions-String umstellen (statt Datum)
|
||||||
|
- feat(update): Nightly-Track vom Stable-Track trennen
|
||||||
|
- fix(release): nightly immer auf nightly-Branch pushen, kein master-Push
|
||||||
|
- fix(camera): exponentielles Backoff bei ffmpeg-Fehler + /api/camera/reset + ↺-Button
|
||||||
|
- fix(release): Nightly-Release vom nightly-Branch erlauben
|
||||||
|
- feat(i18n): fehlende UI-Übersetzungen ergänzt + Keys alphabetisch sortiert (PR #70 @fenopy)
|
||||||
|
- fix: config/config.ini.example beim Release-Sync mitübertragen (Issue #72)
|
||||||
|
- feat(ui): Integrationen-Tab in Settings (Spoolman + Obico-Hinweis)
|
||||||
|
- feat(stack): KobraX Full Stack Compose für Portainer (KX-Bridge + Obico + Spoolman)
|
||||||
|
- feat(release): Nightly/Stable Release-Workflow mit eigenem Docker-Tag
|
||||||
|
- feat(spoolman): optionale Spoolman-Filamentverbrauch-Integration (PR #65, @p2l)
|
||||||
|
- fix(release): Artifact-Download per HTTP statt lokalem Dateipfad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Docker-Image aktualisieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Image-Tag: `gitea.it-drui.de/viewit/kx-bridge:nightly`
|
||||||
102
CONTRIBUTING.md
Normal file
102
CONTRIBUTING.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
```
|
||||||
|
master ← 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 → master` 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.
|
||||||
27
README.de.md
27
README.de.md
@@ -21,7 +21,7 @@ Feedback willkommen.</sub>
|
|||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||||
|
|
||||||
@@ -29,6 +29,11 @@ Feedback willkommen.</sub>
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **Laufende Wartungsarbeiten** — Wir strukturieren das Repository um (Branch-Modell, CI-Workflows, Beitragsprozess). Es kann zu Änderungen bei Branch-Namen, PR-Templates und der Art der Veröffentlichungen kommen. Wir entschuldigen uns für etwaige Unannehmlichkeiten. Handhabung, Workflow und langfristige Wartbarkeit werden dadurch deutlich verbessert.
|
||||||
|
>
|
||||||
|
> 👉 Möchtest du beitragen? Bitte zuerst [CONTRIBUTING.md](CONTRIBUTING.md) lesen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Was kann KX-Bridge?
|
## ✨ Was kann KX-Bridge?
|
||||||
@@ -45,7 +50,7 @@ Feedback willkommen.</sub>
|
|||||||
| 🧩 | **Multi-Printer** — mehrere Drucker in **einer** Bridge-Instanz, Umschalten per Dropdown |
|
| 🧩 | **Multi-Printer** — mehrere Drucker in **einer** Bridge-Instanz, Umschalten per Dropdown |
|
||||||
| ➕ | **Drucker hinzufügen per Klick** — nur die IP eingeben, Zugangsdaten werden automatisch importiert |
|
| ➕ | **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 |
|
| 🔁 | **Robuster MQTT-Reconnect** — Bridge überlebt nächtlichen Drucker-Reboot ohne manuellen Neustart |
|
||||||
| 🌐 | **Mehrsprachiges UI** — DE / EN / ES / 中文, Browser-Sprache automatisch erkannt |
|
| 🌐 | **Mehrsprachiges UI** — DE / EN / ES / FR / IT / 中文, Browser-Sprache automatisch erkannt |
|
||||||
| 🔄 | **Self-Update** — neue Versionen direkt im Browser installieren |
|
| 🔄 | **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); für korrekten Vendor-Match pro Slot den [OrcaSlicer-KX-Build](#-empfohlener-slicer) nutzen |
|
||||||
|
|
||||||
@@ -184,6 +189,24 @@ docker compose up -d --build # lokal selber bauen (statt zu pullen)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🌙 Nightly-Builds
|
||||||
|
|
||||||
|
Nightly-Builds enthalten die neuesten unveröffentlichten Features und werden automatisch bei jedem Entwicklungs-Push gebaut.
|
||||||
|
Sie können instabil sein — für Tests oder frühen Zugriff auf neue Funktionen geeignet.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Zurück zum stabilen Release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🩹 Troubleshooting
|
## 🩹 Troubleshooting
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
147
README.dev.md
Normal file
147
README.dev.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<p align="center"><img src="knlogo.png" alt="KX-Bridge Logo" width="180"/></p>
|
||||||
|
|
||||||
|
# KX-Bridge – Dev Branch
|
||||||
|
|
||||||
|
> **Achtung:** Dies ist der Entwicklungs-Branch. Builds hier sind experimentell und nicht für den produktiven Einsatz geeignet.
|
||||||
|
> Für stabile Releases → [KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Versionsschema
|
||||||
|
|
||||||
|
Dev-Builds verwenden das Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
<basis-version>-dev+<git-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beispiel:** `0.9.1-dev+04a6a20`
|
||||||
|
|
||||||
|
- `0.9.1` – Basis der aktuellen stabilen Version
|
||||||
|
- `-dev` – kennzeichnet den Entwicklungs-Branch
|
||||||
|
- `+04a6a20` – 7-stelliger Git-Commit-Hash, eindeutig je Build
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dev-Binaries testen
|
||||||
|
|
||||||
|
Dev-Releases sind auf Gitea als Pre-Releases verfügbar:
|
||||||
|
[Dev-Releases](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
|
### Docker (empfohlen)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> -b dev
|
||||||
|
cd kobrax
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux-Binary
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dev-Release herunterladen (kx-bridge-linux.zip)
|
||||||
|
unzip kx-bridge-linux.zip
|
||||||
|
chmod +x kx-bridge
|
||||||
|
./kx-bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
`config/config.ini` und `data/` (SQLite + GCode-Store) werden **neben dem Binary**
|
||||||
|
angelegt. Beim Erststart ohne Drucker zeigt die UI auf `http://localhost:7125` den
|
||||||
|
Drucker-Tab mit "+ Drucker hinzufügen" — dort nur die IP eingeben, der Rest wird
|
||||||
|
automatisch importiert.
|
||||||
|
|
||||||
|
### Windows-EXE
|
||||||
|
|
||||||
|
```
|
||||||
|
# Dev-Release herunterladen (kx-bridge-windows.zip)
|
||||||
|
# kx-bridge.exe starten — config/ und data/ liegen daneben
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update-Kanal
|
||||||
|
|
||||||
|
Dev-Versionen prüfen automatisch auf neue **Dev-Releases** — nicht auf stabile Releases.
|
||||||
|
Im Settings-Modal → „Auf Updates prüfen" zeigt den neuesten Dev-Build an.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aktive Entwicklung (Stand 2026-05-10)
|
||||||
|
|
||||||
|
Stand `dev`-Branch über v0.9.7 hinaus:
|
||||||
|
|
||||||
|
| Feature | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| MMU-Emulation (`/printer/objects/query?mmu`) für OrcaSlicer Filament-Sync | ✅ |
|
||||||
|
| GCode Store (SQLite + Thumbnails) | ✅ |
|
||||||
|
| Browser-Tab mit Suche/Filter/Sortierung | ✅ |
|
||||||
|
| Filament-Dialog: Per-Kanal-Remapping (GCode-Kanal → AMS-Slot) | ✅ |
|
||||||
|
| MQTT Print-Payload `ams_settings.ams_box_mapping` (nested) | ✅ |
|
||||||
|
| Print-History in SQLite | ✅ |
|
||||||
|
| Multi-Printer Support (Drucker-Tab + Header-Dropdown) | ✅ |
|
||||||
|
| **Multi-Printer in einer Bridge-Instanz** (ein Prozess, N Listener) | ✅ |
|
||||||
|
| Drucker-Emulator (`_archive/tools/kx_printer_emulator.py`) | ✅ |
|
||||||
|
| i18n DE/EN für alle neuen UI-Elemente | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Multi-Printer-Setup
|
||||||
|
|
||||||
|
Eine Bridge-Instanz kann jetzt mehrere Drucker gleichzeitig verwalten — ein Prozess,
|
||||||
|
N MQTT-Verbindungen, N HTTP-Listener, geteilte SQLite + GCode-Verzeichnis.
|
||||||
|
|
||||||
|
### Konfiguration
|
||||||
|
|
||||||
|
In `config/config.ini` pro Drucker eine `[printer_N]`-Sektion anlegen:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[printer_1]
|
||||||
|
name = Kobra X
|
||||||
|
printer_ip = <DRUCKER_IP_1>
|
||||||
|
mqtt_port = 9883
|
||||||
|
username = <MQTT_USER>
|
||||||
|
password = <MQTT_PASSWORT>
|
||||||
|
mode_id = 20030
|
||||||
|
device_id = <DEVICE_ID_1>
|
||||||
|
http_port = 7125
|
||||||
|
|
||||||
|
[printer_2]
|
||||||
|
name = Drucker 2
|
||||||
|
printer_ip = <DRUCKER_IP_2>
|
||||||
|
mqtt_port = 9883
|
||||||
|
username = <MQTT_USER>
|
||||||
|
password = <MQTT_PASSWORT>
|
||||||
|
mode_id = 20030
|
||||||
|
device_id = <DEVICE_ID_2>
|
||||||
|
http_port = 7126
|
||||||
|
```
|
||||||
|
|
||||||
|
Credentials per `extract_credentials` oder `fetch_credentials` ermitteln (siehe Haupt-README).
|
||||||
|
|
||||||
|
`http_port` ist optional — Default ist `7125 + (N-1)`. Wenn keine `[printer_N]`-Sektionen
|
||||||
|
existieren, läuft die Bridge im klassischen Einzel-Modus mit `[connection]` und einem Listener.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
`docker-compose.yml` exposed jetzt einen Port-Range `7125-7130`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ports:
|
||||||
|
- "7125-7130:7125-7130"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
# Drucker 1: http://localhost:7125
|
||||||
|
# Drucker 2: http://localhost:7126
|
||||||
|
```
|
||||||
|
|
||||||
|
OrcaSlicer / Mainsail richten den Klipper-Endpunkt pro Drucker auf den jeweiligen Port —
|
||||||
|
keine Slicer-Anpassungen nötig.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stabile Version
|
||||||
|
|
||||||
|
Für den produktiven Einsatz bitte die stabile Version verwenden:
|
||||||
|
[→ Zum stabilen Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
61
README.es.md
61
README.es.md
@@ -20,7 +20,7 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
|||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||||
|
|
||||||
@@ -28,6 +28,11 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **Trabajos de mantenimiento en curso** — 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 las molestias. El manejo, el flujo de trabajo y la mantenibilidad a largo plazo mejorarán considerablemente.
|
||||||
|
>
|
||||||
|
> 👉 ¿Quieres contribuir? Por favor lee [CONTRIBUTING.md](CONTRIBUTING.md) primero.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Características
|
## ✨ Características
|
||||||
@@ -44,7 +49,7 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
|||||||
| 🧩 | **Multi-impresora** — múltiples impresoras en **una** instancia del puente, cambia mediante un menú desplegable |
|
| 🧩 | **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 |
|
| ➕ | **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 |
|
| 🔁 | **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 |
|
| 🌐 | **Interfaz multilingüe** — DE / EN / ES / FR / IT / 中文, detecta automáticamente el idioma del navegador |
|
||||||
| 🔄 | **Actualización automática** — instala nuevas versiones directamente desde el 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); usa el [build OrcaSlicer-KX](#-slicer-recomendado) para emparejamiento correcto de vendor por ranura |
|
||||||
|
|
||||||
@@ -66,43 +71,17 @@ docker compose up -d
|
|||||||
|
|
||||||
**Binario Linux (sin Docker):**
|
**Binario Linux (sin Docker):**
|
||||||
```bash
|
```bash
|
||||||
chmod +x kx-bridge-linux-amd64 && ./kx-bridge-linux-amd64
|
chmod +x kx-bridge && ./kx-bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
**EXE Windows (sin Docker):**
|
**EXE Windows (sin Docker):**
|
||||||
```
|
```
|
||||||
kx-bridge.exe
|
kx-bridge.exe
|
||||||
```
|
```
|
||||||
|
> `config\` y `data\` se crean junto al EXE — instalación portátil.
|
||||||
|
|
||||||
> ⚠️ **Certificados TLS necesarios para el binario standalone**
|
> 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.
|
||||||
> 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.
|
|
||||||
|
|
||||||
**Python directamente:**
|
**Python directamente:**
|
||||||
```bash
|
```bash
|
||||||
@@ -208,6 +187,24 @@ docker compose up -d --build # recompilar localmente (en lugar de desc
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🌙 Builds nocturnos (Nightly)
|
||||||
|
|
||||||
|
Los builds nocturnos contienen las últimas funciones no publicadas y se generan automáticamente en cada push de desarrollo.
|
||||||
|
Pueden ser inestables — úsalos para pruebas o acceso anticipado a nuevas funciones.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Volver a la versión estable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🩹 Solución de problemas
|
## 🩹 Solución de problemas
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -20,7 +20,7 @@ officially tested or supported. Feedback welcome.</sub>
|
|||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||||
|
|
||||||
@@ -28,6 +28,11 @@ officially tested or supported. Feedback welcome.</sub>
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **Ongoing maintenance work** — We are restructuring the repository (branch model, CI workflows, contribution process). You may notice changes to branch names, PR templates, and how releases are published. We apologise for any inconvenience. Handling, workflow, and long-term maintainability will be significantly improved as a result.
|
||||||
|
>
|
||||||
|
> 👉 Want to contribute? Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
@@ -44,7 +49,7 @@ officially tested or supported. Feedback welcome.</sub>
|
|||||||
| 🧩 | **Multi-printer** — multiple printers in **one** bridge instance, switch via dropdown |
|
| 🧩 | **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 |
|
| ➕ | **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 |
|
| 🔁 | **Robust MQTT reconnect** — bridge survives overnight printer reboots without manual restart |
|
||||||
| 🌐 | **Multi-language UI** — DE / EN / ES / 中文, auto-detect browser locale |
|
| 🌐 | **Multi-language UI** — DE / EN / ES / FR / IT / 中文, auto-detect browser locale |
|
||||||
| 🔄 | **Self-update** — install new versions directly in the browser |
|
| 🔄 | **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); pair with the [OrcaSlicer-KX build](#-recommended-slicer) for proper per-slot vendor matching |
|
||||||
|
|
||||||
@@ -182,6 +187,24 @@ docker compose up -d --build # rebuild locally (instead of pulling)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🌙 Nightly Builds
|
||||||
|
|
||||||
|
Nightly builds contain the latest unreleased features and are built automatically on every development push.
|
||||||
|
They may be unstable — use them for testing or early access to new functionality.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To go back to the stable release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🩹 Troubleshooting
|
## 🩹 Troubleshooting
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
31
agents.md
Normal file
31
agents.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 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 lane1–lane4
|
||||||
|
- Registry: `gitea.it-drui.de/viewit/kx-bridge`
|
||||||
|
- Default PR target: `nightly`
|
||||||
@@ -31,6 +31,88 @@ default_ams_slot = auto
|
|||||||
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
|
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
|
||||||
auto_leveling = 1
|
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]
|
[bridge]
|
||||||
# Poll-Intervall in Sekunden
|
# Poll-Intervall in Sekunden
|
||||||
poll_interval = 3
|
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
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ _BASE = pathlib.Path(sys.executable).parent if getattr(sys, "frozen", False) els
|
|||||||
CONFIG_SECTION_CONNECTION = "connection"
|
CONFIG_SECTION_CONNECTION = "connection"
|
||||||
CONFIG_SECTION_PRINT = "print"
|
CONFIG_SECTION_PRINT = "print"
|
||||||
CONFIG_SECTION_BRIDGE = "bridge"
|
CONFIG_SECTION_BRIDGE = "bridge"
|
||||||
|
CONFIG_SECTION_SPOOLMAN = "spoolman"
|
||||||
|
|
||||||
|
|
||||||
def _find_config_file() -> pathlib.Path | None:
|
def _find_config_file() -> pathlib.Path | None:
|
||||||
@@ -63,6 +64,8 @@ def _load_config_file(path: pathlib.Path):
|
|||||||
"WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"),
|
"WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"),
|
||||||
"PRINT_START_DIALOG": (CONFIG_SECTION_PRINT, "print_start_dialog"),
|
"PRINT_START_DIALOG": (CONFIG_SECTION_PRINT, "print_start_dialog"),
|
||||||
"BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"),
|
"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():
|
for env_key, (section, option) in mapping.items():
|
||||||
if env_key not in os.environ:
|
if env_key not in os.environ:
|
||||||
@@ -317,3 +320,5 @@ AUTO_LEVELING = int(get("AUTO_LEVELING","1"))
|
|||||||
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT","0"))
|
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT","0"))
|
||||||
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
|
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
|
||||||
PRINT_START_DIALOG = int(get("PRINT_START_DIALOG", get("FILE_READY_DIALOG", "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"))
|
||||||
|
|||||||
210
docker-compose-KX.yml
Normal file
210
docker-compose-KX.yml
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# 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
|
||||||
3
docker-compose.nightly.yml
Normal file
3
docker-compose.nightly.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
services:
|
||||||
|
kx-bridge:
|
||||||
|
image: gitea.it-drui.de/viewit/kx-bridge:nightly
|
||||||
25
docker-compose.portainer-nightly.yml
Normal file
25
docker-compose.portainer-nightly.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# KX-Bridge Nightly — Portainer Stack
|
||||||
|
#
|
||||||
|
# Paste this into Portainer → Stacks → Add stack → Web editor
|
||||||
|
#
|
||||||
|
# Uses the nightly build — may be unstable, for testing new features early.
|
||||||
|
# For production use, see docker-compose.portainer.yml (:latest).
|
||||||
|
|
||||||
|
services:
|
||||||
|
kx-bridge:
|
||||||
|
image: gitea.it-drui.de/viewit/kx-bridge:nightly
|
||||||
|
volumes:
|
||||||
|
- /mnt/dockerdata/kx-nightly/config:/app/config
|
||||||
|
- /mnt/dockerdata/kx-nightly/data:/app/data
|
||||||
|
ports:
|
||||||
|
# Port 7125 = first printer. Add 7126, 7127, … for each additional printer.
|
||||||
|
- "7125:7125"
|
||||||
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# Verzeichnisse müssen auf dem Host existieren:
|
||||||
|
# mkdir -p /mnt/dockerdata/kx-nightly/config /mnt/dockerdata/kx-nightly/data
|
||||||
@@ -602,10 +602,23 @@ class CameraCache:
|
|||||||
self._task_jpeg: "asyncio.Task | None" = None
|
self._task_jpeg: "asyncio.Task | None" = None
|
||||||
self._task_h264: "asyncio.Task | None" = None
|
self._task_h264: "asyncio.Task | None" = None
|
||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
|
self._fail_count_jpeg: int = 0
|
||||||
|
self._fail_count_h264: int = 0
|
||||||
|
|
||||||
def set_url(self, url: str):
|
def set_url(self, url: str):
|
||||||
self._url = url
|
self._url = url
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Backoff-Zähler zurücksetzen und laufende ffmpeg-Prozesse killen."""
|
||||||
|
self._fail_count_jpeg = 0
|
||||||
|
self._fail_count_h264 = 0
|
||||||
|
for proc in (self._proc_jpeg, self._proc_h264):
|
||||||
|
if proc is not None:
|
||||||
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def ensure_running(self):
|
async def ensure_running(self):
|
||||||
if self._proc_jpeg is None or self._proc_jpeg.returncode is not None:
|
if self._proc_jpeg is None or self._proc_jpeg.returncode is not None:
|
||||||
self._task_jpeg = asyncio.create_task(self._run_jpeg_loop())
|
self._task_jpeg = asyncio.create_task(self._run_jpeg_loop())
|
||||||
@@ -629,13 +642,13 @@ class CameraCache:
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self._proc_jpeg = await asyncio.create_subprocess_exec(
|
self._proc_jpeg = await asyncio.create_subprocess_exec(
|
||||||
_find_ffmpeg(), "-loglevel", "quiet",
|
_find_ffmpeg(), "-loglevel", "warning",
|
||||||
*self._input_args(url), "-i", url,
|
*self._input_args(url), "-i", url,
|
||||||
"-vf", "fps=2",
|
"-vf", "fps=2",
|
||||||
"-f", "image2pipe", "-vcodec", "mjpeg", "-q:v", "3",
|
"-f", "image2pipe", "-vcodec", "mjpeg", "-q:v", "3",
|
||||||
"-flush_packets", "1", "pipe:1",
|
"-flush_packets", "1", "pipe:1",
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.DEVNULL,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"CameraCache: ffmpeg-jpeg start fehlgeschlagen: {e}")
|
log.warning(f"CameraCache: ffmpeg-jpeg start fehlgeschlagen: {e}")
|
||||||
@@ -643,6 +656,7 @@ class CameraCache:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
buf = b""
|
buf = b""
|
||||||
|
rc = None
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
chunk = await self._proc_jpeg.stdout.read(self.TS_CHUNK)
|
chunk = await self._proc_jpeg.stdout.read(self.TS_CHUNK)
|
||||||
@@ -676,8 +690,23 @@ class CameraCache:
|
|||||||
await self._proc_jpeg.wait()
|
await self._proc_jpeg.wait()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
rc = self._proc_jpeg.returncode
|
||||||
|
if rc:
|
||||||
|
try:
|
||||||
|
err = await self._proc_jpeg.stderr.read(500)
|
||||||
|
if err:
|
||||||
|
log.warning(f"CameraCache: ffmpeg-jpeg stderr: {err.decode(errors='replace').strip()}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self._proc_jpeg = None
|
self._proc_jpeg = None
|
||||||
await asyncio.sleep(2.0) # restart delay
|
if rc:
|
||||||
|
self._fail_count_jpeg += 1
|
||||||
|
delay = min(2.0 * (2 ** self._fail_count_jpeg), 300.0)
|
||||||
|
log.warning(f"CameraCache: ffmpeg-jpeg exit {rc}, retry in {delay:.0f}s (Versuch {self._fail_count_jpeg})")
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
else:
|
||||||
|
self._fail_count_jpeg = 0
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
async def _run_h264_loop(self):
|
async def _run_h264_loop(self):
|
||||||
"""Hält einen ffmpeg-Prozess am Leben der MPEG-TS an alle Subscriber fanoutet."""
|
"""Hält einen ffmpeg-Prozess am Leben der MPEG-TS an alle Subscriber fanoutet."""
|
||||||
@@ -688,18 +717,19 @@ class CameraCache:
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self._proc_h264 = await asyncio.create_subprocess_exec(
|
self._proc_h264 = await asyncio.create_subprocess_exec(
|
||||||
_find_ffmpeg(), "-loglevel", "quiet",
|
_find_ffmpeg(), "-loglevel", "warning",
|
||||||
*self._input_args(url), "-i", url,
|
*self._input_args(url), "-i", url,
|
||||||
"-c:v", "copy", "-an",
|
"-c:v", "copy", "-an",
|
||||||
"-f", "mpegts", "pipe:1",
|
"-f", "mpegts", "pipe:1",
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.DEVNULL,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"CameraCache: ffmpeg-h264 start fehlgeschlagen: {e}")
|
log.warning(f"CameraCache: ffmpeg-h264 start fehlgeschlagen: {e}")
|
||||||
await asyncio.sleep(3.0)
|
await asyncio.sleep(3.0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
rc = None
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
chunk = await self._proc_h264.stdout.read(self.TS_CHUNK)
|
chunk = await self._proc_h264.stdout.read(self.TS_CHUNK)
|
||||||
@@ -729,8 +759,57 @@ class CameraCache:
|
|||||||
await self._proc_h264.wait()
|
await self._proc_h264.wait()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
rc = self._proc_h264.returncode
|
||||||
|
if rc:
|
||||||
|
try:
|
||||||
|
err = await self._proc_h264.stderr.read(500)
|
||||||
|
if err:
|
||||||
|
log.warning(f"CameraCache: ffmpeg-h264 stderr: {err.decode(errors='replace').strip()}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self._proc_h264 = None
|
self._proc_h264 = None
|
||||||
await asyncio.sleep(2.0)
|
if rc:
|
||||||
|
self._fail_count_h264 += 1
|
||||||
|
delay = min(2.0 * (2 ** self._fail_count_h264), 300.0)
|
||||||
|
log.warning(f"CameraCache: ffmpeg-h264 exit {rc}, retry in {delay:.0f}s (Versuch {self._fail_count_h264})")
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
else:
|
||||||
|
self._fail_count_h264 = 0
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
|
|
||||||
|
class SpoolmanClient:
|
||||||
|
"""Thin synchronous HTTP client for Spoolman filament tracking.
|
||||||
|
|
||||||
|
Designed to be called from daemon threads (poll loop, _on_print callbacks).
|
||||||
|
Uses requests (already in requirements) so no event-loop dependency.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, server_url: str, sync_rate: int = 0):
|
||||||
|
self.server_url = server_url.rstrip("/")
|
||||||
|
self.sync_rate = sync_rate
|
||||||
|
|
||||||
|
def _req(self, method: str, path: str, **kwargs):
|
||||||
|
import requests
|
||||||
|
r = requests.request(method, f"{self.server_url}{path}", timeout=5, **kwargs)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def health_check(self) -> bool:
|
||||||
|
try:
|
||||||
|
self._req("GET", "/api/v1/health")
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def list_spools(self) -> list:
|
||||||
|
return self._req("GET", "/api/v1/spool")
|
||||||
|
|
||||||
|
def use_filament(self, spool_id: int, use_length_mm: float) -> None:
|
||||||
|
"""Report consumed filament length in mm. Spoolman converts to weight
|
||||||
|
using the spool's filament profile density."""
|
||||||
|
self._req("PUT", f"/api/v1/spool/{spool_id}/use",
|
||||||
|
json={"use_length": round(use_length_mm, 2)})
|
||||||
|
|
||||||
|
|
||||||
class KobraXBridge:
|
class KobraXBridge:
|
||||||
@@ -793,6 +872,7 @@ class KobraXBridge:
|
|||||||
"file_ready": "",
|
"file_ready": "",
|
||||||
"print_start_dialog": getattr(args, "print_start_dialog", 1),
|
"print_start_dialog": getattr(args, "print_start_dialog", 1),
|
||||||
"filament_mode": "toolhead",
|
"filament_mode": "toolhead",
|
||||||
|
"supplies_usage": 0,
|
||||||
"ace_drying": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None},
|
"ace_drying": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None},
|
||||||
}
|
}
|
||||||
self._ams_slots: list[dict] = [] # flat global list; each entry has global_index + box_id
|
self._ams_slots: list[dict] = [] # flat global list; each entry has global_index + box_id
|
||||||
@@ -819,6 +899,18 @@ class KobraXBridge:
|
|||||||
self._pending_preprint_skip: list[str] = []
|
self._pending_preprint_skip: list[str] = []
|
||||||
self._pending_preprint_skip_deadline: float = 0.0
|
self._pending_preprint_skip_deadline: float = 0.0
|
||||||
|
|
||||||
|
# Spoolman filament tracking
|
||||||
|
_sm_url = (getattr(args, "spoolman_server", "") or "").strip()
|
||||||
|
self._spoolman: SpoolmanClient | None = (
|
||||||
|
SpoolmanClient(_sm_url, getattr(args, "spoolman_sync_rate", 0))
|
||||||
|
if _sm_url else None
|
||||||
|
)
|
||||||
|
self._spoolman_slot_spools: dict[int, int] = {} # {ams_slot_idx: spoolman_spool_id}
|
||||||
|
self._spoolman_slot_usage: dict[int, float] = {} # per-slot accumulated mm this print
|
||||||
|
self._spoolman_slot_reported: dict[int, float] = {} # per-slot mm already sent to Spoolman
|
||||||
|
self._spoolman_last_usage: float = 0.0 # supplies_usage at last attribution tick
|
||||||
|
self._spoolman_last_sync: float = 0.0
|
||||||
|
|
||||||
# Theme-Name prüfen (keine Sonderzeichen oder Umlaute)
|
# Theme-Name prüfen (keine Sonderzeichen oder Umlaute)
|
||||||
raw_theme = (getattr(args, "ui_theme", None) or "default").strip()
|
raw_theme = (getattr(args, "ui_theme", None) or "default").strip()
|
||||||
if not _UI_THEME_NAME_RE.match(raw_theme):
|
if not _UI_THEME_NAME_RE.match(raw_theme):
|
||||||
@@ -837,6 +929,139 @@ class KobraXBridge:
|
|||||||
client.callbacks["light/report"] = self._on_light
|
client.callbacks["light/report"] = self._on_light
|
||||||
client.callbacks["skip/report"] = self._on_skip
|
client.callbacks["skip/report"] = self._on_skip
|
||||||
|
|
||||||
|
if self._spoolman:
|
||||||
|
threading.Thread(
|
||||||
|
target=lambda: log.info(
|
||||||
|
f"Spoolman: {'OK' if self._spoolman.health_check() else 'unreachable'} "
|
||||||
|
f"at {self._spoolman.server_url}"
|
||||||
|
),
|
||||||
|
daemon=True, name="spoolman-health",
|
||||||
|
).start()
|
||||||
|
|
||||||
|
# ── Spoolman helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _spoolman_filament_mm(self) -> float:
|
||||||
|
"""Total filament_used_mm for the current print file from the GCode DB."""
|
||||||
|
filename = self._state.get("filename", "")
|
||||||
|
if not filename:
|
||||||
|
return 0.0
|
||||||
|
try:
|
||||||
|
gf = self._store.get_file_by_name(filename)
|
||||||
|
return float(gf.get("filament_used_mm") or 0.0) if gf else 0.0
|
||||||
|
except Exception:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _spoolman_attribute_tick(self, activity_map: dict) -> None:
|
||||||
|
"""Attribute the supplies_usage delta since last tick to the active slot.
|
||||||
|
|
||||||
|
Skips attribution during loading/unloading transitions (tool changes +
|
||||||
|
purges) to avoid charging the wrong spool for purge material."""
|
||||||
|
if not self._spoolman or not self._spoolman_slot_spools:
|
||||||
|
return
|
||||||
|
if self._state.get("print_state") != "printing":
|
||||||
|
return
|
||||||
|
current = self._state.get("supplies_usage", 0)
|
||||||
|
delta = current - self._spoolman_last_usage
|
||||||
|
self._spoolman_last_usage = current
|
||||||
|
if delta <= 0:
|
||||||
|
return
|
||||||
|
loaded = self._ams_loaded_slot
|
||||||
|
if loaded < 0:
|
||||||
|
return
|
||||||
|
if activity_map.get(loaded):
|
||||||
|
return
|
||||||
|
self._spoolman_slot_usage[loaded] = self._spoolman_slot_usage.get(loaded, 0.0) + delta
|
||||||
|
|
||||||
|
def _spoolman_unreported(self) -> dict[int, float]:
|
||||||
|
"""Return {slot_idx: mm} of usage not yet reported to Spoolman.
|
||||||
|
|
||||||
|
Falls back to equal split of total supplies_usage when per-slot
|
||||||
|
attribution data is absent (e.g. single-extruder with no AMS)."""
|
||||||
|
total_used = self._state.get("supplies_usage", 0)
|
||||||
|
if self._spoolman_slot_usage:
|
||||||
|
return {
|
||||||
|
slot: self._spoolman_slot_usage.get(slot, 0.0)
|
||||||
|
- self._spoolman_slot_reported.get(slot, 0.0)
|
||||||
|
for slot in self._spoolman_slot_spools
|
||||||
|
}
|
||||||
|
n = len(self._spoolman_slot_spools)
|
||||||
|
already = sum(self._spoolman_slot_reported.values())
|
||||||
|
per = (total_used - already) / n if n else 0.0
|
||||||
|
return {slot: per for slot in self._spoolman_slot_spools}
|
||||||
|
|
||||||
|
def _spoolman_report(self, unreported: dict[int, float], min_mm: float = 0.1) -> None:
|
||||||
|
"""Fire-and-forget report of unreported mm to each mapped spool."""
|
||||||
|
sm = self._spoolman
|
||||||
|
for slot_idx, mm in unreported.items():
|
||||||
|
if mm < min_mm:
|
||||||
|
continue
|
||||||
|
spool_id = self._spoolman_slot_spools.get(slot_idx)
|
||||||
|
if not spool_id:
|
||||||
|
continue
|
||||||
|
self._spoolman_slot_reported[slot_idx] = (
|
||||||
|
self._spoolman_slot_reported.get(slot_idx, 0.0) + mm
|
||||||
|
)
|
||||||
|
def _send(sid=spool_id, length=mm):
|
||||||
|
try:
|
||||||
|
sm.use_filament(sid, length)
|
||||||
|
log.info(f"Spoolman: {length:.1f} mm → spool {sid}")
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"Spoolman: report failed (spool {sid}): {e}")
|
||||||
|
threading.Thread(target=_send, daemon=True, name="spoolman-report").start()
|
||||||
|
|
||||||
|
def _spoolman_notify_end(self):
|
||||||
|
"""Report remaining filament on print end."""
|
||||||
|
if not self._spoolman or not self._spoolman_slot_spools:
|
||||||
|
return
|
||||||
|
self._spoolman_report(self._spoolman_unreported())
|
||||||
|
|
||||||
|
def _spoolman_sync_midprint(self):
|
||||||
|
"""Report incremental filament usage during a print (sync_rate interval)."""
|
||||||
|
if not self._spoolman or not self._spoolman_slot_spools:
|
||||||
|
return
|
||||||
|
self._spoolman_report(self._spoolman_unreported(), min_mm=10.0)
|
||||||
|
|
||||||
|
# ── Spoolman API handlers ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async def handle_kx_spoolman_status(self, request):
|
||||||
|
"""GET /kx/spoolman/status"""
|
||||||
|
return self._json_cors({
|
||||||
|
"configured": bool(self._spoolman),
|
||||||
|
"server": self._spoolman.server_url if self._spoolman else "",
|
||||||
|
"sync_rate": self._spoolman.sync_rate if self._spoolman else 0,
|
||||||
|
"slot_spools": {str(k): v for k, v in self._spoolman_slot_spools.items()},
|
||||||
|
})
|
||||||
|
|
||||||
|
async def handle_kx_spoolman_spools(self, request):
|
||||||
|
"""GET /kx/spoolman/spools — proxied from Spoolman."""
|
||||||
|
if not self._spoolman:
|
||||||
|
return self._json_cors({"error": "Spoolman not configured"}, status=503)
|
||||||
|
try:
|
||||||
|
spools = await asyncio.get_event_loop().run_in_executor(
|
||||||
|
None, self._spoolman.list_spools
|
||||||
|
)
|
||||||
|
return self._json_cors({"spools": spools})
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"Spoolman: list_spools failed: {e}")
|
||||||
|
return self._json_cors({"error": str(e)}, status=502)
|
||||||
|
|
||||||
|
async def handle_kx_spoolman_set_active(self, request):
|
||||||
|
"""POST /kx/spoolman/active-spool
|
||||||
|
Body: {"slot_map": {"0": 42, "2": 17}} — AMS slot index → Spoolman spool ID."""
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
except Exception:
|
||||||
|
return self._json_cors({"error": "invalid JSON"}, status=400)
|
||||||
|
slot_map = data.get("slot_map") or {}
|
||||||
|
self._spoolman_slot_spools = {
|
||||||
|
int(k): int(v) for k, v in slot_map.items()
|
||||||
|
if str(v).isdigit() and int(v) > 0
|
||||||
|
}
|
||||||
|
self._spoolman_slot_usage = {}
|
||||||
|
self._spoolman_slot_reported = {}
|
||||||
|
self._spoolman_last_usage = 0.0
|
||||||
|
return self._json_cors({"slot_spools": {str(k): v for k, v in self._spoolman_slot_spools.items()}})
|
||||||
|
|
||||||
def _default_ace_dry_presets(self) -> dict[str, dict]:
|
def _default_ace_dry_presets(self) -> dict[str, dict]:
|
||||||
return {
|
return {
|
||||||
"pla": {"temp": 45, "duration_sec": 4 * 3600},
|
"pla": {"temp": 45, "duration_sec": 4 * 3600},
|
||||||
@@ -951,15 +1176,21 @@ class KobraXBridge:
|
|||||||
printer_id=self._printer_id,
|
printer_id=self._printer_id,
|
||||||
)
|
)
|
||||||
log.info(f"Job started: {self._current_job_id} for {filename}")
|
log.info(f"Job started: {self._current_job_id} for {filename}")
|
||||||
|
self._spoolman_slot_usage = {}
|
||||||
|
self._spoolman_slot_reported = {}
|
||||||
|
self._spoolman_last_usage = 0.0
|
||||||
|
self._spoolman_last_sync = 0.0
|
||||||
|
|
||||||
# Job-History: Druckende erkennen
|
# Job-History: Druckende erkennen
|
||||||
if kobra_state in ("finished",) and self._current_job_id:
|
if kobra_state in ("finished",) and self._current_job_id:
|
||||||
self._store.finish_job(self._current_job_id, status="completed")
|
self._store.finish_job(self._current_job_id, status="completed")
|
||||||
log.info(f"Job abgeschlossen: {self._current_job_id}")
|
log.info(f"Job abgeschlossen: {self._current_job_id}")
|
||||||
|
self._spoolman_notify_end()
|
||||||
self._current_job_id = ""
|
self._current_job_id = ""
|
||||||
elif kobra_state in ("stoped", "canceled") and self._current_job_id:
|
elif kobra_state in ("stoped", "canceled") and self._current_job_id:
|
||||||
self._store.finish_job(self._current_job_id, status="cancelled")
|
self._store.finish_job(self._current_job_id, status="cancelled")
|
||||||
log.info(f"Job abgebrochen: {self._current_job_id}")
|
log.info(f"Job abgebrochen: {self._current_job_id}")
|
||||||
|
self._spoolman_notify_end()
|
||||||
self._current_job_id = ""
|
self._current_job_id = ""
|
||||||
|
|
||||||
# Nach Druckende das Upload-Banner verschwinden lassen (Issue #29): der
|
# Nach Druckende das Upload-Banner verschwinden lassen (Issue #29): der
|
||||||
@@ -976,6 +1207,7 @@ class KobraXBridge:
|
|||||||
self._state["slicer_time"] = 0
|
self._state["slicer_time"] = 0
|
||||||
self._state["layer_height"] = 0.0
|
self._state["layer_height"] = 0.0
|
||||||
self._state["first_layer_height"] = 0.0
|
self._state["first_layer_height"] = 0.0
|
||||||
|
self._state["supplies_usage"] = 0
|
||||||
self._thumbnail_b64 = ""
|
self._thumbnail_b64 = ""
|
||||||
self._state["filename"] = d.get("filename", self._state["filename"])
|
self._state["filename"] = d.get("filename", self._state["filename"])
|
||||||
if "progress" in d:
|
if "progress" in d:
|
||||||
@@ -990,6 +1222,8 @@ class KobraXBridge:
|
|||||||
self._state["total_layers"] = d["total_layers"]
|
self._state["total_layers"] = d["total_layers"]
|
||||||
if "taskid" in d:
|
if "taskid" in d:
|
||||||
self._state["taskid"] = str(d["taskid"])
|
self._state["taskid"] = str(d["taskid"])
|
||||||
|
if "supplies_usage" in d:
|
||||||
|
self._state["supplies_usage"] = int(d["supplies_usage"])
|
||||||
settings = d.get("settings") or {}
|
settings = d.get("settings") or {}
|
||||||
if "print_speed_mode" in settings:
|
if "print_speed_mode" in settings:
|
||||||
self._state["print_speed_mode"] = int(settings["print_speed_mode"])
|
self._state["print_speed_mode"] = int(settings["print_speed_mode"])
|
||||||
@@ -2967,10 +3201,10 @@ class KobraXBridge:
|
|||||||
if not file_data:
|
if not file_data:
|
||||||
return web.json_response({"error": "no file received"}, status=400)
|
return web.json_response({"error": "no file received"}, status=400)
|
||||||
|
|
||||||
# Nur druckbare Dateien zulassen (Issue #59) — verhindert dass z.B. ein
|
# Nur druckbare Dateien zulassen (Issue #59) — der Kobra X akzeptiert
|
||||||
# JPG im File-Browser landet. OrcaSlicer-Uploads sind .gcode/.gcode.3mf,
|
# ausschließlich .gcode und .bgcode; .3mf-Uploads werden vom Drucker
|
||||||
# der Kobra X akzeptiert .gcode/.3mf/.bgcode.
|
# nicht verarbeitet und daher abgelehnt (Issue #59, @gangoke).
|
||||||
_allowed_ext = (".gcode", ".gcode.3mf", ".3mf", ".bgcode")
|
_allowed_ext = (".gcode", ".bgcode")
|
||||||
_fn_lower = (remote_filename or "").lower()
|
_fn_lower = (remote_filename or "").lower()
|
||||||
if not _fn_lower.endswith(_allowed_ext):
|
if not _fn_lower.endswith(_allowed_ext):
|
||||||
log.warning(f"Upload abgelehnt (kein GCode): {remote_filename}")
|
log.warning(f"Upload abgelehnt (kein GCode): {remote_filename}")
|
||||||
@@ -3674,6 +3908,16 @@ class KobraXBridge:
|
|||||||
self._camera_user_stopped = True
|
self._camera_user_stopped = True
|
||||||
return web.json_response({"result": "ok"})
|
return web.json_response({"result": "ok"})
|
||||||
|
|
||||||
|
async def handle_api_camera_reset(self, request):
|
||||||
|
"""Backoff-Zähler zurücksetzen und ffmpeg sofort neu starten.
|
||||||
|
Nützlich nach 429-Sperre (Retry-After abgelaufen) oder nach Drucker-Neustart."""
|
||||||
|
self.camera_cache.reset()
|
||||||
|
url = self._state.get("camera_url", "")
|
||||||
|
if url:
|
||||||
|
self.camera_cache.set_url(url)
|
||||||
|
await self.camera_cache.ensure_running()
|
||||||
|
return web.json_response({"result": "ok"})
|
||||||
|
|
||||||
async def handle_api_camera_snapshot(self, request):
|
async def handle_api_camera_snapshot(self, request):
|
||||||
"""Letzter JPEG-Frame aus dem CameraCache — instant aus dem RAM,
|
"""Letzter JPEG-Frame aus dem CameraCache — instant aus dem RAM,
|
||||||
keine eigene ffmpeg-Instanz mehr (verhindert Single-Client-429 am
|
keine eigene ffmpeg-Instanz mehr (verhindert Single-Client-429 am
|
||||||
@@ -4029,6 +4273,8 @@ class KobraXBridge:
|
|||||||
"filament_profiles": {str(k): v for k, v in self._filament_profiles.items()},
|
"filament_profiles": {str(k): v for k, v in self._filament_profiles.items()},
|
||||||
"visible_vendors": self._visible_vendors,
|
"visible_vendors": self._visible_vendors,
|
||||||
"ace_dry_presets": self._ace_dry_presets,
|
"ace_dry_presets": self._ace_dry_presets,
|
||||||
|
"spoolman_server": getattr(self._args, "spoolman_server", "") or "",
|
||||||
|
"spoolman_sync_rate": getattr(self._args, "spoolman_sync_rate", 0),
|
||||||
})
|
})
|
||||||
|
|
||||||
async def handle_api_settings_post(self, request):
|
async def handle_api_settings_post(self, request):
|
||||||
@@ -4043,7 +4289,7 @@ class KobraXBridge:
|
|||||||
cfg.read(config_path, encoding="utf-8")
|
cfg.read(config_path, encoding="utf-8")
|
||||||
|
|
||||||
# Sections sicherstellen
|
# Sections sicherstellen
|
||||||
for section in ("connection", "print", "bridge", "ace_dry_presets"):
|
for section in ("connection", "print", "bridge", "ace_dry_presets", "spoolman"):
|
||||||
if not cfg.has_section(section):
|
if not cfg.has_section(section):
|
||||||
cfg.add_section(section)
|
cfg.add_section(section)
|
||||||
|
|
||||||
@@ -4073,6 +4319,16 @@ class KobraXBridge:
|
|||||||
elif cfg.has_option("bridge", "printer_name"):
|
elif cfg.has_option("bridge", "printer_name"):
|
||||||
cfg.remove_option("bridge", "printer_name")
|
cfg.remove_option("bridge", "printer_name")
|
||||||
|
|
||||||
|
# Spoolman
|
||||||
|
if "spoolman_server" in data:
|
||||||
|
cfg.set("spoolman", "server", str(data["spoolman_server"]).strip())
|
||||||
|
if "spoolman_sync_rate" in data:
|
||||||
|
try:
|
||||||
|
sr = max(0, int(data["spoolman_sync_rate"]))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
sr = 30
|
||||||
|
cfg.set("spoolman", "sync_rate", str(sr))
|
||||||
|
|
||||||
incoming_presets = data.get("ace_dry_presets") if isinstance(data, dict) else None
|
incoming_presets = data.get("ace_dry_presets") if isinstance(data, dict) else None
|
||||||
presets = self._sanitize_ace_dry_presets(incoming_presets if isinstance(incoming_presets, dict) else self._ace_dry_presets)
|
presets = self._sanitize_ace_dry_presets(incoming_presets if isinstance(incoming_presets, dict) else self._ace_dry_presets)
|
||||||
for key, val in presets.items():
|
for key, val in presets.items():
|
||||||
@@ -4232,7 +4488,8 @@ class KobraXBridge:
|
|||||||
for _k in ("PRINTER_IP", "MQTT_PORT", "MQTT_USERNAME", "MQTT_PASSWORD",
|
for _k in ("PRINTER_IP", "MQTT_PORT", "MQTT_USERNAME", "MQTT_PASSWORD",
|
||||||
"MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING",
|
"MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING",
|
||||||
"CAMERA_ON_PRINT", "WEB_UPLOAD_WARNING", "PRINT_START_DIALOG",
|
"CAMERA_ON_PRINT", "WEB_UPLOAD_WARNING", "PRINT_START_DIALOG",
|
||||||
"FILE_READY_DIALOG", "BRIDGE_PRINTER_NAME"):
|
"FILE_READY_DIALOG", "BRIDGE_PRINTER_NAME",
|
||||||
|
"SPOOLMAN_SERVER", "SPOOLMAN_SYNC_RATE"):
|
||||||
os.environ.pop(_k, None)
|
os.environ.pop(_k, None)
|
||||||
|
|
||||||
in_docker = os.path.exists("/.dockerenv") or os.environ.get("KX_IN_DOCKER")
|
in_docker = os.path.exists("/.dockerenv") or os.environ.get("KX_IN_DOCKER")
|
||||||
@@ -4268,9 +4525,10 @@ class KobraXBridge:
|
|||||||
|
|
||||||
# ─── Update ──────────────────────────────────────────────────────────────
|
# ─── Update ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1"
|
STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1"
|
||||||
DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true"
|
NIGHTLY_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=5&pre-release=true"
|
||||||
GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag"
|
DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true"
|
||||||
|
GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag"
|
||||||
|
|
||||||
def _read_version(self) -> str:
|
def _read_version(self) -> str:
|
||||||
# PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach
|
# PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach
|
||||||
@@ -4345,8 +4603,14 @@ class KobraXBridge:
|
|||||||
|
|
||||||
async def handle_api_update_check(self, request):
|
async def handle_api_update_check(self, request):
|
||||||
current = self._read_version()
|
current = self._read_version()
|
||||||
|
is_nightly = "nightly" in current
|
||||||
is_dev = "-dev+" in current
|
is_dev = "-dev+" in current
|
||||||
api_url = self.DEV_RELEASE_API if is_dev else self.STABLE_RELEASE_API
|
if is_nightly:
|
||||||
|
api_url = self.NIGHTLY_RELEASE_API
|
||||||
|
elif is_dev:
|
||||||
|
api_url = self.DEV_RELEASE_API
|
||||||
|
else:
|
||||||
|
api_url = self.STABLE_RELEASE_API
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
||||||
@@ -4355,14 +4619,37 @@ class KobraXBridge:
|
|||||||
releases = await resp.json(content_type=None)
|
releases = await resp.json(content_type=None)
|
||||||
if not releases:
|
if not releases:
|
||||||
return web.json_response({"error": "Keine Releases gefunden"}, status=404)
|
return web.json_response({"error": "Keine Releases gefunden"}, status=404)
|
||||||
# Dev: neuestes Release mit "-dev+" im Tag suchen
|
|
||||||
if is_dev:
|
if is_nightly:
|
||||||
|
# Neuestes Prerelease mit nightly-Tag suchen
|
||||||
|
nightly_releases = [r for r in releases if r.get("prerelease") and "nightly" in r.get("tag_name", "")]
|
||||||
|
if not nightly_releases:
|
||||||
|
return web.json_response({"error": "Keine Nightly-Releases gefunden"}, status=404)
|
||||||
|
data = nightly_releases[0]
|
||||||
|
tag = data.get("tag_name", "")
|
||||||
|
# Tag-Format: "nightly-0.9.27-nightly4", current: "0.9.27-nightly4"
|
||||||
|
tag_version = tag[len("nightly-"):] if tag.startswith("nightly-") else tag
|
||||||
|
update_available = tag_version != current
|
||||||
|
latest = tag
|
||||||
|
return web.json_response({
|
||||||
|
"current": current,
|
||||||
|
"latest": latest,
|
||||||
|
"update_available": update_available,
|
||||||
|
"tag": tag,
|
||||||
|
"docker_only": True,
|
||||||
|
"changelog": data.get("body", ""),
|
||||||
|
})
|
||||||
|
elif is_dev:
|
||||||
dev_releases = [r for r in releases if "-dev+" in r.get("tag_name", "")]
|
dev_releases = [r for r in releases if "-dev+" in r.get("tag_name", "")]
|
||||||
if not dev_releases:
|
if not dev_releases:
|
||||||
return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404)
|
return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404)
|
||||||
data = dev_releases[0]
|
data = dev_releases[0]
|
||||||
else:
|
else:
|
||||||
data = releases[0]
|
# Stable: nur non-prerelease nehmen
|
||||||
|
stable_releases = [r for r in releases if not r.get("prerelease")]
|
||||||
|
if not stable_releases:
|
||||||
|
return web.json_response({"error": "Keine Stable-Releases gefunden"}, status=404)
|
||||||
|
data = stable_releases[0]
|
||||||
tag = data.get("tag_name", "")
|
tag = data.get("tag_name", "")
|
||||||
latest = tag.lstrip("v")
|
latest = tag.lstrip("v")
|
||||||
if is_dev:
|
if is_dev:
|
||||||
@@ -4376,6 +4663,7 @@ class KobraXBridge:
|
|||||||
"update_available": update_available,
|
"update_available": update_available,
|
||||||
"tag": tag,
|
"tag": tag,
|
||||||
"download_url": download_url,
|
"download_url": download_url,
|
||||||
|
"docker_only": False,
|
||||||
"changelog": data.get("body", ""),
|
"changelog": data.get("body", ""),
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -4396,6 +4684,10 @@ class KobraXBridge:
|
|||||||
async def handle_api_update_apply(self, request):
|
async def handle_api_update_apply(self, request):
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
new_tag = data.get("tag", "")
|
new_tag = data.get("tag", "")
|
||||||
|
if "nightly" in self._read_version():
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "Nightly-Updates laufen über Docker: "
|
||||||
|
"docker compose pull && docker compose up -d"}, status=400)
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{"error": "Self-Update wird im Binary-Modus nicht unterstützt – "
|
{"error": "Self-Update wird im Binary-Modus nicht unterstützt – "
|
||||||
@@ -4804,6 +5096,14 @@ class KobraXBridge:
|
|||||||
print_r = self.client.publish("print", "query", timeout=3.0)
|
print_r = self.client.publish("print", "query", timeout=3.0)
|
||||||
if print_r:
|
if print_r:
|
||||||
self._on_print(print_r)
|
self._on_print(print_r)
|
||||||
|
# Spoolman mid-print sync
|
||||||
|
if (self._spoolman and self._spoolman.sync_rate > 0
|
||||||
|
and self._spoolman_slot_spools
|
||||||
|
and self._state.get("print_state") == "printing"):
|
||||||
|
now = time.time()
|
||||||
|
if now - self._spoolman_last_sync >= self._spoolman.sync_rate:
|
||||||
|
self._spoolman_sync_midprint()
|
||||||
|
self._spoolman_last_sync = now
|
||||||
box = self.client.query_multicolor_box()
|
box = self.client.query_multicolor_box()
|
||||||
if box:
|
if box:
|
||||||
data = box.get("data") or {}
|
data = box.get("data") or {}
|
||||||
@@ -4820,6 +5120,10 @@ class KobraXBridge:
|
|||||||
if global_slots:
|
if global_slots:
|
||||||
self._ams_slots = global_slots
|
self._ams_slots = global_slots
|
||||||
self._ams_loaded_slot = global_loaded
|
self._ams_loaded_slot = global_loaded
|
||||||
|
self._spoolman_attribute_tick(activity_map)
|
||||||
|
else:
|
||||||
|
# No multiColorBox data — still attribute (no transitions to skip)
|
||||||
|
self._spoolman_attribute_tick({})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"Poll-Fehler: {e}")
|
log.warning(f"Poll-Fehler: {e}")
|
||||||
# Prüfen ob Drucker wirklich weg ist
|
# Prüfen ob Drucker wirklich weg ist
|
||||||
@@ -4919,6 +5223,7 @@ def build_app(bridge: KobraXBridge) -> web.Application:
|
|||||||
r.add_get("/api/camera/snapshot", bridge.handle_api_camera_snapshot)
|
r.add_get("/api/camera/snapshot", bridge.handle_api_camera_snapshot)
|
||||||
r.add_post("/api/camera/start", bridge.handle_api_camera_start)
|
r.add_post("/api/camera/start", bridge.handle_api_camera_start)
|
||||||
r.add_post("/api/camera/stop", bridge.handle_api_camera_stop)
|
r.add_post("/api/camera/stop", bridge.handle_api_camera_stop)
|
||||||
|
r.add_post("/api/camera/reset", bridge.handle_api_camera_reset)
|
||||||
r.add_get("/api/state", bridge.handle_api_state)
|
r.add_get("/api/state", bridge.handle_api_state)
|
||||||
r.add_get("/api/settings", bridge.handle_api_settings_get)
|
r.add_get("/api/settings", bridge.handle_api_settings_get)
|
||||||
r.add_post("/api/settings", bridge.handle_api_settings_post)
|
r.add_post("/api/settings", bridge.handle_api_settings_post)
|
||||||
@@ -4954,6 +5259,9 @@ def build_app(bridge: KobraXBridge) -> web.Application:
|
|||||||
r.add_post("/kx/skip", bridge.handle_kx_skip)
|
r.add_post("/kx/skip", bridge.handle_kx_skip)
|
||||||
r.add_post("/kx/skip/query", bridge.handle_kx_skip_query)
|
r.add_post("/kx/skip/query", bridge.handle_kx_skip_query)
|
||||||
r.add_get("/kx/skip/state", bridge.handle_kx_skip_state)
|
r.add_get("/kx/skip/state", bridge.handle_kx_skip_state)
|
||||||
|
r.add_get("/kx/spoolman/status", bridge.handle_kx_spoolman_status)
|
||||||
|
r.add_get("/kx/spoolman/spools", bridge.handle_kx_spoolman_spools)
|
||||||
|
r.add_post("/kx/spoolman/active-spool", bridge.handle_kx_spoolman_set_active)
|
||||||
r.add_route("OPTIONS", "/kx/{path:.*}", bridge.handle_kx_options)
|
r.add_route("OPTIONS", "/kx/{path:.*}", bridge.handle_kx_options)
|
||||||
|
|
||||||
# Root + Printer-Routen (Single-Page, JS liest Pathname)
|
# Root + Printer-Routen (Single-Page, JS liest Pathname)
|
||||||
@@ -5119,6 +5427,10 @@ def main():
|
|||||||
parser.add_argument("--web-upload-warning", type=int, default=env_loader.WEB_UPLOAD_WARNING)
|
parser.add_argument("--web-upload-warning", type=int, default=env_loader.WEB_UPLOAD_WARNING)
|
||||||
parser.add_argument("--print-start-dialog", dest="print_start_dialog", type=int, default=env_loader.PRINT_START_DIALOG)
|
parser.add_argument("--print-start-dialog", dest="print_start_dialog", type=int, default=env_loader.PRINT_START_DIALOG)
|
||||||
parser.add_argument("--file-ready-dialog", dest="print_start_dialog", type=int)
|
parser.add_argument("--file-ready-dialog", dest="print_start_dialog", type=int)
|
||||||
|
parser.add_argument("--spoolman-server", default=env_loader.SPOOLMAN_SERVER,
|
||||||
|
help="Spoolman URL (e.g. http://192.168.x.x:7912); leave empty to disable")
|
||||||
|
parser.add_argument("--spoolman-sync-rate", type=int, default=env_loader.SPOOLMAN_SYNC_RATE,
|
||||||
|
help="Mid-print filament sync interval in seconds (0 = only on print end)")
|
||||||
|
|
||||||
parser.add_argument("--host", default="0.0.0.0",
|
parser.add_argument("--host", default="0.0.0.0",
|
||||||
help="Bind-Adresse für den Bridge-Server")
|
help="Bind-Adresse für den Bridge-Server")
|
||||||
|
|||||||
18
moonraker-obico.cfg.example
Normal file
18
moonraker-obico.cfg.example
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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
|
||||||
21
pull_request_template.md
Normal file
21
pull_request_template.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## 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
|
||||||
3
pytest.ini
Normal file
3
pytest.ini
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[pytest]
|
||||||
|
asyncio_mode = auto
|
||||||
|
testpaths = tests
|
||||||
11
releases/0.9.27/SHA256SUMS.txt
Normal file
11
releases/0.9.27/SHA256SUMS.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
06873a3b6773e6e755deff33dba468dee3af09859f9b026b14a600501c8285cb anycubic-certs.zip
|
||||||
|
68e08c6b6badb2bf86f61e2164e96b902944c885f083a598ea4e3c854c432e91 extract_credentials
|
||||||
|
8d4d78bdc887e2512790805b711b941420536d5f0f30043b5f3bc74d4fa893d3 extract_credentials.exe
|
||||||
|
9b79a5f8edc734018fc547ac232ddd822ec5fee050e99e3305ccf8f38b177c9e fetch_credentials
|
||||||
|
b79615bc2f9df33bccd5471bfcd83c9bdc5291e857fe5b04927b171342bc1b48 fetch_credentials.exe
|
||||||
|
37ae8e3f4dc7bb6cf434de980db4aed9c1047d995063ce289bdc268f7764380e kx-bridge.exe
|
||||||
|
4f636f91fef7eb1ea1ad1ae8c7f7eaa64b725e4dc2e74db42a53ce5dc5808ba3 kx-bridge-linux-amd64
|
||||||
|
b0ff6fa222cfbf79e0ab609aac4c9d3b1acd80b5c4df10c7847e0c7d125913bc kx-bridge-linux-amd64.zip
|
||||||
|
0cd9448ceec03cc7f545f9c62a21b7075a866dba5ee396ec26b48816f927db1a kx-bridge-linux-arm64
|
||||||
|
99c4641bbdcc000b2cf2142ee01c16942b13da47ea837885fa1be68e984f6101 kx-bridge-linux-arm64.zip
|
||||||
|
31659f76913e1c785cfa9e0a9e87f893a483a6a55248006b046ac07b25b40870 kx-bridge-windows.zip
|
||||||
BIN
releases/0.9.27/anycubic-certs.zip
Normal file
BIN
releases/0.9.27/anycubic-certs.zip
Normal file
Binary file not shown.
24
releases/0.9.27/anycubic_slicer.crt
Normal file
24
releases/0.9.27/anycubic_slicer.crt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEDTCCAvWgAwIBAgICAZAwDQYJKoZIhvcNAQEFBQAwgZsxCzAJBgNVBAYTAkNO
|
||||||
|
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
|
||||||
|
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxEzARBgNVBAMMCkFDIFJvb3Qg
|
||||||
|
Q0ExKjAoBgkqhkiG9w0BCQEWG2FueWN1YmljX2Nsb3VkQGFueWN1YmljLmNvbTAg
|
||||||
|
Fw0yMzA3MjAwMzI3NTFaGA8yMTIzMDcyMTAzMjc1MVowgZ8xCzAJBgNVBAYTAkNO
|
||||||
|
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
|
||||||
|
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxFzAVBgNVBAMMDkFueWN1Ymlj
|
||||||
|
U2xpY2VyMSowKAYJKoZIhvcNAQkBFhthbnljdWJpY19jbG91ZEBhbnljdWJpYy5j
|
||||||
|
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdoQ7g2F/yecfpdlqT
|
||||||
|
b8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYnyPep
|
||||||
|
43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0YpVk
|
||||||
|
K3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5ZwaTP
|
||||||
|
8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0ThecJ
|
||||||
|
cTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUaeLWkI
|
||||||
|
ajSJAgMBAAGjUzBRMB0GA1UdDgQWBBRI4P3/uKdYYFPEcFIwYxdv1p9gETAfBgNV
|
||||||
|
HSMEGDAWgBQlkDqpFERfr3u1rR9gNbNKtgrHIjAPBgNVHRMBAf8EBTADAQH/MA0G
|
||||||
|
CSqGSIb3DQEBBQUAA4IBAQBP3ws80Y9eBR2lpjYP3rVvH8kA6+LnEXT4PpHj+fSw
|
||||||
|
jciaNskzpiwNvBy00m32ACR5YKlMUjevlQuyyw+LQbTUwAEOwyy9SDQpiXdjL6q3
|
||||||
|
SPQ4aB4A57nFXOGrthc/nb9yFcteWrZrKbwvVUu2vqU7U8n7lJKjhVuFRWSXS3SV
|
||||||
|
sPc9JZ21kpPYWKbGtfD6jUlW0Ip+PurLw9FrbVwnEcOMf/ezSlrH5c8mfJyo8pVk
|
||||||
|
aC/6PpReqijusOSRZ5oLyhPvtgddXseJFByun1Ud0CDlFA05nGGPmnVcXD+GMnHH
|
||||||
|
i6baCTeifwp5Jpdzv4imcCPvayKUNuX32vYNfNkWC/R5
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
releases/0.9.27/anycubic_slicer.key
Normal file
28
releases/0.9.27/anycubic_slicer.key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdoQ7g2F/yecfp
|
||||||
|
dlqTb8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYn
|
||||||
|
yPep43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0
|
||||||
|
YpVkK3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5Z
|
||||||
|
waTP8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0T
|
||||||
|
hecJcTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUae
|
||||||
|
LWkIajSJAgMBAAECggEASwRkC9lRiLqN30kvWW5g6hsec8KrTfLm2pMCVy2AlgxB
|
||||||
|
B3VD51YvKzERyBwSKITT/1RPK9K/4xe3NrpAkmGsd3vLd8W+vorvXFePr7gct7VP
|
||||||
|
4Wb+J7D+keKXlg2sswRiHqI0PN45Nzq/iBaCaJiIMiPbB0+PHBl9J/Cv7XsD3tq+
|
||||||
|
9WKhvXf2g1g9GMrLaCCcWXWCqcu0LlbqJnw3yMnJLSltmyFTmlVLjDHM75bMVz97
|
||||||
|
4emQzOlnRN2yA5cWWCaM+mgjNM2aWwUsXBZzCgwSqSaj1QD4B/epCuDBORWHS9D6
|
||||||
|
jL15w8xjly9q8OS+4d6beR5h9GiPyMK4Ff2wXImCXQKBgQDwXxtrL+kVZrQ/qftj
|
||||||
|
24F3+QDN0j5Z3lUMTfZPn6ng/E/aBfn8KcWJHj2vYkKZdB5wOXJr56BYe3Hukzfp
|
||||||
|
QF0E2+g1WAGskF1mb/vVab54geox5Y6CA+ionRn2kcCwybVkktR/0JK2UV9Qjb/z
|
||||||
|
k1WU+RUhNrW/GDBqYulaadnR+wKBgQDsCf2/yKGPxj4pIvAtn5RFSlfscddgkSnc
|
||||||
|
ouBkDXEp5ta+5PGrlrdzS/F0vFhvBPbfbVJxVwRnM/Oqj8c0/bj7oc5RpPxirciO
|
||||||
|
AaovKVPTiORaviytnB2HgkflkJfy5vdXv4ZQahAV/UwtSmLwBshe+Ya68MAFrQRa
|
||||||
|
7M4z6k4QSwKBgQCm7OVVoofzXMeADsONrTpT3pA4XvD95/CYAuwyj2ah35Z0igH4
|
||||||
|
o+mSN3YO/eXSO1mIBdz4Inqv98o/K+2ABjqSzUSNBvjipb63DL2Oj0i+1zmUPR6i
|
||||||
|
G6TOs4r8OGvgWbOmjHEV8fpwskHG5ymONZsRQYjy79N3SY0V1GrJZwjlUQKBgD0x
|
||||||
|
AeWcP7YkMK09b4KEYk3sTgrwIGPafj3Cw+VsTrAMNhPbCoPvWLO9NmWLBmoRoWae
|
||||||
|
0sarRmry3vKSv5QPSsuBURl9aiiy4NFfwRzk2+R1Eq4rqy1+0XD152muKJZCJlFL
|
||||||
|
R6jFNlJdDkiXhjqvp3ZnvfPswfs2tXBU/8gZsA8tAoGBALXfc5m9I5R1l1zN7tpa
|
||||||
|
ncA0S3EKzqmuCc3KzlS6OS0e9Lz1MsmfEsvxvW3w4SrdfTbwQpEy9RNg89dlgPtc
|
||||||
|
rdId1QdN2eWPY5M4lz9n9EYdzi9ufoKAEYu2a0lP+qz690JwmL1Jx49bvQEn5Nu0
|
||||||
|
4swn72uwBRlhjAw46MF77SBQ
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
89
releases/0.9.27/build_exe.log
Normal file
89
releases/0.9.27/build_exe.log
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
pyinstaller --onefile --name extract_credentials /src/extract_credentials.py && cp /src/dist/extract_credentials.exe /out/ && pyinstaller --onefile --name fetch_credentials /src/fetch_credentials.py && cp /src/dist/fetch_credentials.exe /out/
|
||||||
|
114 INFO: PyInstaller: 3.6
|
||||||
|
114 INFO: Python: 3.7.5
|
||||||
|
114 INFO: Platform: Windows-7-6.1.7601-SP1
|
||||||
|
115 INFO: wrote Z:\src\extract_credentials.spec
|
||||||
|
120 INFO: UPX is not available.
|
||||||
|
121 INFO: Extending PYTHONPATH with paths
|
||||||
|
['Z:\\src', 'Z:\\src']
|
||||||
|
121 INFO: checking Analysis
|
||||||
|
121 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||||
|
121 INFO: Initializing module dependency graph...
|
||||||
|
124 INFO: Caching module graph hooks...
|
||||||
|
130 INFO: Analyzing base_library.zip ...
|
||||||
|
2142 INFO: Caching module dependency graph...
|
||||||
|
2226 INFO: running Analysis Analysis-00.toc
|
||||||
|
2234 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
|
||||||
|
required by c:\Python37\python.exe
|
||||||
|
2467 INFO: Analyzing \src\extract_credentials.py
|
||||||
|
2497 INFO: Processing module hooks...
|
||||||
|
2497 INFO: Loading module hook "hook-encodings.py"...
|
||||||
|
2649 INFO: Loading module hook "hook-pydoc.py"...
|
||||||
|
2650 INFO: Loading module hook "hook-xml.py"...
|
||||||
|
2788 INFO: Looking for ctypes DLLs
|
||||||
|
2791 INFO: Analyzing run-time hooks ...
|
||||||
|
2794 INFO: Looking for dynamic libraries
|
||||||
|
2909 INFO: Looking for eggs
|
||||||
|
2909 INFO: Using Python library c:\Python37\python37.dll
|
||||||
|
2909 INFO: Found binding redirects:
|
||||||
|
[]
|
||||||
|
2910 INFO: Warnings written to Z:\src\build\extract_credentials\warn-extract_credentials.txt
|
||||||
|
2927 INFO: Graph cross-reference written to Z:\src\build\extract_credentials\xref-extract_credentials.html
|
||||||
|
2939 INFO: checking PYZ
|
||||||
|
2939 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||||
|
2939 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz
|
||||||
|
3199 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz completed successfully.
|
||||||
|
3203 INFO: checking PKG
|
||||||
|
3203 INFO: Building PKG because PKG-00.toc is non existent
|
||||||
|
3203 INFO: Building PKG (CArchive) PKG-00.pkg
|
||||||
|
4599 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
|
||||||
|
4601 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
|
||||||
|
4601 INFO: checking EXE
|
||||||
|
4601 INFO: Building EXE because EXE-00.toc is non existent
|
||||||
|
4601 INFO: Building EXE from EXE-00.toc
|
||||||
|
4601 INFO: Appending archive to EXE Z:\src\dist\extract_credentials.exe
|
||||||
|
4604 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||||
|
77 INFO: PyInstaller: 3.6
|
||||||
|
77 INFO: Python: 3.7.5
|
||||||
|
77 INFO: Platform: Windows-7-6.1.7601-SP1
|
||||||
|
77 INFO: wrote Z:\src\fetch_credentials.spec
|
||||||
|
83 INFO: UPX is not available.
|
||||||
|
84 INFO: Extending PYTHONPATH with paths
|
||||||
|
['Z:\\src', 'Z:\\src']
|
||||||
|
84 INFO: checking Analysis
|
||||||
|
84 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||||
|
84 INFO: Initializing module dependency graph...
|
||||||
|
88 INFO: Caching module graph hooks...
|
||||||
|
94 INFO: Analyzing base_library.zip ...
|
||||||
|
2079 INFO: Caching module dependency graph...
|
||||||
|
2168 INFO: running Analysis Analysis-00.toc
|
||||||
|
2170 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
|
||||||
|
required by c:\Python37\python.exe
|
||||||
|
2425 INFO: Analyzing \src\fetch_credentials.py
|
||||||
|
2501 INFO: Processing module hooks...
|
||||||
|
2501 INFO: Loading module hook "hook-encodings.py"...
|
||||||
|
2630 INFO: Loading module hook "hook-pydoc.py"...
|
||||||
|
2631 INFO: Loading module hook "hook-xml.py"...
|
||||||
|
2765 INFO: Looking for ctypes DLLs
|
||||||
|
2765 INFO: Analyzing run-time hooks ...
|
||||||
|
2768 INFO: Looking for dynamic libraries
|
||||||
|
2878 INFO: Looking for eggs
|
||||||
|
2878 INFO: Using Python library c:\Python37\python37.dll
|
||||||
|
2878 INFO: Found binding redirects:
|
||||||
|
[]
|
||||||
|
2880 INFO: Warnings written to Z:\src\build\fetch_credentials\warn-fetch_credentials.txt
|
||||||
|
2896 INFO: Graph cross-reference written to Z:\src\build\fetch_credentials\xref-fetch_credentials.html
|
||||||
|
2901 INFO: checking PYZ
|
||||||
|
2901 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||||
|
2901 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz
|
||||||
|
3159 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz completed successfully.
|
||||||
|
3163 INFO: checking PKG
|
||||||
|
3163 INFO: Building PKG because PKG-00.toc is non existent
|
||||||
|
3163 INFO: Building PKG (CArchive) PKG-00.pkg
|
||||||
|
4428 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
|
||||||
|
4430 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
|
||||||
|
4430 INFO: checking EXE
|
||||||
|
4430 INFO: Building EXE because EXE-00.toc is non existent
|
||||||
|
4430 INFO: Building EXE from EXE-00.toc
|
||||||
|
4430 INFO: Appending archive to EXE Z:\src\dist\fetch_credentials.exe
|
||||||
|
4434 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||||
56
releases/0.9.27/build_extract.log
Normal file
56
releases/0.9.27/build_extract.log
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
32 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
|
||||||
|
32 INFO: Python: 3.12.3
|
||||||
|
33 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
|
||||||
|
33 INFO: Python environment: /usr
|
||||||
|
33 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/extract_credentials.spec
|
||||||
|
34 INFO: Module search paths (PYTHONPATH):
|
||||||
|
['/usr/lib/python312.zip',
|
||||||
|
'/usr/lib/python3.12',
|
||||||
|
'/usr/lib/python3.12/lib-dynload',
|
||||||
|
'/home/coding/.local/lib/python3.12/site-packages',
|
||||||
|
'/usr/local/lib/python3.12/dist-packages',
|
||||||
|
'/usr/lib/python3/dist-packages',
|
||||||
|
'/usr/lib/python3.12/dist-packages',
|
||||||
|
'/home/coding/Source/KX-Bridge-Release']
|
||||||
|
352 INFO: checking Analysis
|
||||||
|
352 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||||
|
352 INFO: Looking for Python shared library...
|
||||||
|
364 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
|
||||||
|
364 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
||||||
|
364 INFO: Running Analysis Analysis-00.toc
|
||||||
|
364 INFO: Target bytecode optimization level: 0
|
||||||
|
364 INFO: Initializing module dependency graph...
|
||||||
|
366 INFO: Initializing module graph hook caches...
|
||||||
|
380 INFO: Analyzing modules for base_library.zip ...
|
||||||
|
1005 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
1923 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2491 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2739 INFO: Caching module dependency graph...
|
||||||
|
2764 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/extract_credentials.py
|
||||||
|
2790 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2801 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2808 INFO: Processing module hooks (post-graph stage)...
|
||||||
|
2812 INFO: Performing binary vs. data reclassification (1 entries)
|
||||||
|
2818 INFO: Looking for ctypes DLLs
|
||||||
|
2825 INFO: Analyzing run-time hooks ...
|
||||||
|
2825 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
2829 INFO: Creating base_library.zip...
|
||||||
|
2841 INFO: Looking for dynamic libraries
|
||||||
|
2987 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/warn-extract_credentials.txt
|
||||||
|
2993 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/xref-extract_credentials.html
|
||||||
|
3000 INFO: checking PYZ
|
||||||
|
3000 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||||
|
3000 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz
|
||||||
|
3099 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz completed successfully.
|
||||||
|
3105 INFO: checking PKG
|
||||||
|
3105 INFO: Building PKG because PKG-00.toc is non existent
|
||||||
|
3105 INFO: Building PKG (CArchive) extract_credentials.pkg
|
||||||
|
5376 INFO: Building PKG (CArchive) extract_credentials.pkg completed successfully.
|
||||||
|
5376 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
|
||||||
|
5376 INFO: checking EXE
|
||||||
|
5376 INFO: Building EXE because EXE-00.toc is non existent
|
||||||
|
5376 INFO: Building EXE from EXE-00.toc
|
||||||
|
5376 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/extract_credentials
|
||||||
|
5376 INFO: Appending PKG archive to custom ELF section in EXE
|
||||||
|
5386 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||||
|
5386 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27
|
||||||
91
releases/0.9.27/build_fetch.log
Normal file
91
releases/0.9.27/build_fetch.log
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
35 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
|
||||||
|
35 INFO: Python: 3.12.3
|
||||||
|
36 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
|
||||||
|
36 INFO: Python environment: /usr
|
||||||
|
36 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/fetch_credentials.spec
|
||||||
|
37 WARNING: collect_data_files - skipping data collection for module 'pycryptodome' as it is not a package.
|
||||||
|
37 WARNING: collect_dynamic_libs - skipping library collection for module 'pycryptodome' as it is not a package.
|
||||||
|
117 INFO: Module search paths (PYTHONPATH):
|
||||||
|
['/usr/lib/python312.zip',
|
||||||
|
'/usr/lib/python3.12',
|
||||||
|
'/usr/lib/python3.12/lib-dynload',
|
||||||
|
'/home/coding/.local/lib/python3.12/site-packages',
|
||||||
|
'/usr/local/lib/python3.12/dist-packages',
|
||||||
|
'/usr/lib/python3/dist-packages',
|
||||||
|
'/usr/lib/python3.12/dist-packages',
|
||||||
|
'/home/coding/Source/KX-Bridge-Release']
|
||||||
|
458 INFO: checking Analysis
|
||||||
|
458 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||||
|
458 INFO: Looking for Python shared library...
|
||||||
|
475 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
|
||||||
|
475 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
||||||
|
475 INFO: Running Analysis Analysis-00.toc
|
||||||
|
475 INFO: Target bytecode optimization level: 0
|
||||||
|
476 INFO: Initializing module dependency graph...
|
||||||
|
476 INFO: Initializing module graph hook caches...
|
||||||
|
491 INFO: Analyzing modules for base_library.zip ...
|
||||||
|
1017 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
1871 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2469 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
2692 INFO: Caching module dependency graph...
|
||||||
|
2717 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/fetch_credentials.py
|
||||||
|
2750 INFO: Processing standard module hook 'hook-urllib3.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
2817 INFO: Processing pre-safe-import-module hook 'hook-typing_extensions.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||||
|
2818 INFO: SetuptoolsInfo: initializing cached setuptools info...
|
||||||
|
3154 INFO: Processing standard module hook 'hook-multiprocessing.util.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
3216 INFO: Processing standard module hook 'hook-xml.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
3402 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
4175 INFO: Processing standard module hook 'hook-chardet.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
4759 INFO: Processing standard module hook 'hook-cryptography.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
5142 INFO: hook-cryptography: cryptography uses dynamically-linked OpenSSL: '/lib/x86_64-linux-gnu/libssl.so.3'
|
||||||
|
5351 INFO: Processing standard module hook 'hook-bcrypt.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
5426 INFO: Processing standard module hook 'hook-certifi.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
5502 INFO: Processing standard module hook 'hook-Crypto.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
5706 INFO: Processing standard module hook 'hook-pycparser.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
5802 INFO: Processing standard module hook 'hook-setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
5806 INFO: Processing pre-safe-import-module hook 'hook-distutils.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||||
|
5861 INFO: Processing standard module hook 'hook-sysconfig.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
5896 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
5906 INFO: Processing standard module hook 'hook-_osx_support.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
6028 INFO: Processing pre-safe-import-module hook 'hook-importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||||
|
6141 INFO: Processing pre-safe-import-module hook 'hook-packaging.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||||
|
6502 INFO: Processing standard module hook 'hook-pkg_resources.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
7089 INFO: Processing standard module hook 'hook-xml.dom.domreg.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
7323 INFO: Processing standard module hook 'hook-sqlite3.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
7648 INFO: Processing standard module hook 'hook-difflib.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
7995 INFO: Processing module hooks (post-graph stage)...
|
||||||
|
7997 WARNING: Hidden import "pycparser.lextab" not found!
|
||||||
|
7997 WARNING: Hidden import "pycparser.yacctab" not found!
|
||||||
|
8231 INFO: Processing pre-safe-import-module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||||
|
8237 INFO: Processing standard module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||||
|
8304 INFO: Processing standard module hook 'hook-setuptools._vendor.importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
8391 INFO: Processing standard module hook 'hook-setuptools._vendor.jaraco.text.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||||
|
8490 INFO: Performing binary vs. data reclassification (51 entries)
|
||||||
|
8605 INFO: Looking for ctypes DLLs
|
||||||
|
8618 INFO: Analyzing run-time hooks ...
|
||||||
|
8620 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
8622 INFO: Including run-time hook 'pyi_rth_pkgutil.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
8623 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
8625 INFO: Including run-time hook 'pyi_rth_setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
8626 INFO: Including run-time hook 'pyi_rth_pkgres.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||||
|
8628 INFO: Including run-time hook 'pyi_rth_cryptography_openssl.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/rthooks'
|
||||||
|
8635 INFO: Creating base_library.zip...
|
||||||
|
8664 INFO: Looking for dynamic libraries
|
||||||
|
9293 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/warn-fetch_credentials.txt
|
||||||
|
9315 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/xref-fetch_credentials.html
|
||||||
|
9327 INFO: checking PYZ
|
||||||
|
9327 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||||
|
9327 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz
|
||||||
|
9755 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz completed successfully.
|
||||||
|
9766 INFO: checking PKG
|
||||||
|
9766 INFO: Building PKG because PKG-00.toc is non existent
|
||||||
|
9766 INFO: Building PKG (CArchive) fetch_credentials.pkg
|
||||||
|
14335 INFO: Building PKG (CArchive) fetch_credentials.pkg completed successfully.
|
||||||
|
14336 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
|
||||||
|
14336 INFO: checking EXE
|
||||||
|
14336 INFO: Building EXE because EXE-00.toc is non existent
|
||||||
|
14336 INFO: Building EXE from EXE-00.toc
|
||||||
|
14336 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/fetch_credentials
|
||||||
|
14336 INFO: Appending PKG archive to custom ELF section in EXE
|
||||||
|
14360 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||||
|
14361 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27
|
||||||
BIN
releases/0.9.27/fetch_credentials
Executable file
BIN
releases/0.9.27/fetch_credentials
Executable file
Binary file not shown.
BIN
releases/0.9.27/fetch_credentials.exe
Executable file
BIN
releases/0.9.27/fetch_credentials.exe
Executable file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge-linux-amd64
Executable file
BIN
releases/0.9.27/kx-bridge-linux-amd64
Executable file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge-linux-amd64.zip
Normal file
BIN
releases/0.9.27/kx-bridge-linux-amd64.zip
Normal file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge-linux-arm64
Executable file
BIN
releases/0.9.27/kx-bridge-linux-arm64
Executable file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge-linux-arm64.zip
Normal file
BIN
releases/0.9.27/kx-bridge-linux-arm64.zip
Normal file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge-windows.zip
Normal file
BIN
releases/0.9.27/kx-bridge-windows.zip
Normal file
Binary file not shown.
BIN
releases/0.9.27/kx-bridge.exe
Executable file
BIN
releases/0.9.27/kx-bridge.exe
Executable file
Binary file not shown.
68
tests/conftest.py
Normal file
68
tests/conftest.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
Shared fixtures für KX-Bridge Tests.
|
||||||
|
Startet die Bridge in-process mit einem Mock-MQTT-Client (kein Drucker nötig).
|
||||||
|
"""
|
||||||
|
import sys, types, argparse, pytest, pytest_asyncio
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from aiohttp.test_utils import TestClient, TestServer
|
||||||
|
|
||||||
|
# ── Pfad ──────────────────────────────────────────────────────────────────────
|
||||||
|
sys.path.insert(0, str(__import__("pathlib").Path(__file__).parent.parent / "bridge"))
|
||||||
|
|
||||||
|
# ── env_loader mocken (keine .env nötig) ──────────────────────────────────────
|
||||||
|
env_mod = types.ModuleType("env_loader")
|
||||||
|
env_mod.PRINTER_IP = ""
|
||||||
|
env_mod.MQTT_PORT = 9883
|
||||||
|
env_mod.USERNAME = ""
|
||||||
|
env_mod.PASSWORD = ""
|
||||||
|
env_mod.MODE_ID = "20030"
|
||||||
|
env_mod.DEVICE_ID = ""
|
||||||
|
sys.modules["env_loader"] = env_mod
|
||||||
|
|
||||||
|
# ── Bridge + App importieren ───────────────────────────────────────────────────
|
||||||
|
from kobrax_moonraker_bridge import KobraXBridge, build_app # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def make_mock_client():
|
||||||
|
"""Minimaler Mock-MQTT-Client — keine Verbindung, keine Threads."""
|
||||||
|
c = MagicMock()
|
||||||
|
c.callbacks = {}
|
||||||
|
c.connected = False
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def make_args(**overrides):
|
||||||
|
args = argparse.Namespace(
|
||||||
|
printer_ip = "",
|
||||||
|
mqtt_port = 9883,
|
||||||
|
username = "",
|
||||||
|
password = "",
|
||||||
|
mode_id = "20030",
|
||||||
|
device_id = "",
|
||||||
|
host = "127.0.0.1",
|
||||||
|
port = 7125,
|
||||||
|
)
|
||||||
|
for k, v in overrides.items():
|
||||||
|
setattr(args, k, v)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def client():
|
||||||
|
"""TestClient mit frischer Bridge-Instanz, ohne MQTT-Verbindung."""
|
||||||
|
mock_client = make_mock_client()
|
||||||
|
bridge = KobraXBridge(mock_client, args=make_args())
|
||||||
|
app = build_app(bridge)
|
||||||
|
async with TestClient(TestServer(app)) as c:
|
||||||
|
yield c, bridge
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def client_configured():
|
||||||
|
"""TestClient mit bereits konfigurierten Zugangsdaten."""
|
||||||
|
mock_client = make_mock_client()
|
||||||
|
args = make_args(printer_ip="192.168.1.100", device_id="abc123deadbeef")
|
||||||
|
bridge = KobraXBridge(mock_client, args=args)
|
||||||
|
app = build_app(bridge)
|
||||||
|
async with TestClient(TestServer(app)) as c:
|
||||||
|
yield c, bridge
|
||||||
3
tests/requirements-test.txt
Normal file
3
tests/requirements-test.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pytest
|
||||||
|
pytest-asyncio
|
||||||
|
aiohttp
|
||||||
113
tests/test_api_state.py
Normal file
113
tests/test_api_state.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"""
|
||||||
|
Tests für /api/state — Drucker-Zustandsabfrage.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_returns_200(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/api/state")
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_schema(client):
|
||||||
|
"""Alle erwarteten Felder müssen vorhanden und typsicher sein."""
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/api/state")
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
assert isinstance(data["print_state"], str)
|
||||||
|
assert isinstance(data["kobra_state"], str)
|
||||||
|
assert isinstance(data["nozzle_temp"], float)
|
||||||
|
assert isinstance(data["nozzle_target"], float)
|
||||||
|
assert isinstance(data["bed_temp"], float)
|
||||||
|
assert isinstance(data["bed_target"], float)
|
||||||
|
assert isinstance(data["progress"], float)
|
||||||
|
assert isinstance(data["print_duration"], int)
|
||||||
|
assert isinstance(data["remain_time"], int)
|
||||||
|
assert isinstance(data["curr_layer"], int)
|
||||||
|
assert isinstance(data["total_layers"], int)
|
||||||
|
assert isinstance(data["filename"], str)
|
||||||
|
assert isinstance(data["fan_speed"], int)
|
||||||
|
assert isinstance(data["light_on"], bool)
|
||||||
|
assert isinstance(data["ams_slots"], list)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_initial_values(client):
|
||||||
|
"""Im Offline-Start müssen Temperaturen 0 und Zustand 'standby' sein."""
|
||||||
|
c, _ = client
|
||||||
|
data = await (await c.get("/api/state")).json()
|
||||||
|
|
||||||
|
assert data["print_state"] == "standby"
|
||||||
|
assert data["nozzle_temp"] == 0.0
|
||||||
|
assert data["bed_temp"] == 0.0
|
||||||
|
assert data["progress"] == 0.0
|
||||||
|
assert data["filename"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_updates_after_mqtt_print_report(client):
|
||||||
|
"""Simuliert ein eingehendes print/report MQTT-Paket und prüft State-Update."""
|
||||||
|
c, bridge = client
|
||||||
|
|
||||||
|
# Simuliere MQTT-Nachricht wie vom echten Drucker
|
||||||
|
bridge._on_print({
|
||||||
|
"state": "printing",
|
||||||
|
"data": {
|
||||||
|
"filename": "test.gcode",
|
||||||
|
"progress": 42,
|
||||||
|
"print_time": 10, # Minuten → 600s
|
||||||
|
"remain_time": 5, # Minuten → 300s
|
||||||
|
"curr_layer": 20,
|
||||||
|
"total_layers": 100,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
data = await (await c.get("/api/state")).json()
|
||||||
|
|
||||||
|
assert data["print_state"] == "printing"
|
||||||
|
assert data["filename"] == "test.gcode"
|
||||||
|
assert data["progress"] == pytest.approx(0.42)
|
||||||
|
assert data["print_duration"] == 600
|
||||||
|
assert data["remain_time"] == 300
|
||||||
|
assert data["curr_layer"] == 20
|
||||||
|
assert data["total_layers"] == 100
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_updates_after_mqtt_temp_report(client):
|
||||||
|
"""Simuliert ein tempature/report Paket."""
|
||||||
|
c, bridge = client
|
||||||
|
|
||||||
|
bridge._on_temp({
|
||||||
|
"data": {
|
||||||
|
"curr_nozzle_temp": 215.3,
|
||||||
|
"target_nozzle_temp": 220.0,
|
||||||
|
"curr_hotbed_temp": 59.8,
|
||||||
|
"target_hotbed_temp": 60.0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
data = await (await c.get("/api/state")).json()
|
||||||
|
assert data["nozzle_temp"] == pytest.approx(215.3)
|
||||||
|
assert data["nozzle_target"] == pytest.approx(220.0)
|
||||||
|
assert data["bed_temp"] == pytest.approx(59.8)
|
||||||
|
assert data["bed_target"] == pytest.approx(60.0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_state_resets_on_cancel(client):
|
||||||
|
"""Nach 'stoped' müssen Progress und Filename zurückgesetzt werden."""
|
||||||
|
c, bridge = client
|
||||||
|
|
||||||
|
# Erst Druck simulieren
|
||||||
|
bridge._on_print({"state": "printing", "data": {"filename": "x.gcode", "progress": 50}})
|
||||||
|
# Dann Abbruch
|
||||||
|
bridge._on_print({"state": "stoped", "data": {}})
|
||||||
|
|
||||||
|
data = await (await c.get("/api/state")).json()
|
||||||
|
assert data["progress"] == 0.0
|
||||||
|
assert data["filename"] == ""
|
||||||
109
tests/test_install.sh
Normal file
109
tests/test_install.sh
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_install.sh – Smoke-Test: Release-Repo klonen, start.sh ausführen, HTTP prüfen.
|
||||||
|
#
|
||||||
|
# Simuliert den Weg eines anonymen Nutzers:
|
||||||
|
# 1. Release-Repo klonen
|
||||||
|
# 2. start.sh ausführen (baut Docker-Image, startet Container)
|
||||||
|
# 3. HTTP-Endpunkte prüfen
|
||||||
|
# 4. Aufräumen
|
||||||
|
#
|
||||||
|
# Voraussetzung: Docker installiert, Port 7125 frei
|
||||||
|
#
|
||||||
|
# Verwendung:
|
||||||
|
# bash tests/test_install.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_URL="https://gitea.it-drui.de/viewit/KX-Bridge-Release"
|
||||||
|
WORK_DIR=$(mktemp -d /tmp/kx-bridge-test-XXXXXX)
|
||||||
|
PASS=0; FAIL=0
|
||||||
|
|
||||||
|
ok() { echo " ✓ $*"; PASS=$((PASS+1)); }
|
||||||
|
fail() { echo " ✗ $*"; FAIL=$((FAIL+1)); }
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo ""
|
||||||
|
echo "[cleanup] Stoppe Container und lösche Testverzeichnis ..."
|
||||||
|
cd "$WORK_DIR/KX-Bridge-Release" 2>/dev/null && docker-compose down 2>/dev/null || true
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo "=== KX-Bridge Installations-Smoke-Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Schritt 1: Repo klonen ────────────────────────────────────────────────────
|
||||||
|
echo "[1/5] Klone Release-Repo ..."
|
||||||
|
git clone --depth=1 "$GITEA_URL" "$WORK_DIR/KX-Bridge-Release" > /dev/null 2>&1 \
|
||||||
|
&& ok "Repo geklont" \
|
||||||
|
|| { fail "git clone fehlgeschlagen"; exit 1; }
|
||||||
|
|
||||||
|
cd "$WORK_DIR/KX-Bridge-Release"
|
||||||
|
|
||||||
|
# ── Schritt 2: Erwartete Dateien vorhanden ────────────────────────────────────
|
||||||
|
echo "[2/5] Prüfe Dateien im Repo ..."
|
||||||
|
for f in start.sh docker-compose.yml Dockerfile kobrax_moonraker_bridge.py \
|
||||||
|
anycubic_slicer.crt anycubic_slicer.key .env.example; do
|
||||||
|
[[ -f "$f" ]] && ok "$f vorhanden" || fail "$f FEHLT"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Dockerfile darf keine 05_scripts/-Pfade enthalten
|
||||||
|
if grep -q "05_scripts/" Dockerfile; then
|
||||||
|
fail "Dockerfile enthält '05_scripts/' – falsches Dockerfile im Release-Repo!"
|
||||||
|
else
|
||||||
|
ok "Dockerfile Pfade korrekt (kein 05_scripts/-Präfix)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Schritt 3: start.sh ausführen ────────────────────────────────────────────
|
||||||
|
echo "[3/5] Führe start.sh aus ..."
|
||||||
|
chmod +x start.sh
|
||||||
|
./start.sh > /tmp/kx-bridge-start.log 2>&1 \
|
||||||
|
&& ok "start.sh erfolgreich" \
|
||||||
|
|| { fail "start.sh fehlgeschlagen (siehe /tmp/kx-bridge-start.log)"; cat /tmp/kx-bridge-start.log; exit 1; }
|
||||||
|
|
||||||
|
# Kurz warten bis Bridge hochgefahren
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# ── Schritt 4: HTTP-Endpunkte prüfen ─────────────────────────────────────────
|
||||||
|
echo "[4/5] Prüfe HTTP-Endpunkte ..."
|
||||||
|
BASE="http://localhost:7125"
|
||||||
|
|
||||||
|
check_endpoint() {
|
||||||
|
local path="$1"
|
||||||
|
local desc="$2"
|
||||||
|
local http_code
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE$path")
|
||||||
|
[[ "$http_code" == "200" ]] \
|
||||||
|
&& ok "$desc ($path → $http_code)" \
|
||||||
|
|| fail "$desc ($path → $http_code)"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_endpoint "/" "Web-UI (index.html)"
|
||||||
|
check_endpoint "/api/state" "GET /api/state"
|
||||||
|
check_endpoint "/api/settings" "GET /api/settings"
|
||||||
|
check_endpoint "/server/info" "GET /server/info (Moonraker)"
|
||||||
|
check_endpoint "/printer/info" "GET /printer/info (Moonraker)"
|
||||||
|
check_endpoint "/printer/objects/list" "GET /printer/objects/list"
|
||||||
|
check_endpoint "/api/version" "GET /api/version (OctoPrint compat)"
|
||||||
|
|
||||||
|
# Beim ersten Start: printer_ip muss leer sein → Settings-Modal würde sich öffnen
|
||||||
|
SETTINGS=$(curl -s "$BASE/api/settings")
|
||||||
|
PRINTER_IP=$(echo "$SETTINGS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('printer_ip',''))" 2>/dev/null || echo "ERROR")
|
||||||
|
[[ -z "$PRINTER_IP" ]] \
|
||||||
|
&& ok "Erstkonfiguration erkannt: printer_ip leer → Settings-Modal öffnet sich" \
|
||||||
|
|| fail "printer_ip sollte beim Erststart leer sein, ist: '$PRINTER_IP'"
|
||||||
|
|
||||||
|
# ── Schritt 5: Container läuft stabil ────────────────────────────────────────
|
||||||
|
echo "[5/5] Prüfe Container-Stabilität ..."
|
||||||
|
sleep 2
|
||||||
|
RUNNING=$(docker-compose ps --services --filter "status=running" 2>/dev/null || true)
|
||||||
|
[[ -n "$RUNNING" ]] \
|
||||||
|
&& ok "Container läuft stabil" \
|
||||||
|
|| fail "Container ist nicht mehr aktiv"
|
||||||
|
|
||||||
|
# ── Ergebnis ──────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "══════════════════════════════════════"
|
||||||
|
echo " Ergebnis: $PASS bestanden, $FAIL fehlgeschlagen"
|
||||||
|
echo "══════════════════════════════════════"
|
||||||
|
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
||||||
77
tests/test_moonraker.py
Normal file
77
tests/test_moonraker.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
Tests für Moonraker-kompatible Endpunkte die OrcaSlicer aufruft.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_server_info(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/server/info")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
assert data["result"]["klippy_state"] in ("ready", "standby", "error")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_printer_info(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/printer/info")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
assert "hostname" in data["result"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_objects_list(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/printer/objects/list")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
objects = data["result"]["objects"]
|
||||||
|
# OrcaSlicer erwartet mindestens diese Objekte
|
||||||
|
for obj in ("print_stats", "heater_bed", "extruder", "display_status"):
|
||||||
|
assert obj in objects
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_objects_query_print_stats(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/printer/objects/query?print_stats")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
ps = data["result"]["status"]["print_stats"]
|
||||||
|
assert "state" in ps
|
||||||
|
assert "filename" in ps
|
||||||
|
assert "print_duration" in ps
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_objects_query_temperatures(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/printer/objects/query?extruder&heater_bed")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
status = data["result"]["status"]
|
||||||
|
assert "temperature" in status["extruder"]
|
||||||
|
assert "temperature" in status["heater_bed"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_octoprint_version(client):
|
||||||
|
"""OrcaSlicer probt /api/version um Drucker-Typ zu erkennen."""
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/api/version")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
assert "server" in data
|
||||||
|
assert "api" in data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_files_list(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/server/files/list")
|
||||||
|
assert resp.status == 200
|
||||||
|
data = await resp.json()
|
||||||
|
assert isinstance(data["result"], list)
|
||||||
82
tests/test_settings.py
Normal file
82
tests/test_settings.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
"""
|
||||||
|
Tests für /api/settings — Lesen und Schreiben der Verbindungseinstellungen.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_get_returns_200(client):
|
||||||
|
c, _ = client
|
||||||
|
resp = await c.get("/api/settings")
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_get_schema(client):
|
||||||
|
c, _ = client
|
||||||
|
data = await (await c.get("/api/settings")).json()
|
||||||
|
for key in ("printer_ip", "mqtt_port", "username", "password", "device_id", "mode_id"):
|
||||||
|
assert key in data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_get_empty_when_unconfigured(client):
|
||||||
|
"""Frische Bridge ohne Zugangsdaten → printer_ip und device_id leer."""
|
||||||
|
c, _ = client
|
||||||
|
data = await (await c.get("/api/settings")).json()
|
||||||
|
assert data["printer_ip"] == ""
|
||||||
|
assert data["device_id"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_get_returns_configured_values(client_configured):
|
||||||
|
"""Bridge mit Zugangsdaten → Werte korrekt zurückgegeben."""
|
||||||
|
c, _ = client_configured
|
||||||
|
data = await (await c.get("/api/settings")).json()
|
||||||
|
assert data["printer_ip"] == "192.168.1.100"
|
||||||
|
assert data["device_id"] == "abc123deadbeef"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_post_writes_env(client):
|
||||||
|
"""POST /api/settings schreibt Werte in .env-Datei."""
|
||||||
|
c, bridge = client
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
env_path = pathlib.Path(tmpdir) / ".env"
|
||||||
|
env_path.write_text("")
|
||||||
|
bridge._find_env_path = lambda: env_path
|
||||||
|
|
||||||
|
resp = await c.post("/api/settings", json={
|
||||||
|
"printer_ip": "10.0.0.5",
|
||||||
|
"mqtt_port": 9883,
|
||||||
|
"username": "userABCD",
|
||||||
|
"password": "secret123",
|
||||||
|
"device_id": "deadbeef01234567",
|
||||||
|
"mode_id": "20030",
|
||||||
|
})
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
content = env_path.read_text()
|
||||||
|
assert "PRINTER_IP=10.0.0.5" in content
|
||||||
|
assert "MQTT_USERNAME=userABCD" in content
|
||||||
|
assert "DEVICE_ID=deadbeef01234567" in content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_settings_post_preserves_existing_keys(client):
|
||||||
|
"""POST darf unbekannte Keys in .env nicht löschen (z.B. GITEA_TOKEN)."""
|
||||||
|
c, bridge = client
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
env_path = pathlib.Path(tmpdir) / ".env"
|
||||||
|
env_path.write_text("GITEA_TOKEN=mytoken\nPRINTER_IP=old\n")
|
||||||
|
bridge._find_env_path = lambda: env_path
|
||||||
|
|
||||||
|
await c.post("/api/settings", json={"printer_ip": "10.0.0.99"})
|
||||||
|
|
||||||
|
content = env_path.read_text()
|
||||||
|
assert "GITEA_TOKEN=mytoken" in content
|
||||||
|
assert "PRINTER_IP=10.0.0.99" in content
|
||||||
@@ -44,6 +44,73 @@ var ACE_DRY_PRESETS={
|
|||||||
custom_3:{name:'Custom 3',temp:45,duration_sec:4*3600}
|
custom_3:{name:'Custom 3',temp:45,duration_sec:4*3600}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Spoolman state
|
||||||
|
var _spoolmanStatus={configured:false,server:'',sync_rate:0,slot_spools:{}};
|
||||||
|
var _spoolmanSpools=[];
|
||||||
|
var _slotSpoolMap={}; // {String(global_index): spoolman_spool_id} — last committed assignment
|
||||||
|
|
||||||
|
function _loadSpoolmanStatus(){
|
||||||
|
fetch(_apiUrl('/kx/spoolman/status')).then(function(r){return r.json();}).then(function(d){
|
||||||
|
_spoolmanStatus=d;
|
||||||
|
_slotSpoolMap=d.slot_spools||{};
|
||||||
|
_updateSpoolmanStatusDot();
|
||||||
|
}).catch(function(){});
|
||||||
|
}
|
||||||
|
function _updateSpoolmanStatusDot(){
|
||||||
|
var dot=document.getElementById('spoolman-status-dot');
|
||||||
|
var lbl=document.getElementById('spoolman-status-lbl');
|
||||||
|
if(!dot||!lbl)return;
|
||||||
|
if(_spoolmanStatus.configured){
|
||||||
|
dot.style.color='var(--ok)';lbl.textContent=_spoolmanStatus.server||'verbunden';
|
||||||
|
} else {
|
||||||
|
dot.style.color='var(--txt2)';lbl.textContent='nicht konfiguriert';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildSpoolmanSection(){
|
||||||
|
var sec=document.getElementById('fd-spoolman-section');
|
||||||
|
var rows=document.getElementById('fd-spoolman-rows');
|
||||||
|
var loading=document.getElementById('fd-spoolman-loading');
|
||||||
|
if(!sec||!rows)return;
|
||||||
|
if(!_spoolmanStatus.configured){sec.style.display='none';return;}
|
||||||
|
sec.style.display='';
|
||||||
|
rows.innerHTML='';
|
||||||
|
if(loading)loading.style.display='';
|
||||||
|
|
||||||
|
var usedSlots={};
|
||||||
|
document.querySelectorAll('#fd-slots select').forEach(function(sel){
|
||||||
|
var idx=parseInt(sel.value);
|
||||||
|
if(idx>=0){
|
||||||
|
var slot=(_amsSlots||[]).find(function(s){return s.slot_index===idx;});
|
||||||
|
if(slot&&!usedSlots[idx])usedSlots[idx]=slot;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch(_apiUrl('/kx/spoolman/spools')).then(function(r){return r.json();}).then(function(d){
|
||||||
|
if(loading)loading.style.display='none';
|
||||||
|
_spoolmanSpools=d.spools||[];
|
||||||
|
var slotKeys=Object.keys(usedSlots).map(Number).sort(function(a,b){return a-b;});
|
||||||
|
if(!slotKeys.length){rows.innerHTML='<span style="font-size:11px;color:var(--txt2)">–</span>';return;}
|
||||||
|
rows.innerHTML=slotKeys.map(function(idx){
|
||||||
|
var slot=usedSlots[idx];
|
||||||
|
var col=(slot.color_hex||'#888');
|
||||||
|
var currentSpool=_slotSpoolMap[String(idx)]||'';
|
||||||
|
var opts='<option value="">–</option>'+_spoolmanSpools.map(function(sp){
|
||||||
|
var rem=sp.remaining_weight!=null?' ('+sp.remaining_weight.toFixed(0)+'g)':'';
|
||||||
|
var vendor=sp.filament&&sp.filament.vendor?sp.filament.vendor+' ':'';
|
||||||
|
var name=sp.filament&&sp.filament.name?sp.filament.name:'Spool';
|
||||||
|
return '<option value="'+sp.id+'"'+(sp.id==currentSpool?' selected':'')+'>'+
|
||||||
|
escHtml('#'+sp.id+' '+vendor+name+rem)+'</option>';
|
||||||
|
}).join('');
|
||||||
|
return '<div style="display:flex;align-items:center;gap:8px;font-size:12px">'+
|
||||||
|
'<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:'+col+';border:1px solid var(--border);flex-shrink:0"></span>'+
|
||||||
|
'<span style="color:var(--txt2);min-width:46px">Slot '+(idx+1)+'</span>'+
|
||||||
|
'<select data-spool-slot="'+idx+'" style="flex:1;padding:3px 6px;border-radius:6px;border:1px solid var(--border);background:var(--raised);color:var(--txt);font-size:12px">'+
|
||||||
|
opts+'</select></div>';
|
||||||
|
}).join('');
|
||||||
|
}).catch(function(){if(loading)loading.style.display='none';});
|
||||||
|
}
|
||||||
|
|
||||||
function _aceAutoRefillGet(aceId){return !!aceAutoRefillPrefs[String(aceId)];}
|
function _aceAutoRefillGet(aceId){return !!aceAutoRefillPrefs[String(aceId)];}
|
||||||
function _aceAutoRefillSet(aceId,on){
|
function _aceAutoRefillSet(aceId,on){
|
||||||
aceAutoRefillPrefs[String(aceId)]=!!on;
|
aceAutoRefillPrefs[String(aceId)]=!!on;
|
||||||
@@ -329,6 +396,11 @@ function applyLang(){
|
|||||||
setText('setcat-lbl-display',T.settings_cat_display||'Darstellung');
|
setText('setcat-lbl-display',T.settings_cat_display||'Darstellung');
|
||||||
setText('setcat-lbl-display2',T.settings_cat_display||'Darstellung');
|
setText('setcat-lbl-display2',T.settings_cat_display||'Darstellung');
|
||||||
setText('setcat-lbl-filament',T.settings_cat_filament||'Filament');
|
setText('setcat-lbl-filament',T.settings_cat_filament||'Filament');
|
||||||
|
setText('setcat-lbl-integrations',T.settings_integrations||'Integrationen');
|
||||||
|
setText('modal-sec-spoolman',T.modal_sec_spoolman||'Spoolman');
|
||||||
|
setText('lbl-spoolman-url',T.lbl_spoolman_url||'Server-URL');
|
||||||
|
setText('lbl-spoolman-sync-rate',T.lbl_spoolman_sync_rate||'Sync-Rate (s, 0=aus)');
|
||||||
|
setText('modal-sec-obico',T.modal_sec_obico||'Obico');
|
||||||
setText('setcat-lbl-system',T.settings_version||'System');
|
setText('setcat-lbl-system',T.settings_version||'System');
|
||||||
setText('lbl-set-lang',T.settings_cat_language||'Sprache');
|
setText('lbl-set-lang',T.settings_cat_language||'Sprache');
|
||||||
setText('lbl-set-theme',T.settings_cat_theme||'Hell / Dunkel umschalten');
|
setText('lbl-set-theme',T.settings_cat_theme||'Hell / Dunkel umschalten');
|
||||||
@@ -478,6 +550,7 @@ function ensureAceDryCards(){
|
|||||||
// defer until DOM ready
|
// defer until DOM ready
|
||||||
window.addEventListener('DOMContentLoaded',function(){
|
window.addEventListener('DOMContentLoaded',function(){
|
||||||
setLanguage(currentLang).catch(function(){});
|
setLanguage(currentLang).catch(function(){});
|
||||||
|
_loadSpoolmanStatus();
|
||||||
// Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen")
|
// Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen")
|
||||||
fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){
|
fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){
|
||||||
if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();}
|
if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();}
|
||||||
@@ -1002,6 +1075,10 @@ function openSettings(){
|
|||||||
pi.value=sec;
|
pi.value=sec;
|
||||||
}
|
}
|
||||||
renderFilamentMapping(d.filament_profiles||{});
|
renderFilamentMapping(d.filament_profiles||{});
|
||||||
|
// Spoolman
|
||||||
|
var su=document.getElementById('s-spoolman-url');if(su)su.value=d.spoolman_server||'';
|
||||||
|
var sr=document.getElementById('s-spoolman-sync-rate');if(sr)sr.value=(d.spoolman_sync_rate!==undefined?d.spoolman_sync_rate:30);
|
||||||
|
_updateSpoolmanStatusDot();
|
||||||
});
|
});
|
||||||
// Sprach-Select im Settings-Panel mit aktueller Sprache spiegeln
|
// Sprach-Select im Settings-Panel mit aktueller Sprache spiegeln
|
||||||
var ls=document.getElementById('s-lang-select');
|
var ls=document.getElementById('s-lang-select');
|
||||||
@@ -1570,12 +1647,15 @@ function saveSettings(){
|
|||||||
print_start_dialog: parseInt((document.getElementById('s-file-ready-mode')||{}).value||'1',10),
|
print_start_dialog: parseInt((document.getElementById('s-file-ready-mode')||{}).value||'1',10),
|
||||||
web_upload_warning:webUploadWarning,
|
web_upload_warning:webUploadWarning,
|
||||||
poll_interval: Math.min(60,Math.max(1,parseInt((document.getElementById('s-poll-interval')||{}).value,10)||3)),
|
poll_interval: Math.min(60,Math.max(1,parseInt((document.getElementById('s-poll-interval')||{}).value,10)||3)),
|
||||||
|
spoolman_server: (document.getElementById('s-spoolman-url')||{}).value||'',
|
||||||
|
spoolman_sync_rate: Math.max(0,parseInt((document.getElementById('s-spoolman-sync-rate')||{}).value||'30',10)),
|
||||||
}).then(function(){
|
}).then(function(){
|
||||||
btn.textContent=T.update_restarting;
|
btn.textContent=T.update_restarting;
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
btn.disabled=false;
|
btn.disabled=false;
|
||||||
setText('btn-save-settings',T.settings_save);
|
setText('btn-save-settings',T.settings_save);
|
||||||
closeSettings();
|
closeSettings();
|
||||||
|
_loadSpoolmanStatus();
|
||||||
poll();
|
poll();
|
||||||
},4000);
|
},4000);
|
||||||
}).catch(function(e){
|
}).catch(function(e){
|
||||||
@@ -1583,21 +1663,33 @@ function saveSettings(){
|
|||||||
clog('Settings-Fehler: '+e,'msg-err');
|
clog('Settings-Fehler: '+e,'msg-err');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
var _updateDockerOnly=false;
|
||||||
function checkUpdate(){
|
function checkUpdate(){
|
||||||
var sb=document.getElementById('update-status');
|
var sb=document.getElementById('update-status');
|
||||||
sb.textContent=T.update_checking;
|
sb.textContent=T.update_checking;
|
||||||
document.getElementById('btn-update-apply').style.display='none';
|
document.getElementById('btn-update-apply').style.display='none';
|
||||||
_updateTag='';_updateUrl='';
|
_updateTag='';_updateUrl='';_updateDockerOnly=false;
|
||||||
fetch(_apiUrl('/api/update/check')).then(function(r){return r.json()}).then(function(d){
|
fetch(_apiUrl('/api/update/check')).then(function(r){return r.json()}).then(function(d){
|
||||||
if(d.error){sb.textContent=T.update_error+': '+d.error;return;}
|
if(d.error){sb.textContent=T.update_error+': '+d.error;return;}
|
||||||
var cl=document.getElementById('update-changelog');
|
var cl=document.getElementById('update-changelog');
|
||||||
if(d.changelog&&d.changelog.trim()){cl.textContent=d.changelog;cl.style.display='block';}
|
if(d.changelog&&d.changelog.trim()){
|
||||||
else{cl.style.display='none';}
|
// Changelog als Markdown-Text (pre-formatiert) anzeigen
|
||||||
|
cl.textContent=d.changelog;cl.style.display='block';
|
||||||
|
} else {cl.style.display='none';}
|
||||||
|
_updateDockerOnly=!!d.docker_only;
|
||||||
if(d.update_available){
|
if(d.update_available){
|
||||||
sb.textContent='v'+d.latest+' '+T.update_available;
|
sb.textContent=d.latest+' '+T.update_available;
|
||||||
sb.style.color='var(--ok)';
|
sb.style.color='var(--ok)';
|
||||||
_updateTag=d.tag;_updateUrl=d.download_url;
|
_updateTag=d.tag;_updateUrl=d.download_url||'';
|
||||||
document.getElementById('btn-update-apply').style.display='inline-block';
|
var btn=document.getElementById('btn-update-apply');
|
||||||
|
if(_updateDockerOnly){
|
||||||
|
btn.textContent=T.update_docker||'docker compose pull';
|
||||||
|
btn.title='docker compose pull && docker compose up -d';
|
||||||
|
} else {
|
||||||
|
btn.textContent=T.update_apply;
|
||||||
|
btn.title='';
|
||||||
|
}
|
||||||
|
btn.style.display='inline-block';
|
||||||
} else {
|
} else {
|
||||||
sb.textContent=T.update_none;
|
sb.textContent=T.update_none;
|
||||||
sb.style.color='var(--txt2)';
|
sb.style.color='var(--txt2)';
|
||||||
@@ -1605,9 +1697,21 @@ function checkUpdate(){
|
|||||||
}).catch(function(e){sb.textContent=T.update_error+': '+e;});
|
}).catch(function(e){sb.textContent=T.update_error+': '+e;});
|
||||||
}
|
}
|
||||||
function applyUpdate(){
|
function applyUpdate(){
|
||||||
if(!_updateUrl)return;
|
|
||||||
var sb=document.getElementById('update-status');
|
var sb=document.getElementById('update-status');
|
||||||
var btn=document.getElementById('btn-update-apply');
|
var btn=document.getElementById('btn-update-apply');
|
||||||
|
if(_updateDockerOnly){
|
||||||
|
// Nightly: kein Self-Update, Docker-Befehl in Zwischenablage kopieren
|
||||||
|
var cmd='docker compose pull && docker compose up -d';
|
||||||
|
if(navigator.clipboard){
|
||||||
|
navigator.clipboard.writeText(cmd).then(function(){
|
||||||
|
sb.textContent=T.update_docker_copied||'Befehl kopiert: '+cmd;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sb.textContent=cmd;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!_updateUrl)return;
|
||||||
btn.disabled=true;sb.textContent=T.update_applying;
|
btn.disabled=true;sb.textContent=T.update_applying;
|
||||||
post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){
|
post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){
|
||||||
sb.textContent=T.update_restarting;
|
sb.textContent=T.update_restarting;
|
||||||
@@ -1793,6 +1897,7 @@ function camStart(){
|
|||||||
post('/api/camera/start',{}).then(function(){
|
post('/api/camera/start',{}).then(function(){
|
||||||
camOn=true;
|
camOn=true;
|
||||||
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop');
|
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop');
|
||||||
|
var rb=document.getElementById('cam-reset-btn');if(rb)rb.style.display='';
|
||||||
clog(tr('log_cam_start'),'msg-ok');
|
clog(tr('log_cam_start'),'msg-ok');
|
||||||
setTimeout(function(){ sp.style.display='none'; img.style.display='block'; },1200);
|
setTimeout(function(){ sp.style.display='none'; img.style.display='block'; },1200);
|
||||||
if(/Android/i.test(navigator.userAgent)){
|
if(/Android/i.test(navigator.userAgent)){
|
||||||
@@ -1827,6 +1932,7 @@ function camStop(){
|
|||||||
camOn=false;
|
camOn=false;
|
||||||
camUserStopped=true; // suppress auto-restart for remainder of this print
|
camUserStopped=true; // suppress auto-restart for remainder of this print
|
||||||
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start');
|
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start');
|
||||||
|
var rb=document.getElementById('cam-reset-btn');if(rb)rb.style.display='none';
|
||||||
clog(tr('log_cam_stop'),'msg-ok');
|
clog(tr('log_cam_stop'),'msg-ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2077,6 +2183,13 @@ function aceDryToggle(aceId,on){
|
|||||||
|
|
||||||
function toggleCam(){if(camOn)camStop();else camStart()}
|
function toggleCam(){if(camOn)camStop();else camStart()}
|
||||||
|
|
||||||
|
function resetCamera(){
|
||||||
|
post('/api/camera/reset',{}).then(function(){
|
||||||
|
var btn=document.getElementById('cam-reset-btn');
|
||||||
|
if(btn){btn.textContent='↻';setTimeout(function(){btn.textContent='↺';},1000);}
|
||||||
|
}).catch(function(){});
|
||||||
|
}
|
||||||
|
|
||||||
function aceDryStop(aceId){
|
function aceDryStop(aceId){
|
||||||
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
||||||
return post('/api/ace/dry',{action:'stop',ace_id:aceId})
|
return post('/api/ace/dry',{action:'stop',ace_id:aceId})
|
||||||
@@ -2494,6 +2607,7 @@ function openFilamentDialog(slots){
|
|||||||
_amsSlots=slots
|
_amsSlots=slots
|
||||||
.filter(function(s){return s.status==='loaded';})
|
.filter(function(s){return s.status==='loaded';})
|
||||||
.sort(function(a,b){return (a.slot_index||0)-(b.slot_index||0);});
|
.sort(function(a,b){return (a.slot_index||0)-(b.slot_index||0);});
|
||||||
|
_loadSpoolmanStatus();
|
||||||
var dlg=document.getElementById('filament-dialog');
|
var dlg=document.getElementById('filament-dialog');
|
||||||
var title=document.getElementById('fd-title');
|
var title=document.getElementById('fd-title');
|
||||||
var body=document.getElementById('fd-slots');
|
var body=document.getElementById('fd-slots');
|
||||||
@@ -2623,6 +2737,8 @@ function openFilamentDialog(slots){
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
if(dlg)dlg.classList.add('open');
|
if(dlg)dlg.classList.add('open');
|
||||||
|
// Spoolman-Section nach Slot-Render aufbauen (braucht die selects im DOM)
|
||||||
|
setTimeout(_buildSpoolmanSection, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeFilamentDialog(){
|
function closeFilamentDialog(){
|
||||||
@@ -2675,6 +2791,17 @@ function confirmFilamentPrint(){
|
|||||||
var excludedObjects=_printObjects.filter(function(o){return o.skip;}).map(function(o){return o.name;});
|
var excludedObjects=_printObjects.filter(function(o){return o.skip;}).map(function(o){return o.name;});
|
||||||
var fdAlEl=document.getElementById('fd-auto-leveling');
|
var fdAlEl=document.getElementById('fd-auto-leveling');
|
||||||
var fdAutoLeveling=fdAlEl?( fdAlEl.checked?1:0):(S.auto_leveling===undefined?1:S.auto_leveling?1:0);
|
var fdAutoLeveling=fdAlEl?( fdAlEl.checked?1:0):(S.auto_leveling===undefined?1:S.auto_leveling?1:0);
|
||||||
|
// Spoolman: Slot→Spool-Mapping aus Dialog sammeln und senden
|
||||||
|
if(_spoolmanStatus.configured){
|
||||||
|
var slotMap={};
|
||||||
|
document.querySelectorAll('[data-spool-slot]').forEach(function(sel){
|
||||||
|
var idx=sel.dataset.spoolSlot;
|
||||||
|
var val=parseInt(sel.value);
|
||||||
|
if(val>0)slotMap[idx]=val;
|
||||||
|
});
|
||||||
|
_slotSpoolMap=slotMap;
|
||||||
|
post('/kx/spoolman/active-spool',{slot_map:slotMap}).catch(function(){});
|
||||||
|
}
|
||||||
closeFilamentDialog();
|
closeFilamentDialog();
|
||||||
if(_filamentDialogMode==='banner'){
|
if(_filamentDialogMode==='banner'){
|
||||||
// Banner-Modus: /kx/print bevorzugen wenn _storeFileId bekannt (gleicher Pfad wie File-Browser).
|
// Banner-Modus: /kx/print bevorzugen wenn _storeFileId bekannt (gleicher Pfad wie File-Browser).
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
<div style="font-size:12px;color:#fff" id="cam-fname"></div>
|
<div style="font-size:12px;color:#fff" id="cam-fname"></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="cam-toggle" onclick="toggleCam()" id="cam-toggle-btn">▶ Kamera</button>
|
<button class="cam-toggle" onclick="toggleCam()" id="cam-toggle-btn">▶ Kamera</button>
|
||||||
|
<button class="cam-toggle" onclick="resetCamera()" id="cam-reset-btn" style="display:none;margin-left:4px" title="Kamera-Stream neu verbinden (nach 429-Sperre)">↺</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -435,6 +436,7 @@
|
|||||||
<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-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-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-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>
|
<button class="set-cat" id="setcat-system" onclick="showSettingsCat('system')"><span>⚙</span> <span id="setcat-lbl-system">System</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -570,6 +572,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- System -->
|
||||||
<div class="set-group" id="setgrp-system">
|
<div class="set-group" id="setgrp-system">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -646,6 +675,13 @@
|
|||||||
<label for="fd-auto-leveling" style="margin:0;cursor:pointer;font-size:13px" id="fd-lbl-auto-leveling">Auto-Leveling</label>
|
<label for="fd-auto-leveling" style="margin:0;cursor:pointer;font-size:13px" id="fd-lbl-auto-leveling">Auto-Leveling</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="fd-spoolman-section" style="display:none;margin-bottom:16px;border-top:1px solid var(--border);padding-top:12px">
|
||||||
|
<p style="font-size:12px;color:var(--txt2);margin-bottom:8px;display:flex;align-items:center;gap:6px">
|
||||||
|
<span id="fd-spoolman-lbl">🧵 Spoolman</span>
|
||||||
|
<span id="fd-spoolman-loading" style="display:none;font-size:10px">…</span>
|
||||||
|
</p>
|
||||||
|
<div id="fd-spoolman-rows" style="display:flex;flex-direction:column;gap:6px"></div>
|
||||||
|
</div>
|
||||||
<div style="display:flex;gap:8px;justify-content:flex-end">
|
<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>
|
<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>
|
||||||
<button id="fd-print" onclick="confirmFilamentPrint()" style="padding:8px 18px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600">▶ Drucken</button>
|
<button id="fd-print" onclick="confirmFilamentPrint()" style="padding:8px 18px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600">▶ Drucken</button>
|
||||||
|
|||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "Bereit",
|
"ace_dry_auto_refill": "Auto-Nachschub",
|
||||||
"header_status_printing": "Druckt",
|
"ace_dry_chart": "Verlauf (Temp/Feuchte)",
|
||||||
"header_status_complete": "Fertig",
|
"ace_dry_current_temp": "Temperatur",
|
||||||
"header_status_error": "Fehler",
|
"ace_dry_dialog_cancel": "Abbrechen",
|
||||||
"kobra_free": "Bereit",
|
"ace_dry_dialog_confirm": "Bestätigen",
|
||||||
"kobra_busy": "Beschäftigt",
|
"ace_dry_dialog_custom_name": "Eigener Name",
|
||||||
"kobra_printing": "Druckt",
|
"ace_dry_dialog_reset_default": "Auf Standard zurücksetzen",
|
||||||
"kobra_preheating": "Aufheizen",
|
"ace_dry_dialog_save_restart": "Speichern & Neustart",
|
||||||
"kobra_auto_leveling": "Nivellierung",
|
"ace_dry_dialog_temp": "Temperatur (30-80°C)",
|
||||||
"kobra_checking": "Prüfung",
|
"ace_dry_dialog_time": "Restzeit (h:m:s)",
|
||||||
"kobra_updated": "Aktualisierung",
|
"ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen",
|
||||||
"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",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"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_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_off": "Status: Aus",
|
||||||
"ace_dry_status_on": "Status: Aktiv",
|
"ace_dry_status_on": "Status: Aktiv",
|
||||||
"ace_dry_status_remaining": "Rest",
|
"ace_dry_status_remaining": "Rest",
|
||||||
"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_stop": "■ Stop",
|
||||||
"ace_dry_auto_refill": "Auto-Nachschub",
|
"ace_dry_temp": "Temperatur (°C)",
|
||||||
"ace_dry_enable": "Trocknung aktivieren",
|
|
||||||
"ace_dry_temp_line": "Trocknungstemperatur",
|
"ace_dry_temp_line": "Trocknungstemperatur",
|
||||||
"ace_dry_time_line": "Trocknungszeit",
|
"ace_dry_time_line": "Trocknungszeit",
|
||||||
"ace_dry_ui_pending": "(nur UI, Backend folgt)",
|
"ace_dry_ui_pending": "(nur UI, Backend folgt)",
|
||||||
"ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen",
|
"add_printer": "Drucker hinzufügen",
|
||||||
"ace_dry_dialog_temp": "Temperatur (30-80°C)",
|
"ams_empty": "Leer",
|
||||||
"ace_dry_dialog_time": "Restzeit (h:m:s)",
|
"ams_no_data": "Keine AMS-Daten empfangen",
|
||||||
"ace_dry_dialog_confirm": "Bestätigen",
|
"apd_cancel": "Abbrechen",
|
||||||
"ace_dry_dialog_cancel": "Abbrechen",
|
"apd_confirm": "Hinzufügen",
|
||||||
"ace_dry_dialog_save_restart": "Speichern & Neustart",
|
"apd_err_ip": "Bitte IP-Adresse eingeben",
|
||||||
"ace_dry_dialog_custom_name": "Eigener Name",
|
"apd_fetching": "Hole Daten vom Drucker…",
|
||||||
"ace_dry_dialog_reset_default": "Auf Standard zurücksetzen",
|
"apd_lbl_ip": "Drucker-IP",
|
||||||
"cam_placeholder": "📷 Kamera nicht gestartet",
|
"apd_lbl_name": "Name (optional)",
|
||||||
"cam_stream_unavailable": "Stream nicht verfügbar",
|
"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_start": "▶ Kamera",
|
||||||
|
"btn_cam_start2": "▶ Start",
|
||||||
"btn_cam_stop": "◼ Kamera",
|
"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_pause": "⏸ Pause",
|
||||||
"btn_resume": "▶ Weiter",
|
"btn_resume": "▶ Weiter",
|
||||||
"btn_cancel": "✕ Stopp",
|
"cam_placeholder": "📷 Kamera nicht gestartet",
|
||||||
"label_nozzle": "Düse",
|
"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",
|
||||||
"label_bed": "Bett",
|
"label_bed": "Bett",
|
||||||
"label_fan": "🌀 Lüfter",
|
"label_fan": "🌀 Lüfter",
|
||||||
"label_light": "💡 Licht",
|
"label_light": "💡 Licht",
|
||||||
"label_on_off": "Ein / Aus",
|
"label_nozzle": "Düse",
|
||||||
"label_speed": "Geschwindigkeit",
|
|
||||||
"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)",
|
|
||||||
"label_set": "Setzen",
|
|
||||||
"label_off": "Aus",
|
"label_off": "Aus",
|
||||||
"panel_temps_nozzle": "Düse",
|
"label_on_off": "Ein / Aus",
|
||||||
"panel_temps_bed": "Heizbett",
|
"label_set": "Setzen",
|
||||||
"panel_temps_chart": "Verlauf (letzte 60 Messungen)",
|
|
||||||
"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",
|
"label_slot": "Slot",
|
||||||
"ams_empty": "Leer",
|
"label_speed": "Geschwindigkeit",
|
||||||
"panel_extras_light": "Licht",
|
"label_step": "Schrittweite:",
|
||||||
"panel_extras_fan": "Lüfter",
|
"label_target_c": "Ziel:",
|
||||||
"panel_extras_camera": "Kamera",
|
"lbl_conn_error": "Verbindungsfehler:",
|
||||||
"btn_cam_start2": "▶ Start",
|
"lbl_elapsed": "Verstrichen:",
|
||||||
"btn_cam_stop2": "◼ Stop",
|
"lbl_feed": "Einziehen",
|
||||||
"panel_console_title": "Ereignis-Log",
|
"lbl_layers": "Layer",
|
||||||
"log_light_on": "Licht an",
|
"lbl_light": "💡 Licht",
|
||||||
"log_light_off": "Licht aus",
|
"lbl_remaining": "Restzeit:",
|
||||||
"log_fan": "Lüfter →",
|
"lbl_slicer_time": "Slicer-Schätzung:",
|
||||||
"log_nozzle": "Düse →",
|
"lbl_spoolman_sync_rate": "Sync-Rate (s, 0=aus)",
|
||||||
"log_bed": "Bett →",
|
"lbl_spoolman_url": "Server-URL",
|
||||||
|
"lbl_unload": "Ausziehen",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ Auto",
|
||||||
"log_axis": "Achse",
|
"log_axis": "Achse",
|
||||||
"log_home": "Home",
|
"log_bed": "Bett →",
|
||||||
"log_home_all": "Home All",
|
|
||||||
"log_cam_start": "Kamera gestartet:",
|
"log_cam_start": "Kamera gestartet:",
|
||||||
"log_cam_stop": "Kamera gestoppt",
|
"log_cam_stop": "Kamera gestoppt",
|
||||||
"log_poll_error": "Poll-Fehler:",
|
"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_error": "Fehler:",
|
||||||
"confirm_cancel": "Druck wirklich abbrechen?",
|
"log_fan": "Lüfter →",
|
||||||
"settings_title": "Einstellungen",
|
"log_filter_placeholder": "Filtern…",
|
||||||
"settings_connection": "Verbindung",
|
"log_home": "Home",
|
||||||
"settings_print": "Druckeinstellungen",
|
"log_home_all": "Home All",
|
||||||
"settings_poll": "Poll-Intervall (Sekunden)",
|
"log_light_off": "Licht aus",
|
||||||
"settings_version": "Version",
|
"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_settings": "Einstellungen",
|
||||||
"settings_cat_display": "Darstellung",
|
"nav_temps": "Temperaturen",
|
||||||
"settings_cat_filament": "Filament",
|
"orca_profile_done": "Importiert",
|
||||||
"settings_cat_language": "Sprache",
|
"orca_profile_dropmsg": "Hierher ziehen oder klicken",
|
||||||
"settings_cat_theme": "Hell / Dunkel umschalten",
|
"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/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "Filament-Profil-Mapping (pro Slot)",
|
|
||||||
"settings_filament_mapping_save": "Mapping speichern",
|
|
||||||
"settings_visible_vendors": "Sichtbare Hersteller (Profil-Dropdown)",
|
|
||||||
"settings_visible_vendors_hint": "Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic\" und eigene Profile sind immer sichtbar.",
|
|
||||||
"settings_visible_vendors_save": "Auswahl speichern",
|
|
||||||
"progress_action_print": "Drucken",
|
|
||||||
"progress_action_slots": "Slots zuordnen",
|
|
||||||
"progress_action_clear": "Leeren",
|
|
||||||
"settings_save": "Speichern & Neustart",
|
|
||||||
"settings_printer_name": "Drucker-Name",
|
|
||||||
"settings_printer_ip": "Drucker-IP",
|
|
||||||
"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_camera_on_print": "Kamera bei Druckstart einschalten",
|
|
||||||
"settings_web_upload_warning": "Warnung bei Web-Upload-Druck anzeigen",
|
|
||||||
"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) —",
|
|
||||||
"orca_profile_section": "OrcaSlicer-Profile",
|
|
||||||
"orca_profile_hint": "Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show Configuration Folder)",
|
"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_btn": "Profile importieren",
|
||||||
"orca_profile_import_link": "★ Eigene Profile importieren…",
|
"orca_profile_import_link": "★ Eigene Profile importieren…",
|
||||||
"orca_profile_import_title": "Eigene OrcaSlicer-Profile importieren",
|
"orca_profile_import_title": "Eigene OrcaSlicer-Profile importieren",
|
||||||
"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/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "Hierher ziehen oder klicken",
|
|
||||||
"orca_profile_list_label": "Aktuell importiert",
|
"orca_profile_list_label": "Aktuell importiert",
|
||||||
"orca_profile_user_label": "Eigene Profile",
|
"orca_profile_section": "OrcaSlicer-Profile",
|
||||||
"orca_profile_user_empty": "– keine –",
|
|
||||||
"orca_profile_uploading": "Lade hoch…",
|
|
||||||
"orca_profile_done": "Importiert",
|
|
||||||
"orca_profile_skipped": "übersprungen",
|
"orca_profile_skipped": "übersprungen",
|
||||||
"log_dir_all": "Alle",
|
"orca_profile_uploading": "Lade hoch…",
|
||||||
"log_lvl_label": "Level:",
|
"orca_profile_user_empty": "– keine –",
|
||||||
"file_ready_btn": "▶ Druck starten",
|
"orca_profile_user_label": "Eigene Profile",
|
||||||
"file_slots_btn": "🎨 Slots wählen",
|
"panel_ams_title": "Filament",
|
||||||
"file_cancel_btn": "✕ Abbrechen",
|
"panel_browser_title": "Datei-Browser",
|
||||||
"nav_printers": "Drucker",
|
"panel_console_title": "Ereignis-Log",
|
||||||
"skip_title": "✂ Objekte überspringen",
|
"panel_extras_camera": "Kamera",
|
||||||
"skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:",
|
"panel_extras_fan": "Lüfter",
|
||||||
"skip_btn_label": "Objekte",
|
"panel_extras_light": "Licht",
|
||||||
"skip_no_objects": "Keine Objekte in diesem Druck.",
|
"panel_motion_xy": "XY-Achsen",
|
||||||
|
"panel_motion_z": "Z-Achse",
|
||||||
|
"panel_print_btn_cancel": "✕ Abbrechen",
|
||||||
|
"panel_print_btn_pause": "⏸ Pause",
|
||||||
|
"panel_print_btn_resume": "▶ Fortsetzen",
|
||||||
|
"panel_print_temps_live": "Temperaturen (Live)",
|
||||||
|
"panel_print_title": "Drucksteuerung",
|
||||||
|
"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",
|
||||||
|
"settings_title": "Einstellungen",
|
||||||
|
"settings_username": "MQTT-Benutzername",
|
||||||
|
"settings_vendor_filter_placeholder": "Hersteller suchen…",
|
||||||
|
"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_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_already": "übersprungen",
|
||||||
|
"skip_btn_label": "Objekte",
|
||||||
|
"skip_cancel": "Abbrechen",
|
||||||
|
"skip_confirm": "Überspringen",
|
||||||
|
"skip_confirm_btn": "Überspringen",
|
||||||
|
"skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:",
|
||||||
|
"skip_no_objects": "Keine Objekte in diesem Druck.",
|
||||||
"skip_select_at_least_one": "Bitte mindestens ein Objekt wählen.",
|
"skip_select_at_least_one": "Bitte mindestens ein Objekt wählen.",
|
||||||
"skip_sending": "Sende …",
|
"skip_sending": "Sende …",
|
||||||
"skip_success": "Objekte werden übersprungen.",
|
"skip_success": "Objekte werden übersprungen.",
|
||||||
"fd_objects_hint": "Objekte überspringen (optional):",
|
"skip_title": "✂ Objekte überspringen",
|
||||||
"fd_objects_toggle": "Objekte überspringen",
|
"slot_edit_color": "Farbe",
|
||||||
"fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:",
|
"slot_edit_custom": "z.B. PLA, PETG, ABS…",
|
||||||
"fd_cancel": "Abbrechen",
|
"slot_edit_load": "⬇ Einziehen",
|
||||||
"fd_print": "▶ Drucken",
|
"slot_edit_material": "Material",
|
||||||
"fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?",
|
"slot_edit_ok": "AMS Slot",
|
||||||
"fd_slot": "Slot",
|
"slot_edit_profile": "OrcaSlicer-Profil",
|
||||||
"fd_no_matching_material": "Kein passendes Material",
|
"slot_edit_profile_default": "— Generic (Default) —",
|
||||||
"fd_used": "BELEGT",
|
"slot_edit_profile_hint": "Sendet beim OrcaSlicer-Sync die konkrete Marke statt nur „Generic\"",
|
||||||
"add_printer": "Drucker hinzufügen",
|
"slot_edit_save": "💾 Speichern",
|
||||||
"apd_lbl_ip": "Drucker-IP",
|
"slot_edit_title": "Slot bearbeiten",
|
||||||
"apd_lbl_name": "Name (optional)",
|
"slot_edit_unload": "⬆ Ausziehen",
|
||||||
"apd_placeholder_name": "z.B. Kobra X Wohnzimmer",
|
"speed_normal": "⚡ Normal",
|
||||||
"apd_cancel": "Abbrechen",
|
"speed_silent": "🐢 Leise",
|
||||||
"apd_confirm": "Hinzufügen",
|
"speed_sport": "🚀 Sport",
|
||||||
"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_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",
|
|
||||||
"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}",
|
|
||||||
"store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "Alle",
|
|
||||||
"sf_ok": "✓ Erfolgreich",
|
|
||||||
"sf_err": "✗ Fehler",
|
|
||||||
"sf_new": "Neu",
|
|
||||||
"ss_date": "↓ Datum",
|
"ss_date": "↓ Datum",
|
||||||
"ss_name": "A–Z Name",
|
|
||||||
"ss_dur": "⏱ Druckzeit",
|
"ss_dur": "⏱ Druckzeit",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"ss_name": "A–Z Name",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"store_delete_confirm": "Datei löschen?",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"store_download": "⬇ Download",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"store_empty": "Noch keine Dateien hochgeladen.",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
"store_estimate": "Schätzung",
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
"store_never": "noch nicht gedruckt",
|
||||||
"ace_dry_preset_custom": "Custom",
|
"store_no_results": "Keine Dateien gefunden.",
|
||||||
"fd_options_title": "Optionen",
|
"store_print": "▶ Drucken",
|
||||||
"print_auto_leveling": "Auto-Leveling für diesen Druck",
|
"store_print_confirm": "Datei drucken?",
|
||||||
"settings_file_ready_mode": "Druckdialog starten",
|
"store_refresh": "↻ Aktualisieren",
|
||||||
"settings_file_ready_banner": "Druckleiste",
|
"store_search_placeholder": "🔍 Suche…",
|
||||||
"settings_file_ready_dialog": "Druckdialog",
|
"store_upload_busy": "⏳ Hochladen…",
|
||||||
"log_dir_rx": "RX",
|
"store_upload_error": "✗ {error}",
|
||||||
"log_dir_tx": "TX",
|
"store_upload_label_browse": "durchsuchen",
|
||||||
"log_dir_label": "Richtung:",
|
"store_upload_label_prefix": "GCode hierher ziehen oder ",
|
||||||
"log_lvl_err": "⛔ Fehler",
|
"store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)",
|
||||||
"log_lvl_warn": "⚠ Warnung",
|
"store_upload_success": "✓ {file}",
|
||||||
"log_topic_label": "Thema:",
|
"store_web_verify_abort": "Abbrechen",
|
||||||
"log_topic_ams": "AMS",
|
"store_web_verify_confirm": "Bestätigen",
|
||||||
"log_topic_print": "Druck",
|
"store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.",
|
||||||
"log_topic_info": "Info",
|
"store_web_verify_title": "Datei verifizieren",
|
||||||
"log_topic_status": "Status",
|
"update_apply": "Jetzt installieren",
|
||||||
"log_download": "⬇ Download",
|
"update_applying": "Lade herunter...",
|
||||||
"log_auto": "⬇ Auto",
|
"update_available": "verfügbar",
|
||||||
"log_clear": "✕ Leeren",
|
"update_check": "Auf Updates prüfen",
|
||||||
"log_filter_placeholder": "Filtern…",
|
"update_checking": "Prüfe...",
|
||||||
"skip_cancel": "Abbrechen",
|
"update_docker": "Befehl kopieren",
|
||||||
"skip_confirm": "Überspringen"
|
"update_docker_copied": "Kopiert! Ausführen: docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "Fehler",
|
||||||
|
"update_none": "Bereits aktuell",
|
||||||
|
"update_restarting": "Starte neu..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "Ready",
|
"ace_dry_auto_refill": "Auto Refill",
|
||||||
"header_status_printing": "Printing",
|
"ace_dry_chart": "History (Temp/Humidity)",
|
||||||
"header_status_complete": "Complete",
|
"ace_dry_current_temp": "Temperature",
|
||||||
"header_status_error": "Error",
|
"ace_dry_dialog_cancel": "Cancel",
|
||||||
"kobra_free": "Ready",
|
"ace_dry_dialog_confirm": "Confirm",
|
||||||
"kobra_busy": "Busy",
|
"ace_dry_dialog_custom_name": "Custom Name",
|
||||||
"kobra_printing": "Printing",
|
"ace_dry_dialog_reset_default": "Reset to Default",
|
||||||
"kobra_preheating": "Preheating",
|
"ace_dry_dialog_save_restart": "Save & Restart",
|
||||||
"kobra_auto_leveling": "Auto Leveling",
|
"ace_dry_dialog_temp": "Temperature (30-80°C)",
|
||||||
"kobra_checking": "Checking",
|
"ace_dry_dialog_time": "Rem. Time (h:m:s)",
|
||||||
"kobra_updated": "Updating",
|
"ace_dry_dialog_title": "Dryer Temp/Time Settings",
|
||||||
"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",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"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_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_off": "Status: Off",
|
||||||
"ace_dry_status_on": "Status: Active",
|
"ace_dry_status_on": "Status: Active",
|
||||||
"ace_dry_status_remaining": "Remaining",
|
"ace_dry_status_remaining": "Remaining",
|
||||||
"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_stop": "■ Stop",
|
||||||
"ace_dry_auto_refill": "Auto Refill",
|
"ace_dry_temp": "Temperature (°C)",
|
||||||
"ace_dry_enable": "Enable Drying",
|
|
||||||
"ace_dry_temp_line": "Drying Temperature",
|
"ace_dry_temp_line": "Drying Temperature",
|
||||||
"ace_dry_time_line": "Drying Time",
|
"ace_dry_time_line": "Drying Time",
|
||||||
"ace_dry_ui_pending": "(UI only, backend next)",
|
"ace_dry_ui_pending": "(UI only, backend next)",
|
||||||
"ace_dry_dialog_title": "Dryer Temp/Time Settings",
|
"add_printer": "Add printer",
|
||||||
"ace_dry_dialog_temp": "Temperature (30-80°C)",
|
"ams_empty": "Empty",
|
||||||
"ace_dry_dialog_time": "Rem. Time (h:m:s)",
|
"ams_no_data": "No AMS data received",
|
||||||
"ace_dry_dialog_confirm": "Confirm",
|
"apd_cancel": "Cancel",
|
||||||
"ace_dry_dialog_cancel": "Cancel",
|
"apd_confirm": "Add",
|
||||||
"ace_dry_dialog_save_restart": "Save & Restart",
|
"apd_err_ip": "Please enter an IP address",
|
||||||
"ace_dry_dialog_custom_name": "Custom Name",
|
"apd_fetching": "Fetching data from printer…",
|
||||||
"ace_dry_dialog_reset_default": "Reset to Default",
|
"apd_lbl_ip": "Printer IP",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"apd_lbl_name": "Name (optional)",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"apd_placeholder_name": "e.g. Kobra X Living Room",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"apd_success": "Printer added, bridge restarting…",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"apd_title": "Add printer",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
|
||||||
"ace_dry_preset_custom": "Custom",
|
|
||||||
"cam_placeholder": "📷 Camera not started",
|
|
||||||
"cam_stream_unavailable": "Stream unavailable",
|
|
||||||
"btn_cam_start": "▶ Camera",
|
"btn_cam_start": "▶ Camera",
|
||||||
|
"btn_cam_start2": "▶ Start",
|
||||||
"btn_cam_stop": "◼ Camera",
|
"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_pause": "⏸ Pause",
|
||||||
"btn_resume": "▶ Resume",
|
"btn_resume": "▶ Resume",
|
||||||
"btn_cancel": "✕ Stop",
|
"cam_placeholder": "📷 Camera not started",
|
||||||
"label_nozzle": "Nozzle",
|
"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",
|
||||||
"label_bed": "Bed",
|
"label_bed": "Bed",
|
||||||
"label_fan": "🌀 Fan",
|
"label_fan": "🌀 Fan",
|
||||||
"label_light": "💡 Light",
|
"label_light": "💡 Light",
|
||||||
"label_on_off": "On / Off",
|
"label_nozzle": "Nozzle",
|
||||||
"label_speed": "Speed",
|
|
||||||
"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)",
|
|
||||||
"label_set": "Set",
|
|
||||||
"label_off": "Off",
|
"label_off": "Off",
|
||||||
"panel_temps_nozzle": "Nozzle",
|
"label_on_off": "On / Off",
|
||||||
"panel_temps_bed": "Heated Bed",
|
"label_set": "Set",
|
||||||
"panel_temps_chart": "History (last 60 readings)",
|
|
||||||
"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",
|
"label_slot": "Slot",
|
||||||
"ams_empty": "Empty",
|
"label_speed": "Speed",
|
||||||
"panel_extras_light": "Light",
|
"label_step": "Step size:",
|
||||||
"panel_extras_fan": "Fan",
|
"label_target_c": "Target:",
|
||||||
"panel_extras_camera": "Camera",
|
"lbl_conn_error": "Connection error:",
|
||||||
"btn_cam_start2": "▶ Start",
|
"lbl_elapsed": "Elapsed:",
|
||||||
"btn_cam_stop2": "◼ Stop",
|
"lbl_feed": "Load",
|
||||||
"panel_console_title": "Event Log",
|
"lbl_layers": "Layer",
|
||||||
"log_light_on": "Light on",
|
"lbl_light": "💡 Light",
|
||||||
"log_light_off": "Light off",
|
"lbl_remaining": "Remaining:",
|
||||||
"log_fan": "Fan →",
|
"lbl_slicer_time": "Slicer estimate:",
|
||||||
"log_nozzle": "Nozzle →",
|
"lbl_spoolman_sync_rate": "Sync rate (s, 0=off)",
|
||||||
"log_bed": "Bed →",
|
"lbl_spoolman_url": "Server URL",
|
||||||
|
"lbl_unload": "Unload",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ Auto",
|
||||||
"log_axis": "Axis",
|
"log_axis": "Axis",
|
||||||
"log_home": "Home",
|
"log_bed": "Bed →",
|
||||||
"log_home_all": "Home All",
|
|
||||||
"log_cam_start": "Camera started:",
|
"log_cam_start": "Camera started:",
|
||||||
"log_cam_stop": "Camera stopped",
|
"log_cam_stop": "Camera stopped",
|
||||||
"log_poll_error": "Poll error:",
|
"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_error": "Error:",
|
||||||
"confirm_cancel": "Really cancel the print?",
|
"log_fan": "Fan →",
|
||||||
"settings_title": "Settings",
|
"log_filter_placeholder": "Filter…",
|
||||||
"settings_connection": "Connection",
|
"log_home": "Home",
|
||||||
"settings_print": "Print Settings",
|
"log_home_all": "Home All",
|
||||||
"settings_poll": "Poll Interval (seconds)",
|
"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_settings": "Settings",
|
||||||
"settings_cat_display": "Appearance",
|
"nav_temps": "Temperatures",
|
||||||
"settings_cat_filament": "Filament",
|
"orca_profile_done": "Imported",
|
||||||
"settings_cat_language": "Language",
|
"orca_profile_dropmsg": "Drop here or click",
|
||||||
"settings_cat_theme": "Toggle light / dark",
|
"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/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "Filament profile mapping (per slot)",
|
|
||||||
"settings_filament_mapping_save": "Save mapping",
|
|
||||||
"settings_visible_vendors": "Visible vendors (profile dropdown)",
|
|
||||||
"settings_visible_vendors_hint": "Only these vendors appear in the slot profile dropdown. Nothing selected = show all. \"Generic\" and your own profiles are always visible.",
|
|
||||||
"settings_visible_vendors_save": "Save selection",
|
|
||||||
"progress_action_print": "Print",
|
|
||||||
"progress_action_slots": "Map slots",
|
|
||||||
"progress_action_clear": "Clear",
|
|
||||||
"settings_version": "Version",
|
|
||||||
"settings_save": "Save & Restart",
|
|
||||||
"settings_printer_name": "Printer Name",
|
|
||||||
"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 Default",
|
|
||||||
"fd_options_title": "Print Options",
|
|
||||||
"print_auto_leveling": "Auto-Leveling",
|
|
||||||
"settings_file_ready_mode": "Start Print Behavior",
|
|
||||||
"settings_file_ready_banner": "Print Bar",
|
|
||||||
"settings_file_ready_dialog": "Print Dialog",
|
|
||||||
"settings_camera_on_print": "Turn camera on at print start",
|
|
||||||
"settings_web_upload_warning": "Show warning when printing web uploads",
|
|
||||||
"update_check": "Check for Updates",
|
|
||||||
"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) —",
|
|
||||||
"orca_profile_section": "OrcaSlicer Profiles",
|
|
||||||
"orca_profile_hint": "Import your own OrcaSlicer filament profiles (open the user dir via Help → Show Configuration Folder)",
|
"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_btn": "Import profiles",
|
||||||
"orca_profile_import_link": "★ Import own profiles…",
|
"orca_profile_import_link": "★ Import own profiles…",
|
||||||
"orca_profile_import_title": "Import your OrcaSlicer profiles",
|
"orca_profile_import_title": "Import your OrcaSlicer profiles",
|
||||||
"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/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "Drop here or click",
|
|
||||||
"orca_profile_list_label": "Currently imported",
|
"orca_profile_list_label": "Currently imported",
|
||||||
"orca_profile_user_label": "Own profiles",
|
"orca_profile_section": "OrcaSlicer Profiles",
|
||||||
"orca_profile_user_empty": "– none –",
|
|
||||||
"orca_profile_uploading": "Uploading…",
|
|
||||||
"orca_profile_done": "Imported",
|
|
||||||
"orca_profile_skipped": "skipped",
|
"orca_profile_skipped": "skipped",
|
||||||
"log_dir_all": "All",
|
"orca_profile_uploading": "Uploading…",
|
||||||
"log_dir_rx": "RX",
|
"orca_profile_user_empty": "– none –",
|
||||||
"log_dir_tx": "TX",
|
"orca_profile_user_label": "Own profiles",
|
||||||
"log_dir_label": "Dir:",
|
"panel_ams_title": "Filament",
|
||||||
"log_lvl_label": "Level:",
|
"panel_browser_title": "File Browser",
|
||||||
"log_lvl_err": "⛔ Errors",
|
"panel_console_title": "Event Log",
|
||||||
"log_lvl_warn": "⚠ Warn",
|
"panel_extras_camera": "Camera",
|
||||||
"log_topic_label": "Topic:",
|
"panel_extras_fan": "Fan",
|
||||||
"log_topic_ams": "AMS",
|
"panel_extras_light": "Light",
|
||||||
"log_topic_print": "Print",
|
"panel_motion_xy": "XY Axes",
|
||||||
"log_topic_info": "Info",
|
"panel_motion_z": "Z Axis",
|
||||||
"log_topic_status": "Status",
|
"panel_print_btn_cancel": "✕ Cancel",
|
||||||
"log_download": "⬇ Download",
|
"panel_print_btn_pause": "⏸ Pause",
|
||||||
"log_auto": "⬇ Auto",
|
"panel_print_btn_resume": "▶ Resume",
|
||||||
"log_clear": "✕ Clear",
|
"panel_print_temps_live": "Temperatures (Live)",
|
||||||
"log_filter_placeholder": "Filter…",
|
"panel_print_title": "Print Control",
|
||||||
"file_ready_btn": "▶ Start Print",
|
"panel_temps_bed": "Heated Bed",
|
||||||
"file_slots_btn": "🎨 Select Slots",
|
"panel_temps_chart": "History (last 60 readings)",
|
||||||
"file_cancel_btn": "✕ Cancel",
|
"panel_temps_nozzle": "Nozzle",
|
||||||
"nav_printers": "Printers",
|
"print_auto_leveling": "Auto-Leveling",
|
||||||
"skip_title": "✂ Skip objects",
|
"printers_active": "● active",
|
||||||
"skip_hint": "Uncheck objects you no longer want to print:",
|
"printers_current": "Current printer",
|
||||||
"skip_btn_label": "Objects",
|
"printers_empty_hint": "No printer set up yet.",
|
||||||
"skip_no_objects": "No objects in this print.",
|
"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",
|
||||||
|
"settings_title": "Settings",
|
||||||
|
"settings_username": "MQTT Username",
|
||||||
|
"settings_vendor_filter_placeholder": "Search vendors…",
|
||||||
|
"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_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_already": "skipped",
|
||||||
|
"skip_btn_label": "Objects",
|
||||||
"skip_cancel": "Cancel",
|
"skip_cancel": "Cancel",
|
||||||
"skip_confirm": "Skip",
|
"skip_confirm": "Skip",
|
||||||
|
"skip_confirm_btn": "Skip",
|
||||||
|
"skip_hint": "Uncheck objects you no longer want to print:",
|
||||||
|
"skip_no_objects": "No objects in this print.",
|
||||||
"skip_select_at_least_one": "Please pick at least one object.",
|
"skip_select_at_least_one": "Please pick at least one object.",
|
||||||
"skip_sending": "Sending …",
|
"skip_sending": "Sending …",
|
||||||
"skip_success": "Objects will be skipped.",
|
"skip_success": "Objects will be skipped.",
|
||||||
"fd_objects_hint": "Skip objects (optional):",
|
"skip_title": "✂ Skip objects",
|
||||||
"fd_objects_toggle": "Skip objects",
|
"slot_edit_color": "Color",
|
||||||
"fd_slots_hint": "Assign GCode channel to AMS slot:",
|
"slot_edit_custom": "e.g. PLA, PETG, ABS…",
|
||||||
"fd_cancel": "Cancel",
|
"slot_edit_load": "⬇ Load",
|
||||||
"fd_print": "▶ Print",
|
"slot_edit_material": "Material",
|
||||||
"fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?",
|
"slot_edit_ok": "AMS Slot",
|
||||||
"fd_slot": "Slot",
|
"slot_edit_profile": "OrcaSlicer profile",
|
||||||
"fd_no_matching_material": "No matching material",
|
"slot_edit_profile_default": "— Generic (default) —",
|
||||||
"fd_used": "USED",
|
"slot_edit_profile_hint": "Sent on OrcaSlicer sync as the specific brand instead of just \"Generic\"",
|
||||||
"add_printer": "Add printer",
|
"slot_edit_save": "💾 Save",
|
||||||
"apd_lbl_ip": "Printer IP",
|
"slot_edit_title": "Edit Slot",
|
||||||
"apd_lbl_name": "Name (optional)",
|
"slot_edit_unload": "⬆ Unload",
|
||||||
"apd_placeholder_name": "e.g. Kobra X Living Room",
|
"speed_normal": "⚡ Normal",
|
||||||
"apd_cancel": "Cancel",
|
"speed_silent": "🐢 Silent",
|
||||||
"apd_confirm": "Add",
|
"speed_sport": "🚀 Sport",
|
||||||
"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_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",
|
|
||||||
"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}",
|
|
||||||
"store_upload_only_gcode": "✗ Only GCode files allowed (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "All",
|
|
||||||
"sf_ok": "✓ Completed",
|
|
||||||
"sf_err": "✗ Failed",
|
|
||||||
"sf_new": "New",
|
|
||||||
"ss_date": "↓ Date",
|
"ss_date": "↓ Date",
|
||||||
|
"ss_dur": "⏱ Print time",
|
||||||
"ss_name": "A–Z Name",
|
"ss_name": "A–Z Name",
|
||||||
"ss_dur": "⏱ Print time"
|
"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",
|
||||||
|
"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_web_verify_title": "Verify file",
|
||||||
|
"update_apply": "Install Now",
|
||||||
|
"update_applying": "Downloading...",
|
||||||
|
"update_available": "available",
|
||||||
|
"update_check": "Check for Updates",
|
||||||
|
"update_checking": "Checking...",
|
||||||
|
"update_docker": "Copy Command",
|
||||||
|
"update_docker_copied": "Copied! Run: docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "Error",
|
||||||
|
"update_none": "Already up to date",
|
||||||
|
"update_restarting": "Restarting..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "Listo",
|
"ace_dry_auto_refill": "Relleno automático",
|
||||||
"header_status_printing": "Imprimiendo",
|
"ace_dry_chart": "Historial (Temp/Humedad)",
|
||||||
"header_status_complete": "Completado",
|
"ace_dry_current_temp": "Temperatura",
|
||||||
"header_status_error": "Error",
|
"ace_dry_dialog_cancel": "Cancelar",
|
||||||
"kobra_free": "Listo",
|
"ace_dry_dialog_confirm": "Confirmar",
|
||||||
"kobra_busy": "Ocupado",
|
"ace_dry_dialog_custom_name": "Nombre personalizado",
|
||||||
"kobra_printing": "Imprimiendo",
|
"ace_dry_dialog_reset_default": "Restablecer valores predeterminados",
|
||||||
"kobra_preheating": "Precalentando",
|
"ace_dry_dialog_save_restart": "Guardar y reiniciar",
|
||||||
"kobra_auto_leveling": "Autonivelado",
|
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
|
||||||
"kobra_checking": "Comprobando",
|
"ace_dry_dialog_time": "Tiempo restante (h:m:s)",
|
||||||
"kobra_updated": "Actualizando",
|
"ace_dry_dialog_title": "Ajustes de temp/tiempo del secador",
|
||||||
"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",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"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_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_off": "Estado: Apagado",
|
||||||
"ace_dry_status_on": "Estado: Activo",
|
"ace_dry_status_on": "Estado: Activo",
|
||||||
"ace_dry_status_remaining": "Restante",
|
"ace_dry_status_remaining": "Restante",
|
||||||
"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_stop": "■ Parar",
|
||||||
"ace_dry_auto_refill": "Relleno automático",
|
"ace_dry_temp": "Temperatura (°C)",
|
||||||
"ace_dry_enable": "Activar secado",
|
|
||||||
"ace_dry_temp_line": "Temperatura de secado",
|
"ace_dry_temp_line": "Temperatura de secado",
|
||||||
"ace_dry_time_line": "Tiempo de secado",
|
"ace_dry_time_line": "Tiempo de secado",
|
||||||
"ace_dry_ui_pending": "(solo UI, backend después)",
|
"ace_dry_ui_pending": "(solo UI, backend después)",
|
||||||
"ace_dry_dialog_title": "Ajustes de temp/tiempo del secador",
|
"add_printer": "Añadir impresora",
|
||||||
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
|
"ams_empty": "Vacío",
|
||||||
"ace_dry_dialog_time": "Tiempo restante (h:m:s)",
|
"ams_no_data": "No se recibieron datos de AMS",
|
||||||
"ace_dry_dialog_confirm": "Confirmar",
|
"apd_cancel": "Cancelar",
|
||||||
"ace_dry_dialog_cancel": "Cancelar",
|
"apd_confirm": "Agregar",
|
||||||
"ace_dry_dialog_save_restart": "Guardar y reiniciar",
|
"apd_err_ip": "Introduce una dirección IP",
|
||||||
"ace_dry_dialog_custom_name": "Nombre personalizado",
|
"apd_fetching": "Obteniendo datos de la impresora…",
|
||||||
"ace_dry_dialog_reset_default": "Restablecer valores predeterminados",
|
"apd_lbl_ip": "IP de impresora",
|
||||||
"cam_placeholder": "📷 Cámara no iniciada",
|
"apd_lbl_name": "Nombre (opcional)",
|
||||||
"cam_stream_unavailable": "Stream no disponible",
|
"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_start": "▶ Cámara",
|
||||||
|
"btn_cam_start2": "▶ Iniciar",
|
||||||
"btn_cam_stop": "◼ Cámara",
|
"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_pause": "⏸ Pausa",
|
||||||
"btn_resume": "▶ Reanudar",
|
"btn_resume": "▶ Reanudar",
|
||||||
"btn_cancel": "✕ Detener",
|
"cam_placeholder": "📷 Cámara no iniciada",
|
||||||
"label_nozzle": "Boquilla",
|
"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",
|
||||||
"label_bed": "Cama",
|
"label_bed": "Cama",
|
||||||
"label_fan": "🌀 Ventilador",
|
"label_fan": "🌀 Ventilador",
|
||||||
"label_light": "💡 Luz",
|
"label_light": "💡 Luz",
|
||||||
"label_on_off": "Encendido / Apagado",
|
"label_nozzle": "Boquilla",
|
||||||
"label_speed": "Velocidad",
|
|
||||||
"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)",
|
|
||||||
"label_set": "Set",
|
|
||||||
"label_off": "Apagado",
|
"label_off": "Apagado",
|
||||||
"panel_temps_nozzle": "Boquilla",
|
"label_on_off": "Encendido / Apagado",
|
||||||
"panel_temps_bed": "Cama caliente",
|
"label_set": "Set",
|
||||||
"panel_temps_chart": "Historial (últimas 60 lecturas)",
|
|
||||||
"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",
|
"label_slot": "Ranura",
|
||||||
"ams_empty": "Vacío",
|
"label_speed": "Velocidad",
|
||||||
"panel_extras_light": "Luz",
|
"label_step": "Tamaño del paso:",
|
||||||
"panel_extras_fan": "Ventilador",
|
"label_target_c": "Objetivo:",
|
||||||
"panel_extras_camera": "Cámara",
|
"lbl_conn_error": "Error de conexión:",
|
||||||
"btn_cam_start2": "▶ Iniciar",
|
"lbl_elapsed": "Transcurrido:",
|
||||||
"btn_cam_stop2": "◼ Detener",
|
"lbl_feed": "Cargar",
|
||||||
"panel_console_title": "Registro de eventos",
|
"lbl_layers": "Capa",
|
||||||
"log_light_on": "Luz encendida",
|
"lbl_light": "💡 Luz",
|
||||||
"log_light_off": "Luz apagada",
|
"lbl_remaining": "Restante:",
|
||||||
"log_fan": "Ventilador →",
|
"lbl_slicer_time": "Estimación del slicer:",
|
||||||
"log_nozzle": "Boquilla →",
|
"lbl_spoolman_sync_rate": "Tasa de sincronización (s, 0=desact.)",
|
||||||
"log_bed": "Cama →",
|
"lbl_spoolman_url": "URL del servidor",
|
||||||
|
"lbl_unload": "Descargar",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ Auto",
|
||||||
"log_axis": "Eje",
|
"log_axis": "Eje",
|
||||||
"log_home": "Home",
|
"log_bed": "Cama →",
|
||||||
"log_home_all": "Home All",
|
|
||||||
"log_cam_start": "Cámara iniciada:",
|
"log_cam_start": "Cámara iniciada:",
|
||||||
"log_cam_stop": "Cámara detenida",
|
"log_cam_stop": "Cámara detenida",
|
||||||
"log_poll_error": "Error de sondeo:",
|
"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_error": "Error:",
|
||||||
"confirm_cancel": "¿Realmente cancelar la impresión?",
|
"log_fan": "Ventilador →",
|
||||||
"settings_title": "Configuración",
|
"log_filter_placeholder": "Filtrar…",
|
||||||
"settings_connection": "Conexión",
|
"log_home": "Home",
|
||||||
"settings_print": "Ajustes de impresión",
|
"log_home_all": "Home All",
|
||||||
"settings_poll": "Intervalo de sondeo (segundos)",
|
"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_settings": "Ajustes",
|
||||||
"settings_cat_display": "Apariencia",
|
"nav_temps": "Temperaturas",
|
||||||
"settings_cat_filament": "Filamento",
|
"orca_profile_done": "Importado",
|
||||||
"settings_cat_language": "Idioma",
|
"orca_profile_dropmsg": "Suelta aquí o haz clic",
|
||||||
"settings_cat_theme": "Alternar claro / oscuro",
|
"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/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "Asignación de perfil de filamento (por ranura)",
|
|
||||||
"settings_filament_mapping_save": "Guardar asignación",
|
|
||||||
"settings_visible_vendors": "Fabricantes visibles (lista de perfiles)",
|
|
||||||
"settings_visible_vendors_hint": "Solo estos fabricantes aparecen en la lista de perfiles de ranura. Nada seleccionado = mostrar todos. «Generic» y tus propios perfiles siempre son visibles.",
|
|
||||||
"settings_visible_vendors_save": "Guardar selección",
|
|
||||||
"progress_action_print": "Imprimir",
|
|
||||||
"progress_action_slots": "Asignar ranuras",
|
|
||||||
"progress_action_clear": "Vaciar",
|
|
||||||
"settings_version": "Versión",
|
|
||||||
"settings_save": "Guardar y reiniciar",
|
|
||||||
"settings_printer_name": "Nombre de impresora",
|
|
||||||
"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_camera_on_print": "Encender cámara al iniciar impresión",
|
|
||||||
"settings_web_upload_warning": "Mostrar advertencia al imprimir subidas web",
|
|
||||||
"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) —",
|
|
||||||
"orca_profile_section": "Perfiles de OrcaSlicer",
|
|
||||||
"orca_profile_hint": "Importa tus propios perfiles de filamento de OrcaSlicer (abre el directorio del usuario vía Help → Show Configuration Folder)",
|
"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_btn": "Importar perfiles",
|
||||||
"orca_profile_import_link": "★ Importar perfiles propios…",
|
"orca_profile_import_link": "★ Importar perfiles propios…",
|
||||||
"orca_profile_import_title": "Importar tus perfiles de OrcaSlicer",
|
"orca_profile_import_title": "Importar tus perfiles de OrcaSlicer",
|
||||||
"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/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "Suelta aquí o haz clic",
|
|
||||||
"orca_profile_list_label": "Actualmente importados",
|
"orca_profile_list_label": "Actualmente importados",
|
||||||
"orca_profile_user_label": "Perfiles propios",
|
"orca_profile_section": "Perfiles de OrcaSlicer",
|
||||||
"orca_profile_user_empty": "– ninguno –",
|
|
||||||
"orca_profile_uploading": "Subiendo…",
|
|
||||||
"orca_profile_done": "Importado",
|
|
||||||
"orca_profile_skipped": "omitido",
|
"orca_profile_skipped": "omitido",
|
||||||
"log_dir_all": "Todos",
|
"orca_profile_uploading": "Subiendo…",
|
||||||
"log_lvl_label": "Nivel:",
|
"orca_profile_user_empty": "– ninguno –",
|
||||||
"file_ready_btn": "▶ Iniciar impresión",
|
"orca_profile_user_label": "Perfiles propios",
|
||||||
"file_slots_btn": "🎨 Seleccionar ranuras",
|
"panel_ams_title": "Filamento",
|
||||||
"file_cancel_btn": "✕ Cancelar",
|
"panel_browser_title": "Explorador de archivos",
|
||||||
"nav_printers": "Impresoras",
|
"panel_console_title": "Registro de eventos",
|
||||||
"skip_title": "✂ Omitir objetos",
|
"panel_extras_camera": "Cámara",
|
||||||
"skip_hint": "Deselecciona los objetos que ya no quieras imprimir:",
|
"panel_extras_fan": "Ventilador",
|
||||||
"skip_btn_label": "Objetos",
|
"panel_extras_light": "Luz",
|
||||||
"skip_no_objects": "No hay objetos en esta impresión.",
|
"panel_motion_xy": "Ejes XY",
|
||||||
|
"panel_motion_z": "Eje Z",
|
||||||
|
"panel_print_btn_cancel": "✕ Cancelar",
|
||||||
|
"panel_print_btn_pause": "⏸ Pausa",
|
||||||
|
"panel_print_btn_resume": "▶ Reanudar",
|
||||||
|
"panel_print_temps_live": "Temperaturas (en vivo)",
|
||||||
|
"panel_print_title": "Control de impresión",
|
||||||
|
"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",
|
||||||
|
"settings_title": "Configuración",
|
||||||
|
"settings_username": "Usuario MQTT",
|
||||||
|
"settings_vendor_filter_placeholder": "Buscar fabricantes…",
|
||||||
|
"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_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_already": "omitido",
|
||||||
|
"skip_btn_label": "Objetos",
|
||||||
|
"skip_cancel": "Cancelar",
|
||||||
|
"skip_confirm": "Omitir",
|
||||||
|
"skip_confirm_btn": "Omitir",
|
||||||
|
"skip_hint": "Deselecciona los objetos que ya no quieras imprimir:",
|
||||||
|
"skip_no_objects": "No hay objetos en esta impresión.",
|
||||||
"skip_select_at_least_one": "Selecciona al menos un objeto.",
|
"skip_select_at_least_one": "Selecciona al menos un objeto.",
|
||||||
"skip_sending": "Enviando …",
|
"skip_sending": "Enviando …",
|
||||||
"skip_success": "Se omitirán los objetos.",
|
"skip_success": "Se omitirán los objetos.",
|
||||||
"fd_objects_hint": "Omitir objetos (opcional):",
|
"skip_title": "✂ Omitir objetos",
|
||||||
"fd_objects_toggle": "Omitir objetos",
|
"slot_edit_color": "Color",
|
||||||
"fd_slots_hint": "Asignar canal GCode a la ranura AMS:",
|
"slot_edit_custom": "p. ej. PLA, PETG, ABS…",
|
||||||
"fd_cancel": "Cancelar",
|
"slot_edit_load": "⬇ Cargar",
|
||||||
"fd_print": "▶ Imprimir",
|
"slot_edit_material": "Material",
|
||||||
"fd_no_slots_msg": "No hay slots AMS cargados.{br}¿Iniciar impresión de todos modos?",
|
"slot_edit_ok": "Ranura AMS",
|
||||||
"fd_slot": "Ranura",
|
"slot_edit_profile": "Perfil de OrcaSlicer",
|
||||||
"fd_no_matching_material": "No hay material compatible",
|
"slot_edit_profile_default": "— Genérico (Predeterminado) —",
|
||||||
"fd_used": "USADO",
|
"slot_edit_profile_hint": "Envía al sincronizar con OrcaSlicer la marca concreta en lugar de solo \"Generic\"",
|
||||||
"add_printer": "Añadir impresora",
|
"slot_edit_save": "💾 Guardar",
|
||||||
"apd_lbl_ip": "IP de impresora",
|
"slot_edit_title": "Editar slot",
|
||||||
"apd_lbl_name": "Nombre (opcional)",
|
"slot_edit_unload": "⬆ Descargar",
|
||||||
"apd_placeholder_name": "p. ej. Kobra X Sala",
|
"speed_normal": "⚡ Normal",
|
||||||
"apd_cancel": "Cancelar",
|
"speed_silent": "🐢 Silencioso",
|
||||||
"apd_confirm": "Añadir",
|
"speed_sport": "🚀 Sport",
|
||||||
"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_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",
|
|
||||||
"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}",
|
|
||||||
"store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "Todos",
|
|
||||||
"sf_ok": "✓ Completado",
|
|
||||||
"sf_err": "✗ Fallido",
|
|
||||||
"sf_new": "Nuevo",
|
|
||||||
"ss_date": "↓ Fecha",
|
"ss_date": "↓ Fecha",
|
||||||
"ss_name": "A–Z Nombre",
|
|
||||||
"ss_dur": "⏱ Tiempo de impresión",
|
"ss_dur": "⏱ Tiempo de impresión",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"ss_name": "A–Z Nombre",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"store_delete_confirm": "¿Eliminar archivo?",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"store_download": "⬇ Descargar",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"store_empty": "Aún no hay archivos subidos.",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
"store_estimate": "Estimación",
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
"store_never": "nunca impreso",
|
||||||
"ace_dry_preset_custom": "Personalizado",
|
"store_no_results": "No se encontraron archivos.",
|
||||||
"fd_options_title": "Opciones",
|
"store_print": "▶ Imprimir",
|
||||||
"print_auto_leveling": "Autonivelado para esta impresión",
|
"store_print_confirm": "¿Imprimir archivo?",
|
||||||
"settings_file_ready_mode": "Iniciar diálogo de impresión",
|
"store_refresh": "↻ Actualizar",
|
||||||
"settings_file_ready_banner": "Barra de impresión",
|
"store_search_placeholder": "🔍 Buscar…",
|
||||||
"settings_file_ready_dialog": "Diálogo de impresión",
|
"store_upload_busy": "⏳ Subiendo…",
|
||||||
"log_dir_rx": "RX",
|
"store_upload_error": "✗ {error}",
|
||||||
"log_dir_tx": "TX",
|
"store_upload_label_browse": "buscar",
|
||||||
"log_dir_label": "Dirección:",
|
"store_upload_label_prefix": "Arrastra el GCode aquí o ",
|
||||||
"log_lvl_err": "⛔ Errores",
|
"store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)",
|
||||||
"log_lvl_warn": "⚠ Avisos",
|
"store_upload_success": "✓ {file}",
|
||||||
"log_topic_label": "Tema:",
|
"store_web_verify_abort": "Cancelar",
|
||||||
"log_topic_ams": "AMS",
|
"store_web_verify_confirm": "Confirmar",
|
||||||
"log_topic_print": "Impresión",
|
"store_web_verify_msg": "Verifica que este archivo fue hecho para Anycubic Kobra X.",
|
||||||
"log_topic_info": "Info",
|
"store_web_verify_title": "Verificar archivo",
|
||||||
"log_topic_status": "Estado",
|
"update_apply": "Instalar ahora",
|
||||||
"log_download": "⬇ Descargar",
|
"update_applying": "Descargando...",
|
||||||
"log_auto": "⬇ Auto",
|
"update_available": "disponible",
|
||||||
"log_clear": "✕ Limpiar",
|
"update_check": "Buscar actualizaciones",
|
||||||
"log_filter_placeholder": "Filtrar…",
|
"update_checking": "Comprobando...",
|
||||||
"skip_cancel": "Cancelar",
|
"update_docker": "Copiar comando",
|
||||||
"skip_confirm": "Omitir"
|
"update_docker_copied": "Copiado. Ejecutar: docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "Error",
|
||||||
|
"update_none": "Ya actualizado",
|
||||||
|
"update_restarting": "Reiniciando..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "Prêt",
|
"ace_dry_auto_refill": "Remplissage auto",
|
||||||
"header_status_printing": "Impression",
|
"ace_dry_chart": "Historique (Temp/Humidité)",
|
||||||
"header_status_complete": "Terminé",
|
"ace_dry_current_temp": "Température",
|
||||||
"header_status_error": "Erreur",
|
"ace_dry_dialog_cancel": "Annuler",
|
||||||
"kobra_free": "Disponible",
|
"ace_dry_dialog_confirm": "Confirmer",
|
||||||
"kobra_busy": "Occupé",
|
"ace_dry_dialog_custom_name": "Nom personnalisé",
|
||||||
"kobra_printing": "Impression",
|
"ace_dry_dialog_reset_default": "Réinitialiser",
|
||||||
"kobra_preheating": "Préchauffage",
|
"ace_dry_dialog_save_restart": "Enregistrer et redémarrer",
|
||||||
"kobra_auto_leveling": "Mise à niveau auto",
|
"ace_dry_dialog_temp": "Température (30-80°C)",
|
||||||
"kobra_checking": "Vérification",
|
"ace_dry_dialog_time": "Temps restant (h:m:s)",
|
||||||
"kobra_updated": "Mise à jour",
|
"ace_dry_dialog_title": "Réglages Temp/Durée du séchoir",
|
||||||
"kobra_init": "Initialisation",
|
|
||||||
"kobra_pausing": "Pause en cours…",
|
|
||||||
"kobra_paused": "En pause",
|
|
||||||
"kobra_resuming": "Reprise en cours…",
|
|
||||||
"kobra_resumed": "Repris",
|
|
||||||
"kobra_stopping": "Arrêt en cours…",
|
|
||||||
"kobra_stoped": "Arrêté",
|
|
||||||
"kobra_finished": "Terminé",
|
|
||||||
"kobra_failed": "Erreur",
|
|
||||||
"kobra_canceled": "Annulé",
|
|
||||||
"kobra_offline": "Hors ligne",
|
|
||||||
"nav_dashboard": "Tableau de bord",
|
|
||||||
"nav_print": "Impression",
|
|
||||||
"nav_temps": "Températures",
|
|
||||||
"nav_motion": "Mouvement",
|
|
||||||
"nav_ams": "AMS",
|
|
||||||
"nav_extras": "Lumière / Ventilateur",
|
|
||||||
"nav_console": "Console",
|
|
||||||
"card_progress": "Progression",
|
|
||||||
"card_temps": "Températures",
|
|
||||||
"card_light_fan": "Ventilateur",
|
|
||||||
"card_speed": "Vitesse d'impression",
|
|
||||||
"card_cam": "Caméra",
|
|
||||||
"lbl_elapsed": "Écoulé :",
|
|
||||||
"lbl_remaining": "Restant :",
|
|
||||||
"lbl_slicer_time": "Estimation slicer :",
|
|
||||||
"lbl_layers": "Couche",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"speed_silent": "🐢 Silencieux",
|
|
||||||
"speed_normal": "⚡ Normal",
|
|
||||||
"speed_sport": "🚀 Sport",
|
|
||||||
"lbl_light": "💡 Lumière",
|
|
||||||
"lbl_feed": "Charger",
|
|
||||||
"lbl_unload": "Décharger",
|
|
||||||
"card_ace_dry": "Séchage ACE",
|
|
||||||
"ace_dry_dryer": "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_off": "Statut : Arrêté",
|
||||||
"ace_dry_status_on": "Statut : Actif",
|
"ace_dry_status_on": "Statut : Actif",
|
||||||
"ace_dry_status_remaining": "Restant",
|
"ace_dry_status_remaining": "Restant",
|
||||||
"ace_dry_humidity": "Humidité",
|
|
||||||
"ace_dry_current_temp": "Température",
|
|
||||||
"ace_dry_chart": "Historique (Temp/Humidité)",
|
|
||||||
"ace_dry_temp": "Température (°C)",
|
|
||||||
"ace_dry_duration": "Durée (min)",
|
|
||||||
"ace_dry_start": "▶ Démarrer",
|
|
||||||
"ace_dry_stop": "■ Arrêter",
|
"ace_dry_stop": "■ Arrêter",
|
||||||
"ace_dry_auto_refill": "Remplissage auto",
|
"ace_dry_temp": "Température (°C)",
|
||||||
"ace_dry_enable": "Activer le séchage",
|
|
||||||
"ace_dry_temp_line": "Température de séchage",
|
"ace_dry_temp_line": "Température de séchage",
|
||||||
"ace_dry_time_line": "Durée de séchage",
|
"ace_dry_time_line": "Durée de séchage",
|
||||||
"ace_dry_ui_pending": "(Interface seule, backend suivant)",
|
"ace_dry_ui_pending": "(Interface seule, backend suivant)",
|
||||||
"ace_dry_dialog_title": "Réglages Temp/Durée du séchoir",
|
"add_printer": "Ajouter une imprimante",
|
||||||
"ace_dry_dialog_temp": "Température (30-80°C)",
|
"ams_empty": "Vide",
|
||||||
"ace_dry_dialog_time": "Temps restant (h:m:s)",
|
"ams_no_data": "Aucune donnée AMS reçue",
|
||||||
"ace_dry_dialog_confirm": "Confirmer",
|
"apd_cancel": "Annuler",
|
||||||
"ace_dry_dialog_cancel": "Annuler",
|
"apd_confirm": "Ajouter",
|
||||||
"ace_dry_dialog_save_restart": "Enregistrer et redémarrer",
|
"apd_err_ip": "Veuillez saisir une adresse IP",
|
||||||
"ace_dry_dialog_custom_name": "Nom personnalisé",
|
"apd_fetching": "Récupération des données de l'imprimante…",
|
||||||
"ace_dry_dialog_reset_default": "Réinitialiser",
|
"apd_lbl_ip": "IP de l'imprimante",
|
||||||
"cam_placeholder": "📷 Caméra non démarrée",
|
"apd_lbl_name": "Nom (optionnel)",
|
||||||
"cam_stream_unavailable": "Flux indisponible",
|
"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_start": "▶ Caméra",
|
||||||
|
"btn_cam_start2": "▶ Démarrer",
|
||||||
"btn_cam_stop": "◼ Caméra",
|
"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_pause": "⏸ Pause",
|
||||||
"btn_resume": "▶ Reprendre",
|
"btn_resume": "▶ Reprendre",
|
||||||
"btn_cancel": "✕ Arrêter",
|
"cam_placeholder": "📷 Caméra non démarrée",
|
||||||
"label_nozzle": "Buse",
|
"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_bed": "Plateau",
|
||||||
"label_fan": "🌀 Ventilateur",
|
"label_fan": "🌀 Ventilateur",
|
||||||
"label_light": "💡 Lumière",
|
"label_light": "💡 Lumière",
|
||||||
"label_on_off": "On / Off",
|
"label_nozzle": "Buse",
|
||||||
"label_speed": "Vitesse",
|
|
||||||
"panel_print_title": "Contrôle impression",
|
|
||||||
"panel_print_btn_pause": "⏸ Pause",
|
|
||||||
"panel_print_btn_resume": "▶ Reprendre",
|
|
||||||
"panel_print_btn_cancel": "✕ Annuler",
|
|
||||||
"panel_print_temps_live": "Températures (en direct)",
|
|
||||||
"label_set": "Définir",
|
|
||||||
"label_off": "Éteint",
|
"label_off": "Éteint",
|
||||||
"panel_temps_nozzle": "Buse",
|
"label_on_off": "On / Off",
|
||||||
"panel_temps_bed": "Plateau chauffant",
|
"label_set": "Définir",
|
||||||
"panel_temps_chart": "Historique (60 dernières valeurs)",
|
|
||||||
"label_target_c": "Cible :",
|
|
||||||
"panel_motion_xy": "Axes XY",
|
|
||||||
"panel_motion_z": "Axe Z",
|
|
||||||
"label_step": "Pas :",
|
|
||||||
"btn_home_z": "Origine Z",
|
|
||||||
"btn_home_xy": "Origine XY",
|
|
||||||
"btn_home_all": "Origine Tout",
|
|
||||||
"btn_disable_motors": "Moteurs Off",
|
|
||||||
"panel_ams_title": "Filament",
|
|
||||||
"card_ams": "Filament",
|
|
||||||
"ams_no_data": "Aucune donnée AMS reçue",
|
|
||||||
"label_slot": "Slot",
|
"label_slot": "Slot",
|
||||||
"ams_empty": "Vide",
|
"label_speed": "Vitesse",
|
||||||
"panel_extras_light": "Lumière",
|
"label_step": "Pas :",
|
||||||
"panel_extras_fan": "Ventilateur",
|
"label_target_c": "Cible :",
|
||||||
"panel_extras_camera": "Caméra",
|
"lbl_conn_error": "Erreur de connexion :",
|
||||||
"btn_cam_start2": "▶ Démarrer",
|
"lbl_elapsed": "Écoulé :",
|
||||||
"btn_cam_stop2": "◼ Arrêter",
|
"lbl_feed": "Charger",
|
||||||
"panel_console_title": "Journal d'événements",
|
"lbl_layers": "Couche",
|
||||||
"log_light_on": "Lumière allumée",
|
"lbl_light": "💡 Lumière",
|
||||||
"log_light_off": "Lumière éteinte",
|
"lbl_remaining": "Restant :",
|
||||||
"log_fan": "Ventilateur →",
|
"lbl_slicer_time": "Estimation slicer :",
|
||||||
"log_nozzle": "Buse →",
|
"lbl_spoolman_sync_rate": "Taux de sync. (s, 0=désact.)",
|
||||||
"log_bed": "Plateau →",
|
"lbl_spoolman_url": "URL du serveur",
|
||||||
|
"lbl_unload": "Décharger",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ Auto",
|
||||||
"log_axis": "Axe",
|
"log_axis": "Axe",
|
||||||
"log_home": "Origine",
|
"log_bed": "Plateau →",
|
||||||
"log_home_all": "Origine Tout",
|
|
||||||
"log_cam_start": "Caméra démarrée :",
|
"log_cam_start": "Caméra démarrée :",
|
||||||
"log_cam_stop": "Caméra arrêtée",
|
"log_cam_stop": "Caméra arrêtée",
|
||||||
"log_poll_error": "Erreur de sondage :",
|
"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_error": "Erreur :",
|
||||||
"confirm_cancel": "Vraiment annuler l'impression ?",
|
"log_fan": "Ventilateur →",
|
||||||
"settings_title": "Paramètres",
|
"log_filter_placeholder": "Filtrer…",
|
||||||
"settings_connection": "Connexion",
|
"log_home": "Origine",
|
||||||
"settings_print": "Paramètres d'impression",
|
"log_home_all": "Origine Tout",
|
||||||
"settings_poll": "Intervalle de sondage (secondes)",
|
"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_settings": "Paramètres",
|
||||||
"settings_cat_display": "Apparence",
|
"nav_temps": "Températures",
|
||||||
"settings_cat_filament": "Filament",
|
"orca_profile_done": "Importé",
|
||||||
"settings_cat_language": "Langue",
|
"orca_profile_dropmsg": "Déposez ici ou cliquez",
|
||||||
"settings_cat_theme": "Basculer clair / sombre",
|
"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/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "Mappage du profil de filament (par emplacement)",
|
|
||||||
"settings_filament_mapping_save": "Enregistrer le mappage",
|
|
||||||
"settings_visible_vendors": "Fabricants visibles (liste des profils)",
|
|
||||||
"settings_visible_vendors_hint": "Seuls ces fabricants apparaissent dans la liste des profils d'emplacement. Rien de sélectionné = tout afficher. « Generic » et vos propres profils sont toujours visibles.",
|
|
||||||
"settings_visible_vendors_save": "Enregistrer la sélection",
|
|
||||||
"progress_action_print": "Imprimer",
|
|
||||||
"progress_action_slots": "Affecter les emplacements",
|
|
||||||
"progress_action_clear": "Vider",
|
|
||||||
"settings_version": "Version",
|
|
||||||
"settings_save": "Enregistrer et redémarrer",
|
|
||||||
"settings_printer_name": "Nom de l'imprimante",
|
|
||||||
"settings_printer_ip": "IP de l'imprimante",
|
|
||||||
"settings_mqtt_port": "Port MQTT",
|
|
||||||
"settings_username": "Nom d'utilisateur MQTT",
|
|
||||||
"settings_password": "Mot de passe MQTT",
|
|
||||||
"settings_device_id": "ID de l'appareil",
|
|
||||||
"settings_mode_id": "ID du mode",
|
|
||||||
"hint_ip_no_port": "Adresse IP uniquement, sans port (ex. 192.168.1.102)",
|
|
||||||
"settings_default_slot": "Slot par défaut (couleur unique)",
|
|
||||||
"settings_slot_auto": "Auto (tous les slots chargés)",
|
|
||||||
"settings_auto_leveling": "Mise à niveau auto avant impression",
|
|
||||||
"settings_camera_on_print": "Activer la caméra au démarrage de l'impression",
|
|
||||||
"settings_web_upload_warning": "Afficher un avertissement lors de l'impression de fichiers web",
|
|
||||||
"update_check": "Vérifier les mises à jour",
|
|
||||||
"update_checking": "Vérification…",
|
|
||||||
"update_available": "disponible",
|
|
||||||
"update_none": "Déjà à jour",
|
|
||||||
"update_apply": "Installer maintenant",
|
|
||||||
"update_applying": "Téléchargement…",
|
|
||||||
"update_restarting": "Redémarrage…",
|
|
||||||
"update_error": "Erreur",
|
|
||||||
"btn_connect": "⚡ Connecter",
|
|
||||||
"btn_disconnect": "✕ Déconnecter",
|
|
||||||
"lbl_conn_error": "Erreur de connexion :",
|
|
||||||
"slot_edit_title": "Modifier le slot",
|
|
||||||
"slot_edit_color": "Couleur",
|
|
||||||
"slot_edit_material": "Matériau",
|
|
||||||
"slot_edit_load": "⬇ Charger",
|
|
||||||
"slot_edit_unload": "⬆ Décharger",
|
|
||||||
"slot_edit_save": "💾 Enregistrer",
|
|
||||||
"slot_edit_custom": "ex. PLA, PETG, ABS…",
|
|
||||||
"slot_edit_ok": "Slot AMS",
|
|
||||||
"slot_edit_profile": "Profil OrcaSlicer",
|
|
||||||
"slot_edit_profile_hint": "Envoyé lors de la synchronisation OrcaSlicer comme marque spécifique au lieu de \"Générique\"",
|
|
||||||
"slot_edit_profile_default": "— Générique (défaut) —",
|
|
||||||
"orca_profile_section": "Profils OrcaSlicer",
|
|
||||||
"orca_profile_hint": "Importez vos propres profils de filament OrcaSlicer (ouvrez le dossier utilisateur via Aide → Afficher le dossier de configuration)",
|
"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_btn": "Importer des profils",
|
||||||
"orca_profile_import_link": "★ Importer mes profils…",
|
"orca_profile_import_link": "★ Importer mes profils…",
|
||||||
"orca_profile_import_title": "Importer vos profils OrcaSlicer",
|
"orca_profile_import_title": "Importer vos profils OrcaSlicer",
|
||||||
"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/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "Déposez ici ou cliquez",
|
|
||||||
"orca_profile_list_label": "Profils importés",
|
"orca_profile_list_label": "Profils importés",
|
||||||
"orca_profile_user_label": "Mes profils",
|
"orca_profile_section": "Profils OrcaSlicer",
|
||||||
"orca_profile_user_empty": "– aucun –",
|
|
||||||
"orca_profile_uploading": "Envoi en cours…",
|
|
||||||
"orca_profile_done": "Importé",
|
|
||||||
"orca_profile_skipped": "ignoré",
|
"orca_profile_skipped": "ignoré",
|
||||||
"log_dir_all": "Tout",
|
"orca_profile_uploading": "Envoi en cours…",
|
||||||
"log_lvl_label": "Niveau :",
|
"orca_profile_user_empty": "– aucun –",
|
||||||
"file_ready_btn": "▶ Lancer l'impression",
|
"orca_profile_user_label": "Mes profils",
|
||||||
"file_slots_btn": "🎨 Choisir les slots",
|
"panel_ams_title": "Filament",
|
||||||
"file_cancel_btn": "✕ Annuler",
|
"panel_browser_title": "Explorateur de fichiers",
|
||||||
"nav_printers": "Imprimantes",
|
"panel_console_title": "Journal d'événements",
|
||||||
"skip_title": "✂ Ignorer des objets",
|
"panel_extras_camera": "Caméra",
|
||||||
"skip_hint": "Décochez les objets que vous ne souhaitez plus imprimer :",
|
"panel_extras_fan": "Ventilateur",
|
||||||
"skip_btn_label": "Objets",
|
"panel_extras_light": "Lumière",
|
||||||
"skip_no_objects": "Aucun objet dans cette impression.",
|
"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_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_select_at_least_one": "Veuillez sélectionner au moins un objet.",
|
||||||
"skip_sending": "Envoi …",
|
"skip_sending": "Envoi …",
|
||||||
"skip_success": "Les objets seront ignorés.",
|
"skip_success": "Les objets seront ignorés.",
|
||||||
"fd_objects_hint": "Ignorer des objets (optionnel) :",
|
"skip_title": "✂ Ignorer des objets",
|
||||||
"fd_objects_toggle": "Ignorer des objets",
|
"slot_edit_color": "Couleur",
|
||||||
"fd_slots_hint": "Associer le canal GCode au slot AMS :",
|
"slot_edit_custom": "ex. PLA, PETG, ABS…",
|
||||||
"fd_cancel": "Annuler",
|
"slot_edit_load": "⬇ Charger",
|
||||||
"fd_print": "▶ Imprimer",
|
"slot_edit_material": "Matériau",
|
||||||
"fd_no_slots_msg": "Aucun slot AMS chargé.{br}Lancer l'impression quand même ?",
|
"slot_edit_ok": "Slot AMS",
|
||||||
"fd_slot": "Slot",
|
"slot_edit_profile": "Profil OrcaSlicer",
|
||||||
"fd_no_matching_material": "Aucun matériau correspondant",
|
"slot_edit_profile_default": "— Générique (défaut) —",
|
||||||
"fd_used": "UTILISÉ",
|
"slot_edit_profile_hint": "Envoyé lors de la synchronisation OrcaSlicer comme marque spécifique au lieu de \"Générique\"",
|
||||||
"add_printer": "Ajouter une imprimante",
|
"slot_edit_save": "💾 Enregistrer",
|
||||||
"apd_lbl_ip": "IP de l'imprimante",
|
"slot_edit_title": "Modifier le slot",
|
||||||
"apd_lbl_name": "Nom (optionnel)",
|
"slot_edit_unload": "⬆ Décharger",
|
||||||
"apd_placeholder_name": "ex. Kobra X Salon",
|
"speed_normal": "⚡ Normal",
|
||||||
"apd_cancel": "Annuler",
|
"speed_silent": "🐢 Silencieux",
|
||||||
"apd_confirm": "Ajouter",
|
"speed_sport": "🚀 Sport",
|
||||||
"apd_fetching": "Récupération des données de l'imprimante…",
|
|
||||||
"apd_success": "Imprimante ajoutée, redémarrage du bridge…",
|
|
||||||
"apd_err_ip": "Veuillez saisir une adresse IP",
|
|
||||||
"printers_remove": "Supprimer l'imprimante",
|
|
||||||
"printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.",
|
|
||||||
"printers_active": "● actif",
|
|
||||||
"printers_switch": "Changer →",
|
|
||||||
"printers_current": "Imprimante actuelle",
|
|
||||||
"printers_loading": "Chargement…",
|
|
||||||
"printers_none": "Aucune imprimante configurée.",
|
|
||||||
"printers_empty_hint": "Aucune imprimante configurée.",
|
|
||||||
"nav_browser": "Navigateur",
|
|
||||||
"panel_browser_title": "Explorateur de fichiers",
|
|
||||||
"store_search_placeholder": "🔍 Rechercher…",
|
|
||||||
"store_empty": "Aucun fichier uploadé.",
|
|
||||||
"store_refresh": "↻ Actualiser",
|
|
||||||
"store_print": "▶ Imprimer",
|
|
||||||
"store_download": "⬇ Télécharger",
|
|
||||||
"store_delete_confirm": "Supprimer le fichier ?",
|
|
||||||
"store_print_confirm": "Imprimer le fichier ?",
|
|
||||||
"store_web_verify_title": "Vérifier le fichier",
|
|
||||||
"store_web_verify_msg": "Veuillez vérifier que ce fichier a été créé pour l'Anycubic Kobra X.",
|
|
||||||
"store_web_verify_confirm": "Confirmer",
|
|
||||||
"store_web_verify_abort": "Annuler",
|
|
||||||
"store_no_results": "Aucun fichier trouvé.",
|
|
||||||
"store_never": "jamais imprimé",
|
|
||||||
"store_estimate": "Estimation",
|
|
||||||
"store_upload_label_prefix": "Déposez un GCode ici ou ",
|
|
||||||
"store_upload_label_browse": "parcourir",
|
|
||||||
"store_upload_busy": "⏳ Envoi en cours…",
|
|
||||||
"store_upload_success": "✓ {file}",
|
|
||||||
"store_upload_error": "✗ {error}",
|
|
||||||
"store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "Tout",
|
|
||||||
"sf_ok": "✓ Terminés",
|
|
||||||
"sf_err": "✗ Échoués",
|
|
||||||
"sf_new": "Nouveau",
|
|
||||||
"ss_date": "↓ Date",
|
"ss_date": "↓ Date",
|
||||||
"ss_name": "A–Z Nom",
|
|
||||||
"ss_dur": "⏱ Durée d'impression",
|
"ss_dur": "⏱ Durée d'impression",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"ss_name": "A–Z Nom",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"store_delete_confirm": "Supprimer le fichier ?",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"store_download": "⬇ Télécharger",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"store_empty": "Aucun fichier uploadé.",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
"store_estimate": "Estimation",
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
"store_never": "jamais imprimé",
|
||||||
"ace_dry_preset_custom": "Personnalisé",
|
"store_no_results": "Aucun fichier trouvé.",
|
||||||
"fd_options_title": "Options",
|
"store_print": "▶ Imprimer",
|
||||||
"print_auto_leveling": "Mise à niveau auto pour cette impression",
|
"store_print_confirm": "Imprimer le fichier ?",
|
||||||
"settings_file_ready_mode": "Démarrer le dialogue d'impression",
|
"store_refresh": "↻ Actualiser",
|
||||||
"settings_file_ready_banner": "Barre d'impression",
|
"store_search_placeholder": "🔍 Rechercher…",
|
||||||
"settings_file_ready_dialog": "Dialogue d'impression",
|
"store_upload_busy": "⏳ Envoi en cours…",
|
||||||
"log_dir_rx": "RX",
|
"store_upload_error": "✗ {error}",
|
||||||
"log_dir_tx": "TX",
|
"store_upload_label_browse": "parcourir",
|
||||||
"log_dir_label": "Sens :",
|
"store_upload_label_prefix": "Déposez un GCode ici ou ",
|
||||||
"log_lvl_err": "⛔ Erreurs",
|
"store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)",
|
||||||
"log_lvl_warn": "⚠ Avert.",
|
"store_upload_success": "✓ {file}",
|
||||||
"log_topic_label": "Sujet :",
|
"store_web_verify_abort": "Annuler",
|
||||||
"log_topic_ams": "AMS",
|
"store_web_verify_confirm": "Confirmer",
|
||||||
"log_topic_print": "Impression",
|
"store_web_verify_msg": "Veuillez vérifier que ce fichier a été créé pour Anycubic Kobra X.",
|
||||||
"log_topic_info": "Info",
|
"store_web_verify_title": "Vérifier le fichier",
|
||||||
"log_topic_status": "Statut",
|
"update_apply": "Installer maintenant",
|
||||||
"log_download": "⬇ Télécharger",
|
"update_applying": "Téléchargement…",
|
||||||
"log_auto": "⬇ Auto",
|
"update_available": "disponible",
|
||||||
"log_clear": "✕ Effacer",
|
"update_check": "Vérifier les mises à jour",
|
||||||
"log_filter_placeholder": "Filtrer…",
|
"update_checking": "Vérification…",
|
||||||
"skip_cancel": "Annuler",
|
"update_docker": "Copier la commande",
|
||||||
"skip_confirm": "Ignorer"
|
"update_docker_copied": "Copié ! Exécuter : docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "Erreur",
|
||||||
|
"update_none": "Déjà à jour",
|
||||||
|
"update_restarting": "Redémarrage…"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "Pronto",
|
"ace_dry_auto_refill": "Ricarica automatica",
|
||||||
"header_status_printing": "In stampa",
|
"ace_dry_chart": "Cronologia (Temp/Umidità)",
|
||||||
"header_status_complete": "Completato",
|
"ace_dry_current_temp": "Temperatura",
|
||||||
"header_status_error": "Errore",
|
"ace_dry_dialog_cancel": "Annulla",
|
||||||
"kobra_free": "Pronto",
|
"ace_dry_dialog_confirm": "Conferma",
|
||||||
"kobra_busy": "Occupato",
|
"ace_dry_dialog_custom_name": "Nome personalizzato",
|
||||||
"kobra_printing": "In stampa",
|
"ace_dry_dialog_reset_default": "Ripristina predefiniti",
|
||||||
"kobra_preheating": "Preriscaldamento",
|
"ace_dry_dialog_save_restart": "Salva e riavvia",
|
||||||
"kobra_auto_leveling": "Livellamento automatico",
|
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
|
||||||
"kobra_checking": "Verifica",
|
"ace_dry_dialog_time": "Tempo rim. (h:m:s)",
|
||||||
"kobra_updated": "Aggiornamento",
|
"ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore",
|
||||||
"kobra_init": "Inizializzazione",
|
|
||||||
"kobra_pausing": "Pausa in corso...",
|
|
||||||
"kobra_paused": "In pausa",
|
|
||||||
"kobra_resuming": "Ripresa...",
|
|
||||||
"kobra_resumed": "Ripreso",
|
|
||||||
"kobra_stopping": "Arresto...",
|
|
||||||
"kobra_stoped": "Arrestato",
|
|
||||||
"kobra_finished": "Finito",
|
|
||||||
"kobra_failed": "Errore",
|
|
||||||
"kobra_canceled": "Annullato",
|
|
||||||
"kobra_offline": "Offline",
|
|
||||||
"nav_dashboard": "Dashboard",
|
|
||||||
"nav_print": "Stampa",
|
|
||||||
"nav_temps": "Temperature",
|
|
||||||
"nav_motion": "Movimento",
|
|
||||||
"nav_ams": "AMS",
|
|
||||||
"nav_extras": "Luce / Ventola",
|
|
||||||
"nav_console": "Console",
|
|
||||||
"card_progress": "Avanzamento",
|
|
||||||
"card_temps": "Temperature",
|
|
||||||
"card_light_fan": "Ventola",
|
|
||||||
"card_speed": "Velocità di stampa",
|
|
||||||
"card_cam": "Camera",
|
|
||||||
"lbl_elapsed": "Trascorso:",
|
|
||||||
"lbl_remaining": "Rimanente:",
|
|
||||||
"lbl_slicer_time": "Stima slicer:",
|
|
||||||
"lbl_layers": "Layer",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"speed_silent": "🐢 Silenzioso",
|
|
||||||
"speed_normal": "⚡ Normale",
|
|
||||||
"speed_sport": "🚀 Sport",
|
|
||||||
"lbl_light": "💡 Luce",
|
|
||||||
"lbl_feed": "Carica",
|
|
||||||
"lbl_unload": "Rimuovi",
|
|
||||||
"card_ace_dry": "Essiccazione ACE",
|
|
||||||
"ace_dry_dryer": "Essiccatore",
|
"ace_dry_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_off": "Stato: Spento",
|
||||||
"ace_dry_status_on": "Stato: Attivo",
|
"ace_dry_status_on": "Stato: Attivo",
|
||||||
"ace_dry_status_remaining": "Rimanente",
|
"ace_dry_status_remaining": "Rimanente",
|
||||||
"ace_dry_humidity": "Umidità",
|
|
||||||
"ace_dry_current_temp": "Temperatura",
|
|
||||||
"ace_dry_chart": "Cronologia (Temp/Umidità)",
|
|
||||||
"ace_dry_temp": "Temperatura (°C)",
|
|
||||||
"ace_dry_duration": "Durata (min)",
|
|
||||||
"ace_dry_start": "▶ Avvia",
|
|
||||||
"ace_dry_stop": "■ Ferma",
|
"ace_dry_stop": "■ Ferma",
|
||||||
"ace_dry_auto_refill": "Ricarica automatica",
|
"ace_dry_temp": "Temperatura (°C)",
|
||||||
"ace_dry_enable": "Abilita essiccazione",
|
|
||||||
"ace_dry_temp_line": "Temperatura di essiccazione",
|
"ace_dry_temp_line": "Temperatura di essiccazione",
|
||||||
"ace_dry_time_line": "Tempo di essiccazione",
|
"ace_dry_time_line": "Tempo di essiccazione",
|
||||||
"ace_dry_ui_pending": "(Solo interfaccia, backend a seguire)",
|
"ace_dry_ui_pending": "(Solo interfaccia, backend a seguire)",
|
||||||
"ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore",
|
"add_printer": "Aggiungi stampante",
|
||||||
"ace_dry_dialog_temp": "Temperatura (30-80°C)",
|
"ams_empty": "Vuoto",
|
||||||
"ace_dry_dialog_time": "Tempo rim. (h:m:s)",
|
"ams_no_data": "Nessun dato ricevuto dall' AMS",
|
||||||
"ace_dry_dialog_confirm": "Conferma",
|
"apd_cancel": "Annulla",
|
||||||
"ace_dry_dialog_cancel": "Annulla",
|
"apd_confirm": "Aggiungi",
|
||||||
"ace_dry_dialog_save_restart": "Salva e riavvia",
|
"apd_err_ip": "Inserisci un indirizzo IP",
|
||||||
"ace_dry_dialog_custom_name": "Nome personalizzato",
|
"apd_fetching": "Recupero dati dalla stampante…",
|
||||||
"ace_dry_dialog_reset_default": "Ripristina predefiniti",
|
"apd_lbl_ip": "IP stampante",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"apd_lbl_name": "Nome (opzionale)",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"apd_placeholder_name": "es. Kobra X Soggiorno",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"apd_success": "Stampante aggiunta, riavvio del bridge in corso…",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"apd_title": "Aggiungi stampante",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
|
||||||
"ace_dry_preset_custom": "Personalizzato",
|
|
||||||
"cam_placeholder": "📷 Camera non avviata",
|
|
||||||
"cam_stream_unavailable": "Flusso video non disponibile",
|
|
||||||
"btn_cam_start": "▶ Camera",
|
"btn_cam_start": "▶ Camera",
|
||||||
|
"btn_cam_start2": "▶ Avvia",
|
||||||
"btn_cam_stop": "◼ Camera",
|
"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_pause": "⏸ Pausa",
|
||||||
"btn_resume": "▶ Riprendi",
|
"btn_resume": "▶ Riprendi",
|
||||||
"btn_cancel": "✕ Stop",
|
"cam_placeholder": "📷 Camera non avviata",
|
||||||
"label_nozzle": "Ugello",
|
"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_bed": "Piatto",
|
||||||
"label_fan": "🌀 Ventola",
|
"label_fan": "🌀 Ventola",
|
||||||
"label_light": "💡 Luce",
|
"label_light": "💡 Luce",
|
||||||
"label_on_off": "On / Off",
|
"label_nozzle": "Ugello",
|
||||||
"label_speed": "Velocità",
|
|
||||||
"panel_print_title": "Controllo stampa",
|
|
||||||
"panel_print_btn_pause": "⏸ Pausa",
|
|
||||||
"panel_print_btn_resume": "▶ Riprendi",
|
|
||||||
"panel_print_btn_cancel": "✕ Annulla",
|
|
||||||
"panel_print_temps_live": "Temperature (In tempo reale)",
|
|
||||||
"label_set": "Imposta",
|
|
||||||
"label_off": "Off",
|
"label_off": "Off",
|
||||||
"panel_temps_nozzle": "Ugello",
|
"label_on_off": "On / Off",
|
||||||
"panel_temps_bed": "Piatto riscaldato",
|
"label_set": "Imposta",
|
||||||
"panel_temps_chart": "Cronologia (ultime 60 letture)",
|
|
||||||
"label_target_c": "Target:",
|
|
||||||
"panel_motion_xy": "Assi XY",
|
|
||||||
"panel_motion_z": "Asse Z",
|
|
||||||
"label_step": "Ampiezza passo:",
|
|
||||||
"btn_home_z": "Home Z",
|
|
||||||
"btn_home_xy": "Home XY",
|
|
||||||
"btn_home_all": "Home generale",
|
|
||||||
"btn_disable_motors": "Spegni motori",
|
|
||||||
"panel_ams_title": "Filamento",
|
|
||||||
"card_ams": "Filamento",
|
|
||||||
"ams_no_data": "Nessun dato ricevuto dall' AMS",
|
|
||||||
"label_slot": "Slot",
|
"label_slot": "Slot",
|
||||||
"ams_empty": "Vuoto",
|
"label_speed": "Velocità",
|
||||||
"panel_extras_light": "Luce",
|
"label_step": "Ampiezza passo:",
|
||||||
"panel_extras_fan": "Ventola",
|
"label_target_c": "Target:",
|
||||||
"panel_extras_camera": "Camera",
|
"lbl_conn_error": "Errore di connessione:",
|
||||||
"btn_cam_start2": "▶ Avvia",
|
"lbl_elapsed": "Trascorso:",
|
||||||
"btn_cam_stop2": "◼ Ferma",
|
"lbl_feed": "Carica",
|
||||||
"panel_console_title": "Registro eventi",
|
"lbl_layers": "Layer",
|
||||||
"log_light_on": "Luce accesa",
|
"lbl_light": "💡 Luce",
|
||||||
"log_light_off": "Luce spenta",
|
"lbl_remaining": "Rimanente:",
|
||||||
"log_fan": "Ventola →",
|
"lbl_slicer_time": "Stima slicer:",
|
||||||
"log_nozzle": "Ugello →",
|
"lbl_spoolman_sync_rate": "Frequenza sync (s, 0=disatt.)",
|
||||||
"log_bed": "Piatto →",
|
"lbl_spoolman_url": "URL server",
|
||||||
|
"lbl_unload": "Rimuovi",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ Auto",
|
||||||
"log_axis": "Asse",
|
"log_axis": "Asse",
|
||||||
"log_home": "Home",
|
"log_bed": "Piatto →",
|
||||||
"log_home_all": "Home generale",
|
|
||||||
"log_cam_start": "Camera avviata:",
|
"log_cam_start": "Camera avviata:",
|
||||||
"log_cam_stop": "Camera arrestata",
|
"log_cam_stop": "Camera arrestata",
|
||||||
"log_poll_error": "Errore di sincronizzazione:",
|
"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_error": "Errore:",
|
||||||
"confirm_cancel": "Annullare davvero la stampa?",
|
"log_fan": "Ventola →",
|
||||||
"settings_title": "Impostazioni",
|
"log_filter_placeholder": "Filtra…",
|
||||||
"settings_connection": "Connessione",
|
"log_home": "Home",
|
||||||
"settings_print": "Impostazioni di stampa",
|
"log_home_all": "Home generale",
|
||||||
"settings_poll": "Intervallo di sincronizzazione (secondi)",
|
"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_settings": "Impostazioni",
|
||||||
"settings_cat_display": "Aspetto",
|
"nav_temps": "Temperature",
|
||||||
"settings_cat_filament": "Filamento",
|
"orca_profile_done": "Importato",
|
||||||
"settings_cat_language": "Lingua",
|
"orca_profile_dropmsg": "Trascina qui o fai clic",
|
||||||
"settings_cat_theme": "Alterna chiaro / scuro",
|
"orca_profile_help_html": "Carica un file <b>ZIP</b> della tua cartella filamenti di OrcaSlicer o file singoli <b>.json</b>.<br>In OrcaSlicer: <i>Aiuto → Mostra cartella di configurazione → user/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "Mappatura profilo filamento (per slot)",
|
|
||||||
"settings_filament_mapping_save": "Salva mappatura",
|
|
||||||
"settings_visible_vendors": "Produttori visibili (menu del profilo)",
|
|
||||||
"settings_visible_vendors_hint": "Solo questi produttori appariranno nel menu del profilo dello slot. Se non selezioni nulla = mostra tutti. I profili \"Generici\" e i tuoi personali sono sempre visibili.",
|
|
||||||
"settings_visible_vendors_save": "Salva selezione",
|
|
||||||
"progress_action_print": "Stampa",
|
|
||||||
"progress_action_slots": "Mappa slot",
|
|
||||||
"progress_action_clear": "Cancella",
|
|
||||||
"settings_version": "Versione",
|
|
||||||
"settings_save": "Salva e riavvia",
|
|
||||||
"settings_printer_name": "Nome stampante",
|
|
||||||
"settings_printer_ip": "IP stampante",
|
|
||||||
"settings_mqtt_port": "Porta MQTT",
|
|
||||||
"settings_username": "Nome utente MQTT",
|
|
||||||
"settings_password": "Password MQTT",
|
|
||||||
"settings_device_id": "ID dispositivo",
|
|
||||||
"settings_mode_id": "ID modalità",
|
|
||||||
"hint_ip_no_port": "Solo indirizzo IP, senza porta (es. 192.168.1.102)",
|
|
||||||
"settings_default_slot": "Slot predefinito (colore singolo)",
|
|
||||||
"settings_slot_auto": "Auto (tutti gli slot caricati)",
|
|
||||||
"settings_auto_leveling": "Livellamento automatico predefinito",
|
|
||||||
"fd_options_title": "Opzioni di stampa",
|
|
||||||
"print_auto_leveling": "Livellamento automatico",
|
|
||||||
"settings_file_ready_mode": "Comportamento all'avvio stampa",
|
|
||||||
"settings_file_ready_banner": "Barra di stampa",
|
|
||||||
"settings_file_ready_dialog": "Finestra di dialogo di stampa",
|
|
||||||
"settings_camera_on_print": "Attiva la camera all'avvio della stampa",
|
|
||||||
"settings_web_upload_warning": "Mostra un avviso quando si stampano caricamenti web",
|
|
||||||
"update_check": "Controlla aggiornamenti",
|
|
||||||
"update_checking": "Verifica in corso...",
|
|
||||||
"update_available": "disponibile",
|
|
||||||
"update_none": "Già aggiornato",
|
|
||||||
"update_apply": "Installa ora",
|
|
||||||
"update_applying": "Download in corso...",
|
|
||||||
"update_restarting": "Riavvio in corso...",
|
|
||||||
"update_error": "Errore",
|
|
||||||
"btn_connect": "⚡ Connetti",
|
|
||||||
"btn_disconnect": "✕ Disconnetti",
|
|
||||||
"lbl_conn_error": "Errore di connessione:",
|
|
||||||
"slot_edit_title": "Modifica slot",
|
|
||||||
"slot_edit_color": "Colore",
|
|
||||||
"slot_edit_material": "Materiale",
|
|
||||||
"slot_edit_load": "⬇ Carica",
|
|
||||||
"slot_edit_unload": "⬆ Rimuovi",
|
|
||||||
"slot_edit_save": "💾 Salva",
|
|
||||||
"slot_edit_custom": "es. PLA, PETG, ABS…",
|
|
||||||
"slot_edit_ok": "Slot AMS",
|
|
||||||
"slot_edit_profile": "Profilo OrcaSlicer",
|
|
||||||
"slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"",
|
|
||||||
"slot_edit_profile_default": "— Generico (predefinito) —",
|
|
||||||
"orca_profile_section": "Profili OrcaSlicer",
|
|
||||||
"orca_profile_hint": "Importa i tuoi profili di filamento OrcaSlicer (apri la cartella utente tramite Aiuto → Mostra cartella di configurazione)",
|
"orca_profile_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_btn": "Importa profili",
|
||||||
"orca_profile_import_link": "★ Importa i tuoi profili…",
|
"orca_profile_import_link": "★ Importa i tuoi profili…",
|
||||||
"orca_profile_import_title": "Importa i tuoi profili OrcaSlicer",
|
"orca_profile_import_title": "Importa i tuoi profili OrcaSlicer",
|
||||||
"orca_profile_help_html": "Carica un file <b>ZIP</b> della tua cartella filamenti di OrcaSlicer o file singoli <b>.json</b>.<br>In OrcaSlicer: <i>Aiuto → Mostra cartella di configurazione → user/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "Trascina qui o fai clic",
|
|
||||||
"orca_profile_list_label": "Attualmente importati",
|
"orca_profile_list_label": "Attualmente importati",
|
||||||
"orca_profile_user_label": "Profili personali",
|
"orca_profile_section": "Profili OrcaSlicer",
|
||||||
"orca_profile_user_empty": "– nessuno –",
|
|
||||||
"orca_profile_uploading": "Caricamento in corso…",
|
|
||||||
"orca_profile_done": "Importato",
|
|
||||||
"orca_profile_skipped": "saltato",
|
"orca_profile_skipped": "saltato",
|
||||||
"log_dir_all": "Tutti",
|
"orca_profile_uploading": "Caricamento in corso…",
|
||||||
"log_dir_rx": "RX",
|
"orca_profile_user_empty": "– nessuno –",
|
||||||
"log_dir_tx": "TX",
|
"orca_profile_user_label": "Profili personali",
|
||||||
"log_dir_label": "Dir:",
|
"panel_ams_title": "Filamento",
|
||||||
"log_lvl_label": "Livello:",
|
"panel_browser_title": "Browser dei file",
|
||||||
"log_lvl_err": "⛔ Errori",
|
"panel_console_title": "Registro eventi",
|
||||||
"log_lvl_warn": "⚠ Avvisi",
|
"panel_extras_camera": "Camera",
|
||||||
"log_topic_label": "Argomento:",
|
"panel_extras_fan": "Ventola",
|
||||||
"log_topic_ams": "AMS",
|
"panel_extras_light": "Luce",
|
||||||
"log_topic_print": "Stampa",
|
"panel_motion_xy": "Assi XY",
|
||||||
"log_topic_info": "Info",
|
"panel_motion_z": "Asse Z",
|
||||||
"log_topic_status": "Stato",
|
"panel_print_btn_cancel": "✕ Annulla",
|
||||||
"log_download": "⬇ Scarica",
|
"panel_print_btn_pause": "⏸ Pausa",
|
||||||
"log_auto": "⬇ Auto",
|
"panel_print_btn_resume": "▶ Riprendi",
|
||||||
"log_clear": "✕ Cancella",
|
"panel_print_temps_live": "Temperature (In tempo reale)",
|
||||||
"log_filter_placeholder": "Filtra…",
|
"panel_print_title": "Controllo stampa",
|
||||||
"file_ready_btn": "▶ Avvia stampa",
|
"panel_temps_bed": "Piatto riscaldato",
|
||||||
"file_slots_btn": "🎨 Seleziona slot",
|
"panel_temps_chart": "Cronologia (ultime 60 letture)",
|
||||||
"file_cancel_btn": "✕ Annulla",
|
"panel_temps_nozzle": "Ugello",
|
||||||
"nav_printers": "Stampanti",
|
"print_auto_leveling": "Livellamento automatico",
|
||||||
"skip_title": "✂ Salta oggetti",
|
"printers_active": "● attiva",
|
||||||
"skip_hint": "Deseleziona gli oggetti che non vuoi più stampare:",
|
"printers_current": "Stampante corrente",
|
||||||
"skip_btn_label": "Oggetti",
|
"printers_empty_hint": "Nessuna stampante ancora configurata.",
|
||||||
"skip_no_objects": "Nessun oggetto in questa stampa.",
|
"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_already": "saltato",
|
||||||
|
"skip_btn_label": "Oggetti",
|
||||||
"skip_cancel": "Annulla",
|
"skip_cancel": "Annulla",
|
||||||
"skip_confirm": "Salta",
|
"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_select_at_least_one": "Seleziona almeno un oggetto.",
|
||||||
"skip_sending": "Invio in corso …",
|
"skip_sending": "Invio in corso …",
|
||||||
"skip_success": "Gli oggetti verranno saltati.",
|
"skip_success": "Gli oggetti verranno saltati.",
|
||||||
"fd_objects_hint": "Salta oggetti (opzionale):",
|
"skip_title": "✂ Salta oggetti",
|
||||||
"fd_objects_toggle": "Salta oggetti",
|
"slot_edit_color": "Colore",
|
||||||
"fd_slots_hint": "Assegna il canale GCode allo slot AMS:",
|
"slot_edit_custom": "es. PLA, PETG, ABS…",
|
||||||
"fd_cancel": "Annulla",
|
"slot_edit_load": "⬇ Carica",
|
||||||
"fd_print": "▶ Stampa",
|
"slot_edit_material": "Materiale",
|
||||||
"fd_no_slots_msg": "Nessuno slot AMS caricato.{br}Avviare comunque la stampa?",
|
"slot_edit_ok": "Slot AMS",
|
||||||
"fd_slot": "Slot",
|
"slot_edit_profile": "Profilo OrcaSlicer",
|
||||||
"fd_no_matching_material": "Nessun materiale corrispondente",
|
"slot_edit_profile_default": "— Generico (predefinito) —",
|
||||||
"fd_used": "USATO",
|
"slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"",
|
||||||
"add_printer": "Aggiungi stampante",
|
"slot_edit_save": "💾 Salva",
|
||||||
"apd_lbl_ip": "IP stampante",
|
"slot_edit_title": "Modifica slot",
|
||||||
"apd_lbl_name": "Nome (opzionale)",
|
"slot_edit_unload": "⬆ Rimuovi",
|
||||||
"apd_placeholder_name": "es. Kobra X Soggiorno",
|
"speed_normal": "⚡ Normale",
|
||||||
"apd_cancel": "Annulla",
|
"speed_silent": "🐢 Silenzioso",
|
||||||
"apd_confirm": "Aggiungi",
|
"speed_sport": "🚀 Sport",
|
||||||
"apd_fetching": "Recupero dati dalla stampante…",
|
|
||||||
"apd_success": "Stampante aggiunta, riavvio del bridge in corso…",
|
|
||||||
"apd_err_ip": "Inserisci un indirizzo IP",
|
|
||||||
"printers_remove": "Rimuovi stampante",
|
|
||||||
"printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.",
|
|
||||||
"printers_active": "● attiva",
|
|
||||||
"printers_switch": "Cambia →",
|
|
||||||
"printers_current": "Stampante corrente",
|
|
||||||
"printers_loading": "Caricamento in corso…",
|
|
||||||
"printers_none": "Nessuna stampante configurata.",
|
|
||||||
"printers_empty_hint": "Nessuna stampante ancora configurata.",
|
|
||||||
"nav_browser": "Browser",
|
|
||||||
"panel_browser_title": "Browser dei file",
|
|
||||||
"store_search_placeholder": "🔍 Cerca…",
|
|
||||||
"store_empty": "Nessun file caricato.",
|
|
||||||
"store_refresh": "↻ Aggiorna",
|
|
||||||
"store_print": "▶ Stampa",
|
|
||||||
"store_download": "⬇ Scarica",
|
|
||||||
"store_delete_confirm": "Eliminare il file?",
|
|
||||||
"store_print_confirm": "Stampare il file?",
|
|
||||||
"store_web_verify_title": "Verifica file",
|
|
||||||
"store_web_verify_msg": "Verifica che questo file sia stato creato per Anycubic Kobra X.",
|
|
||||||
"store_web_verify_confirm": "Conferma",
|
|
||||||
"store_web_verify_abort": "Interrompi",
|
|
||||||
"store_no_results": "Nessun file trovato.",
|
|
||||||
"store_never": "mai stampato",
|
|
||||||
"store_estimate": "Stima",
|
|
||||||
"store_upload_label_prefix": "Trascina il GCode qui o ",
|
|
||||||
"store_upload_label_browse": "sfoglia",
|
|
||||||
"store_upload_busy": "⏳ Caricamento in corso…",
|
|
||||||
"store_upload_success": "✓ {file}",
|
|
||||||
"store_upload_error": "✗ {error}",
|
|
||||||
"store_upload_only_gcode": "✗ Sono consentiti solo file GCode (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "Tutti",
|
|
||||||
"sf_ok": "✓ Completato",
|
|
||||||
"sf_err": "✗ Fallito",
|
|
||||||
"sf_new": "Nuovo",
|
|
||||||
"ss_date": "↓ Data",
|
"ss_date": "↓ Data",
|
||||||
|
"ss_dur": "⏱ Tempo di stampa",
|
||||||
"ss_name": "Nome A–Z",
|
"ss_name": "Nome A–Z",
|
||||||
"ss_dur": "⏱ Tempo di stampa"
|
"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_docker": "Copia comando",
|
||||||
|
"update_docker_copied": "Copiato! Eseguire: docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "Errore",
|
||||||
|
"update_none": "Già aggiornato",
|
||||||
|
"update_restarting": "Riavvio in corso..."
|
||||||
}
|
}
|
||||||
@@ -1,291 +1,329 @@
|
|||||||
{
|
{
|
||||||
"header_status_standby": "就绪",
|
"ace_dry_auto_refill": "自动补料",
|
||||||
"header_status_printing": "打印中",
|
"ace_dry_chart": "历史 (温度/湿度)",
|
||||||
"header_status_complete": "完成",
|
"ace_dry_current_temp": "温度",
|
||||||
"header_status_error": "错误",
|
"ace_dry_dialog_cancel": "取消",
|
||||||
"kobra_free": "就绪",
|
"ace_dry_dialog_confirm": "确认",
|
||||||
"kobra_busy": "忙碌",
|
"ace_dry_dialog_custom_name": "自定义名称",
|
||||||
"kobra_printing": "打印中",
|
"ace_dry_dialog_reset_default": "恢复默认",
|
||||||
"kobra_preheating": "预热中",
|
"ace_dry_dialog_save_restart": "保存并重启",
|
||||||
"kobra_auto_leveling": "自动调平",
|
"ace_dry_dialog_temp": "温度 (30-80°C)",
|
||||||
"kobra_checking": "检查中",
|
"ace_dry_dialog_time": "剩余时间 (h:m:s)",
|
||||||
"kobra_updated": "更新中",
|
"ace_dry_dialog_title": "烘干温度/时间设置",
|
||||||
"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": "层",
|
|
||||||
"lbl_zpos": "Z (mm)",
|
|
||||||
"speed_silent": "🐢 静音",
|
|
||||||
"speed_normal": "⚡ 标准",
|
|
||||||
"speed_sport": "🚀 运动",
|
|
||||||
"lbl_light": "💡 灯光",
|
|
||||||
"lbl_feed": "进料",
|
|
||||||
"lbl_unload": "退料",
|
|
||||||
"card_ace_dry": "ACE 烘干",
|
|
||||||
"ace_dry_dryer": "烘干机",
|
"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_off": "状态: 关闭",
|
||||||
"ace_dry_status_on": "状态: 运行中",
|
"ace_dry_status_on": "状态: 运行中",
|
||||||
"ace_dry_status_remaining": "剩余",
|
"ace_dry_status_remaining": "剩余",
|
||||||
"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_stop": "■ 停止",
|
||||||
"ace_dry_auto_refill": "自动补料",
|
"ace_dry_temp": "温度 (°C)",
|
||||||
"ace_dry_enable": "启用烘干",
|
|
||||||
"ace_dry_temp_line": "烘干温度",
|
"ace_dry_temp_line": "烘干温度",
|
||||||
"ace_dry_time_line": "烘干时间",
|
"ace_dry_time_line": "烘干时间",
|
||||||
"ace_dry_ui_pending": "(仅 UI,后端稍后支持)",
|
"ace_dry_ui_pending": "(仅 UI,后端稍后支持)",
|
||||||
"ace_dry_dialog_title": "烘干温度/时间设置",
|
"add_printer": "添加打印机",
|
||||||
"ace_dry_dialog_temp": "温度 (30-80°C)",
|
"ams_empty": "空",
|
||||||
"ace_dry_dialog_time": "剩余时间 (h:m:s)",
|
"ams_no_data": "未收到 AMS 数据",
|
||||||
"ace_dry_dialog_confirm": "确认",
|
"apd_cancel": "取消",
|
||||||
"ace_dry_dialog_cancel": "取消",
|
"apd_confirm": "添加",
|
||||||
"ace_dry_dialog_save_restart": "保存并重启",
|
"apd_err_ip": "请输入 IP 地址",
|
||||||
"ace_dry_dialog_custom_name": "自定义名称",
|
"apd_fetching": "正在从打印机获取数据…",
|
||||||
"ace_dry_dialog_reset_default": "恢复默认",
|
"apd_lbl_ip": "打印机 IP",
|
||||||
"cam_placeholder": "📷 相机未启动",
|
"apd_lbl_name": "名称 (可选)",
|
||||||
"cam_stream_unavailable": "视频流不可用",
|
"apd_placeholder_name": "例如 Kobra X 客厅",
|
||||||
|
"apd_success": "打印机已添加,Bridge 正在重启…",
|
||||||
|
"apd_title": "添加打印机",
|
||||||
"btn_cam_start": "▶ 相机",
|
"btn_cam_start": "▶ 相机",
|
||||||
|
"btn_cam_start2": "▶ 启动",
|
||||||
"btn_cam_stop": "◼ 相机",
|
"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_pause": "⏸ 暂停",
|
||||||
"btn_resume": "▶ 继续",
|
"btn_resume": "▶ 继续",
|
||||||
"btn_cancel": "✕ 停止",
|
"cam_placeholder": "📷 相机未启动",
|
||||||
"label_nozzle": "喷嘴",
|
"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": "更新中",
|
||||||
"label_bed": "热床",
|
"label_bed": "热床",
|
||||||
"label_fan": "🌀 风扇",
|
"label_fan": "🌀 风扇",
|
||||||
"label_light": "💡 灯光",
|
"label_light": "💡 灯光",
|
||||||
"label_on_off": "开 / 关",
|
"label_nozzle": "喷嘴",
|
||||||
"label_speed": "速度",
|
|
||||||
"panel_print_title": "打印控制",
|
|
||||||
"panel_print_btn_pause": "⏸ 暂停",
|
|
||||||
"panel_print_btn_resume": "▶ 继续",
|
|
||||||
"panel_print_btn_cancel": "✕ 取消",
|
|
||||||
"panel_print_temps_live": "温度 (实时)",
|
|
||||||
"label_set": "设置",
|
|
||||||
"label_off": "关闭",
|
"label_off": "关闭",
|
||||||
"panel_temps_nozzle": "喷嘴",
|
"label_on_off": "开 / 关",
|
||||||
"panel_temps_bed": "热床",
|
"label_set": "设置",
|
||||||
"panel_temps_chart": "历史 (最近 60 次读数)",
|
|
||||||
"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": "槽位",
|
"label_slot": "槽位",
|
||||||
"ams_empty": "空",
|
"label_speed": "速度",
|
||||||
"panel_extras_light": "灯光",
|
"label_step": "步进:",
|
||||||
"panel_extras_fan": "风扇",
|
"label_target_c": "目标:",
|
||||||
"panel_extras_camera": "相机",
|
"lbl_conn_error": "连接错误:",
|
||||||
"btn_cam_start2": "▶ 启动",
|
"lbl_elapsed": "已用时间:",
|
||||||
"btn_cam_stop2": "◼ 停止",
|
"lbl_feed": "进料",
|
||||||
"panel_console_title": "事件日志",
|
"lbl_layers": "层",
|
||||||
"log_light_on": "灯光已开",
|
"lbl_light": "💡 灯光",
|
||||||
"log_light_off": "灯光已关",
|
"lbl_remaining": "剩余时间:",
|
||||||
"log_fan": "风扇 →",
|
"lbl_slicer_time": "切片预估:",
|
||||||
"log_nozzle": "喷嘴 →",
|
"lbl_spoolman_sync_rate": "同步频率(秒,0=关闭)",
|
||||||
"log_bed": "热床 →",
|
"lbl_spoolman_url": "服务器地址",
|
||||||
|
"lbl_unload": "退料",
|
||||||
|
"lbl_zpos": "Z (mm)",
|
||||||
|
"log_auto": "⬇ 自动",
|
||||||
"log_axis": "轴",
|
"log_axis": "轴",
|
||||||
"log_home": "回零",
|
"log_bed": "热床 →",
|
||||||
"log_home_all": "全部回零",
|
|
||||||
"log_cam_start": "相机已启动:",
|
"log_cam_start": "相机已启动:",
|
||||||
"log_cam_stop": "相机已停止",
|
"log_cam_stop": "相机已停止",
|
||||||
"log_poll_error": "轮询错误:",
|
"log_clear": "✕ 清空",
|
||||||
|
"log_delete_failed": "删除失败",
|
||||||
|
"log_dir_all": "全部",
|
||||||
|
"log_dir_label": "方向:",
|
||||||
|
"log_dir_rx": "RX",
|
||||||
|
"log_dir_tx": "TX",
|
||||||
|
"log_download": "⬇ 下载",
|
||||||
"log_error": "错误:",
|
"log_error": "错误:",
|
||||||
"confirm_cancel": "确定要取消打印吗?",
|
"log_fan": "风扇 →",
|
||||||
"settings_title": "设置",
|
"log_filter_placeholder": "筛选…",
|
||||||
"settings_connection": "连接",
|
"log_home": "回零",
|
||||||
"settings_print": "打印设置",
|
"log_home_all": "全部回零",
|
||||||
"settings_poll": "轮询间隔(秒)",
|
"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_settings": "设置",
|
||||||
"settings_cat_display": "外观",
|
"nav_temps": "温度",
|
||||||
"settings_cat_filament": "耗材",
|
"orca_profile_done": "已导入",
|
||||||
"settings_cat_language": "语言",
|
"orca_profile_dropmsg": "拖到此处或点击",
|
||||||
"settings_cat_theme": "切换浅色 / 深色",
|
"orca_profile_help_html": "上传 OrcaSlicer 耗材文件夹的 <b>ZIP</b> 或单个 <b>.json</b> 文件。<br>在 OrcaSlicer 中: <i>Help → Show Configuration Folder → user/<id>/filament/</i>",
|
||||||
"settings_filament_mapping": "耗材配置映射(每槽位)",
|
|
||||||
"settings_filament_mapping_save": "保存映射",
|
|
||||||
"settings_visible_vendors": "可见厂商(配置下拉框)",
|
|
||||||
"settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。",
|
|
||||||
"settings_visible_vendors_save": "保存选择",
|
|
||||||
"progress_action_print": "打印",
|
|
||||||
"progress_action_slots": "分配槽位",
|
|
||||||
"progress_action_clear": "清除",
|
|
||||||
"settings_version": "版本",
|
|
||||||
"settings_save": "保存并重启",
|
|
||||||
"settings_printer_name": "打印机名称",
|
|
||||||
"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_camera_on_print": "打印开始时开启相机",
|
|
||||||
"settings_web_upload_warning": "打印网页上传文件时显示警告",
|
|
||||||
"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": "— 通用 (默认) —",
|
|
||||||
"orca_profile_section": "OrcaSlicer 配置",
|
|
||||||
"orca_profile_hint": "导入你自己的 OrcaSlicer 耗材配置(在 Help → Show Configuration Folder 打开用户目录)",
|
"orca_profile_hint": "导入你自己的 OrcaSlicer 耗材配置(在 Help → Show Configuration Folder 打开用户目录)",
|
||||||
"orca_profile_import_btn": "导入配置",
|
"orca_profile_import_btn": "导入配置",
|
||||||
"orca_profile_import_link": "★ 导入自己的配置…",
|
"orca_profile_import_link": "★ 导入自己的配置…",
|
||||||
"orca_profile_import_title": "导入你的 OrcaSlicer 配置",
|
"orca_profile_import_title": "导入你的 OrcaSlicer 配置",
|
||||||
"orca_profile_help_html": "上传 OrcaSlicer 耗材文件夹的 <b>ZIP</b> 或单个 <b>.json</b> 文件。<br>在 OrcaSlicer 中: <i>Help → Show Configuration Folder → user/<id>/filament/</i>",
|
|
||||||
"orca_profile_dropmsg": "拖到此处或点击",
|
|
||||||
"orca_profile_list_label": "已导入",
|
"orca_profile_list_label": "已导入",
|
||||||
"orca_profile_user_label": "自己的配置",
|
"orca_profile_section": "OrcaSlicer 配置",
|
||||||
"orca_profile_user_empty": "– 无 –",
|
|
||||||
"orca_profile_uploading": "上传中…",
|
|
||||||
"orca_profile_done": "已导入",
|
|
||||||
"orca_profile_skipped": "跳过",
|
"orca_profile_skipped": "跳过",
|
||||||
"log_dir_all": "全部",
|
"orca_profile_uploading": "上传中…",
|
||||||
"log_lvl_label": "级别:",
|
"orca_profile_user_empty": "– 无 –",
|
||||||
"file_ready_btn": "▶ 开始打印",
|
"orca_profile_user_label": "自己的配置",
|
||||||
"file_slots_btn": "🎨 选择槽位",
|
"panel_ams_title": "耗材",
|
||||||
"file_cancel_btn": "✕ 取消",
|
"panel_browser_title": "文件浏览器",
|
||||||
"nav_printers": "打印机",
|
"panel_console_title": "事件日志",
|
||||||
"skip_title": "✂ 跳过对象",
|
"panel_extras_camera": "相机",
|
||||||
"skip_hint": "取消勾选不想继续打印的对象:",
|
"panel_extras_fan": "风扇",
|
||||||
"skip_btn_label": "对象",
|
"panel_extras_light": "灯光",
|
||||||
"skip_no_objects": "此打印任务没有对象。",
|
"panel_motion_xy": "XY 轴",
|
||||||
|
"panel_motion_z": "Z 轴",
|
||||||
|
"panel_print_btn_cancel": "✕ 取消",
|
||||||
|
"panel_print_btn_pause": "⏸ 暂停",
|
||||||
|
"panel_print_btn_resume": "▶ 继续",
|
||||||
|
"panel_print_temps_live": "温度 (实时)",
|
||||||
|
"panel_print_title": "打印控制",
|
||||||
|
"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": "切换浅色 / 深色",
|
||||||
|
"settings_title": "设置",
|
||||||
|
"settings_username": "MQTT 用户名",
|
||||||
|
"settings_vendor_filter_placeholder": "搜索厂商…",
|
||||||
|
"settings_version": "版本",
|
||||||
|
"settings_visible_vendors": "可见厂商(配置下拉框)",
|
||||||
|
"settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。",
|
||||||
|
"settings_visible_vendors_label": "可见厂商(配置下拉框)",
|
||||||
|
"settings_visible_vendors_save": "保存选择",
|
||||||
|
"settings_visible_vendors_save_label": "保存选择",
|
||||||
|
"settings_web_upload_warning": "打印网页上传文件时显示警告",
|
||||||
|
"sf_all": "全部",
|
||||||
|
"sf_err": "✗ 失败",
|
||||||
|
"sf_new": "新",
|
||||||
|
"sf_ok": "✓ 已完成",
|
||||||
"skip_already": "已跳过",
|
"skip_already": "已跳过",
|
||||||
|
"skip_btn_label": "对象",
|
||||||
|
"skip_cancel": "取消",
|
||||||
|
"skip_confirm": "跳过",
|
||||||
|
"skip_confirm_btn": "跳过",
|
||||||
|
"skip_hint": "取消勾选不想继续打印的对象:",
|
||||||
|
"skip_no_objects": "此打印任务没有对象。",
|
||||||
"skip_select_at_least_one": "请至少选择一个对象。",
|
"skip_select_at_least_one": "请至少选择一个对象。",
|
||||||
"skip_sending": "发送中 …",
|
"skip_sending": "发送中 …",
|
||||||
"skip_success": "对象将被跳过。",
|
"skip_success": "对象将被跳过。",
|
||||||
"fd_objects_hint": "跳过对象 (可选):",
|
"skip_title": "✂ 跳过对象",
|
||||||
"fd_objects_toggle": "跳过对象",
|
"slot_edit_color": "颜色",
|
||||||
"fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:",
|
"slot_edit_custom": "例如 PLA, PETG, ABS…",
|
||||||
"fd_cancel": "取消",
|
"slot_edit_load": "⬇ 进料",
|
||||||
"fd_print": "▶ 打印",
|
"slot_edit_material": "材料",
|
||||||
"fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?",
|
"slot_edit_ok": "AMS 槽位",
|
||||||
"fd_slot": "槽位",
|
"slot_edit_profile": "OrcaSlicer 配置",
|
||||||
"fd_no_matching_material": "无匹配材料",
|
"slot_edit_profile_default": "— 通用 (默认) —",
|
||||||
"fd_used": "已用",
|
"slot_edit_profile_hint": "在 OrcaSlicer 同步时发送具体品牌,而不仅仅是“Generic”",
|
||||||
"add_printer": "添加打印机",
|
"slot_edit_save": "💾 保存",
|
||||||
"apd_lbl_ip": "打印机 IP",
|
"slot_edit_title": "编辑槽位",
|
||||||
"apd_lbl_name": "名称 (可选)",
|
"slot_edit_unload": "⬆ 退料",
|
||||||
"apd_placeholder_name": "例如 Kobra X 客厅",
|
"speed_normal": "⚡ 标准",
|
||||||
"apd_cancel": "取消",
|
"speed_silent": "🐢 静音",
|
||||||
"apd_confirm": "添加",
|
"speed_sport": "🚀 运动",
|
||||||
"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_empty": "尚未上传文件。",
|
|
||||||
"store_refresh": "↻ 刷新",
|
|
||||||
"store_print": "▶ 打印",
|
|
||||||
"store_download": "⬇ 下载",
|
|
||||||
"store_delete_confirm": "删除文件?",
|
|
||||||
"store_print_confirm": "打印文件?",
|
|
||||||
"store_web_verify_title": "验证文件",
|
|
||||||
"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}",
|
|
||||||
"store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)",
|
|
||||||
"sf_all": "全部",
|
|
||||||
"sf_ok": "✓ 已完成",
|
|
||||||
"sf_err": "✗ 失败",
|
|
||||||
"sf_new": "新",
|
|
||||||
"ss_date": "↓ 日期",
|
"ss_date": "↓ 日期",
|
||||||
"ss_name": "A–Z 名称",
|
|
||||||
"ss_dur": "⏱ 打印时间",
|
"ss_dur": "⏱ 打印时间",
|
||||||
"ace_dry_preset_pla": "PLA",
|
"ss_name": "A–Z 名称",
|
||||||
"ace_dry_preset_pla_plus": "PLA+",
|
"store_delete_confirm": "删除文件?",
|
||||||
"ace_dry_preset_petg": "PETG",
|
"store_download": "⬇ 下载",
|
||||||
"ace_dry_preset_tpu": "TPU",
|
"store_empty": "尚未上传文件。",
|
||||||
"ace_dry_preset_abs_asa": "ABS / ASA",
|
"store_estimate": "估算",
|
||||||
"ace_dry_preset_pa_pc": "PA / PC",
|
"store_never": "从未打印",
|
||||||
"ace_dry_preset_custom": "自定义",
|
"store_no_results": "未找到文件。",
|
||||||
"fd_options_title": "选项",
|
"store_print": "▶ 打印",
|
||||||
"print_auto_leveling": "本次打印自动调平",
|
"store_print_confirm": "打印文件?",
|
||||||
"settings_file_ready_mode": "开始打印对话框",
|
"store_refresh": "↻ 刷新",
|
||||||
"settings_file_ready_banner": "打印栏",
|
"store_search_placeholder": "🔍 搜索…",
|
||||||
"settings_file_ready_dialog": "打印对话框",
|
"store_upload_busy": "⏳ 上传中…",
|
||||||
"log_dir_rx": "RX",
|
"store_upload_error": "✗ {error}",
|
||||||
"log_dir_tx": "TX",
|
"store_upload_label_browse": "浏览",
|
||||||
"log_dir_label": "方向:",
|
"store_upload_label_prefix": "将 GCode 拖到这里或 ",
|
||||||
"log_lvl_err": "⛔ 错误",
|
"store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)",
|
||||||
"log_lvl_warn": "⚠ 警告",
|
"store_upload_success": "✓ {file}",
|
||||||
"log_topic_label": "主题:",
|
"store_web_verify_abort": "取消",
|
||||||
"log_topic_ams": "AMS",
|
"store_web_verify_confirm": "确认",
|
||||||
"log_topic_print": "打印",
|
"store_web_verify_msg": "请验证此文件是为Anycubic Kobra X创建的。",
|
||||||
"log_topic_info": "信息",
|
"store_web_verify_title": "验证文件",
|
||||||
"log_topic_status": "状态",
|
"update_apply": "立即安装",
|
||||||
"log_download": "⬇ 下载",
|
"update_applying": "下载中...",
|
||||||
"log_auto": "⬇ 自动",
|
"update_available": "可用",
|
||||||
"log_clear": "✕ 清空",
|
"update_check": "检查更新",
|
||||||
"log_filter_placeholder": "筛选…",
|
"update_checking": "检查中...",
|
||||||
"skip_cancel": "取消",
|
"update_docker": "复制命令",
|
||||||
"skip_confirm": "跳过"
|
"update_docker_copied": "已复制!执行:docker compose pull && docker compose up -d",
|
||||||
|
"update_error": "错误",
|
||||||
|
"update_none": "已是最新版本",
|
||||||
|
"update_restarting": "重启中..."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user