diff --git a/multiace/i18n/de.json b/multiace/i18n/de.json index 6f2b5fcb..8d38a54e 100644 --- a/multiace/i18n/de.json +++ b/multiace/i18n/de.json @@ -54,7 +54,7 @@ "mode_1": "Kammer kühlen", "mode_2": "Kammer vorheizen", "mode_3": "Heiße Kammer", - "enc_locked_print": "Nicht während eines Drucks: der Gehäusemodus kommt aus der Slicer-Datei.", + "enc_hint_print": "Ein laufender Modus kann den Abluftwert während des Drucks wieder überschreiben.", "enc_locked_mode": "Das Gehäuse läuft in einem Modus und steuert seine Lüfter selbst. Zuerst Lüfter aus.", "restart_blocked": "Gesperrt, solange ein Druck läuft", "light": "Licht", diff --git a/multiace/i18n/en.json b/multiace/i18n/en.json index 69477920..22c89675 100644 --- a/multiace/i18n/en.json +++ b/multiace/i18n/en.json @@ -54,7 +54,7 @@ "mode_1": "Cooling chamber", "mode_2": "Preheating chamber", "mode_3": "Hot chamber", - "enc_locked_print": "Not during a print: the enclosure mode comes from the sliced file.", + "enc_hint_print": "A running mode may override the exhaust value while the print runs.", "enc_locked_mode": "The enclosure is running a mode and drives its fans itself. Use Fans off first.", "restart_blocked": "Blocked while a print is running", "light": "Light", diff --git a/multiace/i18n/zh.json b/multiace/i18n/zh.json index 568ee73f..6a95b795 100644 --- a/multiace/i18n/zh.json +++ b/multiace/i18n/zh.json @@ -54,7 +54,7 @@ "mode_1": "腔体降温", "mode_2": "腔体预热", "mode_3": "高温腔体", - "enc_locked_print": "打印期间不可用:机箱模式由切片文件决定。", + "enc_hint_print": "模式运行期间,打印过程中排气风扇的数值可能会被其覆盖。", "enc_locked_mode": "机箱正在运行某个模式并自行控制风扇。请先点击“关闭风扇”。", "restart_blocked": "打印进行中,已禁用", "light": "灯光", diff --git a/multiace/web/backend/main.py b/multiace/web/backend/main.py index fdf1b684..84409c66 100644 --- a/multiace/web/backend/main.py +++ b/multiace/web/backend/main.py @@ -2652,12 +2652,13 @@ async def _require_object(name: str, what: str) -> dict: raise HTTPException(409, f"this printer has no {what}") return status -async def _require_enclosure_idle() -> None: +async def _require_enclosure_drivable() -> None: """409 unless the enclosure fans may be driven directly: an enclosure is - attached, no print is running, and stock is not running a mode. In modes - 1 to 3 stock drives the fans itself (dynamically in cool mode), so a - direct speed would compete with it, and during a print the mode usually - comes from the sliced file.""" + attached and, outside a print, stock is not running a mode. During a + print the fans are allowed whatever the mode (stock itself accepts + SET_PURIFIER at any time); a running mode may override the exhaust value + again, which the UI says. Outside a print, modes 1 to 3 drive the fans + themselves (dynamically in cool mode), so a direct speed would compete.""" try: status = await _print_status() except httpx.HTTPError as e: @@ -2668,8 +2669,7 @@ async def _require_enclosure_idle() -> None: if not purifier.get("power_detected"): raise HTTPException(409, "no enclosure is attached") if (status.get("print_stats") or {}).get("state") in ("printing", "paused"): - raise HTTPException(409, "not during a print: the enclosure mode comes " - "from the sliced file") + return if purifier.get("mode") != 0: raise HTTPException(409, "the enclosure is running a mode and drives " "its fans itself; stop it first") @@ -2712,7 +2712,7 @@ async def print_control(request: Request, req: PrintControl) -> dict: # One enclosure fan directly (stock SET_PURIFIER). SPEED=0 starts # stock's delayed turn-off rather than stopping at once. if not mocked: - await _require_enclosure_idle() + await _require_enclosure_drivable() script = f"SET_PURIFIER FAN={verb} SPEED={applied / 100:.2f}" else: if not mocked: diff --git a/multiace/web/frontend/app.js b/multiace/web/frontend/app.js index 361dcbe9..6f71c110 100644 --- a/multiace/web/frontend/app.js +++ b/multiace/web/frontend/app.js @@ -7497,9 +7497,10 @@ createApp({ resume: printCtl.state === "paused", cancel: printCtl.state === "printing" || printCtl.state === "paused", })); - // The enclosure fans may be driven directly only when stock is not: not - // during a print (the mode comes from the sliced file) and not while a - // mode runs. The server enforces both; this only explains it. + // Outside a print the fans may be driven directly only while no mode + // runs; the server enforces it, this only explains it. During a print + // they are always allowed (stock accepts it too); a running mode may + // override the exhaust value, which is only a hint. // Stock's defect detection watches the print with the camera, which needs // the chamber light: while it is on during a print the light stays on. // The server refuses it too; this only disables the switch and explains. @@ -7508,12 +7509,15 @@ createApp({ && (printCtl.state === "printing" || printCtl.state === "paused")); const enclosureLock = computed(() => { const e = printCtl.enclosure; - if (printCtl.state === "printing" || printCtl.state === "paused") { - return t("ui.control.enc_locked_print"); - } + if (printCtl.state === "printing" || printCtl.state === "paused") return ""; if (e && e.mode) return t("ui.control.enc_locked_mode"); return ""; }); + const enclosureHint = computed(() => { + const e = printCtl.enclosure; + return (printCtl.state === "printing" || printCtl.state === "paused") + && e && e.mode ? t("ui.control.enc_hint_print") : ""; + }); const enclosureModeText = computed(() => { const e = printCtl.enclosure; if (!e || e.mode === null || e.mode === undefined) return ""; @@ -7609,6 +7613,16 @@ createApp({ function printCtlReset(verb) { printCtlRelease(verb, verb === "fan" ? 0 : 100); } + // The target boxes show the current target, so a spinner arrow steps from + // the real value instead of from an empty box. While the user is in a + // box the typed value wins over the readback; leaving the box (blur) + // goes back to showing the target. + const printCtlTempDraft = reactive({nozzle: null, bed: null}); + function printCtlTempShown(verb) { + if (printCtlTempDraft[verb] !== null) return printCtlTempDraft[verb]; + const target = printCtl[verb].target; + return target === null ? "" : Math.round(target); + } // An empty box is "nothing typed", not 0: sending 0 would switch the // heater off. function printCtlHeat(verb, ev) { @@ -7616,7 +7630,6 @@ createApp({ if (raw === "") return; const v = Number(raw); if (Number.isFinite(v)) sendPrintControl(verb, v); - ev.target.value = ""; } // Restart Firmware / Restart Printer. Refused by the server while a // print runs and under a managed install; the buttons mirror that. After @@ -7887,7 +7900,8 @@ createApp({ webcam, webcamSrc, webcamVideo, reloadWebcam, printCtl, printCtlLimits, printCtlDraft, printCtlBusy, printCtlError, printCtlCan, printCtlNotice, printCtlQueued, printCtlRestart, printCtlRestartBlocked, - enclosureLock, enclosureModeText, lightLocked, + printCtlTempDraft, printCtlTempShown, + enclosureLock, enclosureHint, enclosureModeText, lightLocked, printCtlSlide, printCtlRelease, printCtlReset, printCtlHeat, printCtlCancel, sendPrintControl, consoleLines, consoleFollow, consoleEl, loadConsole, consoleTime, diff --git a/multiace/web/frontend/index.html b/multiace/web/frontend/index.html index b7b76a61..6caa36ae 100644 --- a/multiace/web/frontend/index.html +++ b/multiace/web/frontend/index.html @@ -1429,6 +1429,9 @@

{{ t('ui.control.print') }}

@@ -1438,6 +1441,9 @@

{{ t('ui.control.print') }}

@@ -1463,9 +1469,10 @@

{{ t('ui.control.print') }}

{{ t('ui.control.light_locked') }}

+ Outside a print the fans can be set directly only while no mode + runs (the sliders say why). During a print they always can; a + running mode may override the exhaust value, which the hint + says. "Fans off" returns to idle and stops both at once. -->
{{ t('ui.control.enclosure_fans') }} @@ -1483,6 +1490,7 @@

{{ t('ui.control.print') }}

{{ Math.round(printCtl.enclosure[f]) }} %

{{ enclosureLock }}

+

{{ enclosureHint }}