diff --git a/config.ini.example b/config.ini.example index f0fa3b3..0ae3034 100644 --- a/config.ini.example +++ b/config.ini.example @@ -34,6 +34,9 @@ auto_leveling = 1 # Kamera-Stream bei Druckstart automatisch einschalten (1 = an, 0 = aus) camera_on_print = 0 +# Statt grünem Ready-Banner den Filament/Color-Selector automatisch öffnen (1 = an, 0 = aus) +file_ready_dialog = 1 + # Warnung vor Druck von Web-Uploads (1 = an, 0 = aus) web_upload_warning = 1 diff --git a/config_loader.py b/config_loader.py index 807f911..458c425 100644 --- a/config_loader.py +++ b/config_loader.py @@ -60,6 +60,7 @@ def _load_config_file(path: pathlib.Path): "DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"), "AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"), "CAMERA_ON_PRINT": (CONFIG_SECTION_PRINT, "camera_on_print"), + "FILE_READY_DIALOG": (CONFIG_SECTION_PRINT, "file_ready_dialog"), "WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"), "BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"), } @@ -98,6 +99,7 @@ def migrate_env_to_config(env_path: pathlib.Path, config_path: pathlib.Path): "default_ams_slot": env_vals.get("DEFAULT_AMS_SLOT", "auto"), "auto_leveling": env_vals.get("AUTO_LEVELING", "1"), "camera_on_print": env_vals.get("CAMERA_ON_PRINT", "0"), + "file_ready_dialog": env_vals.get("FILE_READY_DIALOG", "1"), "web_upload_warning": env_vals.get("WEB_UPLOAD_WARNING", "1"), } cfg[CONFIG_SECTION_BRIDGE] = { @@ -258,4 +260,5 @@ DEVICE_ID = get("DEVICE_ID", "") DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto") AUTO_LEVELING = int(get("AUTO_LEVELING","1")) CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT","0")) +FILE_READY_DIALOG = int(get("FILE_READY_DIALOG","1")) WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1")) diff --git a/docker-compose.yml b/docker-compose.yml index 21fa90c..374b4fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ services: kx-bridge: - image: gitea.it-drui.de/viewit/kx-bridge:latest + # image: gitea.it-drui.de/viewit/kx-bridge:latest # Selbst bauen statt das Registry-Image zu pullen? # Dann image-Zeile auskommentieren und folgende aktivieren: - # build: . + build: . volumes: - ./config:/app/config - ./data:/app/data diff --git a/env_loader.py b/env_loader.py index 342df7b..c83488c 100644 --- a/env_loader.py +++ b/env_loader.py @@ -48,3 +48,4 @@ MODE_ID = get("MODE_ID", "") DEVICE_ID = get("DEVICE_ID", "") DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto") AUTO_LEVELING = int(get("AUTO_LEVELING", "1")) +FILE_READY_DIALOG = int(get("FILE_READY_DIALOG", "1")) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 8caf876..54d4f82 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -783,6 +783,7 @@ class KobraXBridge: "print_speed_mode": 2, "connection_error": "", "file_ready": "", + "file_ready_dialog": getattr(args, "file_ready_dialog", 0), "filament_mode": "toolhead", "ace_drying": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None}, } @@ -3659,6 +3660,7 @@ class KobraXBridge: "thumbnail": thumbnail, "connection_error": s["connection_error"], "file_ready": s["file_ready"], + "file_ready_dialog": getattr(self._args, "file_ready_dialog", 1), "version": self._read_version(), }) @@ -3796,6 +3798,7 @@ class KobraXBridge: "default_ams_slot": getattr(self._args, "default_ams_slot", "auto"), "auto_leveling": getattr(self._args, "auto_leveling", 1), "camera_on_print": getattr(self._args, "camera_on_print", 0), + "file_ready_dialog": getattr(self._args, "file_ready_dialog", 1), "web_upload_warning": getattr(self._args, "web_upload_warning", 1), "ace_dry_presets": self._ace_dry_presets, }) @@ -3826,6 +3829,8 @@ class KobraXBridge: cfg.set("print", "default_ams_slot", str(data.get("default_ams_slot", getattr(self._args, "default_ams_slot", "auto")))) cfg.set("print", "auto_leveling", str(data.get("auto_leveling", getattr(self._args, "auto_leveling", 1)))) cfg.set("print", "camera_on_print", str(int(bool(data.get("camera_on_print", getattr(self._args, "camera_on_print", 0)))))) + file_ready_dialog = int(bool(data.get("file_ready_dialog", getattr(self._args, "file_ready_dialog", 0)))) + cfg.set("print", "file_ready_dialog", str(file_ready_dialog)) cfg.set("print", "web_upload_warning", str(int(bool(data.get("web_upload_warning", getattr(self._args, "web_upload_warning", 1)))))) if not cfg.has_option("bridge", "poll_interval"): cfg.set("bridge", "poll_interval", "3") @@ -3835,6 +3840,8 @@ class KobraXBridge: elif cfg.has_option("bridge", "printer_name"): cfg.remove_option("bridge", "printer_name") + self._args.file_ready_dialog = file_ready_dialog + 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) for key, val in presets.items(): @@ -3992,8 +3999,8 @@ class KobraXBridge: # Bei einem Restart muss environ bereinigt werden, sonst liest der neue Prozess # die alten Werte statt der geänderten config.ini. for _k in ("PRINTER_IP", "MQTT_PORT", "MQTT_USERNAME", "MQTT_PASSWORD", - "MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING", - "CAMERA_ON_PRINT", "WEB_UPLOAD_WARNING", "BRIDGE_PRINTER_NAME"): + "MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING", + "CAMERA_ON_PRINT", "FILE_READY_DIALOG", "WEB_UPLOAD_WARNING", "BRIDGE_PRINTER_NAME"): os.environ.pop(_k, None) in_docker = os.path.exists("/.dockerenv") or os.environ.get("KX_IN_DOCKER") @@ -4885,6 +4892,7 @@ def main(): parser.add_argument("--default-ams-slot",default=env_loader.DEFAULT_AMS_SLOT) parser.add_argument("--auto-leveling", type=int, default=env_loader.AUTO_LEVELING) parser.add_argument("--camera-on-print", type=int, default=env_loader.CAMERA_ON_PRINT) + parser.add_argument("--file-ready-dialog", type=int, default=env_loader.FILE_READY_DIALOG) parser.add_argument("--web-upload-warning", type=int, default=env_loader.WEB_UPLOAD_WARNING) parser.add_argument("--host", default="0.0.0.0", diff --git a/web/themes/default/app.js b/web/themes/default/app.js index a84ba72..42373de 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -339,6 +339,9 @@ function applyLang(){ setText('opt-slot-auto',T.settings_slot_auto); setText('lbl-auto-leveling',T.settings_auto_leveling); setText('lbl-camera-on-print',T.settings_camera_on_print); + setText('lbl-file-ready-mode',T.settings_file_ready_mode); + setText('opt-file-ready-banner',T.settings_file_ready_banner); + setText('opt-file-ready-dialog',T.settings_file_ready_dialog); setText('lbl-web-upload-warning',T.settings_web_upload_warning); setText('lbl-update-check',T.update_check); @@ -620,12 +623,25 @@ function applyState(){ var banner=document.getElementById('conn-error-banner'); if(banner){if(s.connection_error&&_printers.length>0){banner.textContent='⚠ '+tr('lbl_conn_error')+' '+s.connection_error;banner.style.display='block';}else{banner.style.display='none';}} var frb=document.getElementById('file-ready-banner'); + var filamentDialog=document.getElementById('filament-dialog'); + var filamentDialogOpen=!!(filamentDialog&&filamentDialog.classList.contains('open')); + var readyFile=s.file_ready||''; + var readyStandby=!!(readyFile&&s.print_state==='standby'); + var dialogMode=!!s.file_ready_dialog; if(frb){ - if(s.file_ready&&s.print_state==='standby'){ - document.getElementById('file-ready-name').textContent=s.file_ready; + if(readyStandby&&!dialogMode){ + document.getElementById('file-ready-name').textContent=readyFile; frb.style.display='flex'; - }else{frb.style.display='none';} + }else{ + frb.style.display='none'; + } } + var shouldAutoOpen=(_readyStateInitialized&&readyStandby&&dialogMode&&!filamentDialogOpen&&readyFile!==_lastReadyFile); + if(shouldAutoOpen){ + setTimeout(function(){ startReadyFileWithSlots(); },0); + } + _lastReadyFile=readyFile; + _readyStateInitialized=true; // skip-button (mid-print) – nur sichtbar wenn aktuell gedruckt wird var printing=(s.print_state==='printing'||s.print_state==='paused'); var skipBtn=document.getElementById('d-btn-skip'); @@ -911,6 +927,7 @@ function openSettings(){ document.getElementById('s-default-slot').value=d.default_ams_slot||'auto'; document.getElementById('s-auto-leveling').checked=(d.auto_leveling===undefined?true:!!d.auto_leveling); var cop=document.getElementById('s-camera-on-print');if(cop)cop.checked=!!d.camera_on_print; + var frm=document.getElementById('s-file-ready-mode');if(frm)frm.value=Number(d.file_ready_dialog)?'dialog':'banner'; var wuw=document.getElementById('s-web-upload-warning');if(wuw)wuw.checked=(d.web_upload_warning===undefined?true:!!d.web_upload_warning); }); var v=localStorage.getItem('pollInterval')||'2000'; @@ -1269,6 +1286,8 @@ function saveSettings(){ btn.disabled=true;btn.textContent='…'; var webUploadWarning=(document.getElementById('s-web-upload-warning')||{}).checked?1:0; S.web_upload_warning=webUploadWarning; + var fileReadyDialog=(document.getElementById('s-file-ready-mode')||{}).value==='dialog'?1:0; + S.file_ready_dialog=fileReadyDialog; post('/api/settings',{ printer_name: document.getElementById('s-printer-name').value, printer_ip: document.getElementById('s-printer-ip').value, @@ -1280,6 +1299,7 @@ function saveSettings(){ default_ams_slot: document.getElementById('s-default-slot').value, auto_leveling: document.getElementById('s-auto-leveling').checked?1:0, camera_on_print: (document.getElementById('s-camera-on-print')||{}).checked?1:0, + file_ready_dialog:fileReadyDialog, web_upload_warning:webUploadWarning, }).then(function(){ btn.textContent=T.update_restarting; @@ -1927,6 +1947,8 @@ function formatDur(sec){ var _storeFileId=null; var _storeFilename=null; var _filamentDialogMode='store'; // 'store' oder 'banner' +var _readyStateInitialized=false; +var _lastReadyFile=''; var _pendingWebVerifyFileId=null; var _pendingWebVerifyFilename=''; var _pendingWebVerifyAction=null; @@ -1960,9 +1982,13 @@ function storePrint(fileId, filename){ } function openStorePrintDialog(fileId, filename, fileObj){ + openStoreLikePrintDialog(fileId, filename, fileObj, 'store'); +} + +function openStoreLikePrintDialog(fileId, filename, fileObj, dialogMode){ _storeFileId=fileId; _storeFilename=filename; - _filamentDialogMode='store'; + _filamentDialogMode=dialogMode||'store'; maybeGateWebUpload(fileObj, function(){ // GCode-Filamente aus Store-Datei holen (für Vorschau im Dialog) _setGcodeFilamentsFromFileObj(fileObj); @@ -2069,37 +2095,36 @@ function startReadyFileWithSlots(){ maybeGateWebUpload(currentFile, function(){ startReadyFileWithSlots(); }); return; } - _filamentDialogMode='banner'; - _storeFilename=S.file_ready||''; - // Banner must never reuse stale store-file context. - _storeFileId=null; - _gcodeFilaments=[]; - - function openWithSlots(){ - fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json()}).then(function(d){ - openFilamentDialog(d.result||[]); - }).catch(function(){openFilamentDialog([]);}); - } - - var fileObj=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;}); + var targetFilename=S.file_ready||''; + var fileObj=(storeFiles||[]).find(function(f){return f.filename===targetFilename;}); if(fileObj){ - _storeFileId=fileObj.id; - _setGcodeFilamentsFromFileObj(fileObj); - openWithSlots(); + openStoreLikePrintDialog(fileObj.id, targetFilename, fileObj, 'store'); return; } // Fallback: refresh file list, then resolve current file by filename. fetch(_apiUrl('/kx/files')).then(function(r){return r.json()}).then(function(d){ storeFiles=d.result||[]; - var refreshed=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;}); + var refreshed=(storeFiles||[]).find(function(f){return f.filename===targetFilename;}); if(refreshed){ - _storeFileId=refreshed.id; - _setGcodeFilamentsFromFileObj(refreshed); + openStoreLikePrintDialog(refreshed.id, targetFilename, refreshed, 'store'); + return; } - openWithSlots(); + _filamentDialogMode='banner'; + _storeFilename=targetFilename; + _storeFileId=null; + _gcodeFilaments=[]; + fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json()}).then(function(data){ + openFilamentDialog(data.result||[]); + }).catch(function(){openFilamentDialog([]);}); }).catch(function(){ - openWithSlots(); + _filamentDialogMode='banner'; + _storeFilename=targetFilename; + _storeFileId=null; + _gcodeFilaments=[]; + fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json()}).then(function(data){ + openFilamentDialog(data.result||[]); + }).catch(function(){openFilamentDialog([]);}); }); } diff --git a/web/themes/default/index.html b/web/themes/default/index.html index d662a58..a7ae099 100644 --- a/web/themes/default/index.html +++ b/web/themes/default/index.html @@ -89,6 +89,13 @@
+