diff --git a/CHANGELOG.md b/CHANGELOG.md index fba58c1..c23159f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Alle Änderungen an Grotti. Format nach [Keep a Changelog](https://keepachangelo ## [Unreleased] +## [3.5.2] – 2026-10-21 + +### Geändert + +- **Wenig Rest:** ACE oder Spule unter dem Dateigewicht warnt unter Dateien, bricht Start und Warteschlange nicht mehr ab. Falsche Sorte bleibt ein Wartegrund. + +### Behoben + +- **Queue „Nochmal — Start abgebrochen (Neustart)“ während der Drucker anläuft:** Nach dem Upload bleibt der Auftrag in `await_print`, bis Moonraker druckt. Die 45-Sekunden-Regel gilt nur noch, wenn der Upload-Thread weg ist. Wand und Seitenleiste zeigen „startet“ und dass es etwa eine Minute dauern kann. + ## [3.5.1] – 2026-10-20 ### Geändert diff --git a/README.md b/README.md index f40f184..5fce73e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-
+
diff --git a/docs/Anleitung-ACE.md b/docs/Anleitung-ACE.md
index 5ea6563..bde6863 100644
--- a/docs/Anleitung-ACE.md
+++ b/docs/Anleitung-ACE.md
@@ -1,6 +1,6 @@
# ACE und Spulen
-Stand **18.10.2026**. Mehrfarb-Drucke brauchen vor dem Start eine Zuordnung Datei-Farbe → ACE-Platz und eine passende Spule am Drucker.
+Stand **21.10.2026**. Mehrfarb-Drucke brauchen vor dem Start eine Zuordnung Datei-Farbe → ACE-Platz und eine passende Spule am Drucker.

@@ -44,7 +44,7 @@ Nach einem Grotti-Update mit ACE-Farben: unter Dateien **neu lesen**, damit alte
| „T… fehlt in der Map“ | Datei neu lesen, Zuordnung erneut setzen (Slicer-T ≠ ACE-Platz) |
| **invalid filament id** (10011700) | Alte Grotti-Version ohne T-Verdichtung, oder Map nicht neu gesetzt. Grotti neu starten, Datei neu lesen, nochmal senden. |
| Material-Warnung | Eingelegte Sorte ≠ Datei. Der Start geht trotzdem. PA12-CF steht in Grotti so, an den ACE geht PA6-CF. |
-| Rest zu gering | Andere Spule oder leichteres Teil |
+| Rest zu gering | Warnung unter Dateien („nur … g, Datei braucht … g“). Der Start geht trotzdem; leer pausiert der Drucker. |
| ACE blinkt nicht / kein Wechsel | Spule und Platz prüfen, ggf. Druck neu starten |
| Drucker hängt kurz nach Multi-Color-Start | Oft falsche Map (T10 gemappt als Platz 0). Neu lesen + Zuordnung. |
diff --git a/grotti/jetzt.py b/grotti/jetzt.py
index 4d22fdb..de15911 100644
--- a/grotti/jetzt.py
+++ b/grotti/jetzt.py
@@ -255,13 +255,17 @@ def build(conn, watcher_latest, printers=None, send_jobs=None):
live_by_host[host]["display_step_index"] = job.get("display_step_index")
live_by_host[host]["step_index"] = job.get("step_index")
else:
+ phase = (job.get("phase") or "").lower()
+ starting = phase in ("starting", "await_print")
running.append({
"host": host,
"name": job.get("printer_name") or host,
- "state": "running",
+ "state": "starting" if starting else "running",
+ "starting": starting,
"progress": job.get("progress") or 0,
"remain": job.get("remain") or 0,
"filename": job.get("filename") or "",
+ "message": job.get("message") or "",
"queue_id": job.get("id"),
"is_ablauf": bool(job.get("is_ablauf")),
})
diff --git a/grotti/jobs.py b/grotti/jobs.py
index a0e071f..c248755 100644
--- a/grotti/jobs.py
+++ b/grotti/jobs.py
@@ -415,44 +415,8 @@ def sync_history(conn, cfg):
def assert_filament_enough(conn, file_row, spool_id=None, ace_map=None, host=""):
- """Dateien-Gewicht gegen Spule oder ACE-Plätze prüfen."""
- need = float((file_row or {}).get("weight_g") or 0)
- if need <= 0:
- return
- mapping = acm.normalize_mapping(ace_map)
- if mapping and host:
- leftovers = []
- for item in mapping:
- slot = int(item["ams_index"]) + 1
- row = conn.execute(
- """
- SELECT remaining_g, name FROM spools
- WHERE printer_host = %s AND ace_slot = %s
- ORDER BY id LIMIT 1
- """,
- (host, slot),
- ).fetchone()
- if row:
- leftovers.append(float(row["remaining_g"] or 0))
- if leftovers and sum(leftovers) + 0.5 < need:
- raise GrottiError(
- "ACE-Plätze haben nur %.0f g, Datei braucht %.0f g."
- % (sum(leftovers), need),
- "spule",
- )
- return
- if not spool_id:
- return
- spool = store.get_spool_row(conn, spool_id)
- if not spool:
- return
- left = float(spool["remaining_g"] or 0)
- if left + 0.5 < need:
- raise GrottiError(
- "Spule „%s“ hat nur %.0f g, Datei braucht %.0f g."
- % (spool["name"], left, need),
- "spule",
- )
+ """Zu wenig Rest bricht den Start nicht ab. Die Oberfläche warnt vorher."""
+ return
def prepare_send(conn, file_id, host, confirm, override, spool_id, ace_map=None):
diff --git a/grotti/print_queue.py b/grotti/print_queue.py
index 0f44ebe..0c7d33b 100644
--- a/grotti/print_queue.py
+++ b/grotti/print_queue.py
@@ -30,7 +30,9 @@ BUSY = {"printing", "paused", "heating", "error"}
KEEPALIVE_S = 120
# Nach Start ohne print_active: Upload/MQTT/LeviQ brauchen Zeit, aber nicht ewig
PRINT_START_TIMEOUT_S = 600
-# Einfacher Queue-Start ohne Worker (typisch Grotti-Neustart)
+# Upload-Worker weg (typisch Grotti-Neustart mitten im Hochladen).
+# Nach erfolgreichem Senden ist die Phase await_print — dort gilt das nicht,
+# sonst wird ein noch anlaufender Druck als „Neustart“ neu eingereiht.
SIMPLE_START_STUCK_S = 45
@@ -421,16 +423,18 @@ class PrintQueueDispatcher:
from grotti import orders
orders.attach_order_item_to_live_run(wconn, host, name, oid)
- # Phase „starting“ lassen, bis Moonraker printing meldet.
- # Sofort print_active + alter complete/standby → fälschlich „Fertig.“
+ # await_print: Datei ist raus, Drucker meldet printing oft erst
+ # nach einer Minute. Nicht bei starting lassen — sonst gilt nach
+ # 45 s der Neustart-Retry und der zweite Start trifft „druckt schon“.
store.update_print_queue(
wconn,
item_id,
{
"status": "running",
- "message": "Startet auf %s: %s" % (printer_copy["name"], name),
+ "message": "Datei ist auf %s. Druckstart kann etwa eine Minute dauern."
+ % printer_copy["name"],
"host": host,
- "phase": "starting",
+ "phase": "await_print",
"phase_started_at": "now",
},
)
@@ -487,7 +491,7 @@ class PrintQueueDispatcher:
"""Einfach-Druck: running bleiben bis Moonraker fertig/abgebrochen."""
phase = (item.get("phase") or "").lower()
state = (live.get("state") or "").lower()
- if phase == "starting":
+ if phase in ("starting", "await_print"):
if state in ("printing", "paused", "heating") or live.get("print_active"):
store.update_print_queue(
conn,
@@ -498,6 +502,20 @@ class PrintQueueDispatcher:
"message": "Druckt…",
},
)
+ return
+ if phase == "await_print" and self._print_start_timed_out(item):
+ store.update_print_queue(
+ conn,
+ item["id"],
+ {
+ "status": "error",
+ "message": "Druckstart-Timeout: Drucker hat nach %d min nicht angefangen."
+ % (PRINT_START_TIMEOUT_S // 60),
+ "phase": "",
+ "phase_started_at": None,
+ },
+ )
+ return
# complete/standby vom vorherigen Auftrag: noch warten, nicht „Fertig.“
return
if phase != "print_active":
diff --git a/grotti/store.py b/grotti/store.py
index 41c3152..2637795 100644
--- a/grotti/store.py
+++ b/grotti/store.py
@@ -2858,7 +2858,10 @@ def _match_spools_for_need(need, have, used_ids=None):
def material_fit_score(conn, host, file_id):
- """Höher = besser. -1 = warten (falsche Sorte mit Profil, oder zu wenig Rest)."""
+ """Höher = besser. -1 = warten (falsche Sorte mit Profil).
+
+ Zu wenig Rest blockiert nicht; genug Rest gibt Zusatzpunkte.
+ """
need, weight = _file_needs(conn, file_id)
have = _spool_material_set(conn, host)
if not need:
@@ -2883,11 +2886,6 @@ def material_fit_score(conn, host, file_id):
score += 5
if weight and best["remaining_g"] + 1e-6 >= weight:
score += 5
- elif weight and all(h["remaining_g"] + 1e-6 < weight for h in matched):
- return -1, "braucht %s (%s g fehlen)" % (
- _need_label(n),
- int(max(0, weight - max(h["remaining_g"] for h in matched))),
- )
return score, ""
have_types = {h["material_type"] for h in have if h["material_type"]}
@@ -2900,11 +2898,6 @@ def material_fit_score(conn, host, file_id):
score += 5
if weight and any(h["remaining_g"] + 1e-6 >= weight for h in matched):
score += 5
- elif weight and all(h["remaining_g"] + 1e-6 < weight for h in matched):
- return -1, "braucht %s (%s g fehlen)" % (
- _need_label(n),
- int(max(0, weight - max(h["remaining_g"] for h in matched))),
- )
return score, ""
diff --git a/grotti/version.py b/grotti/version.py
index d002ae5..e76cd41 100644
--- a/grotti/version.py
+++ b/grotti/version.py
@@ -1,3 +1,3 @@
"""Version von Grotti (SemVer). Bei jeder Änderung CHANGELOG.md mitführen."""
-__version__ = "3.5.1"
+__version__ = "3.5.2"
diff --git a/web/static/api.js b/web/static/api.js
index 01411bb..7d1a9e8 100644
--- a/web/static/api.js
+++ b/web/static/api.js
@@ -4,7 +4,7 @@ const CSRF = document.querySelector('meta[name="csrf"]')?.content || "";
const STATE_LABELS = {
printing: "druckt", paused: "pausiert", complete: "fertig", standby: "bereit",
heating: "heizt", error: "Fehler", cancelled: "abgebrochen", offline: "offline",
- waiting: "wartet", running: "läuft",
+ waiting: "wartet", running: "läuft", starting: "startet",
};
function escapeHtml(value) {
diff --git a/web/templates/doku.html b/web/templates/doku.html
index fe2e384..ea5b863 100644
--- a/web/templates/doku.html
+++ b/web/templates/doku.html
@@ -49,7 +49,7 @@