diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index fd7d764c45..b79a8b1e0c 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -172,17 +172,17 @@ jobs: # binary); this covers src/engine PRs with the PR-built binary. Runs in # parallel off build_linux's artifact so it doesn't lengthen the build leg. slice_check_linux: - name: Slice check (Linux x86_64) + name: Slice check (Linux aarch64) needs: build_linux if: ${{ !cancelled() && success() }} - runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }} + runs-on: ubuntu-24.04-arm steps: - name: Checkout repository uses: actions/checkout@v7 - name: Download profile validator uses: actions/download-artifact@v8 with: - name: ${{ github.sha }}-profile-validator-linux-x86_64 + name: ${{ github.sha }}-profile-validator-linux-aarch64 path: validator-bin - name: Validate slice (expand custom g-code) timeout-minutes: 60 diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 273c5074ce..fbd05df857 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -510,13 +510,15 @@ jobs: # Ship the freshly-built validator so the parallel slice_check_linux job # (build_all.yml) can slice-sweep the shipped profiles with this PR's - # engine. Stable sha-based name mirrors the tests artifact above so the + # engine. Taken from the aarch64 leg so the sweep runs on arm64 (its + # GitHub-hosted runner is free, and it also exercises the arm build). + # Stable sha-based name mirrors the tests artifact above so the # downstream job downloads it by exact name. - name: Upload profile validator (for slice check) - if: runner.os == 'Linux' && inputs.arch != 'aarch64' + if: runner.os == 'Linux' && inputs.arch == 'aarch64' uses: actions/upload-artifact@v7 with: - name: ${{ github.sha }}-profile-validator-linux-x86_64 + name: ${{ github.sha }}-profile-validator-linux-aarch64 overwrite: true path: ./build/src/Release/OrcaSlicer_profile_validator retention-days: 5 diff --git a/localization/i18n/de/OrcaSlicer_de.po b/localization/i18n/de/OrcaSlicer_de.po index 2fcddbf963..b47f37ba60 100644 --- a/localization/i18n/de/OrcaSlicer_de.po +++ b/localization/i18n/de/OrcaSlicer_de.po @@ -8891,10 +8891,10 @@ msgid "Show incompatible/unsupported presets in the printer and filament dropdow msgstr "Zeigt inkompatible/nicht unterstützte Profile in den Dropdown-Listen für Drucker und Filament an. Diese Profile können nicht ausgewählt werden." msgid "Experimental Features" -msgstr "" +msgstr "Experimentelle Funktionen" msgid "Keep painted feature after mesh change" -msgstr "" +msgstr "Bemalte Funktionen nach Mesh-Änderung beibehalten" msgid "" "Attempt to keep painted features (color/seam/support/fuzzy etc.) after changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\n" @@ -9719,10 +9719,10 @@ msgid "Search in preset" msgstr "In Profilen suchen" msgid "Synchronization of different extruder drives or nozzle volume types is not supported." -msgstr "" +msgstr "Die Synchronisierung verschiedener Extrudertypen oder Düsenvolumentypen wird nicht unterstützt." msgid "Synchronize the modification of parameters to the corresponding parameters of another extruder." -msgstr "" +msgstr "Änderungen der Parameter mit den entsprechenden Parametern eines anderen Extruders synchronisieren." msgid "Click to reset all settings to the last saved preset." msgstr "Klicken Sie hier, um alle Einstellungen auf die zuletzt gespeicherten Parameter zurückzusetzen." @@ -10018,13 +10018,13 @@ msgid "Chamber temperature" msgstr "Druckraum Temperatur" msgid "Target chamber temperature, and the minimal chamber temperature at which printing should start" -msgstr "" +msgstr "Ziel-Druckraumtemperatur und die minimale Druckraumtemperatur, bei der der Druck beginnen sollte" msgid "Target" -msgstr "" +msgstr "Ziel" msgid "Minimal" -msgstr "" +msgstr "Minimal" msgid "Print temperature" msgstr "Drucktemperatur" @@ -10047,7 +10047,7 @@ msgstr "Strukturierte kalte Druckplatte" # TODO: Review, changed by lang refactor. PR 14254 msgid "This is the bed temperature when the Textured Cool Plate is installed. A value of 0 means the filament does not support printing on the Textured Cool Plate." -msgstr "Dies ist die Betttemperatur, wenn die kalte Druckplatte installiert ist. Ein Wert von 0 bedeutet, dass das Filament auf der texturierten kalten Druckplatte nicht unterstützt wird." +msgstr "Dies ist die Betttemperatur, wenn die texturierte kalte Druckplatte installiert ist. Ein Wert von 0 bedeutet, dass das Filament auf der texturierten kalten Druckplatte nicht unterstützt wird." # TODO: Review, changed by lang refactor. PR 14254 msgid "This is the bed temperature when the engineering plate is installed. A value of 0 means the filament does not support printing on the Engineering Plate." @@ -10368,21 +10368,21 @@ msgstr "Nicht erneut für dieses Profil warnen" #, c-format, boost-format msgid "%s: %s" -msgstr "" +msgstr "%s: %s" msgid "No modifications need to be copied." -msgstr "" +msgstr "Es müssen keine Änderungen kopiert werden." msgid "Copy paramters" -msgstr "" +msgstr "Parameter kopieren" #, c-format, boost-format msgid "Modify paramters of %s" -msgstr "" +msgstr "Parameter von %s ändern" #, c-format, boost-format msgid "Do you want to modify the following parameters of the %s to that of the %s?" -msgstr "" +msgstr "Möchten Sie die folgenden Parameter von %s auf die von %s ändern?" msgid "Click to reset current value and attach to the global value." msgstr "Klicken Sie hier, um den aktuellen Wert zurückzusetzen und ihn dem globalen Wert zuzuordnen." @@ -10528,10 +10528,10 @@ msgid "Capabilities" msgstr "Fähigkeiten" msgid "Left: " -msgstr "" +msgstr "Links: " msgid "Right: " -msgstr "" +msgstr "Rechts: " msgid "Show all presets (including incompatible)" msgstr "Alle Profile anzeigen (auch inkompatible)" @@ -13887,7 +13887,7 @@ msgid "layer" msgstr "Schicht" msgid "First layer fan speed" -msgstr "" +msgstr "Lüftergeschwindigkeit der ersten Schicht" msgid "" "Sets an exact fan speed for the first layer, overriding all other cooling settings. Useful for protecting 3D-printed toolhead parts (e.g. Voron-style ABS/ASA ducts) from a hot bed. A small amount of airflow cools the ducts down, without using full cooling that may in certain conditions hurt first-layer adhesion.\n" @@ -13896,6 +13896,11 @@ msgid "" "Only available when \"No cooling for the first\" is 0.\n" "Set to -1 to disable it." msgstr "" +"Legt eine genaue Lüftergeschwindigkeit für die erste Schicht fest und überschreibt alle anderen Kühlungseinstellungen. Nützlich zum Schutz von 3D-gedruckten Werkzeugkopfteilen (z.B. Voron-Style ABS/ASA-Düsen) vor einem heißen Bett. Eine kleine Menge Luftstrom kühlt die Düsen ab, ohne die volle Kühlung zu verwenden, die unter bestimmten Bedingungen die Haftung der ersten Schicht beeinträchtigen kann.\n" +"Ab der zweiten Schicht wird die normale Kühlung wieder aufgenommen.\n" +"Wenn auch \"Volle Lüfterdrehzahl ab Schicht\" eingestellt ist, steigt der Lüfter von diesem Wert in der ersten Schicht bis zu Ihrem Zielwert in der gewählten Schicht.\n" +"Nur verfügbar, wenn \"Keine Kühlung für die erste\" 0 ist.\n" +"Auf -1 setzen, um es zu deaktivieren." msgid "Support interface fan speed" msgstr "Stützstruktur-Schnittstelle" @@ -16297,9 +16302,14 @@ msgid "" "\n" "Unlike the \"Target\" chamber temperature, this option does not emit any M141/M191 commands; it only exposes the value to your custom G-code. It should not exceed the \"Target\" chamber temperature." msgstr "" +"Dies ist die Druckraumtemperatur, bei der der Druckvorgang beginnen soll, während der Druckraum weiterhin auf die \"Ziel\"-Druckraumtemperatur erhitzt wird. Zum Beispiel: Setzen Sie das Ziel auf 60 und das Minimum auf 50, um mit dem Drucken zu beginnen, sobald der Druckraum 50°C erreicht hat, ohne auf die vollen 60°C zu warten.\n" +"\n" +"Es wird eine G-Code-Variable namens chamber_minimal_temperature gesetzt, die an Ihr Druckstart-Makro oder ein Wärmespeicher-Makro übergeben werden kann, wie z.B.: PRINT_START (andere Variablen) CHAMBER_MIN_TEMP=[chamber_minimal_temperature].\n" +"\n" +"Im Gegensatz zur \"Ziel\"-Druckraumtemperatur sendet diese Option keine M141/M191-Befehle; sie stellt den Wert nur Ihrem benutzerdefinierten G-Code zur Verfügung. Sie sollte die \"Ziel\"-Druckraumtemperatur nicht überschreiten." msgid "Chamber minimal temperature" -msgstr "" +msgstr "Minimale Druckraumtemperatur" # TODO: Review, changed by lang refactor. PR 14254 msgid "Nozzle temperature after the first layer" @@ -19110,130 +19120,132 @@ msgid "Connection to printers connected via the print host failed." msgstr "Die Verbindung zu den über den Druck-Host verbundenen Druckern ist fehlgeschlagen." msgid "Detect Creality K-series printer" -msgstr "" +msgstr "Creality K-Serie Drucker erkennen" msgid "Click Scan to look for K-series printers on your network." -msgstr "" +msgstr "Klicken Sie auf 'Scannen', um nach K-Serie Druckern in Ihrem Netzwerk zu suchen." msgid "Use Selected" -msgstr "" +msgstr "Ausgewählten verwenden" msgid "Scanning the LAN for K-series printers... this takes a few seconds." -msgstr "" +msgstr "Suche im LAN nach K-Serie Druckern... dies dauert einige Sekunden." msgid "No K-series printers found. Make sure the printer is on the same network and not blocked by Wi-Fi client isolation, then click Scan again." -msgstr "" +msgstr "Keine K-Serie Drucker gefunden. Stellen Sie sicher, dass der Drucker im selben Netzwerk ist und nicht durch Wi-Fi-Client-Isolation blockiert wird, und klicken Sie dann erneut auf 'Scannen'." #, c-format msgid "Found %zu Creality printer(s). Select one and click Use Selected." -msgstr "" +msgstr "Es wurden %zu Creality-Drucker gefunden. Wählen Sie einen aus und klicken Sie auf 'Ausgewählten verwenden'." msgid "Active" -msgstr "" +msgstr "Aktiv" msgid "Printers" -msgstr "" +msgstr "Drucker" msgid "Processes" -msgstr "" +msgstr "Prozesse" msgid "Show/Hide system information" -msgstr "" +msgstr "Systeminformationen anzeigen/verbergen" msgid "Copy system information to clipboard" -msgstr "" +msgstr "Systeminformationen in die Zwischenablage kopieren" msgid "We need information for diagnosing source of the issue. Check wiki page for detailed guide." -msgstr "" +msgstr "Wir benötigen Informationen zur Diagnose der Ursache des Problems. Überprüfen Sie die Wiki-Seite für eine detaillierte Anleitung." msgid "Pack button collects project file and logs of current session onto a zip file." -msgstr "" +msgstr "Die Schaltfläche „Packen“ sammelt die Projektdatei und Protokolle der aktuellen Sitzung in einer ZIP-Datei." msgid "Any additional visual examples like images or screen recordings might be helpful while reporting the issue." -msgstr "" +msgstr "Zusätzliche visuelle Beispiele wie Bilder oder Bildschirmaufnahmen könnten beim Melden des Problems hilfreich sein." msgid "Report issue" -msgstr "" +msgstr "Problem melden" msgid "Pack" -msgstr "" +msgstr "Packen" msgid "Cleans and rebuilds system profiles cache on next launch" -msgstr "" +msgstr "Bereinigt und erstellt den Systemprofil-Cache beim nächsten Start neu" msgid "Clean system profiles cache" -msgstr "" +msgstr "Systemprofil-Cache bereinigen" msgid "Clean" -msgstr "" +msgstr "Bereinigen" msgid "Loaded profiles overview" -msgstr "" +msgstr "Übersicht der geladenen Profile" msgid "This section shows information for loaded profiles" -msgstr "" +msgstr "Dieser Abschnitt zeigt Informationen zu den geladenen Profilen" msgid "Exports detailed overview of loaded profiles in json format" -msgstr "" +msgstr "Exportiert eine detaillierte Übersicht der geladenen Profile im JSON-Format" msgid "Configurations folder" -msgstr "" +msgstr "Konfigurationsordner" msgid "Opens configurations folder" -msgstr "" +msgstr "Öffnet den Konfigurationsordner" msgid "Log level" -msgstr "" +msgstr "Protokollstufe" msgid "Stored logs" -msgstr "" +msgstr "Gespeicherte Protokolle" msgid "Packs all stored logs onto a zip file." -msgstr "" +msgstr "Packt alle gespeicherten Protokolle in eine ZIP-Datei." msgid "Profiles" -msgstr "" +msgstr "Profile" msgid "Select NO to close dialog and review project" -msgstr "" +msgstr "Wählen Sie NEIN, um das Dialogfeld zu schließen und das Projekt zu überprüfen" msgid "No project file on current session. Only logs will be included to package" -msgstr "" +msgstr "Keine Projektdatei in der aktuellen Sitzung. Nur Protokolle werden in das Paket aufgenommen" msgid "Select NO to close dialog and review project." -msgstr "" +msgstr "Wählen Sie NEIN, um das Dialogfeld zu schließen und das Projekt zu überprüfen." msgid "Please make sure any instances of OrcaSlicer are not running" -msgstr "" +msgstr "Bitte stellen Sie sicher, dass keine Instanzen von OrcaSlicer ausgeführt werden." msgid "System folder cannot be deleted because some files are in use by another application. Please close any applications using these files and try again." -msgstr "" +msgstr "Der Systemordner kann nicht gelöscht werden, da einige Dateien von einer anderen Anwendung verwendet werden. Bitte schließen Sie alle Anwendungen, die diese Dateien verwenden, und versuchen Sie es erneut." msgid "Failed to delete system folder..." -msgstr "" +msgstr "Systemordner konnte nicht gelöscht werden..." msgid "Failed to determine executable path." -msgstr "" +msgstr "Konnte den Pfad zur ausführbaren Datei nicht bestimmen." msgid "Failed to launch a new instance." -msgstr "" +msgstr "Konnte keine neue Instanz starten." msgid "log(s)" -msgstr "" +msgstr "Protokoll(e)" msgid "Choose where to save the exported JSON file" -msgstr "" +msgstr "Wählen Sie, wo die exportierte JSON-Datei gespeichert werden soll" msgid "" "Export failed\n" "Please check write permissions or file in use by another application" msgstr "" +"Export fehlgeschlagen\n" +"Bitte überprüfen Sie die Schreibberechtigungen oder ob die Datei von einer anderen Anwendung verwendet wird." msgid "Choose where to save the exported ZIP file" -msgstr "" +msgstr "Wählen Sie, wo die exportierte ZIP-Datei gespeichert werden soll" msgid "File already exists. Overwrite?" -msgstr "" +msgstr "Datei existiert bereits. Überschreiben?" msgid "3DPrinterOS Cloud upload options" msgstr "3DPrinterOS Cloud-Upload-Optionen" @@ -19318,17 +19330,17 @@ msgid "Could not connect to MKS" msgstr "Konnte keine Verbindung zu MKS herstellen" msgid "Connection to Moonraker is working correctly." -msgstr "" +msgstr "Verbindung zu Moonraker funktioniert korrekt." msgid "Could not connect to Moonraker" -msgstr "" +msgstr "Konnte keine Verbindung zu Moonraker herstellen" msgid "The host responded but it doesn't look like Moonraker (missing result.klippy_state)." -msgstr "" +msgstr "Der Host hat geantwortet, sieht aber nicht wie Moonraker aus (fehlender result.klippy_state)." #, c-format, boost-format msgid "Could not parse Moonraker server response: %s" -msgstr "" +msgstr "Konnte die Moonraker-Serverantwort nicht analysieren: %s" msgid "Connection to OctoPrint is working correctly." msgstr "Verbindung zu OctoPrint funktioniert korrekt." diff --git a/localization/i18n/list.txt b/localization/i18n/list.txt index 3e99dbbaae..1614bc453b 100644 --- a/localization/i18n/list.txt +++ b/localization/i18n/list.txt @@ -287,4 +287,6 @@ src/slic3r/GUI/DownloaderFileGet.cpp src/slic3r/GUI/FileArchiveDialog.cpp src/slic3r/GUI/PrinterCloudAuthDialog.cpp src/slic3r/GUI/PrinterWebViewHandler.cpp +src/slic3r/GUI/AMSDryControl.cpp +src/slic3r/GUI/AMSDryControl.hpp src/libslic3r/PresetBundle.cpp diff --git a/resources/images/dev_ams_dry_ctr_disable.svg b/resources/images/dev_ams_dry_ctr_disable.svg new file mode 100644 index 0000000000..f2212204e9 --- /dev/null +++ b/resources/images/dev_ams_dry_ctr_disable.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/images/dev_ams_dry_ctr_enable.svg b/resources/images/dev_ams_dry_ctr_enable.svg new file mode 100644 index 0000000000..31eb482d17 --- /dev/null +++ b/resources/images/dev_ams_dry_ctr_enable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/dev_ams_dry_ctr_filament_in_chamber.png b/resources/images/dev_ams_dry_ctr_filament_in_chamber.png new file mode 100644 index 0000000000..1b36643c42 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_filament_in_chamber.png differ diff --git a/resources/images/dev_ams_dry_ctr_heating_icon.svg b/resources/images/dev_ams_dry_ctr_heating_icon.svg new file mode 100644 index 0000000000..614b0c9a92 --- /dev/null +++ b/resources/images/dev_ams_dry_ctr_heating_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/dev_ams_dry_ctr_n3f_cooling.png b/resources/images/dev_ams_dry_ctr_n3f_cooling.png new file mode 100644 index 0000000000..6a43425da5 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3f_cooling.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3f_dehumidifying.png b/resources/images/dev_ams_dry_ctr_n3f_dehumidifying.png new file mode 100644 index 0000000000..92064da370 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3f_dehumidifying.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3f_error.png b/resources/images/dev_ams_dry_ctr_n3f_error.png new file mode 100644 index 0000000000..e0e49df9d1 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3f_error.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3f_heating.png b/resources/images/dev_ams_dry_ctr_n3f_heating.png new file mode 100644 index 0000000000..7c48397b63 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3f_heating.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3s_cooling.png b/resources/images/dev_ams_dry_ctr_n3s_cooling.png new file mode 100644 index 0000000000..f663c57c5f Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3s_cooling.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3s_dehumidifying.png b/resources/images/dev_ams_dry_ctr_n3s_dehumidifying.png new file mode 100644 index 0000000000..5926a6cb54 Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3s_dehumidifying.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3s_error.png b/resources/images/dev_ams_dry_ctr_n3s_error.png new file mode 100644 index 0000000000..0b3df1c3fe Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3s_error.png differ diff --git a/resources/images/dev_ams_dry_ctr_n3s_heating.png b/resources/images/dev_ams_dry_ctr_n3s_heating.png new file mode 100644 index 0000000000..3613a3aefc Binary files /dev/null and b/resources/images/dev_ams_dry_ctr_n3s_heating.png differ diff --git a/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json b/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json index 2dcf633a26..1a36118d7f 100644 --- a/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json +++ b/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json @@ -42,8 +42,8 @@ "filament_z_hop_types": [ "Normal Lift" ], - "filament_scarf_seam_type": [ - "none" + "filament_dev_drying_cooling_temperature": [ + "70" ], "impact_strength_z": [ "3.4" diff --git a/resources/profiles/BBL/filament/Bambu ASA-CF @base.json b/resources/profiles/BBL/filament/Bambu ASA-CF @base.json index 9c425dba90..69624032bb 100644 --- a/resources/profiles/BBL/filament/Bambu ASA-CF @base.json +++ b/resources/profiles/BBL/filament/Bambu ASA-CF @base.json @@ -32,6 +32,9 @@ "filament_vendor": [ "Bambu Lab" ], + "filament_dev_drying_cooling_temperature": [ + "95" + ], "hot_plate_temp": [ "100" ], diff --git a/resources/profiles/BBL/filament/Bambu PAHT-CF @base.json b/resources/profiles/BBL/filament/Bambu PAHT-CF @base.json index 54122f3bbf..28b1391f84 100644 --- a/resources/profiles/BBL/filament/Bambu PAHT-CF @base.json +++ b/resources/profiles/BBL/filament/Bambu PAHT-CF @base.json @@ -33,6 +33,9 @@ "full_fan_speed_layer": [ "2" ], + "filament_dev_chamber_drying_time": [ + "16" + ], "impact_strength_z": [ "13.3" ], diff --git a/resources/profiles/BBL/filament/Bambu PET-CF @base.json b/resources/profiles/BBL/filament/Bambu PET-CF @base.json index b982cd1361..ff8b40070f 100644 --- a/resources/profiles/BBL/filament/Bambu PET-CF @base.json +++ b/resources/profiles/BBL/filament/Bambu PET-CF @base.json @@ -45,6 +45,27 @@ "filament_adhesiveness_category": [ "800" ], + "filament_dev_chamber_drying_bed_temperature": [ + "90" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "165" + ], + "filament_dev_drying_cooling_temperature": [ + "150" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "150" + ], "hot_plate_temp": [ "100" ], diff --git a/resources/profiles/BBL/filament/Bambu Support For PA PET @base.json b/resources/profiles/BBL/filament/Bambu Support For PA PET @base.json index a622bff105..d199cdb0a0 100644 --- a/resources/profiles/BBL/filament/Bambu Support For PA PET @base.json +++ b/resources/profiles/BBL/filament/Bambu Support For PA PET @base.json @@ -24,6 +24,24 @@ "filament_adhesiveness_category": [ "703" ], + "filament_dev_chamber_drying_time": [ + "16" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "75" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "85" + ], "nozzle_temperature": [ "280" ], diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA @base.json b/resources/profiles/BBL/filament/Bambu Support For PLA @base.json index 10e4983ebf..9bfdef5bf9 100644 --- a/resources/profiles/BBL/filament/Bambu Support For PLA @base.json +++ b/resources/profiles/BBL/filament/Bambu Support For PLA @base.json @@ -24,11 +24,20 @@ "filament_vendor": [ "Bambu Lab" ], - "filament_scarf_seam_type": [ - "none" - ], "filament_adhesiveness_category": [ "702" + ], + "filament_dev_ams_drying_temperature": [ + "60", + "60", + "50", + "50" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "50" + ], + "filament_dev_drying_softening_temperature": [ + "55" ], "slow_down_layer_time": [ "8" diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json index 0f1b87e83f..38443f5be3 100644 --- a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json @@ -27,14 +27,26 @@ "filament_max_volumetric_speed": [ "6" ], - "filament_scarf_seam_type": [ - "none" - ], "filament_vendor": [ "Bambu Lab" ], "filament_adhesiveness_category": [ "705" + ], + "filament_dev_chamber_drying_bed_temperature": [ + "80" + ], + "filament_dev_ams_drying_temperature": [ + "60", + "60", + "50", + "50" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "50" + ], + "filament_dev_drying_softening_temperature": [ + "55" ], "hot_plate_temp": [ "60" diff --git a/resources/profiles/BBL/filament/Bambu Support G @base.json b/resources/profiles/BBL/filament/Bambu Support G @base.json index c903f92d07..9cb01972b8 100644 --- a/resources/profiles/BBL/filament/Bambu Support G @base.json +++ b/resources/profiles/BBL/filament/Bambu Support G @base.json @@ -21,11 +21,26 @@ "filament_vendor": [ "Bambu Lab" ], - "filament_scarf_seam_type": [ - "none" - ], "filament_adhesiveness_category": [ "701" + ], + "filament_dev_chamber_drying_time": [ + "16" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "75" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "85" ], "nozzle_temperature": [ "280" diff --git a/resources/profiles/BBL/filament/Bambu Support for ABS @base.json b/resources/profiles/BBL/filament/Bambu Support for ABS @base.json index 35e13e67a0..bc8867db2a 100644 --- a/resources/profiles/BBL/filament/Bambu Support for ABS @base.json +++ b/resources/profiles/BBL/filament/Bambu Support for ABS @base.json @@ -27,11 +27,14 @@ "filament_vendor": [ "Bambu Lab" ], - "filament_scarf_seam_type": [ - "none" - ], "filament_adhesiveness_category": [ "706" + ], + "filament_dev_ams_drying_time": [ + "12", + "4", + "12", + "4" ], "nozzle_temperature_range_high": [ "270" diff --git a/resources/profiles/BBL/filament/Generic PA @base.json b/resources/profiles/BBL/filament/Generic PA @base.json index 5947f5a859..fe819487b0 100644 --- a/resources/profiles/BBL/filament/Generic PA @base.json +++ b/resources/profiles/BBL/filament/Generic PA @base.json @@ -21,6 +21,21 @@ "filament_max_volumetric_speed": [ "12" ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "65" + ], + "filament_dev_drying_softening_temperature": [ + "85" + ], "nozzle_temperature": [ "260" ], diff --git a/resources/profiles/BBL/filament/Generic PCTG @base.json b/resources/profiles/BBL/filament/Generic PCTG @base.json index d0ec60848f..e8b7c1ce0a 100644 --- a/resources/profiles/BBL/filament/Generic PCTG @base.json +++ b/resources/profiles/BBL/filament/Generic PCTG @base.json @@ -38,6 +38,34 @@ "filament_max_volumetric_speed": [ "6" ], + "filament_dev_chamber_drying_bed_temperature": [ + "80" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "75" + ], + "filament_dev_drying_cooling_temperature": [ + "55" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "65", + "55", + "55" + ], + "filament_dev_drying_softening_temperature": [ + "60" + ], "hot_plate_temp": [ "70" ], diff --git a/resources/profiles/BBL/filament/Generic PPS @base.json b/resources/profiles/BBL/filament/Generic PPS @base.json index e8da776dfc..882fea94e0 100644 --- a/resources/profiles/BBL/filament/Generic PPS @base.json +++ b/resources/profiles/BBL/filament/Generic PPS @base.json @@ -4,5 +4,26 @@ "inherits": "fdm_filament_pps", "from": "system", "filament_id": "GFT97", - "instantiation": "false" + "instantiation": "false", + "filament_dev_chamber_drying_bed_temperature": [ + "90" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "80" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "85" + ] } diff --git a/resources/profiles/BBL/filament/Polymaker/Fiberon PET-CF @base.json b/resources/profiles/BBL/filament/Polymaker/Fiberon PET-CF @base.json index 921ce55dd6..365ebb2fd7 100644 --- a/resources/profiles/BBL/filament/Polymaker/Fiberon PET-CF @base.json +++ b/resources/profiles/BBL/filament/Polymaker/Fiberon PET-CF @base.json @@ -41,6 +41,30 @@ "filament_vendor": [ "Polymaker" ], + "filament_adhesiveness_category": [ + "800" + ], + "filament_dev_chamber_drying_bed_temperature": [ + "90" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "165" + ], + "filament_dev_drying_cooling_temperature": [ + "150" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "150" + ], "hot_plate_temp": [ "70" ], @@ -83,9 +107,6 @@ "textured_plate_temp_initial_layer": [ "70" ], - "filament_adhesiveness_category": [ - "800" - ], "filament_start_gcode": [ "; filament start gcode\n{if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255\n{elsif (bed_temperature[current_extruder] >60)||(bed_temperature_initial_layer[current_extruder] >60)}M106 P3 S180\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}" ] diff --git a/resources/profiles/BBL/filament/fdm_filament_abs.json b/resources/profiles/BBL/filament/fdm_filament_abs.json index d05b005db9..4eb47ce0bd 100644 --- a/resources/profiles/BBL/filament/fdm_filament_abs.json +++ b/resources/profiles/BBL/filament/fdm_filament_abs.json @@ -43,6 +43,27 @@ "filament_adhesiveness_category": [ "200" ], + "filament_dev_ams_drying_time": [ + "12", + "8.0", + "12", + "8.0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "75" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "75" + ], + "filament_dev_drying_softening_temperature": [ + "80" + ], "hot_plate_temp": [ "90" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_asa.json b/resources/profiles/BBL/filament/fdm_filament_asa.json index de053b5e14..b5ae3ad8b5 100644 --- a/resources/profiles/BBL/filament/fdm_filament_asa.json +++ b/resources/profiles/BBL/filament/fdm_filament_asa.json @@ -43,6 +43,27 @@ "filament_adhesiveness_category": [ "200" ], + "filament_dev_ams_drying_time": [ + "12", + "8.0", + "12", + "8.0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "100" + ], + "filament_dev_drying_cooling_temperature": [ + "85" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "85" + ], "hot_plate_temp": [ "90" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_bvoh.json b/resources/profiles/BBL/filament/fdm_filament_bvoh.json index 1a766a5a71..ee15cc5e30 100644 --- a/resources/profiles/BBL/filament/fdm_filament_bvoh.json +++ b/resources/profiles/BBL/filament/fdm_filament_bvoh.json @@ -43,6 +43,34 @@ "filament_type": [ "BVOH" ], + "filament_dev_chamber_drying_bed_temperature": [ + "80" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "65" + ], + "filament_dev_drying_cooling_temperature": [ + "40" + ], + "filament_dev_ams_drying_temperature": [ + "60", + "60", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" + ], "hot_plate_temp": [ "55" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_eva.json b/resources/profiles/BBL/filament/fdm_filament_eva.json index 283606c21e..a3f9ab0011 100644 --- a/resources/profiles/BBL/filament/fdm_filament_eva.json +++ b/resources/profiles/BBL/filament/fdm_filament_eva.json @@ -7,6 +7,34 @@ "filament_type": [ "EVA" ], + "filament_dev_chamber_drying_bed_temperature": [ + "70" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "45" + ], + "filament_dev_drying_cooling_temperature": [ + "40" + ], + "filament_dev_ams_drying_temperature": [ + "45", + "45", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" + ], "supertack_plate_temp": [ "0" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_hips.json b/resources/profiles/BBL/filament/fdm_filament_hips.json index 25c0acbb6f..7016a866f6 100644 --- a/resources/profiles/BBL/filament/fdm_filament_hips.json +++ b/resources/profiles/BBL/filament/fdm_filament_hips.json @@ -4,9 +4,6 @@ "inherits": "fdm_filament_common", "from": "system", "instantiation": "false", - "additional_cooling_fan_speed": [ - "0" - ], "cool_plate_temp": [ "0" ], @@ -42,6 +39,27 @@ ], "filament_adhesiveness_category": [ "798" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "90" + ], + "filament_dev_drying_cooling_temperature": [ + "75" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "75" + ], + "filament_dev_drying_softening_temperature": [ + "80" ], "hot_plate_temp": [ "90" diff --git a/resources/profiles/BBL/filament/fdm_filament_pa.json b/resources/profiles/BBL/filament/fdm_filament_pa.json index 629ef70885..61aeed66ce 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pa.json +++ b/resources/profiles/BBL/filament/fdm_filament_pa.json @@ -43,6 +43,30 @@ "filament_adhesiveness_category": [ "400" ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_chamber_drying_time": [ + "18" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "165" + ], + "filament_dev_drying_cooling_temperature": [ + "150" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "85", + "65", + "85" + ], + "filament_dev_drying_softening_temperature": [ + "150" + ], "hot_plate_temp": [ "100" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pc.json b/resources/profiles/BBL/filament/fdm_filament_pc.json index 11dcf5ea46..a73a56b440 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pc.json +++ b/resources/profiles/BBL/filament/fdm_filament_pc.json @@ -40,6 +40,27 @@ "filament_adhesiveness_category": [ "500" ], + "filament_dev_ams_drying_time": [ + "12", + "8.0", + "12", + "8.0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "105" + ], + "filament_dev_drying_cooling_temperature": [ + "90" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "80", + "65", + "80" + ], + "filament_dev_drying_softening_temperature": [ + "90" + ], "hot_plate_temp": [ "110" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pctg.json b/resources/profiles/BBL/filament/fdm_filament_pctg.json index f735c26f48..01810a2ddc 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pctg.json +++ b/resources/profiles/BBL/filament/fdm_filament_pctg.json @@ -28,6 +28,37 @@ "filament_type": [ "PCTG" ], + "filament_dev_chamber_drying_bed_temperature": [ + "80" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_chamber_drying_time": [ + "12" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "75" + ], + "filament_dev_drying_cooling_temperature": [ + "55" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "65", + "55", + "55" + ], + "filament_dev_drying_softening_temperature": [ + "60" + ], "hot_plate_temp": [ "80" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pe.json b/resources/profiles/BBL/filament/fdm_filament_pe.json index 51549b2d8d..9235c3301e 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pe.json +++ b/resources/profiles/BBL/filament/fdm_filament_pe.json @@ -40,6 +40,31 @@ "filament_type": [ "PE" ], + "filament_dev_chamber_drying_bed_temperature": [ + "70" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_drying_cooling_temperature": [ + "50" + ], + "filament_dev_ams_drying_temperature": [ + "45", + "45", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" + ], "hot_plate_temp": [ "55" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pet.json b/resources/profiles/BBL/filament/fdm_filament_pet.json index 8fde5f2faa..23a8f73fe4 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pet.json +++ b/resources/profiles/BBL/filament/fdm_filament_pet.json @@ -31,6 +31,34 @@ "filament_adhesiveness_category": [ "300" ], + "filament_dev_chamber_drying_bed_temperature": [ + "80" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "75" + ], + "filament_dev_drying_cooling_temperature": [ + "55" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "65", + "55", + "55" + ], + "filament_dev_drying_softening_temperature": [ + "60" + ], "hot_plate_temp": [ "80" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pha.json b/resources/profiles/BBL/filament/fdm_filament_pha.json index b9ac18587d..2bd348e2de 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pha.json +++ b/resources/profiles/BBL/filament/fdm_filament_pha.json @@ -40,6 +40,31 @@ "filament_type": [ "PHA" ], + "filament_dev_chamber_drying_bed_temperature": [ + "70" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_drying_cooling_temperature": [ + "45" + ], + "filament_dev_ams_drying_temperature": [ + "45", + "45", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" + ], "hot_plate_temp": [ "55" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pla.json b/resources/profiles/BBL/filament/fdm_filament_pla.json index 3c6713b86d..f1e1c9a244 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pla.json +++ b/resources/profiles/BBL/filament/fdm_filament_pla.json @@ -37,14 +37,39 @@ "filament_max_volumetric_speed": [ "12" ], - "filament_scarf_seam_type": [ - "none" - ], "filament_scarf_gap": [ "15%" ], "filament_adhesiveness_category": [ "100" + ], + "filament_dev_chamber_drying_bed_temperature": [ + "70" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "45" + ], + "filament_dev_drying_cooling_temperature": [ + "45" + ], + "filament_dev_ams_drying_temperature": [ + "45", + "45", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" ], "hot_plate_temp": [ "55" diff --git a/resources/profiles/BBL/filament/fdm_filament_pp.json b/resources/profiles/BBL/filament/fdm_filament_pp.json index dd390e0a82..2d8c3772ba 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pp.json +++ b/resources/profiles/BBL/filament/fdm_filament_pp.json @@ -43,6 +43,34 @@ "filament_adhesiveness_category": [ "902" ], + "filament_dev_chamber_drying_bed_temperature": [ + "70" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [ + "1", + "0" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "60" + ], + "filament_dev_drying_cooling_temperature": [ + "50" + ], + "filament_dev_ams_drying_temperature": [ + "60", + "60", + "50", + "50" + ], + "filament_dev_drying_softening_temperature": [ + "55" + ], "hot_plate_temp": [ "55" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_ppa.json b/resources/profiles/BBL/filament/fdm_filament_ppa.json index bb06a73c76..3bba318bc5 100644 --- a/resources/profiles/BBL/filament/fdm_filament_ppa.json +++ b/resources/profiles/BBL/filament/fdm_filament_ppa.json @@ -46,6 +46,34 @@ "filament_vendor": [ "Bambu Lab" ], + "filament_dev_chamber_drying_bed_temperature": [ + "100" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [], + "filament_dev_chamber_drying_time": [ + "18" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "165" + ], + "filament_dev_drying_cooling_temperature": [ + "150" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "85", + "65", + "85" + ], + "filament_dev_drying_softening_temperature": [ + "150" + ], "hot_plate_temp": [ "100" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pps.json b/resources/profiles/BBL/filament/fdm_filament_pps.json index a19a5aca2b..d1c6a6aed2 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pps.json +++ b/resources/profiles/BBL/filament/fdm_filament_pps.json @@ -43,6 +43,31 @@ "filament_adhesiveness_category": [ "801" ], + "filament_dev_chamber_drying_bed_temperature": [ + "100" + ], + "filament_dev_ams_drying_time": [ + "12", + "12", + "12", + "12" + ], + "filament_dev_ams_drying_ams_limitations": [], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "165" + ], + "filament_dev_drying_cooling_temperature": [ + "150" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "85", + "65", + "85" + ], + "filament_dev_drying_softening_temperature": [ + "150" + ], "hot_plate_temp": [ "110" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_pva.json b/resources/profiles/BBL/filament/fdm_filament_pva.json index 98ca7e63b9..55d83517ce 100644 --- a/resources/profiles/BBL/filament/fdm_filament_pva.json +++ b/resources/profiles/BBL/filament/fdm_filament_pva.json @@ -49,6 +49,27 @@ "filament_adhesiveness_category": [ "704" ], + "filament_dev_ams_drying_time": [ + "12", + "18", + "12", + "18" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "75" + ], + "filament_dev_drying_cooling_temperature": [ + "40" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "85", + "65", + "70" + ], + "filament_dev_drying_softening_temperature": [ + "75" + ], "hot_plate_temp": [ "55" ], diff --git a/resources/profiles/BBL/filament/fdm_filament_tpu.json b/resources/profiles/BBL/filament/fdm_filament_tpu.json index f6ab1d3edf..d2ef5db84e 100644 --- a/resources/profiles/BBL/filament/fdm_filament_tpu.json +++ b/resources/profiles/BBL/filament/fdm_filament_tpu.json @@ -46,6 +46,30 @@ "filament_adhesiveness_category": [ "600" ], + "filament_dev_ams_drying_time": [ + "12", + "18", + "12", + "18" + ], + "filament_dev_chamber_drying_time": [ + "16" + ], + "filament_dev_ams_drying_heat_distortion_temperature": [ + "45" + ], + "filament_dev_drying_cooling_temperature": [ + "40" + ], + "filament_dev_ams_drying_temperature": [ + "65", + "75", + "45", + "45" + ], + "filament_dev_drying_softening_temperature": [ + "50" + ], "hot_plate_temp": [ "35" ], diff --git a/resources/profiles/Creality/process/0.40mm Standard @Creality K1 SE 0.8 nozzle.json b/resources/profiles/Creality/process/0.40mm Standard @Creality K1 SE 0.8 nozzle.json index 46a80e8498..88bc46add2 100644 --- a/resources/profiles/Creality/process/0.40mm Standard @Creality K1 SE 0.8 nozzle.json +++ b/resources/profiles/Creality/process/0.40mm Standard @Creality K1 SE 0.8 nozzle.json @@ -3,7 +3,7 @@ "name": "0.40mm Standard @Creality K1 SE 0.8 nozzle", "inherits": "fdm_process_creality_common", "from": "system", - "setting_id": "caYmVSsFmtESzLNF", + "setting_id": "5mfhrVJYlK4lWDUs", "instantiation": "true", "reduce_crossing_wall": "0", "max_travel_detour_distance": "0", diff --git a/resources/profiles/Qidi.json b/resources/profiles/Qidi.json index d3d083c074..9e7a290f96 100644 --- a/resources/profiles/Qidi.json +++ b/resources/profiles/Qidi.json @@ -1,6 +1,6 @@ { "name": "Qidi", - "version": "02.04.00.07", + "version": "02.04.00.08", "force_update": "0", "description": "Qidi configurations", "machine_model_list": [ diff --git a/resources/profiles/Qidi/machine/fdm_qidi_x3_common.json b/resources/profiles/Qidi/machine/fdm_qidi_x3_common.json index ada1be5d24..9796856379 100644 --- a/resources/profiles/Qidi/machine/fdm_qidi_x3_common.json +++ b/resources/profiles/Qidi/machine/fdm_qidi_x3_common.json @@ -10,36 +10,6 @@ "machine_pause_gcode": "M0", "support_chamber_temp_control": "1", "wipe_tower_type": "type1", - "filament_dev_ams_drying_ams_limitations": [ - "1" - ], - "filament_dev_ams_drying_temperature": [ - "40.0", - "40.0", - "40.0", - "40.0" - ], - "filament_dev_ams_drying_time": [ - "8.0", - "8.0", - "8.0", - "8.0" - ], - "filament_dev_drying_softening_temperature": [ - "40.0" - ], - "filament_dev_ams_drying_heat_distortion_temperature": [ - "45.0" - ], - "filament_dev_drying_cooling_temperature": [ - "35.0" - ], - "filament_dev_chamber_drying_bed_temperature": [ - "90.0" - ], - "filament_dev_chamber_drying_time": [ - "12.0" - ], "retraction_length": [ "1" ], diff --git a/resources/profiles/Ratrig/process/0.30mm Big @RatRig V-Core 4 0.8.json b/resources/profiles/Ratrig/process/0.30mm Big @RatRig V-Core 4 0.8.json index 2eee1dc710..9069167cde 100644 --- a/resources/profiles/Ratrig/process/0.30mm Big @RatRig V-Core 4 0.8.json +++ b/resources/profiles/Ratrig/process/0.30mm Big @RatRig V-Core 4 0.8.json @@ -3,7 +3,7 @@ "name": "0.30mm Big @RatRig V-Core 4 0.8", "inherits": "fdm_process_ratrig_common", "from": "system", - "setting_id": "7FOG8fkUbrLknvuz", + "setting_id": "2tM04aSQ8NcTsmQo", "instantiation": "true", "layer_height": "0.3", "inital_layer_height": "0.35", diff --git a/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ2 (0.25 Nozzle).json b/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ2 (0.25 Nozzle).json index 7bbb5a15f3..1791058107 100644 --- a/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ2 (0.25 Nozzle).json +++ b/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ2 (0.25 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "PNxZXC0peOlGlKwG", "instantiation": "true", "compatible_printers": [ "iQ TiQ2 0.25 Nozzle" diff --git a/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ8 (0.25 Nozzle).json b/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ8 (0.25 Nozzle).json index 0cfcdae16e..b643441055 100644 --- a/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ8 (0.25 Nozzle).json +++ b/resources/profiles/iQ/process/0.15mm Standard @iQ TiQ8 (0.25 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.15mm Standard @iQ TiQ8 (0.25 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "K0Ybw0qUqEIL05PY", "instantiation": "true", "compatible_printers": [ "iQ TiQ8 0.25 Nozzle" diff --git a/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ2 (0.6 Nozzle).json b/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ2 (0.6 Nozzle).json index e44c962ebf..6a71a4ba84 100644 --- a/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ2 (0.6 Nozzle).json +++ b/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ2 (0.6 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "IGN3ZUR73J0veLrE", "instantiation": "true", "compatible_printers": [ "iQ TiQ2 0.6 Nozzle" diff --git a/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ8 (0.6 Nozzle).json b/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ8 (0.6 Nozzle).json index e71c6e80ce..0c8a567846 100644 --- a/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ8 (0.6 Nozzle).json +++ b/resources/profiles/iQ/process/0.30mm Standard @iQ TiQ8 (0.6 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.30mm Standard @iQ TiQ8 (0.6 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "QEODQzrO9UZEgrtB", "instantiation": "true", "compatible_printers": [ "iQ TiQ8 0.6 Nozzle" diff --git a/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ2 (0.8 Nozzle).json b/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ2 (0.8 Nozzle).json index b487541526..5dba6b07c2 100644 --- a/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ2 (0.8 Nozzle).json +++ b/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ2 (0.8 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "ZoiYkGXOTBh39HTB", "instantiation": "true", "compatible_printers": [ "iQ TiQ2 0.8 Nozzle" diff --git a/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ8 (0.8 Nozzle).json b/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ8 (0.8 Nozzle).json index 7c5bd973e7..5cd189771f 100644 --- a/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ8 (0.8 Nozzle).json +++ b/resources/profiles/iQ/process/0.40mm Standard @iQ TiQ8 (0.8 Nozzle).json @@ -3,6 +3,7 @@ "name": "0.40mm Standard @iQ TiQ8 (0.8 Nozzle)", "inherits": "fdm_process_tiq_common", "from": "system", + "setting_id": "C7TZWdCzQroB8pKL", "instantiation": "true", "compatible_printers": [ "iQ TiQ8 0.8 Nozzle" diff --git a/resources/shaders/110/imgui.fs b/resources/shaders/110/imgui.fs index 4b0e27ce9d..3af9c82653 100644 --- a/resources/shaders/110/imgui.fs +++ b/resources/shaders/110/imgui.fs @@ -1,11 +1,11 @@ #version 110 -uniform sampler2D Texture; +uniform sampler2D s_texture; varying vec2 Frag_UV; varying vec4 Frag_Color; void main() { - gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st); + gl_FragColor = Frag_Color * texture2D(s_texture, Frag_UV.st); } \ No newline at end of file diff --git a/resources/shaders/140/imgui.fs b/resources/shaders/140/imgui.fs index 1666c814eb..daebb189de 100644 --- a/resources/shaders/140/imgui.fs +++ b/resources/shaders/140/imgui.fs @@ -1,6 +1,6 @@ #version 140 -uniform sampler2D Texture; +uniform sampler2D s_texture; in vec2 Frag_UV; in vec4 Frag_Color; @@ -9,5 +9,5 @@ out vec4 out_color; void main() { - out_color = Frag_Color * texture(Texture, Frag_UV.st); + out_color = Frag_Color * texture(s_texture, Frag_UV.st); } \ No newline at end of file diff --git a/src/dev-utils/OrcaSlicer_profile_validator.cpp b/src/dev-utils/OrcaSlicer_profile_validator.cpp index 051758fc19..49a38cd1c4 100644 --- a/src/dev-utils/OrcaSlicer_profile_validator.cpp +++ b/src/dev-utils/OrcaSlicer_profile_validator.cpp @@ -212,14 +212,26 @@ void install_slice_context_log_sink() // -v). Unlike the static reference/placeholder checks, this expands every custom *_gcode - including // change_filament_gcode at the one filament change - against the printer's fully-resolved config, so // undefined-placeholder / invalid-flow bugs surface here. Reports every offending printer and returns 1 -// if any failed, 0 otherwise. The sweep is SEQUENTIAL by necessity: Print::process() keeps -// process-global state, so slicing printers concurrently in one process races even with per-slice -// Model+Print. Load in validation mode so the vendors are read straight from the -p profiles dir -// (no data_dir/system tree) and -v scoping is honoured for free. -int slice_all_printers(const std::string &vendor) +// if any failed, 0 otherwise. When outdir is non-empty, each printer's g-code is also written there as +// "__.gcode" for manual inspection. The sweep is SEQUENTIAL by necessity: +// Print::process() keeps process-global state, so slicing printers concurrently in one process races +// even with per-slice Model+Print. Load in validation mode so the vendors are read straight from the -p +// profiles dir (no data_dir/system tree) and -v scoping is honoured for free. +int slice_all_printers(const std::string &vendor, const std::string &outdir) { install_slice_context_log_sink(); + if (!outdir.empty()) { + boost::system::error_code ec; + fs::create_directories(outdir, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << "Could not create output directory \"" << outdir << "\": " << ec.message(); + std::cout << "Validation failed" << std::endl; + return 1; + } + std::cout << "Saving sliced g-code to " << outdir << std::endl; + } + PresetBundle bundle; bundle.set_is_validation_mode(true); bundle.set_vendor_to_validate(vendor); // empty == all vendors @@ -323,6 +335,10 @@ int slice_all_printers(const std::string &vendor) try { const std::string out = slice_two_color_cube_and_export(cfg, bundle.is_bbl_vendor()); + if (!outdir.empty() && !out.empty()) { + const fs::path f = fs::path(outdir) / (sanitize_filename(vendor_name) + "__" + sanitize_filename(printer) + ".gcode"); + save_string_file(f, out); + } if (out.empty() || out.find("G1") == std::string::npos) { BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" produced no g-code"; ++failures; @@ -364,6 +380,7 @@ int main(int argc, char* argv[]) ("vendor,v", po::value()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified") ("generate_presets,g", po::value()->default_value(false), "Generate user presets for mock test") ("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.") + ("outdir,o", po::value()->default_value(""), "With -s, also save each printer's g-code to this folder (as __.gcode) for manual inspection. Optional.") ("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.") ("log_level,l", po::value()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs."); // clang-format on @@ -389,6 +406,7 @@ int main(int argc, char* argv[]) int log_level = vm["log_level"].as(); bool generate_user_preset = vm["generate_presets"].as(); bool slice_mode = vm["slice"].as(); + std::string slice_outdir = vm["outdir"].as(); bool check_filament_subtypes = vm["check_filament_subtypes"].as(); // check if path is valid, and return error if not @@ -419,7 +437,7 @@ int main(int argc, char* argv[]) // Slice mode expands every printer's custom g-code by actually slicing (see slice_all_printers). // A distinct opt-in mode so the default static checks stay fast for every profile PR. if (slice_mode) - return slice_all_printers(vendor); + return slice_all_printers(vendor, slice_outdir); auto preset_bundle = new PresetBundle(); // preset_bundle->setup_directories(); diff --git a/src/libslic3r/FilamentGroup.cpp b/src/libslic3r/FilamentGroup.cpp index f5b5564538..97da94652c 100644 --- a/src/libslic3r/FilamentGroup.cpp +++ b/src/libslic3r/FilamentGroup.cpp @@ -352,8 +352,7 @@ namespace Slic3r int k, const std::vector& used_filaments, const std::unordered_map>& unplaceable_limits, - int* cost, - int timeout_ms) + int* cost) { auto distance_evaluator = std::make_shared(ctx.model_info.flush_matrix, used_filaments, ctx.model_info.layer_filaments); KMediods PAM(k, (int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id); @@ -369,7 +368,7 @@ namespace Slic3r } PAM.set_cluster_group_size(cluster_size_limit); - PAM.do_clustering(ctx, timeout_ms, 30); + PAM.do_clustering(ctx, m_clustering_budget); m_memoryed_heap = PAM.get_memoryed_groups(); @@ -793,7 +792,7 @@ namespace Slic3r 2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid 2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step */ - void KMediods::do_clustering(const FilamentGroupContext &context, int timeout_ms, int retry) + void KMediods::do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget) { FlushTimeMachine T; T.time_machine_start(); @@ -817,7 +816,11 @@ namespace Slic3r double best_cluster_cost = std::numeric_limits::max(); int retry_count = 0; - while (retry_count < retry && T.time_machine_end() < timeout_ms) { + // Run at least one restart; otherwise every filament would stay in the default group. + const int retry = std::max(1, budget.max_restarts); + auto within_budget = [&]() { return budget.timeout_ms <= 0 || T.time_machine_end() < budget.timeout_ms; }; + + while (retry_count < retry && within_budget()) { std::vector curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count); std::vector curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size); double curr_cluster_cost = evaluate_labels(curr_cluster_labels); @@ -826,7 +829,7 @@ namespace Slic3r update_memoryed_groups(g, memory_threshold, memoryed_groups); bool mediods_changed = true; - while (mediods_changed && T.time_machine_end() < timeout_ms) { + while (mediods_changed && within_budget()) { mediods_changed = false; double best_swap_cost = curr_cluster_cost; int best_swap_cluster = -1; @@ -889,7 +892,7 @@ namespace Slic3r if (estimated < ENUM_THRESHOLD) result = calc_group_by_enum(k, used_filaments, unplaceable_limits, cost); else - result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost, 3000); + result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost); change_memoryed_heaps_to_arrays(m_memoryed_heap, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups); diff --git a/src/libslic3r/FilamentGroup.hpp b/src/libslic3r/FilamentGroup.hpp index 828b673b38..9172685bee 100644 --- a/src/libslic3r/FilamentGroup.hpp +++ b/src/libslic3r/FilamentGroup.hpp @@ -142,6 +142,16 @@ namespace Slic3r FilamentGroupContext::SpeedInfo m_speed_info; }; + // Search budget for the k-medoids clustering, an anytime search. Each restart is seeded from its + // own index, so what it returns depends on how many restarts complete before the clock expires, + // and therefore on the speed of the machine. A timeout_ms <= 0 removes the clock and bounds the + // search by max_restarts alone. + struct ClusteringBudget + { + int timeout_ms = 3000; + int max_restarts = 30; + }; + class FilamentGroup { using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; @@ -149,6 +159,8 @@ namespace Slic3r public: explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {} public: + void set_clustering_budget(const ClusteringBudget& budget) { m_clustering_budget = budget; } + std::vector calc_filament_group(int * cost = nullptr); std::vector> get_memoryed_groups()const { return m_memoryed_groups; } @@ -162,7 +174,7 @@ namespace Slic3r std::vector calc_group_by_enum(int k, const std::vector& used_filaments, const std::unordered_map>& unplaceable_limits, int* cost = nullptr); std::vector calc_group_by_kmedoids(int k, const std::vector& used_filaments, - const std::unordered_map>& unplaceable_limits, int* cost = nullptr, int timeout_ms = 500); + const std::unordered_map>& unplaceable_limits, int* cost = nullptr); std::map rebuild_unprintables(const std::vector& used_filaments, const std::map& extruder_unprintables); std::unordered_map> rebuild_nozzle_unprintables(const std::vector& used_filaments, const std::unordered_map>& extruder_unprintables, const std::vector& filament_volume_map); @@ -175,6 +187,7 @@ namespace Slic3r FilamentGroupContext ctx; MemoryedGroupHeap m_memoryed_heap; std::vector> m_memoryed_groups; + ClusteringBudget m_clustering_budget; public: std::optional&)>> get_custom_seq; }; @@ -220,7 +233,7 @@ namespace Slic3r void set_memory_threshold(double threshold) { memory_threshold = threshold; } MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; } - void do_clustering(const FilamentGroupContext& context, int timeout_ms = 100, int retry = 10); + void do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget); std::vector get_cluster_labels()const { return m_cluster_labels; } protected: diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 29eb6120ed..2606529099 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -162,11 +162,14 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para out.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = this->no_sort(); + // ORCA: special flag for flow rate calibration + auto is_flow_calib = params.extrusion_role == erTopSolidInfill && this->print_object_config->has("calib_flowrate_topinfill_special_order") && + this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool(); // Orca: a forced surface fill order must survive the G-code path planner, which would - // otherwise re-chain and possibly reverse the paths. This also covers the flow rate - // calibration, which forces an outward fill order on its top surfaces. + // otherwise re-chain and possibly reverse the paths. The same applies to the flow rate + // calibration's special toolpath order. const bool keep_fill_order = params.fill_order != SurfaceFillOrder::Default; - if (keep_fill_order) { + if (is_flow_calib || keep_fill_order) { eec->no_sort = true; } size_t idx = eec->entities.size(); @@ -181,7 +184,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para params.extrusion_role, flow_mm3_per_mm, float(flow_width), params.flow.height()); } - if (!params.can_reverse || keep_fill_order) { + if (!params.can_reverse || is_flow_calib || keep_fill_order) { for (size_t i = idx; i < eec->entities.size(); i++) eec->entities[i]->set_reverse(); } diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index edfbaef7d6..7ce8e4abb3 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -134,7 +134,34 @@ void FillPlanePath::_fill_surface_single( if (!polylines.empty()) { Polylines chained; if (params.dont_connect() || params.density > 0.5) { - if (params.fill_order != SurfaceFillOrder::Default) { + // ORCA: special flag for flow rate calibration. The chords chained ahead of the + // inside-out center spiral collide with it in opposing directions, raising a + // tactile lip that the calibration reads. Only applies while the fill order is + // Default, so it can be overridden from the calibration objects. + auto is_flow_calib = params.fill_order == SurfaceFillOrder::Default && + params.extrusion_role == erTopSolidInfill && + this->print_object_config->has("calib_flowrate_topinfill_special_order") && + this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() && + dynamic_cast(this); + if (is_flow_calib) { + // We want the spiral part to be printed inside-out + // Find the center spiral line first, by looking for the longest one + auto it = std::max_element(polylines.begin(), polylines.end(), + [](const Polyline& a, const Polyline& b) { return a.length() < b.length(); }); + Polyline center_spiral = std::move(*it); + + // Ensure the spiral is printed from inside to out + if (center_spiral.first_point().squaredNorm() > center_spiral.last_point().squaredNorm()) { + center_spiral.reverse(); + } + + // Chain the other polylines + polylines.erase(it); + chained = chain_polylines(std::move(polylines), nullptr); + + // Then add the center spiral back + chained.push_back(std::move(center_spiral)); + } else if (params.fill_order != SurfaceFillOrder::Default) { // Orca: print the fragments in the order they appear along the generated // path, which runs from the center outwards. The Euclidean distance from // the center cannot be used for this: along the Octagram Spiral the radius diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 500ef42738..16ae49bdc1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -470,6 +470,8 @@ void Preset::normalize(DynamicPrintConfig &config) continue; if (filament_options_with_variant.find(key) != filament_options_with_variant.end()) continue; + if (filament_dev_options.find(key) != filament_dev_options.end()) + continue; auto *opt = config.option(key, false); /*assert(opt != nullptr); assert(opt->is_vector());*/ @@ -1308,6 +1310,7 @@ static std::vector s_Preset_print_options{ "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", + "calib_flowrate_topinfill_special_order", // Z Anti-Aliasing (ZAA) "zaa_enabled", "zaa_minimize_perimeter_height", @@ -1371,7 +1374,11 @@ static std::vector s_Preset_filament_options {/*"filament_colour", "filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc", "filament_preheat_temperature_delta", "filament_retract_length_nc", "filament_change_length_nc", "filament_prime_volume_nc", - "long_retractions_when_ec", "retraction_distances_when_ec" + "long_retractions_when_ec", "retraction_distances_when_ec", + //ams chamber + "filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature", + "filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time", + "filament_dev_drying_softening_temperature", "filament_dev_drying_cooling_temperature" }; static std::vector s_Preset_machine_limits_options { @@ -3812,6 +3819,26 @@ void PresetCollection::set_custom_preset_alias(Preset &preset) set_printer_hold_alias(preset.alias, preset); } +std::string PresetCollection::get_preset_alias(Preset &preset, bool force) +{ + if (!preset.alias.empty()) + return preset.alias; + else + set_custom_preset_alias(preset); + + if (!preset.alias.empty() || !force) + return preset.alias; + + std::string alias_name; + std::string preset_name = preset.name; + size_t end_pos = preset_name.find_first_of("@"); + if (end_pos != std::string::npos) { + alias_name = preset_name.substr(0, end_pos); + boost::trim_right(alias_name); + } + return alias_name; +} + void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove) { auto compatible_printers = dynamic_cast(preset.config.option("compatible_printers")); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 75150f85c9..8863f04672 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -805,6 +805,9 @@ public: std::string path_from_name(const std::string &new_name, bool detach = false) const; std::string path_for_preset(const Preset & preset) const; + // Get the alias of a preset, setting it if it's empty + std::string get_preset_alias(Preset &preset, bool force = false); + size_t num_default_presets() { return m_num_default_presets; } protected: diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d8ed7ace58..7a71c444f9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4647,6 +4647,11 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(2)); + // ORCA: special flag for flow rate calibration + def = this->add("calib_flowrate_topinfill_special_order", coBool); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("ironing_type", coEnum); def->label = L("Ironing type"); def->category = L("Quality"); @@ -7797,6 +7802,30 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionPercent(85)); + def = this->add("filament_dev_ams_drying_ams_limitations", coStrings); + def->set_default_value(new ConfigOptionStrings{""}); + + def = this->add("filament_dev_ams_drying_temperature", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_ams_drying_time", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_ams_drying_heat_distortion_temperature", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_chamber_drying_bed_temperature", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_chamber_drying_time", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_drying_softening_temperature", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("filament_dev_drying_cooling_temperature", coFloats); + def->set_default_value(new ConfigOptionFloats{0}); + // Declare retract values for filament profile, overriding the printer's extruder profile. for (auto& opt_key : filament_extruder_override_keys) { const std::string filament_prefix = "filament_"; @@ -8992,7 +9021,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time", "smooth_coefficient", "overhang_totally_speed", "silent_mode", "overhang_speed_classic", "filament_prime_volume", - "calib_flowrate_topinfill_special_order", "anisotropic_surfaces", // superseded by top_surface_fill_order / bottom_surface_fill_order }; @@ -9232,6 +9260,17 @@ std::set printer_options_with_variant_2 = { std::set empty_options; +std::set filament_dev_options = { + "filament_dev_ams_drying_ams_limitations", + "filament_dev_ams_drying_temperature", + "filament_dev_ams_drying_time", + "filament_dev_ams_drying_heat_distortion_temperature", + "filament_dev_chamber_drying_bed_temperature", + "filament_dev_chamber_drying_time", + "filament_dev_drying_softening_temperature", + "filament_dev_drying_cooling_temperature" +}; + DynamicPrintConfig DynamicPrintConfig::full_print_config() { return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2cad1da3d2..7b611dec9f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -840,6 +840,8 @@ extern std::set printer_options_with_variant_1; extern std::set printer_options_with_variant_2; extern std::set empty_options; +extern std::set filament_dev_options; + extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride = 1); extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices); @@ -1219,6 +1221,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, interlocking_beam_layer_count)) ((ConfigOptionInt, interlocking_depth)) ((ConfigOptionInt, interlocking_boundary_avoidance)) + + // Orca: internal use only + ((ConfigOptionBool, calib_flowrate_topinfill_special_order)) // ORCA: special flag for flow rate calibration ) // This object is mapped to Perl as Slic3r::Config::PrintRegion. @@ -1678,6 +1683,16 @@ PRINT_CONFIG_CLASS_DEFINE( // Printer flag: whether the printer offers the fast-purge mode selector. // Default false; no shipping profile sets it, so the fast-purge UI stays hidden. ((ConfigOptionBool, support_fast_purge_mode)) + + //ams chamber + ((ConfigOptionStrings, filament_dev_ams_drying_ams_limitations)) + ((ConfigOptionFloats, filament_dev_ams_drying_temperature)) + ((ConfigOptionFloats, filament_dev_ams_drying_time)) + ((ConfigOptionFloats, filament_dev_ams_drying_heat_distortion_temperature)) + ((ConfigOptionFloats, filament_dev_chamber_drying_bed_temperature)) + ((ConfigOptionFloats, filament_dev_chamber_drying_time)) + ((ConfigOptionFloats, filament_dev_drying_softening_temperature)) + ((ConfigOptionFloats, filament_dev_drying_cooling_temperature)) ) // This object is mapped to Perl as Slic3r::Config::Print. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 78ceb762a1..087825fffa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -28,6 +28,8 @@ set(SLIC3R_GUI_SOURCES GUI/AMSMaterialsSetting.hpp GUI/AMSSetting.cpp GUI/AMSSetting.hpp + GUI/AMSDryControl.cpp + GUI/AMSDryControl.hpp GUI/AmsWidgets.cpp GUI/AmsWidgets.hpp GUI/Auxiliary.cpp diff --git a/src/slic3r/GUI/AMSDryControl.cpp b/src/slic3r/GUI/AMSDryControl.cpp new file mode 100644 index 0000000000..9efe7faa87 --- /dev/null +++ b/src/slic3r/GUI/AMSDryControl.cpp @@ -0,0 +1,1864 @@ +#include "AMSDryControl.hpp" +#include "slic3r/GUI/DeviceCore/DevFilaSystem.h" +#include "GUI_App.hpp" +#include "I18N.hpp" + +#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h" + +#include "slic3r/GUI/DeviceCore/DevManager.h" + +#include "slic3r/GUI/MsgDialog.hpp" + +#include "slic3r/GUI/Widgets/AnimaController.hpp" +#include "slic3r/GUI/Widgets/Label.hpp" +#include "slic3r/GUI/Widgets/ComboBox.hpp" +#include "slic3r/GUI/Widgets/ProgressBar.hpp" +#include "slic3r/GUI/DeviceCore/DevUtilBackend.h" + +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Preset.hpp" + +#include + +namespace Slic3r { namespace GUI { + +const int AMS_DRY_STATUS_IMAGE_SIZE = 96; + +static std::string get_humidity_level_img_path(int humidity_percent) +{ + const int num_levels = 5; + int hum_level; + + if (humidity_percent <= 100 / num_levels) { + hum_level = 5; + } else if (humidity_percent <= 100 / num_levels * 2) { + hum_level = 4; + } else if (humidity_percent <= 100 / num_levels * 3) { + hum_level = 3; + } else if (humidity_percent <= 100 / num_levels * 4) { + hum_level = 2; + } else { + hum_level = 1; + } + + if (wxGetApp().dark_mode()) { + return "hum_level" + std::to_string(hum_level) + "_no_num_dark"; + } else { + return "hum_level" + std::to_string(hum_level) + "_no_num_light"; + } + +} + +static std::string get_dry_status_img_path(DevAmsType type, DevAms::DryStatus status, DevAms::DrySubStatus sub_status) +{ + std::string img_name = "dev_ams_dry_ctr_"; + switch (type) { + case DevAmsType::N3S: + img_name += "n3s"; + break; + case DevAmsType::N3F: + img_name += "n3f"; + break; + default: + return ""; + } + + img_name += "_"; + switch (status) { + case DevAms::DryStatus::Error: + img_name += "error"; + return img_name; + } + + switch (sub_status) { + case DevAms::DrySubStatus::Heating: + img_name += "heating"; + return img_name; + case DevAms::DrySubStatus::Dehumidify: + img_name += "dehumidifying"; + return img_name; + } + + return ""; +} + +FilamentItemPanel::FilamentItemPanel(wxWindow* parent, const wxString& text, const std::string& icon_name, wxWindowID id) + : wxPanel(parent, id) + , m_icon_name(icon_name) +{ + SetBackgroundColour(wxColour("#F0F0F1")); // Light gray background + SetMinSize(wxSize(FromDIP(64), FromDIP(106))); // Width: 64, Height: 106 + SetSize(wxSize(FromDIP(64), FromDIP(106))); // Fixed size + + // Create sizer for vertical layout + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + // Top section with text - moved to be closer to center + wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); + top_sizer->AddStretchSpacer(5); + + m_text_label = new Label(this, text); + m_text_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour(*wxBLACK))); + m_text_label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F0F0F1"))); + m_text_label->SetFont(Label::Body_12); + m_text_label->Wrap(FromDIP(40)); + top_sizer->Add(m_text_label, 0, wxALIGN_CENTER_HORIZONTAL); + + top_sizer->AddStretchSpacer(1); // Increased bottom spacer to push text down + sizer->Add(top_sizer, 1, wxEXPAND); + + // Bottom section with icon - vertically centered in bottom half + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxVERTICAL); + bottom_sizer->AddStretchSpacer(1); + + m_icon_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); + m_icon_bitmap->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F0F0F1"))); + m_icon_bitmap->SetMinSize(wxSize(FromDIP(24), FromDIP(24))); + m_icon_bitmap->SetMaxSize(wxSize(FromDIP(24), FromDIP(24))); + bottom_sizer->Add(m_icon_bitmap, 0, wxALIGN_CENTER_HORIZONTAL); + + bottom_sizer->AddStretchSpacer(1); + sizer->Add(bottom_sizer, 1, wxEXPAND); + + SetSizer(sizer); + + // Bind events + Bind(wxEVT_PAINT, &FilamentItemPanel::OnPaint, this); + Bind(wxEVT_SIZE, &FilamentItemPanel::OnSize, this); + + SetIcon(icon_name); + wxGetApp().UpdateDarkUI(this); +} + +void FilamentItemPanel::SetText(const wxString& text) +{ + m_text_label->SetLabel(text); + Layout(); +} + +void FilamentItemPanel::SetIcon(const std::string& icon_name) +{ + m_icon_name = icon_name; + if (!icon_name.empty()) { + try { + m_icon = ScalableBitmap(this, icon_name, 20); + m_icon.msw_rescale(); + m_icon_bitmap->SetBitmap(m_icon.bmp()); + } catch (Exception&) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": failed to load filament icon"; + m_icon_bitmap->SetBitmap(wxNullBitmap); + } + } else { + m_icon_bitmap->SetBitmap(wxNullBitmap); + } + m_icon_bitmap->Refresh(); +} + +void FilamentItemPanel::msw_rescale() +{ + if (m_icon_bitmap && !m_icon_name.empty()) { + m_icon.msw_rescale(); + m_icon_bitmap->SetBitmap(m_icon.bmp()); + m_icon_bitmap->Refresh(); + } + Layout(); +} + +void FilamentItemPanel::OnPaint(wxPaintEvent& event) +{ + wxPaintDC dc(this); + wxSize size = GetSize(); + + // bool is_dark_mode = wxGetApp().dark_mode(); + wxColour backgroundColor = StateColor::darkModeColorFor(wxColour("#F0F0F1")); + wxColour borderColor = StateColor::darkModeColorFor(wxColour("#DBDBDB")); + + // Draw white background rectangle with rounded corners inside the thick vertical lines + dc.SetBrush(wxBrush(backgroundColor)); + dc.SetPen(wxPen(backgroundColor)); + dc.DrawRectangle(FromDIP(6), FromDIP(12), size.GetWidth() - FromDIP(12), size.GetHeight() - 2 * FromDIP(12)); + + // Draw much thicker vertical rounded rectangles on left and right (3 times thicker) + dc.SetBrush(wxBrush(wxColour(borderColor))); + dc.SetPen(wxPen(wxColour(borderColor))); + dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), FromDIP(6), size.GetHeight(), FromDIP(6)); // Left rounded rectangle + dc.DrawRoundedRectangle(size.GetWidth() - FromDIP(6), FromDIP(0), FromDIP(6), size.GetHeight(), FromDIP(6)); // Right rounded rectangle + + // Draw thin horizontal rounded rectangles on top and bottom (moved closer to center by half the distance) + dc.SetBrush(wxBrush(wxColour(borderColor))); + dc.SetPen(wxPen(wxColour(borderColor))); + dc.DrawRoundedRectangle(FromDIP(6), FromDIP(12), size.GetWidth() - FromDIP(12), FromDIP(3), FromDIP(1)); // Top rounded rectangle + dc.DrawRoundedRectangle(FromDIP(6), size.GetHeight() - FromDIP(13), size.GetWidth() - FromDIP(12), FromDIP(3), FromDIP(1)); // Bottom rounded rectangle +} + +void FilamentItemPanel::OnSize(wxSizeEvent& event) +{ + Refresh(); + event.Skip(); +} + +// class AMSFilamentPanel + +AMSFilamentPanel::AMSFilamentPanel(wxWindow* parent, const wxString& ams_name, wxWindowID id) + : wxPanel(parent, id) +{ + SetBackgroundColour(wxColour("#DBDBDB")); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + // Filament items section + m_filament_container = new wxPanel(this); + m_filament_container->SetBackgroundColour(wxColour("#F0F0F1")); + m_filament_sizer = new wxBoxSizer(wxHORIZONTAL); + m_filament_container->SetSizer(m_filament_sizer); + + // AMS name section + m_ams_name_label = new Label(this, ams_name); + m_ams_name_label->SetForegroundColour(wxColour("#858585")); + m_ams_name_label->SetFont(Label::Body_14); + m_ams_name_label->SetBackgroundColour(wxColour("#DBDBDB")); + + main_sizer->Add(m_filament_container, 1, wxEXPAND | wxALL, 0); + main_sizer->Add(m_ams_name_label, 0, wxALIGN_LEFT | wxALL, FromDIP(5)); + + SetSizer(main_sizer); + wxGetApp().UpdateDarkUI(this); +} + +void AMSFilamentPanel::AddFilamentItem(FilamentItemPanel* panel) +{ + if (panel) { + panel->Reparent(m_filament_container); + m_filament_sizer->Add(panel, 0, wxALL, FromDIP(5)); + m_filament_items.push_back(panel); + Layout(); + } +} + +void AMSFilamentPanel::AddFilamentItem(const wxString& text, const std::string& icon_name) +{ + FilamentItemPanel* item = new FilamentItemPanel(m_filament_container, text, icon_name); + m_filament_items.push_back(item); + m_filament_sizer->Add(item, 0, wxALL, FromDIP(5)); + Layout(); +} + +void AMSFilamentPanel::SetAmsName(const wxString& ams_name) +{ + m_ams_name_label->SetLabel(ams_name); + Layout(); +} + +void AMSFilamentPanel::Clear() +{ + m_filament_sizer->Clear(true); + m_filament_items.clear(); +} + +void AMSFilamentPanel::msw_rescale() +{ + for (auto item : m_filament_items) { + item->msw_rescale(); + } + Layout(); +} + + +AMSDryCtrWin::AMSDryCtrWin(wxWindow *parent) + :DPIDialog(parent, wxID_ANY, _L("AMS Dryness Control"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + create(); +} + +AMSDryCtrWin::~AMSDryCtrWin() +{ + if (m_progress_timer) { + delete m_progress_timer; + } +} + + +wxScrolledWindow* AMSDryCtrWin::create_preview_scrolled_window(wxWindow* parent) +{ + wxScrolledWindow* panel = new wxScrolledWindow(parent, wxID_ANY); + panel->SetScrollRate(10, 0); + panel->SetSize(AMS_ITEMS_PANEL_SIZE); + panel->SetMinSize(AMS_ITEMS_PANEL_SIZE); + panel->SetBackgroundColour(AMS_CONTROL_DEF_BLOCK_BK_COLOUR); + + return panel; +} + +wxBoxSizer* AMSDryCtrWin::create_humidity_status_section(wxPanel* parent) +{ + wxBoxSizer* image_sizer = new wxBoxSizer(wxVERTICAL); + + m_humidity_img = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + image_sizer->Add(m_humidity_img, 1, wxALIGN_CENTER_HORIZONTAL, 0); + + m_humidity_img->SetBitmap(wxNullBitmap); + + // Create a horizontal sizer for description and icon + wxBoxSizer* desc_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_image_description_icon = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + m_image_description_icon->SetBackgroundColour(*wxWHITE); + m_image_description_icon->SetMinSize(wxSize(FromDIP(20), FromDIP(20))); + m_image_description_icon->SetMaxSize(wxSize(FromDIP(20), FromDIP(20))); + m_image_description_icon->Show(false); + desc_sizer->Add(m_image_description_icon, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5)); + + m_image_description = new Label(parent, _L("Idle")); + m_image_description->SetFont(Label::Head_14); + m_image_description->SetForegroundColour(*wxBLACK); + m_image_description->SetBackgroundColour(*wxWHITE); + desc_sizer->Add(m_image_description, 0, wxALIGN_CENTER_VERTICAL); + + image_sizer->Add(desc_sizer, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); + + return image_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_description_item(wxPanel* parent, const wxString& title, Label*& dataLabel) +{ + wxBoxSizer* item_sizer = new wxBoxSizer(wxVERTICAL); + + Label* titleLabel = new Label(parent, title); + titleLabel->SetForegroundColour(*wxBLACK); + titleLabel->SetFont(Label::Body_16); + + dataLabel = new Label(parent, wxT("--")); + dataLabel->SetForegroundColour(*wxBLACK); + dataLabel->SetFont(Label::Body_14); + + item_sizer->AddStretchSpacer(); + item_sizer->Add(titleLabel, 0, wxALIGN_CENTER | wxALL, FromDIP(2)); + item_sizer->Add(dataLabel, 0, wxALIGN_CENTER | wxALL, FromDIP(2)); + item_sizer->AddStretchSpacer(); + + return item_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_status_descriptions_section(wxPanel* parent) +{ + wxBoxSizer* desc_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer* hum_desc_sizer = create_description_item(parent, _L("Humidity"), m_humidity_data_label); + desc_sizer->Add(hum_desc_sizer, 1, wxEXPAND, FromDIP(1)); + + wxStaticLine* vert_separator = new wxStaticLine(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL); + desc_sizer->Add(vert_separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5)); + + wxBoxSizer* temp_desc_sizer = create_description_item(parent, _L("Temperature"), m_temperature_data_label); + desc_sizer->Add(temp_desc_sizer, 1, wxEXPAND, FromDIP(1)); + + m_time_descrition_container = new wxBoxSizer(wxHORIZONTAL); + wxStaticLine* vert_separator2 = new wxStaticLine(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL); + m_time_descrition_container->Add(vert_separator2, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5)); + + wxBoxSizer* time_desc_sizer = create_description_item(parent, _L("Left Time"), m_time_data_label); + m_time_descrition_container->Add(time_desc_sizer, 1, wxEXPAND, FromDIP(1)); + + desc_sizer->Add(m_time_descrition_container, 1, wxEXPAND, 0); + // m_time_descrition_container->Show(false); + + return desc_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_left_panel(wxPanel* parent) +{ + wxBoxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer* image_section = create_humidity_status_section(parent); + left_sizer->Add(image_section, 1, wxEXPAND | wxALL, FromDIP(5)); + + wxBoxSizer* descriptions_section = create_status_descriptions_section(parent); + left_sizer->Add(descriptions_section, 0, wxEXPAND | wxALL, FromDIP(20)); + + return left_sizer; +} + +Button* AMSDryCtrWin::create_button(wxPanel* parent, const wxString& title, + const wxColour& background_color, const wxColour& border_color, const wxColour& text_color) +{ + Button* button = new Button(parent, title); + + // Create state colors for background + StateColor bg_color( + std::pair(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled), + std::pair(background_color.ChangeLightness(80), StateColor::Pressed), + std::pair(background_color.ChangeLightness(120), StateColor::Hovered), + std::pair(background_color, StateColor::Normal) + ); + + // Create state colors for border + StateColor bd_color( + std::pair(AMS_CONTROL_WHITE_COLOUR, StateColor::Disabled), + std::pair(border_color, StateColor::Enabled) + ); + + button->SetBackgroundColor(bg_color); + button->SetBorderColor(bd_color); + button->SetTextColor(text_color); + button->SetFont(Label::Body_14); + + // Auto-size button based on text content with padding + wxSize best_size = button->GetBestSize(); + int padding_width = FromDIP(4); + int padding_height = FromDIP(2); + wxSize final_size(best_size.GetWidth() + padding_width, best_size.GetHeight() + padding_height); + button->SetMinSize(final_size); + + return button; +} + +wxBoxSizer* AMSDryCtrWin::create_normal_state_panel(wxPanel* parent) +{ + wxBoxSizer* normal_state_sizer = new wxBoxSizer(wxVERTICAL); + + Label* description_label = new Label(parent, _L("Filament Drying Settings")); + description_label->SetForegroundColour(*wxBLACK); + description_label->SetFont(Label::Head_14); + normal_state_sizer->Add(description_label, 0, wxALL, FromDIP(5)); + + // Part 2: ComboBox for material selection + wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); + m_trays_combo = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, FromDIP(26)), 0, nullptr, wxCB_READONLY); + m_trays_combo->SetFont(Label::Body_14); + + combo_sizer->Add(m_trays_combo, 1, wxEXPAND); + normal_state_sizer->Add(combo_sizer, 0, wxEXPAND | wxALL, FromDIP(5)); + + m_trays_combo->Bind(wxEVT_COMBOBOX, &AMSDryCtrWin::OnFilamentSelectionChanged, this); + + // part 3 time and temperature + // Temperature part + wxBoxSizer* temp_sizer = new wxBoxSizer(wxHORIZONTAL); + m_temperature_input = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(80), -1)); + m_temperature_input->SetMaxLength(3); // Limit to 3 digits + + m_temperature_input->Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { + int keycode = event.GetKeyCode(); + if (keycode >= '0' && keycode <= '9') { + event.Skip(); + } else if (keycode == WXK_BACK || keycode == WXK_DELETE || keycode == WXK_LEFT || keycode == WXK_RIGHT) { + event.Skip(); + } + }); + + m_temperature_input->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { + m_next_button->Disable(); + m_start_button->Disable(); + }); + + m_temperature_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); + m_temperature_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK)); + + Label* temp_unit_label = new Label(parent, wxString::FromUTF8("℃")); + temp_unit_label->SetForegroundColour(*wxBLACK); + temp_sizer->Add(m_temperature_input, 1, wxRIGHT, FromDIP(1)); + temp_sizer->Add(temp_unit_label, 0, wxALIGN_CENTER_VERTICAL); + + // Time part + wxBoxSizer* time_sizer = new wxBoxSizer(wxHORIZONTAL); + m_time_input = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(100), -1)); + m_time_input->SetMaxLength(3); // Limit to 3 digits + + m_time_input->Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { + int keycode = event.GetKeyCode(); + if (keycode >= '0' && keycode <= '9') { + event.Skip(); + } else if (keycode == WXK_BACK || keycode == WXK_DELETE || keycode == WXK_LEFT || keycode == WXK_RIGHT) { + event.Skip(); + } + }); + + m_time_input->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { + m_next_button->Disable(); + m_start_button->Disable(); + }); + + m_time_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); + m_time_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK)); + + Label* time_unit_label = new Label(parent, "H"); + time_unit_label->SetForegroundColour(*wxBLACK); + time_sizer->Add(m_time_input, 1, wxRIGHT, FromDIP(1)); + time_sizer->Add(time_unit_label, 0, wxALIGN_CENTER_VERTICAL); + + wxBoxSizer* input_sizer = new wxBoxSizer(wxHORIZONTAL); + input_sizer->Add(temp_sizer, 1, wxRIGHT, FromDIP(10)); + input_sizer->Add(time_sizer, 1, 0); + normal_state_sizer->Add(input_sizer, 0, wxEXPAND | wxALL, FromDIP(5)); + + // Part 4: Abnormal description/message area + m_normal_description = new Label(parent, ""); + m_normal_description->SetForegroundColour(wxColour("#F09A17")); + m_normal_description->SetFont(Label::Body_12); + normal_state_sizer->Add(m_normal_description, 0, wxALL, FromDIP(5)); + + // Part 5: Start button + m_next_button = create_button( + parent, + _L("Start"), + AMS_CONTROL_BRAND_COLOUR, // Background color - green + AMS_CONTROL_BRAND_COLOUR, // Border color - green + wxColour("#FFFFFE") // Text color - white + ); + + m_next_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { + m_main_simplebook->SetSelection(1); + }); + + normal_state_sizer->Add(m_next_button, 0, wxALL, FromDIP(5)); + m_next_button->Disable(); + + m_stop_button = create_button( + parent, + _L("Stop"), + wxColour(255, 0, 0), // Background color - red + wxColour(255, 0, 0), // Border color - red + wxColour("#FFFFFE") // Text color - white + ); + + m_stop_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { + auto fila_system = get_fila_system(); + if (!fila_system) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Invalid FilaSystem Pointer"; + return; + } + + fila_system->CtrlAmsStopDrying(std::stoi(m_ams_info.m_ams_id)); + // Temporarily show stopping state, then restore after 2 seconds + if (m_stop_button) { + m_stop_button->SetLabel(_L("Stopping")); + update_button_size(m_stop_button); // Adjust button size for longer text + m_stop_button->Disable(); + m_stop_button->Layout(); + m_stop_button->GetParent()->Layout(); // Relayout parent container + m_stop_button->Refresh(); + + m_stop_button_restore_deadline.reset(); + m_stop_button_restore_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + } + }); + + m_stop_button->Show(false); + normal_state_sizer->Add(m_stop_button, 0, wxALL, FromDIP(5)); + + return normal_state_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_cannot_dry_panel(wxPanel* parent) +{ + wxBoxSizer* abnormal_sizer = new wxBoxSizer(wxVERTICAL); + + Label* description_label = new Label(parent, _L("Unable to dry temporarily due to ...")); + description_label->SetForegroundColour(*wxBLACK); + description_label->SetFont(Label::Head_16); + description_label->Wrap(FromDIP(300)); + abnormal_sizer->Add(description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + // Add a description label for the abnormal state + m_cannot_dry_description_label = new Label(parent, ("")); + m_cannot_dry_description_label->SetForegroundColour(*wxBLACK); + m_cannot_dry_description_label->SetFont(Label::Body_14); + m_cannot_dry_description_label->Wrap(FromDIP(250)); // Wrap text to fit within panel + + abnormal_sizer->Add(m_cannot_dry_description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + // Unload button shown only when ConsumableAtAmsOutlet reason is active + m_unload_button = create_button( + parent, + _L("Unload"), + AMS_CONTROL_BRAND_COLOUR, // Background color - light gray + AMS_CONTROL_BRAND_COLOUR, // Border color - gray + *wxWHITE // Text color - white + ); + + m_unload_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { + auto fila_system = get_fila_system(); + if (!fila_system) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::unload_button: Invalid FilaSystem Pointer"; + return; + } + + MachineObject* obj = fila_system->GetOwner(); + if (!obj) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::unload_button: Invalid MachineObject Pointer"; + return; + } + + obj->command_ams_change_filament(false, m_ams_info.m_ams_id, "255"); + + m_unload_button->Disable(); + m_unload_button_restore_deadline.reset(); + m_unload_button_restore_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + }); + + m_unload_button->Show(false); + abnormal_sizer->Add(m_unload_button, 0, wxALL, FromDIP(5)); + + return abnormal_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_drying_error_panel(wxPanel* parent) +{ + wxBoxSizer* err_sizer = new wxBoxSizer(wxVERTICAL); + Label* description_label = new Label(parent, _L("Drying Error")); + description_label->SetForegroundColour(*wxRED); + description_label->SetFont(Label::Body_14); // Wrap text to fit within panel + err_sizer->Add(description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + Label* additional_description_label = new Label(parent, _L("Please check the Assistant for troubleshooting")); + additional_description_label->SetForegroundColour(*wxBLACK); + additional_description_label->SetFont(Label::Body_14); // Wrap text to fit within panel + err_sizer->Add(additional_description_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return err_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_right_panel(wxPanel* parent) +{ + wxBoxSizer* right_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_normal_state_sizer = create_normal_state_panel(parent); + m_cannot_dry_sizer = create_cannot_dry_panel(parent); + m_dry_error_sizer = create_drying_error_panel(parent); + + right_sizer->Add(m_normal_state_sizer, 1, wxEXPAND); + right_sizer->Add(m_cannot_dry_sizer, 1, wxEXPAND); + + m_cannot_dry_sizer->Show(false); + m_dry_error_sizer->Show(false); + + return right_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_main_content_section(wxPanel* parent) +{ + wxBoxSizer* content_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer* left_panel = create_left_panel(parent); + content_sizer->Add(left_panel, 2, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); + left_panel->SetMinSize(wxSize(FromDIP(250), -1)); + + wxBoxSizer* right_panel = create_right_panel(parent); + content_sizer->Add(right_panel, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); + right_panel->SetMinSize(wxSize(FromDIP(250), -1)); + + return content_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_main_page_sizer(wxPanel* parent) +{ + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer* content_section = create_main_content_section(parent); + main_sizer->Add(content_section, 1, wxEXPAND | wxALL, FromDIP(5)); + + return main_sizer; +} + +wxBoxSizer* AMSDryCtrWin::create_guide_info_filament(wxPanel* parent) +{ + wxBoxSizer* filament_section = new wxBoxSizer(wxHORIZONTAL); + + m_ams_filament_panel = new AMSFilamentPanel(parent, ""); + + filament_section->Add(m_ams_filament_panel, 0, wxEXPAND | wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5)); + + return filament_section; +} + +wxBoxSizer* AMSDryCtrWin::create_guide_info_section(wxPanel* parent) +{ + wxBoxSizer* info_section = new wxBoxSizer(wxVERTICAL); + + // Part 1: Title + m_guide_title_label = new Label(parent, _L("Please remove and store the filament (as shown).")); + m_guide_title_label->SetForegroundColour(*wxBLACK); + m_guide_title_label->SetFont(Label::Head_18); + info_section->Add(m_guide_title_label, 0, wxEXPAND | wxALL, FromDIP(5)); + + // Part 2: Description + m_guide_description_label = new Label(parent, _L("The AMS can rotate the filament which is properly stored, providing better drying results.")); + m_guide_description_label->SetForegroundColour(*wxBLACK); + m_guide_description_label->SetFont(Label::Body_14); + m_guide_description_label->Wrap(FromDIP(300)); // Wrap text to fit within panel + info_section->Add(m_guide_description_label, 0, wxEXPAND | wxALL, FromDIP(5)); + + // Part 3: filament panel + wxBoxSizer* fila_section = create_guide_info_filament(parent); + info_section->Add(fila_section, 0, wxEXPAND | wxALL, FromDIP(5)); + + // Part 4: Circular toggle with description + wxBoxSizer* toggle_section = new wxBoxSizer(wxHORIZONTAL); + + m_rotate_spool_toggle = new wxCheckBox(parent, wxID_ANY, ""); + m_rotate_spool_toggle->SetValue(false); + + m_rotate_spool_toggle->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { + bool is_checked = event.IsChecked(); + // Add toggle behavior logic here + }); + + toggle_section->Add(m_rotate_spool_toggle, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(10)); + + // Toggle description + Label* toggle_description = new Label(parent, _L("Rotate spool when drying"), LB_AUTO_WRAP); + toggle_description->SetForegroundColour(*wxBLACK); + toggle_description->SetFont(Label::Body_12); + toggle_section->Add(toggle_description, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0); + + info_section->Add(toggle_section, 0, wxEXPAND | wxALL, FromDIP(5)); + + return info_section; +} + +wxBoxSizer* AMSDryCtrWin::create_guide_right_section(wxPanel* parent) +{ + wxBoxSizer* right_section = new wxBoxSizer(wxVERTICAL); + + // Upper part: Image section + m_image_placeholder = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + m_guide_image = ScalableBitmap(parent, "dev_ams_dry_ctr_filament_in_chamber", 256); + m_image_placeholder->SetBitmap(m_guide_image.bmp()); + + wxBoxSizer* image_container = new wxBoxSizer(wxHORIZONTAL); + image_container->AddStretchSpacer(1); + image_container->Add(m_image_placeholder, 0, wxALL, FromDIP(5)); + + right_section->Add(image_container, 0, wxEXPAND | wxALL, FromDIP(5)); + + wxBoxSizer* buttons_container = new wxBoxSizer(wxHORIZONTAL); + buttons_container->AddStretchSpacer(1); + + m_back_button = create_button( + parent, + wxString::FromUTF8(_CTX_utf8(L_CONTEXT("Back", "amsdrying"), "amsdrying")), + wxColour("#F8F8F8"), // Background color - light gray + wxColour("#D0D0D0"), // Border color - gray + *wxBLACK // Text color - black + ); + + m_back_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { + m_main_simplebook->SetSelection(0); + }); + + buttons_container->Add(m_back_button, 0, wxALL, FromDIP(5)); + + m_start_button = create_button( + parent, + _L("Start"), + AMS_CONTROL_BRAND_COLOUR, // Background color - green + AMS_CONTROL_BRAND_COLOUR, // Border color - green + wxColour("#FFFFFE") // Text color - white + ); + + m_start_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { + m_main_simplebook->SetSelection(2); + m_progress_gauge->SetValue(0); + + m_progress_timer->Start(70); + m_progress_value = 0; + start_sending_drying_command(); + }); + + buttons_container->Add(m_start_button, 0, wxALL, FromDIP(5)); + + right_section->Add(buttons_container, 0, wxEXPAND | wxALL, FromDIP(5)); + + return right_section; +} + +wxBoxSizer* AMSDryCtrWin::create_guide_page_sizer(wxPanel* parent) +{ + wxBoxSizer* guide_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer* info_section = create_guide_info_section(parent); + guide_sizer->Add(info_section, 1, wxEXPAND | wxALL, FromDIP(10)); + + wxBoxSizer* right_section = create_guide_right_section(parent); + guide_sizer->Add(right_section, 0, wxEXPAND | wxALL, FromDIP(10)); + + return guide_sizer; +} + +void AMSDryCtrWin::start_sending_drying_command() +{ + auto fila_system = get_fila_system(); + if (!fila_system) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Invalid FilaSystem Pointer"; + return; + } + + if (m_temperature_input->GetValue().IsEmpty() || m_time_input->GetValue().IsEmpty()) { + // Show error message to user + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Time or temperature is empty"; + return; + } + + long temperature, time; + if (!m_temperature_input->GetValue().ToLong(&temperature) || + !m_time_input->GetValue().ToLong(&time)) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::start_sending_drying_command: Failed to convert temperature or time"; + return; + } + + int tray_index; + tray_index = m_trays_combo->GetSelection(); + if (m_tray_ids.empty() || tray_index < 0 || tray_index >= m_tray_ids.size()) { + BOOST_LOG_TRIVIAL(warning) << "AMSDryCtrWin::start_sending_drying_command: Invalid tray_index " << tray_index + << ", m_tray_ids.size=" << m_tray_ids.size(); + return; + } + + int cooling_temp = 50; + std::optional preset = DevUtilBackend::GetFilamentDryingPreset(m_tray_ids[tray_index].filament_id); + if (preset.has_value()) { + cooling_temp = static_cast(preset.value().filament_dev_drying_softening_temperature); + } + + fila_system->CtrlAmsStartDryingHour(std::stoi(m_ams_info.m_ams_id), m_tray_ids[tray_index].filament_type, + temperature, time, m_rotate_spool_toggle->GetValue(), cooling_temp, false); + + m_dry_setting.m_filament_names[m_ams_info.m_ams_id] = m_tray_ids[tray_index].filament_name; + m_dry_setting.m_filament_type[m_ams_info.m_ams_id] = m_tray_ids[tray_index].filament_type; + m_dry_setting.m_dry_temp[m_ams_info.m_ams_id] = temperature; + m_dry_setting.m_dry_time[m_ams_info.m_ams_id] = time; +} + +void AMSDryCtrWin::OnProgressTimer(wxTimerEvent& event) +{ + m_progress_value += 1; + + if (m_progress_value <= 100) + m_progress_gauge->SetValue(m_progress_value); + + if (m_progress_value == 1) { // First tick, reset message index + m_progress_message_index = 0; + if (!m_progress_text.empty()) { + m_progress_title->SetLabel(m_progress_text[0]); + } + } + else if (m_progress_value % 15 == 0) { // Approximately every second + m_progress_message_index++; + if (m_progress_message_index < m_progress_text.size()) { + m_progress_title->SetLabel(m_progress_text[m_progress_message_index]); + m_progress_title->Refresh(); + } + } + + if (m_progress_value >= 100 && !is_dry_ctr_idle()) { + m_progress_value = 0; + m_progress_timer->Stop(); + m_main_simplebook->SetSelection(0); + } +} + +void AMSDryCtrWin::restore_stop_button_if_deadline_passed() +{ + if (m_stop_button_restore_deadline.has_value()) { + if (std::chrono::steady_clock::now() >= m_stop_button_restore_deadline.value()) { + if (m_stop_button) { + m_stop_button->SetLabel(_L("Stop")); + update_button_size(m_stop_button); // Adjust button size back to original + m_stop_button->Enable(); + m_stop_button->Layout(); + m_stop_button->GetParent()->Layout(); + m_stop_button->Refresh(); + } + m_stop_button_restore_deadline.reset(); + } + } +} + +void AMSDryCtrWin::restore_unload_button_if_deadline_passed() +{ + if (m_unload_button_restore_deadline.has_value()) { + if (std::chrono::steady_clock::now() >= m_unload_button_restore_deadline.value()) { + if (m_unload_button) { + m_unload_button->Enable(); + } + m_unload_button_restore_deadline.reset(); + } + } +} + +void AMSDryCtrWin::update_button_size(Button* button) +{ + if (!button) return; + + // Reset MinSize first to avoid accumulation of size + button->SetMinSize(wxSize(-1, -1)); + + // Recalculate button size based on current text and DPI settings + wxSize best_size = button->GetBestSize(); + int padding_width = FromDIP(4); + int padding_height = FromDIP(2); + wxSize final_size(best_size.GetWidth() + padding_width, best_size.GetHeight() + padding_height); + button->SetMinSize(final_size); +} + +void AMSDryCtrWin::OnShow(wxShowEvent& event) +{ + if (event.IsShown()) { + wxGetApp().UpdateDlgDarkUI(this); + msw_rescale(); + } + event.Skip(); +} + +void AMSDryCtrWin::OnClose(wxCloseEvent& event) +{ + if (m_progress_timer && m_progress_timer->IsRunning()) { + m_progress_timer->Stop(); + } + + if (m_progress_gauge) { + m_progress_gauge->SetValue(0); + } + + m_progress_value = 0; + m_progress_message_index = 0; + + if (m_main_simplebook) { + m_main_simplebook->SetSelection(0); + } + + if (m_progress_title && !m_progress_text.empty()) { + m_progress_title->SetLabel(m_progress_text[0]); + } + + // Clean up and restore stop button state + if (m_stop_button) { + m_stop_button->SetLabel(_L("Stop")); + update_button_size(m_stop_button); // Adjust button size back to original + m_stop_button->Enable(); + m_stop_button->Layout(); + m_stop_button->GetParent()->Layout(); + m_stop_button->Refresh(); + } + m_stop_button_restore_deadline.reset(); + + // Clean up unload button state + if (m_unload_button) { + m_unload_button->Enable(); + } + m_unload_button_restore_deadline.reset(); + + event.Skip(); +} + +wxBoxSizer* AMSDryCtrWin::create_progress_page_sizer(wxPanel* parent) +{ + wxBoxSizer* progress_sizer = new wxBoxSizer(wxVERTICAL); + + m_progress_title = new Label(parent, m_progress_text[0]); + m_progress_title->SetForegroundColour(*wxBLACK); + m_progress_title->SetFont(Label::Body_16); + + progress_sizer->Add(0, 0, 1, wxEXPAND, 0); // Spacer + progress_sizer->Add(m_progress_title, 0, wxALIGN_CENTER | wxALL, FromDIP(30)); + + m_progress_gauge = new ProgressBar(parent, wxID_ANY, 100, wxDefaultPosition, wxSize(FromDIP(300), FromDIP(8))); + m_progress_gauge->SetValue(0); + m_progress_gauge->SetHeight(FromDIP(8)); + + progress_sizer->Add(m_progress_gauge, 0, wxALIGN_CENTER | wxALL, FromDIP(10)); + progress_sizer->Add(0, 0, 1, wxEXPAND, 0); + + return progress_sizer; +} + +void AMSDryCtrWin::create() +{ + // set title icon + SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); + this->SetDoubleBuffered(true); + std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + SetSize(wxSize(FromDIP(700), FromDIP(500))); + SetMinSize(wxSize(FromDIP(700), FromDIP(500))); + SetMaxSize(wxSize(FromDIP(700), FromDIP(500))); + + m_main_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + + // Create main page + m_original_page = new wxPanel(m_main_simplebook, wxID_ANY); + m_original_page->SetBackgroundColour(*wxWHITE); + wxBoxSizer* main_sizer = create_main_page_sizer(m_original_page); + m_original_page->SetSizer(main_sizer); + + m_main_simplebook->AddPage(m_original_page, "Main Page"); + + m_guide_page = new wxPanel(m_main_simplebook, wxID_ANY); + m_guide_page->SetBackgroundColour(*wxWHITE); + wxBoxSizer* guide_sizer = create_guide_page_sizer(m_guide_page); + m_guide_page->SetSizer(guide_sizer); + m_main_simplebook->AddPage(m_guide_page, "Guide Page"); + + // Create progress page + m_progress_page = new wxPanel(m_main_simplebook, wxID_ANY); + m_progress_page->SetBackgroundColour(*wxWHITE); + wxBoxSizer* progress_sizer = create_progress_page_sizer(m_progress_page); + m_progress_page->SetSizer(progress_sizer); + m_main_simplebook->AddPage(m_progress_page, "Progress Page"); + + m_progress_timer = new wxTimer(this, wxID_ANY); + Bind(wxEVT_TIMER, &AMSDryCtrWin::OnProgressTimer, this, m_progress_timer->GetId()); + + Bind(wxEVT_CLOSE_WINDOW, &AMSDryCtrWin::OnClose, this); + + wxBoxSizer* top_level_sizer = new wxBoxSizer(wxVERTICAL); + top_level_sizer->Add(m_main_simplebook, 1, wxEXPAND | wxALL, FromDIP(10)); + + Bind(wxEVT_SHOW, &AMSDryCtrWin::OnShow, this); + + SetSizer(top_level_sizer); + Layout(); + Refresh(); + wxGetApp().UpdateDlgDarkUI(this); +} + +void AMSDryCtrWin::on_dpi_changed(const wxRect &suggested_rect) +{ + msw_rescale(); +} + +void AMSDryCtrWin::msw_rescale() +{ + if (!IsShown()) { + return; + } + + if (m_ams_filament_panel) {m_ams_filament_panel->msw_rescale();} + if (m_trays_combo) {m_trays_combo->Rescale(); m_trays_combo->Layout(); m_trays_combo->SetFont(Label::Body_14), m_trays_combo->Refresh();} + if (m_humidity_img && m_humidity_image.bmp().IsOk()) {m_humidity_image.msw_rescale(); m_humidity_img->SetBitmap(m_humidity_image.bmp());} + if (m_image_placeholder && m_guide_image.bmp().IsOk()) {m_guide_image.msw_rescale();m_image_placeholder->SetBitmap(m_guide_image.bmp());} + if (m_image_description_icon && m_description_icon_bitmap.bmp().IsOk()) {m_description_icon_bitmap.msw_rescale(); m_image_description_icon->SetBitmap(m_description_icon_bitmap.bmp());} + + // Rescale and update button sizes based on text content + if (m_next_button) { + m_next_button->Rescale(); + update_button_size(m_next_button); + m_next_button->Layout(); + m_next_button->Refresh(); + } + if (m_stop_button) { + m_stop_button->Rescale(); + update_button_size(m_stop_button); + m_stop_button->Layout(); + m_stop_button->Refresh(); + } + if (m_start_button) { + m_start_button->Rescale(); + update_button_size(m_start_button); + m_start_button->Layout(); + m_start_button->Refresh(); + } + if (m_back_button) { + m_back_button->Rescale(); + update_button_size(m_back_button); + m_back_button->Layout(); + m_back_button->Refresh(); + } + if (m_unload_button) { + m_unload_button->Rescale(); + update_button_size(m_unload_button); + m_unload_button->Layout(); + m_unload_button->Refresh(); + } + + if (m_guide_page) {m_guide_page->Layout();} + if (m_original_page) {m_original_page->Layout();} + + Fit(); + Layout(); + Refresh(); +} + +void AMSDryCtrWin::set_ams_id(const std::string& ams_id) +{ + m_ams_info.m_ams_id = ams_id; + m_is_ams_changed = true; +} + +void AMSDryCtrWin::update_img_description(DevAms::DryStatus status, DevAms::DrySubStatus sub_status) +{ + if (status == DevAms::DryStatus::Off || status == DevAms::DryStatus::Cooling) { + m_image_description->SetLabel(_L("Idle")); + m_image_description_icon->Show(false); + return; + } + + // Determine label text for non-idle states + wxString label_text; + if (status == DevAms::DryStatus::Error) { + label_text = _L("Drying"); + } else if (sub_status == DevAms::DrySubStatus::Heating) { + label_text = _L("Drying-Heating"); + } else if (sub_status == DevAms::DrySubStatus::Dehumidify) { + label_text = _L("Drying-Dehumidifying"); + } else { + m_image_description_icon->Show(false); + return; + } + + m_image_description->SetLabel(label_text); + + try { + m_description_icon_bitmap = ScalableBitmap(this, "dev_ams_dry_ctr_heating_icon", 20); + m_description_icon_bitmap.msw_rescale(); + m_image_description_icon->SetBitmap(m_description_icon_bitmap.bmp()); + m_image_description_icon->Show(true); + } catch (Exception&) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Error loading drying icon"; + m_image_description_icon->Show(false); + } +} + +int AMSDryCtrWin::update_image(DevAmsType model, DevAms::DryStatus status, DevAms::DrySubStatus sub_status, int humidity_percent) +{ + if (model == m_ams_info.m_model && status == m_ams_info.m_dry_status + && sub_status == m_ams_info.m_dry_sub_status && humidity_percent == m_ams_info.m_humidity_percent) { + return 0; + } + + std::string img_path; + + if (status == DevAms::DryStatus::Off && sub_status == DevAms::DrySubStatus::Off) { + img_path = get_humidity_level_img_path(humidity_percent); + } else { + img_path = get_dry_status_img_path(model, status, sub_status); + } + + if (img_path.empty()) { + return 0; + } + + m_humidity_image = ScalableBitmap(this, img_path, AMS_DRY_STATUS_IMAGE_SIZE); + m_humidity_img->SetBitmap(m_humidity_image.bmp()); + + update_img_description(status, sub_status); + + return 1; +} + +bool AMSDryCtrWin::check_values_changed(DevAms* dev_ams) +{ + bool changed = false; + + if (m_ams_info.m_model != dev_ams->GetAmsType()) { + m_ams_info.m_model = dev_ams->GetAmsType(); + changed = true; + } + + if (dev_ams->GetDryStatus().has_value() && m_ams_info.m_dry_status != dev_ams->GetDryStatus()) { + m_ams_info.m_dry_status = dev_ams->GetDryStatus().value(); + changed = true; + } + + if (dev_ams->GetDrySubStatus().has_value() && m_ams_info.m_dry_sub_status != dev_ams->GetDrySubStatus()) { + m_ams_info.m_dry_sub_status = dev_ams->GetDrySubStatus().value(); + changed = true; + } + + if (m_ams_info.m_humidity_percent != dev_ams->GetHumidityPercent()) { + m_ams_info.m_humidity_percent = dev_ams->GetHumidityPercent(); + changed = true; + } + + return changed; +} + +void AMSDryCtrWin::update_normal_description(DevAms* dev_ams) +{ + if (m_tray_ids.empty() || m_trays_combo->GetSelection() >= m_tray_ids.size()) { return; } + + FilamentBaseInfo info = m_tray_ids[m_trays_combo->GetSelection()]; + wxString warning_text; + bool can_enable_button = true; + long temp_val = 0, time_val = 0; + m_temperature_input->GetValue().ToLong(&temp_val); + m_time_input->GetValue().ToLong(&time_val); + std::optional preset = DevUtilBackend::GetFilamentDryingPreset(info.filament_id); + auto total_dry = preset.has_value() ? preset.value().ams_limitations : std::unordered_set(); + + struct AmsTempLimit { DevAmsType type; int min_temp; int max_temp; const char* name; }; + static const AmsTempLimit ams_limits[] = { + { DevAmsType::N3F, 45, 65, "AMS2" }, + { DevAmsType::N3S, 45, 85, "AMS-S" } + }; + + for (const auto& lim : ams_limits) { + if (dev_ams->GetAmsType() == lim.type) { + if (temp_val > lim.max_temp) { + wxString msg = wxString(lim.name) + _L(" maximum drying temperature is ") + wxString::Format(wxT("%d"), lim.max_temp) + wxString::FromUTF8("°C."); + warning_text += msg + "\n"; + can_enable_button = false; + } else if (temp_val < lim.min_temp) { + wxString msg = wxString(lim.name) + _L(" minimum drying temperature is ") + wxString::Format(wxT("%d"), lim.min_temp) + wxString::FromUTF8("°C."); + warning_text += msg + "\n"; + can_enable_button = false; + } + if (total_dry.find(lim.type) == total_dry.end()) { + warning_text += _L("This filament may not be completely dried.") + "\n\n"; + } + break; + } + } + + if (m_printer_status.m_is_printing && temp_val > m_ams_info.m_recommand_dry_temp) { + warning_text += _L("This AMS is currently printing. To ensure print quality, the drying temperature cannot exceed the recommended drying temperature.") + "\n"; + can_enable_button = false; + } else if (preset.has_value()) { + // Only check heat distortion temperature when AMS has filament inserted; + // empty AMS only needs to respect the device hardware temperature limit. + bool has_filament_inserted = false; + for (const auto& tray_pair : dev_ams->GetTrays()) { + if (tray_pair.second && tray_pair.second->is_exists) { + has_filament_inserted = true; + break; + } + } + if (has_filament_inserted) { + auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature; + if (temp_val > limit_temperature) { + warning_text += _L("The temperature shall not exceed the filament's heat distortion temperature") + "(" + + wxString::Format(wxT("%d"), static_cast(limit_temperature)) + wxString::FromUTF8("°C)\n"); + can_enable_button = false; + } + } + } + + if (time_val < 1) { + warning_text += _L("Minimum time value cannot be less than 1.") + "\n"; + can_enable_button = false; + } else if (time_val > 24) { + warning_text += _L("Maximum time value cannot be greater than 24.") + "\n"; + can_enable_button = false; + } + + m_next_button->Enable(can_enable_button); + m_normal_description->SetLabel(warning_text); + m_normal_description->Wrap(FromDIP(250)); + m_normal_description->GetParent()->Layout(); +} + +void AMSDryCtrWin::update_normal_state(DevAms* dev_ams) +{ + if (is_dry_ctr_idle(dev_ams)) { + m_next_button->Show(true); + m_normal_description->Show(true); + m_trays_combo->Enable(); + m_temperature_input->SetEditable(true); + m_time_input->SetEditable(true); + + m_stop_button->Hide(); + m_dry_error_sizer->Show(false); + } else if (is_dry_ctr_err(dev_ams)) { + m_dry_error_sizer->Show(true); + m_next_button->Hide(); + m_normal_description->Hide(); + + m_trays_combo->Hide(); + m_temperature_input->Hide(); + m_time_input->Hide(); + m_stop_button->Show(true); + } else { + m_next_button->Hide(); + m_normal_description->Hide(); + + m_trays_combo->Disable(); + m_temperature_input->SetEditable(false); + m_time_input->SetEditable(false); + m_stop_button->Show(true); + m_dry_error_sizer->Show(false); + } + + if (m_temperature_input->GetValue().IsEmpty() || m_time_input->GetValue().IsEmpty()) { + m_next_button->Disable(); + m_start_button->Disable(); + } else { + m_next_button->Enable(); + } + + update_normal_description(dev_ams); +} + +void AMSDryCtrWin::OnFilamentSelectionChanged(wxCommandEvent& event) +{ + int selectionIndex = event.GetSelection(); + wxString selectedFilament = event.GetString(); + + auto fila_system = get_fila_system(); + if (!fila_system) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::OnFilamentSelectionChanged: Invalid FilaSystem Pointer"; + return; + } + + DevAms* dev_ams = fila_system->GetAmsById(m_ams_info.m_ams_id); + if (!dev_ams) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::OnFilamentSelectionChanged: Invalid AMS id"; + return; + } + + std::optional preset = DevUtilBackend::GetFilamentDryingPreset(m_tray_ids[selectionIndex].filament_id); + if (preset.has_value()) { + DevFilamentDryingPreset info = preset.value(); + if (m_printer_status.m_is_printing) { + m_temperature_input->SetValue(std::to_string(static_cast(info.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()]))); + m_time_input->SetValue(std::to_string(static_cast(info.filament_dev_ams_drying_time_on_print[dev_ams->GetAmsType()]))); + } else { + m_temperature_input->SetValue(std::to_string(static_cast(info.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()]))); + m_time_input->SetValue(std::to_string(static_cast(info.filament_dev_ams_drying_time_on_idle[dev_ams->GetAmsType()]))); + } + } + + update_filament_guide_info(dev_ams); +} + +wxString get_cannot_reason_text(DevAms::CannotDryReason reason) +{ + wxString cannot_reason_text; + switch (reason) + { + case DevAms::CannotDryReason::InsufficientPower: + cannot_reason_text = "*" + _L("Insufficient power") + "\n"; + cannot_reason_text += _L(" Too many AMS drying simultaneously. Please plug in the power or stop other drying processes before starting.") + "\n"; + break; + case DevAms::CannotDryReason::AmsBusy: + cannot_reason_text = "*" + _L("AMS is busy") + "\n"; + cannot_reason_text += _L(" AMS is calibrating | reading RFID | loading/unloading material, please wait.") + "\n"; + break; + case DevAms::CannotDryReason::ConsumableAtAmsOutlet: + cannot_reason_text = "*" + _L("Filament in AMS outlet") + "\n"; + cannot_reason_text += _L(" The high drying temperature may cause AMS blockage, please unload first."); + break; + case DevAms::CannotDryReason::InitiatingAmsDrying: + cannot_reason_text = "*" + _L("Initiating AMS drying") + "\n"; + break; + case DevAms::CannotDryReason::NotSupportedIn2dMode: + cannot_reason_text = "*" + _L("Not supported in 2D mode") + "\n"; + break; + case DevAms::CannotDryReason::DryingInProgress: + cannot_reason_text = "*" + _L("Task in progress") + "\n"; + cannot_reason_text += _L(" The AMS might be in use during Task.") + "\n"; + break; + case DevAms::CannotDryReason::Upgrading: + cannot_reason_text = "*" + _L("Upgrading") + "\n"; + cannot_reason_text += _L(" Firmware update in progress, please wait...") + "\n"; + break; + case DevAms::CannotDryReason::InsufficientPowerNeedPluginPower: + cannot_reason_text = "*" + _L("Insufficient power") + "\n"; + cannot_reason_text += _L(" Please plug in the power and then use the drying function.") + "\n"; + break; + case DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload: + cannot_reason_text = "*" + _L("Filament in AMS outlet") + "\n"; + cannot_reason_text += _L(" The high drying temperature may cause AMS blockage. Please unload the filament manually before proceeding.") + "\n"; + break; + default: + cannot_reason_text = "*" + _L("System is busy") + "\n"; + cannot_reason_text += _L(" Initiating other drying processes, please wait a few seconds...") + "\n"; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unknown cannot dry reason"; + break; + } + return cannot_reason_text; +} + +wxString organize_cannot_reasons_text(std::vector& reasons) +{ + wxString cannot_reasons_text; + if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::DryingInProgress) != reasons.end()) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::DryingInProgress); + } else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::AmsBusy) != reasons.end()) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::AmsBusy); + } else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::ConsumableAtAmsOutlet) != reasons.end()) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::ConsumableAtAmsOutlet); + } else if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload) != reasons.end()) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::FilamentAtAmsOutletManualUnload); + } + + if (std::find(reasons.begin(), reasons.end(), DevAms::CannotDryReason::InsufficientPower) != reasons.end()) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InsufficientPower); + } + + for (auto reason : reasons) { + if (reason == DevAms::CannotDryReason::InsufficientPowerNeedPluginPower) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InsufficientPowerNeedPluginPower); + } else if (reason == DevAms::CannotDryReason::NotSupportedIn2dMode) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::NotSupportedIn2dMode); + } else if (reason == DevAms::CannotDryReason::InitiatingAmsDrying) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::InitiatingAmsDrying); + } else if (reason == DevAms::CannotDryReason::Upgrading) { + cannot_reasons_text += get_cannot_reason_text(DevAms::CannotDryReason::Upgrading); + } + } + + if (cannot_reasons_text.empty()) { + cannot_reasons_text += "*" + _L("System is busy") + "\n"; + cannot_reasons_text += _L(" Initiating other drying processes, please wait a few seconds...") + "\n"; + } + + return cannot_reasons_text; +} + +int AMSDryCtrWin::update_state(DevAms* dev_ams) +{ + std::vector cannot_reasons = dev_ams->GetCannotDryReason().has_value()? + dev_ams->GetCannotDryReason().value(): std::vector(); + + if (cannot_reasons.size() == 0 || + (cannot_reasons.size() == 1 && cannot_reasons[0] == DevAms::CannotDryReason::DryingInProgress)) { + m_normal_state_sizer->Show(true); + m_cannot_dry_sizer->Show(false); + m_unload_button->Show(false); + update_normal_state(dev_ams); + } else if (cannot_reasons.size() > 0) { + m_normal_state_sizer->Show(false); + m_cannot_dry_sizer->Show(true); + + m_cannot_dry_description_label->SetLabel(organize_cannot_reasons_text(cannot_reasons)); + m_cannot_dry_description_label->Wrap(FromDIP(300)); + + // Show unload button when ConsumableAtAmsOutlet reason is present + bool has_consumable_at_outlet = std::find(cannot_reasons.begin(), cannot_reasons.end(), + DevAms::CannotDryReason::ConsumableAtAmsOutlet) != cannot_reasons.end(); + m_unload_button->Show(has_consumable_at_outlet); + } + + restore_stop_button_if_deadline_passed(); + restore_unload_button_if_deadline_passed(); + + return 1; +} + +int AMSDryCtrWin::update_ams_change(DevAms* dev_ams) +{ + if (!is_ams_changed(dev_ams)) { + return 0; + } + + m_ams_info.m_ams_id = dev_ams->GetAmsId(); + if (dev_ams->GetAmsType() == DevAmsType::N3F) { + m_temperature_input->SetHint("45-65" + wxString::FromUTF8("°C")); + } else if (dev_ams->GetAmsType() == DevAmsType::N3S) { + m_temperature_input->SetHint("45-85" + wxString::FromUTF8("°C")); + } + + m_time_input->SetHint("1-24 h"); + + return 0; +} + +int AMSDryCtrWin::update_dryness_status(DevAms* dev_ams) +{ + int updated = 0; + + if (m_ams_info.m_humidity_percent != dev_ams->GetHumidityPercent()) { + updated += 1; + m_ams_info.m_humidity_percent = dev_ams->GetHumidityPercent(); + m_humidity_data_label->SetLabel(std::to_string(m_ams_info.m_humidity_percent) + "%"); + } + + if (m_ams_info.m_temperature != dev_ams->GetCurrentTemperature()) { + updated += 1; + m_ams_info.m_temperature = dev_ams->GetCurrentTemperature(); + m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8("°C")); + } + + if (is_dry_ctr_idle(dev_ams)) { + m_time_descrition_container->Show(false); + m_original_page->Layout(); + } else { + if (is_dry_status_changed(dev_ams)) { + m_time_descrition_container->Show(true); + m_original_page->Layout(); + } + + if (m_ams_info.m_left_dry_time != dev_ams->GetLeftDryTime()) { + updated += 1; + m_ams_info.m_left_dry_time = dev_ams->GetLeftDryTime(); + m_time_data_label->SetLabel(wxString::Format("%02d : %02d", + m_ams_info.m_left_dry_time / 60, m_ams_info.m_left_dry_time % 60)); + } + } + + return updated; +} + +bool AMSDryCtrWin::is_tray_changed(DevAms* dev_ams) +{ + return false; +} + +void AMSDryCtrWin::update_filament_guide_info(DevAms* dev_ams) +{ + if (!IsShown()) { + return; + } + + m_guide_page->Freeze(); + m_ams_filament_panel->Clear(); + m_ams_filament_panel->SetAmsName(dev_ams->GetDisplayName()); + + // Get the temperature input value + long input_temp = 0; + bool valid_temp = !m_temperature_input->GetValue().IsEmpty() && + m_temperature_input->GetValue().ToLong(&input_temp); + bool can_start = true; + + int slot_count = 0, empty_count = 0; + for (auto& tray_pair : dev_ams->GetTrays()) { + if (!tray_pair.second) { + continue; + } + ++slot_count; + if (!tray_pair.second->is_exists) { + m_ams_filament_panel->AddFilamentItem("/", "dev_ams_dry_ctr_enable"); + ++empty_count; + continue; + } + auto preset_opt = tray_pair.second->get_ams_drying_preset(); + wxString filament_type = tray_pair.second->get_display_filament_type(); + DevFilamentDryingPreset preset; + if (filament_type.IsEmpty()) { + auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00"); + preset = fallback_preset.value(); + filament_type = "?"; + } else if (preset_opt.has_value()) { + preset = preset_opt.value(); + } else { + auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00"); + preset = fallback_preset.value(); + } + std::string icon_path = "dev_ams_dry_ctr_enable"; + int distortion_temp = static_cast(preset.filament_dev_ams_drying_heat_distortion_temperature); + if (valid_temp && distortion_temp < input_temp) { + icon_path = "dev_ams_dry_ctr_disable"; + can_start = false; + } + m_ams_filament_panel->AddFilamentItem(filament_type, icon_path); + } + if (slot_count == 0) { + can_start = false; + } + + if (can_start) { + m_guide_title_label->SetLabel(_L("For better drying results, remove the filament and allow it to rotate.")); + m_guide_description_label->SetLabel(_L("The AMS will automatically rotate the stored filament slots to enhance the drying performance.") + + "\n" + _L("Alternatively, you can dry the filament without removing it.") + + "\n" + "*" + _L("Unknown filaments will be treated as PLA.")); + m_guide_description_label->Wrap(FromDIP(300)); + } else { + m_guide_title_label->SetLabel(_L("Please store the filament marked with an exclamation mark.")); + m_guide_description_label->SetLabel(_L("Filament left in the feeder during drying may soften because the drying temperature exceeds the softening point of materials like PLA and TPU.") + + "\n" + "*" + _L("Unknown filaments will be treated as PLA.")); + m_guide_description_label->Wrap(FromDIP(300)); + } + + m_start_button->Enable(can_start); + m_guide_page->Layout(); + m_guide_page->Thaw(); +} + +int AMSDryCtrWin::update_filament_list(DevAms* dev_ams, MachineObject* obj) +{ + const bool ams_changed = is_ams_changed(dev_ams); + bool rebuilt_filament_list = false; + + // Build `m_tray_ids` and the combobox items from the preset bundle. + // This list is also used as a lookup table in non-idle state (type -> name), + // so it must be available even when the combobox is disabled. + auto rebuild_filament_list = [&]() -> bool { + m_trays_combo->Clear(); + m_tray_ids.clear(); + + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + if (!preset_bundle || !obj) { + return false; + } + + std::set filament_id_set; + auto & filaments = preset_bundle->filaments; + + // Get nozzle diameter using the same method as AMSMaterialsSetting::Popup + std::ostringstream stream; + int extruder_id = obj->GetFilaSystem()->GetExtruderIdByAmsId(m_ams_info.m_ams_id); + if (!obj->GetExtderSystem()->GetExtderById(extruder_id)) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " get extruder id failed"; + extruder_id = 0; + } + stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id); + std::string nozzle_diameter_str = stream.str(); + std::set printer_names = preset_bundle->get_printer_names_by_printer_type_and_nozzle( + DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str); + + for (auto filament_it = filaments.begin(); filament_it != filaments.end(); ++filament_it) { + Preset& preset = *filament_it; + // Filter by system preset: root preset and (system preset or user preset is supported) + if (filaments.get_preset_base(*filament_it) != &preset || (!filament_it->is_system && !obj->is_support_user_preset)) { + continue; + } + + ConfigOption * printer_opt = filament_it->config.option("compatible_printers"); + ConfigOptionStrings *printer_strs = dynamic_cast(printer_opt); + if (!printer_strs) continue; + + for (auto printer_str : printer_strs->values) { + if (printer_names.find(printer_str) != printer_names.end()) { + if (filament_id_set.find(filament_it->filament_id) != filament_id_set.end()) { + continue; + } + + filament_id_set.insert(filament_it->filament_id); + auto filament_alias = filaments.get_preset_alias(*filament_it, true); + if (!filament_alias.empty()) { + auto opt_info = preset_bundle->get_filament_by_filament_id(filament_it->filament_id); + if (opt_info.has_value()) { + auto real_info = opt_info.value(); + real_info.filament_name = filament_alias; + m_tray_ids.push_back(std::move(real_info)); + m_trays_combo->Append(wxString::FromUTF8(filament_alias)); + } + } + } + } + } + + if (m_tray_ids.empty()) { + BOOST_LOG_TRIVIAL(warning) << "AMSDryCtrWin::update_filament_list: No valid filaments found"; + return false; + } + + return true; + }; + + if (ams_changed || m_tray_ids.empty() || m_trays_combo->GetCount() == 0) { + // Disable start button during rebuild + if (m_start_button) { + m_start_button->Disable(); + } + + rebuilt_filament_list = rebuild_filament_list(); + } + + if (!is_dry_ctr_idle(dev_ams)) { + const std::string& ams_id = dev_ams->GetAmsId(); + const auto settings_opt = dev_ams->GetDrySettings(); + + // Set the combobox display label by the filament "type" string. + // Priority: last saved selection for this AMS (if the type matches) -> fallback to current `m_tray_ids`. + auto set_filament_label_by_type = [&](const std::string& filament_type) { + if (filament_type.empty()) { + return; + } + + auto it_name = m_dry_setting.m_filament_names.find(ams_id); + auto it_type = m_dry_setting.m_filament_type.find(ams_id); + if (it_name != m_dry_setting.m_filament_names.end() && it_type != m_dry_setting.m_filament_type.end() + && it_type->second == filament_type) { + m_trays_combo->SetLabel(wxString::FromUTF8(it_name->second)); + return; + } + + const auto it = std::find_if(m_tray_ids.begin(), m_tray_ids.end(), [&](const FilamentBaseInfo& tray) { + return tray.filament_type == filament_type; + }); + if (it != m_tray_ids.end()) { + m_trays_combo->SetLabel(wxString::FromUTF8(it->filament_name)); + } + }; + + if (settings_opt.has_value()) { + const auto& settings = settings_opt.value(); + m_temperature_input->SetValue(std::to_string(settings.dry_temp)); + m_time_input->SetValue(std::to_string(settings.dry_hour)); + set_filament_label_by_type(settings.dry_filament); + } else { + auto it_name = m_dry_setting.m_filament_names.find(ams_id); + if (it_name != m_dry_setting.m_filament_names.end()) { + m_trays_combo->SetLabel(wxString::FromUTF8(it_name->second)); + m_temperature_input->SetValue(std::to_string(m_dry_setting.m_dry_temp[ams_id])); + m_time_input->SetValue(std::to_string(m_dry_setting.m_dry_time[ams_id])); + } + } + + return 0; + } + + // Idle state: avoid re-selecting / firing selection change unless the list was rebuilt. + if (!rebuilt_filament_list && !ams_changed && is_dry_ctr_idle()) { + return 0; + } + + if (m_tray_ids.empty()) { + return 0; + } + + // Select recommended drying temperature and default filament + float min_dry_temp = std::numeric_limits::max(); + std::string default_filament_id = "GFA00"; + bool has_ready = false; + const auto fallback_preset = DevUtilBackend::GetFilamentDryingPreset("GFA00"); + for (const auto& tray_pair : dev_ams->GetTrays()) { + if (!tray_pair.second || !tray_pair.second->is_tray_info_ready()) continue; + has_ready = true; + wxString filament_type = tray_pair.second->get_display_filament_type(); + Slic3r::DevFilamentDryingPreset preset; + if (filament_type.IsEmpty()) { + // no filament type, is PLA + if (!fallback_preset) continue; + preset = fallback_preset.value(); + } else { + auto preset_opt = tray_pair.second->get_ams_drying_preset(); + if (preset_opt.has_value()) { + preset = preset_opt.value(); + } else { + // no preset,is PLA + if (!fallback_preset) continue; + preset = fallback_preset.value(); + } + } + float cur_temp = m_printer_status.m_is_printing + ? std::min({preset.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()], + preset.filament_dev_drying_softening_temperature, preset.filament_dev_ams_drying_heat_distortion_temperature}) + : preset.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()]; + if (cur_temp < min_dry_temp) { + min_dry_temp = cur_temp; + default_filament_id = preset.filament_id; + } + } + // if no tray is ready, use PLA as default + if (!has_ready) { + if (fallback_preset.has_value()) { + Slic3r::DevFilamentDryingPreset preset = fallback_preset.value(); + float cur_temp = m_printer_status.m_is_printing + ? std::min({preset.filament_dev_ams_drying_temperature_on_print[dev_ams->GetAmsType()], + preset.filament_dev_drying_softening_temperature, preset.filament_dev_ams_drying_heat_distortion_temperature}) + : preset.filament_dev_ams_drying_temperature_on_idle[dev_ams->GetAmsType()]; + min_dry_temp = cur_temp; + default_filament_id = preset.filament_id; + } + } + m_ams_info.m_recommand_dry_temp = min_dry_temp; + + // Set default selection + unsigned int default_index = 0; + for (unsigned int i = 0; i < m_tray_ids.size(); i++) { + if (m_tray_ids[i].filament_id == default_filament_id) { + default_index = i; + break; + } + } + m_trays_combo->SetSelection(default_index); + wxCommandEvent evt(wxEVT_COMBOBOX, m_trays_combo->GetId()); + evt.SetInt(default_index); + OnFilamentSelectionChanged(evt); + return 1; +} + +std::shared_ptr AMSDryCtrWin::get_fila_system() const +{ + std::shared_ptr fila_system = m_fila_system.lock(); + if (!fila_system) { + const_cast(this)->Close(); + } + return fila_system; +} + +void AMSDryCtrWin::update_printer_state(MachineObject* obj) +{ + m_printer_status.m_is_printing = obj->is_in_printing() + && obj->GetExtderSystem()->GetCurrentAmsId() == m_ams_info.m_ams_id; +} + +void AMSDryCtrWin::update(std::shared_ptr fila_system, MachineObject* obj) +{ + if (!fila_system || !obj) { + return; + } + + update_printer_state(obj); + m_fila_system = fila_system; + + DevAms* dev_ams = fila_system->GetAmsById(m_ams_info.m_ams_id); + if (!dev_ams) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::update: Invalid AMS id"; + m_ams_info.m_ams_id = ""; + Close(); + return; + } + + if (!dev_ams->IsSupportRemoteDry(fila_system->GetOwner())) { + BOOST_LOG_TRIVIAL(info) << "AMSDryCtrWin::update: Selected AMS does not support remote drying"; + Close(); + return; + } + + + update_ams_change(dev_ams); + + update_image(dev_ams->GetAmsType(), + dev_ams->GetDryStatus().has_value()? dev_ams->GetDryStatus().value(): DevAms::DryStatus::Off, + dev_ams->GetDrySubStatus().has_value()? dev_ams->GetDrySubStatus().value(): DevAms::DrySubStatus::Off, + dev_ams->GetHumidityPercent()); + + update_state(dev_ams); + update_dryness_status(dev_ams); + + update_filament_list(dev_ams, obj); + update_filament_guide_info(dev_ams); + + m_is_ams_changed = false; + + check_values_changed(dev_ams); + + Layout(); + Refresh(); +} + +bool AMSDryCtrWin::is_ams_changed(DevAms* dev_ams) +{ + return m_ams_info.m_ams_id != dev_ams->GetAmsId() || m_is_ams_changed; +} + +bool AMSDryCtrWin::is_dry_status_changed(DevAms* dev_ams) +{ + if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) { + return true; + } + + return dev_ams->GetDryStatus().value() != m_ams_info.m_dry_status + || dev_ams->GetDrySubStatus().value() != m_ams_info.m_dry_sub_status; +} + +bool AMSDryCtrWin::is_dry_ctr_idle(DevAms* dev_ams) +{ + if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) { + return true; + } + + return dev_ams->GetDryStatus().value() == DevAms::DryStatus::Off + || dev_ams->GetDryStatus().value() == DevAms::DryStatus::Cooling; +} + +bool AMSDryCtrWin::is_dry_ctr_idle() +{ + return m_ams_info.m_dry_status == DevAms::DryStatus::Off || m_ams_info.m_dry_status == DevAms::DryStatus::Cooling; +} + +bool AMSDryCtrWin::is_dry_ctr_err(DevAms* dev_ams) +{ + if (!dev_ams->GetDryStatus().has_value() || !dev_ams->GetDrySubStatus().has_value()) { + return false; + } + + return dev_ams->GetDryStatus().value() == DevAms::DryStatus::Error; +} + +} // GUI +} // Slic3r diff --git a/src/slic3r/GUI/AMSDryControl.hpp b/src/slic3r/GUI/AMSDryControl.hpp new file mode 100644 index 0000000000..43e177eeb1 --- /dev/null +++ b/src/slic3r/GUI/AMSDryControl.hpp @@ -0,0 +1,253 @@ +#pragma once +#include "GUI_ObjectLayers.hpp" +#include "slic3r/GUI/Widgets/AMSItem.hpp" +#include "slic3r/GUI/Widgets/Label.hpp" +#include "slic3r/GUI/Widgets/PopupWindow.hpp" + +#include "slic3r/GUI/wxExtensions.hpp" +#include "slic3r/GUI/DeviceCore/DevFilaSystem.h" +#include "slic3r/GUI/I18N.hpp" + +#include +#include + + +//Previous defintions +class wxGrid; + +namespace Slic3r { + +namespace GUI { + + +enum class DryCtrState { + IDLE, + DRY_WHEN_PRINT, + UNKNOWN +}; + +enum class DryCtrDev { + N3S, + N3F, + UNKNOWN +}; + +struct DryingPreset { + DryCtrState state; + DryCtrDev dev; + int dry_temp; + int dry_time; +}; + +class FilamentItemPanel : public wxPanel +{ +public: + FilamentItemPanel(wxWindow* parent, const wxString& text, const std::string& icon_name = "", + wxWindowID id = wxID_ANY); + + void SetText(const wxString& text); + void SetIcon(const std::string& icon_name); + void msw_rescale(); + +private: + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + + Label* m_text_label; + wxStaticBitmap* m_icon_bitmap; + int m_target_size; + std::string m_icon_name; + ScalableBitmap m_icon; +}; + +class AMSFilamentPanel : public wxPanel +{ + wxBoxSizer* m_filament_sizer; + Label* m_ams_name_label; + int m_border_radius; + wxPanel* m_filament_container{nullptr}; + std::vector m_filament_items; + +public: + AMSFilamentPanel(wxWindow* parent, const wxString& ams_name, wxWindowID id = wxID_ANY); + + void AddFilamentItem(const wxString& text, const std::string& icon_name); + void AddFilamentItem(FilamentItemPanel* panel); + void SetAmsName(const wxString& ams_name); + void Clear(); + void msw_rescale(); +private: + void OnPaint(wxPaintEvent& event); +}; + + +class AMSDryCtrWin : public DPIDialog +{ +public: + AMSDryCtrWin(wxWindow *parent); + ~AMSDryCtrWin(); + + void msw_rescale(); + void update(std::shared_ptr fila_system, MachineObject* obj); + void set_ams_id(const std::string& ams_id); + +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + +private: + wxSimplebook* m_main_simplebook{nullptr}; + wxPanel* m_original_page{nullptr}; + + wxWindow* m_amswin{nullptr}; + wxBoxSizer* m_sizer_ams_items{nullptr}; + wxScrolledWindow* m_panel_prv_left {nullptr}; + wxScrolledWindow* m_panel_prv_right{nullptr}; + wxBoxSizer* m_sizer_prv_left{nullptr}; + wxBoxSizer* m_sizer_prv_right{nullptr}; + + // left panel related members + ScalableBitmap m_humidity_image; + wxStaticBitmap* m_humidity_img{nullptr}; + Label* m_image_description{nullptr}; + wxStaticBitmap* m_image_description_icon{nullptr}; + ScalableBitmap m_description_icon_bitmap; + + Label* m_humidity_data_label = nullptr; + Label* m_temperature_data_label = nullptr; + Label* m_time_data_label = nullptr; + wxBoxSizer* m_time_descrition_container = nullptr; + + // right panel related members + wxBoxSizer* m_normal_state_sizer{nullptr}; + wxBoxSizer* m_cannot_dry_sizer{nullptr}; + wxBoxSizer* m_dry_error_sizer{nullptr}; + Label* m_cannot_dry_description_label = nullptr; + + // right panel normal state + ComboBox* m_trays_combo; + std::vector m_tray_ids; + wxTextCtrl* m_temperature_input; + wxTextCtrl* m_time_input; + Label* m_normal_description; + Button* m_start_button{nullptr}; + Button* m_next_button{nullptr}; + Button* m_stop_button{nullptr}; + Button* m_back_button{nullptr}; + Button* m_unload_button{nullptr}; + + // guide page description + Label* m_guide_title_label{nullptr}; + Label* m_guide_description_label{nullptr}; + + wxCheckBox* m_rotate_spool_toggle{nullptr}; + + wxPanel* m_progress_page; + ProgressBar* m_progress_gauge; + wxTimer* m_progress_timer; + + std::optional m_stop_button_restore_deadline; + std::optional m_unload_button_restore_deadline; + + Label* m_progress_title; + int m_progress_value; + int m_progress_message_index; + std::vector m_progress_text = { + _L("Starting: Checking adapter connection"), + _L("Starting: Checking filament status"), + _L("Starting: Checking drying presets"), + _L("Starting: Checking filament location"), + _L("Starting: Checking air intake"), + _L("Starting: Checking air vent") + }; + + // Guide page + wxPanel* m_guide_page{nullptr}; + AMSFilamentPanel* m_ams_filament_panel{nullptr}; + std::map m_filament_items; + wxStaticBitmap* m_image_placeholder{nullptr}; + ScalableBitmap m_guide_image; + + + bool m_is_ams_changed = false; + std::weak_ptr m_fila_system; + struct { + std::string m_ams_id; + DevAmsType m_model = DevAmsType::EXT_SPOOL; + DevAms::DryStatus m_dry_status = DevAms::DryStatus::Off; + DevAms::DrySubStatus m_dry_sub_status = DevAms::DrySubStatus::Off; + int m_humidity_percent; + int m_temperature; + int m_left_dry_time; + float m_recommand_dry_temp; + } m_ams_info; + + struct { + std::unordered_map m_filament_names; + std::unordered_map m_filament_type; + std::unordered_map m_dry_temp; + std::unordered_map m_dry_time; + } m_dry_setting; + + struct { + bool m_is_printing = false; + } m_printer_status; + +private: + void create(); + wxBoxSizer* create_guide_page_sizer(wxPanel* parent); + wxBoxSizer* create_main_content_section(wxPanel* parent); + wxBoxSizer* create_guide_info_filament(wxPanel* parent); + wxBoxSizer* create_guide_info_section(wxPanel* parent); + wxBoxSizer* create_guide_right_section(wxPanel* parent); + wxBoxSizer* create_main_page_sizer(wxPanel* parent); + wxBoxSizer* create_left_panel(wxPanel* parent); + wxBoxSizer* create_humidity_status_section(wxPanel* parent); + wxBoxSizer* create_description_item(wxPanel* parent, const wxString& title, Label*& dataLabel); + wxBoxSizer* create_status_descriptions_section(wxPanel* parent); + + wxBoxSizer* create_right_panel(wxPanel* parent); + wxBoxSizer* create_normal_state_panel(wxPanel* parent); + wxBoxSizer* create_cannot_dry_panel(wxPanel* parent); + wxBoxSizer* create_drying_error_panel(wxPanel* parent); + Button* create_button(wxPanel* parent, const wxString& title, + const wxColour& background_color, const wxColour& border_color, const wxColour& text_color); + + wxBoxSizer* create_progress_page_sizer(wxPanel* parent); + void OnProgressTimer(wxTimerEvent& event); + void OnClose(wxCloseEvent& event); + void OnShow(wxShowEvent& event); + + void OnFilamentSelectionChanged(wxCommandEvent& event); + + + wxScrolledWindow* create_preview_scrolled_window(wxWindow* parent); + + bool check_values_changed(DevAms* dev_ams); + int update_image(DevAmsType type, DevAms::DryStatus status, DevAms::DrySubStatus sub_status, int humidity_percent); + void update_img_description(DevAms::DryStatus status, DevAms::DrySubStatus sub_status); + void update_normal_description(DevAms* dev_ams); + int update_state(DevAms* dev_ams); + int update_dryness_status(DevAms* dev_ams); + int update_ams_change(DevAms* dev_ams); + int update_filament_list(DevAms* dev_ams, MachineObject* obj); + void update_filament_guide_info(DevAms* dev_ams); + void update_normal_state(DevAms* dev_ams); + void update_printer_state(MachineObject* obj); + + std::shared_ptr get_fila_system() const; + void start_sending_drying_command(); + void restore_stop_button_if_deadline_passed(); + void restore_unload_button_if_deadline_passed(); + void update_button_size(Button* button); + + bool is_dry_status_changed(DevAms* dev_ams); + bool is_dry_ctr_idle(DevAms* dev_ams); + bool is_ams_changed(DevAms* dev_ams); + bool is_dry_ctr_idle(); + bool is_tray_changed(DevAms* dev_ams); + bool is_dry_ctr_err(DevAms* dev_ams); + +}; + +} // GUI +} // Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevDefs.h b/src/slic3r/GUI/DeviceCore/DevDefs.h index 4e55f7cbae..640f984024 100644 --- a/src/slic3r/GUI/DeviceCore/DevDefs.h +++ b/src/slic3r/GUI/DeviceCore/DevDefs.h @@ -41,6 +41,16 @@ enum AmsStatusMain AMS_STATUS_MAIN_UNKNOWN = 0xFF, }; +enum DevAmsType : int +{ + EXT_SPOOL = 0, // EXT + AMS = 1, // AMS1 + AMS_LITE = 2, // AMS-Lite + N3F = 3, // N3F, AMS 2PRO + N3S = 4, // N3S, AMS HT + AMS_LITE_MIXED = 5, // AMS-Lite for N9 +}; + // Device-numbered filament-change step codes. Newer firmware reports the exact change-step // sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding // steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum. diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index fccc31c47b..038032d0ca 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/I18N.hpp" #include "DevUtil.h" +#include "DevUtilBackend.h" using namespace nlohmann; @@ -99,6 +100,11 @@ std::string DevAmsTray::get_filament_type() return m_fila_type; } +std::optional DevAmsTray::get_ams_drying_preset() const +{ + return DevUtilBackend::GetFilamentDryingPreset(setting_id); +} + DevAms::DevAms(const std::string& ams_id, int extruder_id, AmsType type) { @@ -112,7 +118,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type) m_ams_id = ams_id; m_ext_id = nozzle_id; m_ams_type = (AmsType)type; - assert(DUMMY < type && m_ams_type <= AMS_LITE_MIXED); + assert(EXT_SPOOL < type && m_ams_type <= AMS_LITE_MIXED); } DevAms::~DevAms() @@ -193,6 +199,27 @@ DevAmsTray* DevAms::GetTray(const std::string& tray_id) const return nullptr; } +bool DevAms::IsSupportRemoteDry(const MachineObject* obj) const +{ + if (obj && obj->is_support_remote_dry) { + return SupportDrying(); + } + + return false; +} + +bool DevAms::AmsIsDrying() +{ + if (!GetDryStatus().has_value()) { + return false; + } + + return GetDryStatus().value() == DevAms::DryStatus::Checking + || GetDryStatus().value() == DevAms::DryStatus::Drying + || GetDryStatus().value() == DevAms::DryStatus::Error + || GetDryStatus().value() == DevAms::DryStatus::CannotStopHeatOutofControl; +} + DevFilaSystem::~DevFilaSystem() { for (auto it = amsList.begin(); it != amsList.end(); it++) @@ -515,9 +542,18 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS ; } - if (it->contains("dry_time") && (*it)["dry_time"].is_number()) + + if (it->contains("temp")) { - curr_ams->m_left_dry_time = (*it)["dry_time"].get(); + std::string temp = (*it)["temp"].get(); + try + { + curr_ams->m_current_temperature = DevUtil::string_to_float(temp); + } + catch (...) + { + curr_ams->m_current_temperature = INVALID_AMS_TEMPERATURE; + } } if (it->contains("humidity")) @@ -546,17 +582,32 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS } } - - if (it->contains("temp")) + if (it->contains("dry_time") && (*it)["dry_time"].is_number()) { - std::string temp = (*it)["temp"].get(); - try - { - curr_ams->m_current_temperature = DevUtil::string_to_float(temp); + curr_ams->m_left_dry_time = (*it)["dry_time"].get(); + } + + // Drying status — only parse if printer supports remote drying + if (obj->is_support_remote_dry) { + if (it->contains("info")) { + const std::string& info = (*it)["info"].get(); + curr_ams->m_dry_status = (DevAms::DryStatus)DevUtil::get_flag_bits(info, 4, 4); + curr_ams->m_dry_fan1_status = (DevAms::DryFanStatus)DevUtil::get_flag_bits(info, 18, 2); + curr_ams->m_dry_fan2_status = (DevAms::DryFanStatus)DevUtil::get_flag_bits(info, 20, 2); + curr_ams->m_dry_sub_status = (DevAms::DrySubStatus)DevUtil::get_flag_bits(info, 22, 2); } - catch (...) - { - curr_ams->m_current_temperature = INVALID_AMS_TEMPERATURE; + + if (it->contains("dry_setting")) { + const auto& j_dry_settings = (*it)["dry_setting"]; + DevAms::DrySettings dry_settings; + DevJsonValParser::ParseVal(j_dry_settings, "dry_filament", dry_settings.dry_filament); + DevJsonValParser::ParseVal(j_dry_settings, "dry_temperature", dry_settings.dry_temp); + DevJsonValParser::ParseVal(j_dry_settings, "dry_duration", dry_settings.dry_hour); + curr_ams->m_dry_settings = dry_settings; + } + + if (it->contains("dry_sf_reason")) { + curr_ams->m_dry_cannot_reasons = DevJsonValParser::GetVal>((*it), "dry_sf_reason"); } } diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h index 604f228c13..27c59183c1 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include @@ -20,6 +23,7 @@ namespace Slic3r { class MachineObject; +struct DevFilamentDryingPreset; /** * DevAmsTray - Represents a single filament tray/slot in an AMS unit or virtual tray. @@ -106,6 +110,8 @@ public: // static static wxColour decode_color(const std::string& color); + + std::optional get_ams_drying_preset() const; }; /** @@ -133,14 +139,67 @@ class DevAms { friend class DevFilaSystemParser; public: - enum AmsType : int + using AmsType = DevAmsType; + static constexpr AmsType EXT_SPOOL = DevAmsType::EXT_SPOOL; + static constexpr AmsType AMS = DevAmsType::AMS; + static constexpr AmsType AMS_LITE = DevAmsType::AMS_LITE; + static constexpr AmsType N3F = DevAmsType::N3F; + static constexpr AmsType N3S = DevAmsType::N3S; + static constexpr AmsType AMS_LITE_MIXED = DevAmsType::AMS_LITE_MIXED; + +public: + + enum class DryCtrlMode : int { - DUMMY = 0, - AMS = 1, // AMS - AMS_LITE = 2, // AMS-Lite - N3F = 3, // N3F - N3S = 4, // N3S - AMS_LITE_MIXED = 5, // AMS-Lite mixed (A2L / N9). Presents as AMS_LITE to generic callers; see GetAmsType()/IsAmsLiteMixed(). + Off = 0, + OnTime = 1, + OnHumidity = 2, + }; + + enum class DryStatus : char + { + Off = 0, + Checking = 1, + Drying = 2, + Cooling = 3, + Stopping = 4, + Error = 5, + CannotStopHeatOutofControl = 6, + PrdTesting = 7, + }; + + enum class DrySubStatus + { + Off = 0, + Heating = 1, + Dehumidify = 2, + }; + + enum class DryFanStatus : char + { + Off = 0, + On = 1, + }; + + enum class CannotDryReason : int + { + TaskOccupied = 0, + InsufficientPower = 1, + AmsBusy = 2, + ConsumableAtAmsOutlet = 3, + InitiatingAmsDrying = 4, + NotSupportedIn2dMode = 5, + DryingInProgress = 6, + Upgrading = 7, + InsufficientPowerNeedPluginPower = 8, + FilamentAtAmsOutletManualUnload = 10, + }; + + struct DrySettings + { + std::string dry_filament; + int dry_temp = -1; // -1 means invalid + int dry_hour = -1; // -1 means invalid, hours }; public: @@ -193,11 +252,20 @@ public: int GetHumidityLevel() const { return m_humidity_level; } int GetHumidityPercent() const { return m_humidity_percent; } - // Only N3F/N3S dry. Written explicitly (not `> AMS_LITE`) so the new AMS_LITE_MIXED (N9) is - // excluded; identical to the old expression for every pre-existing type. - bool SupportDrying() const { return (m_ams_type == N3F) || (m_ams_type == N3S); } + bool SupportDrying() const { return m_ams_type == DevAmsType::N3F || m_ams_type == DevAmsType::N3S; } int GetLeftDryTime() const { return m_left_dry_time; } + // remote drying control + bool IsSupportRemoteDry(const MachineObject* obj) const; + std::optional GetDryStatus() const { return m_dry_status; }; + std::optional GetDrySubStatus() const { return m_dry_sub_status; } + std::optional GetFan1Status() const { return m_dry_fan1_status; } + std::optional GetFan2Status() const { return m_dry_fan2_status; } + std::optional> GetCannotDryReason() const { return m_dry_cannot_reasons; } + std::optional GetDrySettings() const { return m_dry_settings; }; + + bool AmsIsDrying(); + private: AmsType m_ams_type = AmsType::AMS; std::string m_ams_id; @@ -218,6 +286,14 @@ private: int m_humidity_level = 5; // AmsType::AMS int m_humidity_percent = -1; // N3F N3S, the percentage, -1 means invalid. eg. 100 means 100% int m_left_dry_time = 0; + + // see is_support_remote_dry + std::optional m_dry_status; + std::optional m_dry_sub_status; + std::optional m_dry_fan1_status; + std::optional m_dry_fan2_status; + std::optional> m_dry_cannot_reasons; + std::optional m_dry_settings; }; /** @@ -299,7 +375,11 @@ public: public: // ctrls int CtrlAmsReset() const; - + + // crtl + int CtrlAmsStartDryingHour(int ams_id, std::string filament_type, int tag_temp, int tag_duration_hour, bool rotate_tray, int cooling_temp, bool close_power_conflict = false) const; + int CtrlAmsStopDrying(int ams_id) const; + public: static bool IsBBL_Filament(std::string tag_uid); @@ -337,4 +417,18 @@ public: static void ParseV1_0(const json& print_json, MachineObject* obj, DevFilaSystem* system, bool key_field_only); }; +struct DevFilamentDryingPreset +{ + std::string filament_id; + + std::unordered_set ams_limitations; // only use ams types in the set + std::unordered_map filament_dev_ams_drying_time_on_idle; // hour + std::unordered_map filament_dev_ams_drying_temperature_on_idle; + std::unordered_map filament_dev_ams_drying_time_on_print; // hour + std::unordered_map filament_dev_ams_drying_temperature_on_print; + float filament_dev_drying_cooling_temperature = 0.0f; + float filament_dev_drying_softening_temperature = 0.0f; + float filament_dev_ams_drying_heat_distortion_temperature = 0.0f; +}; + }// namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystemCtrl.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystemCtrl.cpp index f81d6a2835..acdc81dd8c 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystemCtrl.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystemCtrl.cpp @@ -16,4 +16,44 @@ int DevFilaSystem::CtrlAmsReset() const return m_owner->publish_json(jj_command); } +int DevFilaSystem::CtrlAmsStartDryingHour(int ams_id, + std::string filament_type, + int tag_temp, + int tag_duration_hour, + bool rotate_tray, + int cooling_temp, + bool close_power_conflict) const +{ + json jj_command; + jj_command["print"]["command"] = "ams_filament_drying"; + jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + jj_command["print"]["ams_id"] = ams_id; + jj_command["print"]["mode"] = static_cast(DevAms::DryCtrlMode::OnTime); + jj_command["print"]["filament"] = filament_type; + jj_command["print"]["temp"] = tag_temp; + jj_command["print"]["duration"] = tag_duration_hour; + jj_command["print"]["humidity"] = 0; + jj_command["print"]["rotate_tray"] = rotate_tray; + jj_command["print"]["cooling_temp"] = cooling_temp; + jj_command["print"]["close_power_conflict"] = close_power_conflict; + return m_owner->publish_json(jj_command); +} + +int DevFilaSystem::CtrlAmsStopDrying(int ams_id) const +{ + json jj_command; + jj_command["print"]["command"] = "ams_filament_drying"; + jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + jj_command["print"]["ams_id"] = ams_id; + jj_command["print"]["mode"] = static_cast(DevAms::DryCtrlMode::Off); + jj_command["print"]["filament"] = ""; + jj_command["print"]["temp"] = 0; + jj_command["print"]["duration"] = 0; + jj_command["print"]["humidity"] = 0; + jj_command["print"]["rotate_tray"] = false; + jj_command["print"]["cooling_temp"] = 0; + jj_command["print"]["close_power_conflict"] = false; + return m_owner->publish_json(jj_command); +} + } \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevMapping.cpp b/src/slic3r/GUI/DeviceCore/DevMapping.cpp index cccc62d606..5dc16f4250 100644 --- a/src/slic3r/GUI/DeviceCore/DevMapping.cpp +++ b/src/slic3r/GUI/DeviceCore/DevMapping.cpp @@ -199,7 +199,7 @@ namespace Slic3r } } FilamentInfo info; - _parse_tray_info(atoi(tray.id.c_str()), 0, DevAms::DUMMY, tray, info); + _parse_tray_info(atoi(tray.id.c_str()), 0, DevAms::EXT_SPOOL, tray, info); tray_filaments.emplace(std::make_pair(info.tray_id, info)); } } diff --git a/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp b/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp index 4d909bf68f..32d30379f6 100644 --- a/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp +++ b/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp @@ -1,7 +1,11 @@ #include "DevUtilBackend.h" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" + #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_App.hpp" + +#include namespace Slic3r { @@ -15,4 +19,68 @@ std::shared_ptr DevUtilBackend::GetNozz return nullptr; } +static std::unordered_map s_ams_type_map = { + {"0", DevAmsType::N3F}, + {"1", DevAmsType::N3S}, +}; + +std::optional DevUtilBackend::GetFilamentDryingPreset(const std::string& fila_id) +{ + if (fila_id.empty() || !GUI::wxGetApp().preset_bundle) { + return std::nullopt; + } + + for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter) { + const Preset& filament_preset = *iter; + const auto& config = filament_preset.config; + if (filament_preset.filament_id == fila_id) { + DevFilamentDryingPreset info; + info.filament_id = fila_id; + try { + if (config.has("filament_dev_ams_drying_ams_limitations")) { + std::vector types = config.option("filament_dev_ams_drying_ams_limitations")->values; + for (auto type : types) { + if (s_ams_type_map.count(type) == 0) { + continue; + } + info.ams_limitations.insert(s_ams_type_map[type]); + } + } + + if (config.has("filament_dev_ams_drying_temperature")) { + info.filament_dev_ams_drying_temperature_on_idle[DevAmsType::N3F] = config.option("filament_dev_ams_drying_temperature")->get_at(0); + info.filament_dev_ams_drying_temperature_on_idle[DevAmsType::N3S] = config.option("filament_dev_ams_drying_temperature")->get_at(1); + info.filament_dev_ams_drying_temperature_on_print[DevAmsType::N3F] = config.option("filament_dev_ams_drying_temperature")->get_at(2); + info.filament_dev_ams_drying_temperature_on_print[DevAmsType::N3S] = config.option("filament_dev_ams_drying_temperature")->get_at(3); + } + + if (config.has("filament_dev_ams_drying_time")) { + info.filament_dev_ams_drying_time_on_idle[DevAmsType::N3F] = config.option("filament_dev_ams_drying_time")->get_at(0); + info.filament_dev_ams_drying_time_on_idle[DevAmsType::N3S] = config.option("filament_dev_ams_drying_time")->get_at(1); + info.filament_dev_ams_drying_time_on_print[DevAmsType::N3F] = config.option("filament_dev_ams_drying_time")->get_at(2); + info.filament_dev_ams_drying_time_on_print[DevAmsType::N3S] = config.option("filament_dev_ams_drying_time")->get_at(3); + } + + if (config.has("filament_dev_drying_softening_temperature")) { + info.filament_dev_drying_softening_temperature = config.option("filament_dev_drying_softening_temperature")->get_at(0); + } + + if (config.has("filament_dev_ams_drying_heat_distortion_temperature")) { + info.filament_dev_ams_drying_heat_distortion_temperature = config.option("filament_dev_ams_drying_heat_distortion_temperature")->get_at(0); + } + + if (config.has("filament_dev_drying_cooling_temperature")) { + info.filament_dev_drying_cooling_temperature = config.option("filament_dev_drying_cooling_temperature")->get_at(0); + } + + return info; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what(); + } + } + } + + return std::nullopt; +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevUtilBackend.h b/src/slic3r/GUI/DeviceCore/DevUtilBackend.h index 54839d9315..5c7869f1e4 100644 --- a/src/slic3r/GUI/DeviceCore/DevUtilBackend.h +++ b/src/slic3r/GUI/DeviceCore/DevUtilBackend.h @@ -1,18 +1,15 @@ /** * @file DevUtilBackend.h - * @brief Static helpers bridging the backend (slicer/preset) to the device GUI. - * - * Scope (H2C nozzle rack): exposes only GetNozzleGroupResult — the accessor the print-dispatch - * nozzle-mapping V1 request and result-resolve helpers read. Related helpers (per-nozzle info - * collection via the ExtruderNozzleInfos/NozzleDef type pair, and the drying-preset lookup) are - * not implemented here and left to the consuming code. + * @brief Provides common static utility methods for backend (preset/slicing). */ #pragma once +#include "DevDefs.h" +#include "DevFilaSystem.h" #include "libslic3r/MultiNozzleUtils.hpp" - -#include +#include +#include namespace Slic3r { @@ -23,10 +20,15 @@ class DevUtilBackend public: DevUtilBackend() = delete; +public: + // for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off // the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()). // Returns nullptr when there is no plater / no current result. static std::shared_ptr GetNozzleGroupResult(Slic3r::GUI::Plater* plater); + + // for filament preset + static std::optional GetFilamentDryingPreset(const std::string& fila_id); }; }; // namespace Slic3r diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 527fe4f372..b23528a372 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -568,7 +568,7 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s m_extder_system = new DevExtderSystem(this); m_extension_tool = DevExtensionTool::Create(this); m_nozzle_system = new DevNozzleSystem(this); - m_fila_system = new DevFilaSystem(this); + m_fila_system = std::make_shared(this); m_fila_switch = new DevFilaSwitch(this); m_hms_system = new DevHMS(this); m_config = new DevConfig(this); @@ -617,8 +617,6 @@ MachineObject::~MachineObject() delete m_ctrl; m_ctrl = nullptr; - delete m_fila_system; - m_fila_system = nullptr; delete m_fila_switch; m_fila_switch = nullptr; @@ -3770,7 +3768,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ update_printer_preset_name(); update_filament_list(); if (jj.contains("ams")) { - DevFilaSystemParser::ParseV1_0(jj, this, m_fila_system, key_field_only); + DevFilaSystemParser::ParseV1_0(jj, this, m_fila_system.get(), key_field_only); } /* vitrual tray*/ @@ -5095,6 +5093,7 @@ void MachineObject::parse_new_info(json print) // fun2 may have infinite length, use get_flag_bits_no_border if (!fun2.empty()) { is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1; + is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1); is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1; } diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 773bdc200b..4ce4cad8de 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -117,7 +117,7 @@ private: std::shared_ptr m_extension_tool; DevExtderSystem* m_extder_system; DevNozzleSystem* m_nozzle_system; - DevFilaSystem* m_fila_system; + std::shared_ptr m_fila_system; DevFilaSwitch* m_fila_switch; DevFan* m_fan; DevBed * m_bed; @@ -342,7 +342,7 @@ public: std::shared_ptr get_nozzle_mapping_result() const { return m_nozzle_mapping_ptr; } void clear_auto_nozzle_mapping();// defined in DevMappingNozzle.cpp - DevFilaSystem* GetFilaSystem() const { return m_fila_system;} + std::shared_ptr GetFilaSystem() const { return m_fila_system;} DevFilaSwitch* GetFilaSwitch() const { return m_fila_switch;} bool HasAms() const; @@ -635,6 +635,7 @@ public: // fun2 bool is_support_print_with_emmc{false}; + bool is_support_remote_dry = false; bool is_support_check_track_switch_match_slice_printer{false}; bool installed_upgrade_kit{false}; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2789d0b21b..ceb09426a3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2338,11 +2338,6 @@ void GLCanvas3D::remove_curr_plate_all() m_dirty = true; } -void GLCanvas3D::update_plate_thumbnails() -{ - _update_imgui_select_plate_toolbar(); -} - void GLCanvas3D::select_all() { if (!m_gizmos.is_allow_select_all()) { @@ -3223,7 +3218,6 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) // BBS //m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); - _update_imgui_select_plate_toolbar(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); @@ -4833,11 +4827,6 @@ void GLCanvas3D::force_set_focus() { void GLCanvas3D::on_set_focus(wxFocusEvent& evt) { m_tooltip_enabled = false; - if (m_canvas_type == ECanvasType::CanvasPreview) { - // update thumbnails and update plate toolbar - wxGetApp().plater()->update_all_plate_thumbnails(); - _update_imgui_select_plate_toolbar(); - } _refresh_if_shown_on_screen(); m_tooltip_enabled = true; m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style"); @@ -6920,13 +6909,28 @@ void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) { bool GLCanvas3D::_update_imgui_select_plate_toolbar() { bool result = true; - if (!m_sel_plate_toolbar.is_enabled() || m_sel_plate_toolbar.is_render_finish) return false; + if (!m_sel_plate_toolbar.is_enabled()) { + return false; + } + + const auto& p_plater = wxGetApp().plater(); + if (!p_plater) { + return false; + } + + if (!p_plater->is_plate_toolbar_image_dirty()) { + return false; + } + + if (!p_plater->is_gcode_3mf()) { + p_plater->update_all_plate_thumbnails(true); + } _update_select_plate_toolbar_stats_item(); m_sel_plate_toolbar.del_all_item(); - PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); + PartPlateList& plate_list = p_plater->get_partplate_list(); for (int i = 0; i < plate_list.get_plate_count(); i++) { IMToolbarItem* item = new IMToolbarItem(); PartPlate* plate = plate_list.get_plate(i); @@ -6939,7 +6943,7 @@ bool GLCanvas3D::_update_imgui_select_plate_toolbar() } m_sel_plate_toolbar.m_items.push_back(item); } - + p_plater->clear_plate_toolbar_image_dirty(); m_sel_plate_toolbar.is_display_scrollbar = false; return result; } @@ -8797,6 +8801,8 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() return; } + _update_imgui_select_plate_toolbar(); + IMToolbarItem* all_plates_stats_item = m_sel_plate_toolbar.m_all_plates_stats_item; PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); @@ -9213,7 +9219,6 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered; imgui.end(); - m_sel_plate_toolbar.is_render_finish = true; } //BBS: GUI refactor: GLToolbar adjust diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 4c2402c546..55e118fa4c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -997,7 +997,6 @@ public: void select_curr_plate_all(); void select_object_from_idx(std::vector& object_idxs); void remove_curr_plate_all(); - void update_plate_thumbnails(); void select_all(); void deselect_all(); diff --git a/src/slic3r/GUI/IMToolbar.cpp b/src/slic3r/GUI/IMToolbar.cpp index 733683366f..2a569c960b 100644 --- a/src/slic3r/GUI/IMToolbar.cpp +++ b/src/slic3r/GUI/IMToolbar.cpp @@ -61,8 +61,6 @@ void IMToolbar::del_stats_item() void IMToolbar::set_enabled(bool enable) { m_enabled = enable; - if (!m_enabled) - is_render_finish = false; } bool IMReturnToolbar::init() diff --git a/src/slic3r/GUI/IMToolbar.hpp b/src/slic3r/GUI/IMToolbar.hpp index 24152f20ee..bf5bf70756 100644 --- a/src/slic3r/GUI/IMToolbar.hpp +++ b/src/slic3r/GUI/IMToolbar.hpp @@ -51,7 +51,6 @@ public: float icon_height; bool is_display_scrollbar; bool show_stats_item{ false }; - bool is_render_finish{false}; IMToolbar() { icon_width = DEFAULT_TOOLBAR_BUTTON_WIDTH; icon_height = DEFAULT_TOOLBAR_BUTTON_HEIGHT; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c7609be5ef..8974a169d1 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -3158,6 +3158,9 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) shader->set_uniform("Texture", 0); shader->set_uniform("ProjMtx", ortho_projection); + const uint8_t stage = 0; + shader->set_uniform("s_texture", stage); + // Will project scissor/clipping rectangles into framebuffer space const ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports const ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) @@ -3222,6 +3225,7 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) glsafe(::glScissor((int)clip_min.x, (int)(fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y))); // Bind texture, Draw + glsafe(::glActiveTexture(GL_TEXTURE0 + stage)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)))); } diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 711228c0bc..a9a66cb96f 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -348,6 +348,8 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) } m_plater->update(); } + + m_plater->mark_plate_toolbar_image_dirty(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 2b5ebcd086..382a9176b9 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -266,7 +266,12 @@ bool OpenGLManager::init_gl(bool popup_error) else s_compressed_textures_supported = false; - if (GLAD_GL_ARB_framebuffer_object) { + if (s_gl_info.is_version_greater_or_equal_to(3, 0)) { + // ARB framebuffer became a mandatory part of core OpenGL 3.0 + s_framebuffers_type = EFramebufferType::Arb; + BOOST_LOG_TRIVIAL(info) << "Opengl version >= 30, FrameBuffer Type ARB." << std::endl; + } + else if (GLAD_GL_ARB_framebuffer_object) { s_framebuffers_type = EFramebufferType::Arb; BOOST_LOG_TRIVIAL(info) << "Found Framebuffer Type ARB."<< std::endl; } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 1f0454c778..415e1ea0f5 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -2433,6 +2433,9 @@ void PartPlate::set_pos_and_size(Vec3d& origin, int width, int depth, int height m_depth = depth; m_height = height; + if (with_instance_move && m_plater) + m_plater->mark_plate_toolbar_image_dirty(); + return; } @@ -2780,6 +2783,8 @@ int PartPlate::add_instance(int obj_id, int instance_id, bool move_position, Bou } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": plate %1% , m_ready_for_slice changes to %2%") % m_plate_index %m_ready_for_slice; + if (m_plater) + m_plater->mark_plate_toolbar_image_dirty(); return 0; } @@ -5344,6 +5349,9 @@ int PartPlateList::notify_instance_removed(int obj_id, int instance_id) unprintable_plate.update_object_index(obj_id, m_model->objects.size()); } + if (m_plater) + m_plater->mark_plate_toolbar_image_dirty(); + return 0; } @@ -6265,6 +6273,9 @@ int PartPlateList::rebuild_plates_after_arrangement(bool recycle_plates, bool ex } #endif + if (m_plater) + m_plater->mark_plate_toolbar_image_dirty(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":after rebuild, plates count %1%") % m_plate_list.size(); return ret; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45c3098d36..0a1164c3a1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -277,6 +277,21 @@ void Plater::show_illegal_characters_warning(wxWindow* parent) show_error(parent, _L("Invalid name, the following characters are not allowed:") + " <>:/\\|?*\""); } +void Plater::mark_plate_toolbar_image_dirty() +{ + m_b_plate_toolbar_image_dirty = true; +} + +bool Plater::is_plate_toolbar_image_dirty() const +{ + return m_b_plate_toolbar_image_dirty; +} + +void Plater::clear_plate_toolbar_image_dirty() +{ + m_b_plate_toolbar_image_dirty = false; +} + static std::map bed_type_thumbnails = { {BedType::btPC, "bed_cool" }, {BedType::btEP, "bed_engineering" }, @@ -6326,10 +6341,8 @@ void Plater::priv::update(unsigned int flags) //BBS assemble view this->assemble_view->reload_scene(false, flags); - if (current_panel && is_preview_shown()) { - q->force_update_all_plate_thumbnails(); - //update_fff_scene_only_shells(true); - } + // todo: better to mark thumbnail dirty here + q->mark_plate_toolbar_image_dirty(); if (force_background_processing_restart) this->restart_background_process(update_status); @@ -7884,6 +7897,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } q->schedule_background_process(true); + q->mark_plate_toolbar_image_dirty(); return obj_idxs; } @@ -8263,6 +8277,8 @@ void Plater::priv::object_list_changed() main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, can_slice); wxGetApp().params_panel()->notify_object_config_changed(); + + q->mark_plate_toolbar_image_dirty(); } void Plater::priv::select_curr_plate_all() @@ -9166,6 +9182,8 @@ void Plater::priv::update_fff_scene() view3D->reload_scene(true); //BBS: add assemble view related logic assemble_view->reload_scene(true); + + q->mark_plate_toolbar_image_dirty(); } //BBS: add print project related logic @@ -10074,9 +10092,6 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) preview->get_canvas3d()->enable_select_plate_toolbar(true); } } - else { - preview->get_canvas3d()->enable_select_plate_toolbar(false); - } if (current_panel == panel) { @@ -10935,6 +10950,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":finished, reload print soon"); m_is_slicing = false; this->preview->reload_print(false); + q->mark_plate_toolbar_image_dirty(); /* BBS if in publishing progress */ if (m_is_publishing) { if (m_publish_dlg && !m_publish_dlg->was_cancelled()) { @@ -13913,8 +13929,13 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i _obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum(SeamScarfType::None)); _obj->config.set_key_value("gap_fill_target", new ConfigOptionEnum(GapFillTarget::gftNowhere)); print_config->set_key_value("max_volumetric_extrusion_rate_slope", new ConfigOptionFloat(0)); - // ORCA: print the top surface spiral from the center outwards, so the tiles are comparable. - _obj->config.set_key_value("top_surface_fill_order", new ConfigOptionEnum(SurfaceFillOrder::Outward)); + // ORCA: request the calibration's special toolpath order (chords first, center spiral + // last and inside-out) so opposing directions collide into the tactile lip the test + // reads. The special order only applies while the fill order is Default, so reset the + // profile's fill order on the calibration objects; changing the setting on the object + // afterwards deliberately overrides the special order. + _obj->config.set_key_value("calib_flowrate_topinfill_special_order", new ConfigOptionBool(true)); + _obj->config.set_key_value("top_surface_fill_order", new ConfigOptionEnum(SurfaceFillOrder::Default)); // extract flowrate from name, filename format: flowrate_xxx std::string obj_name = _obj->name; @@ -14651,6 +14672,7 @@ void Plater::invalid_all_plate_thumbnails() plate->thumbnail_data.reset(); plate->no_light_thumbnail_data.reset(); } + mark_plate_toolbar_image_dirty(); } void Plater::force_update_all_plate_thumbnails() @@ -14661,7 +14683,6 @@ void Plater::force_update_all_plate_thumbnails() invalid_all_plate_thumbnails(); update_all_plate_thumbnails(true); } - get_preview_canvas3D()->update_plate_thumbnails(); } // BBS: backup diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 1730bd2705..fdba3496e0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -936,6 +936,10 @@ public: bool is_loading_project() const { return m_loading_project; } + void mark_plate_toolbar_image_dirty(); + bool is_plate_toolbar_image_dirty() const; + void clear_plate_toolbar_image_dirty(); + private: struct priv; std::unique_ptr p; @@ -956,6 +960,7 @@ private: std::string m_preview_only_filename; int m_valid_plates_count { 0 }; int m_check_status = 0; // 0 not check, 1 check success, 2 check failed + bool m_b_plate_toolbar_image_dirty{ true }; void suppress_snapshots(); void allow_snapshots(); diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index d8bab12a03..f7d7b6b6fe 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -521,6 +521,14 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater) //m_change_filament_times_sizer->Add(m_img_change_filament_times, 0, wxTOP, FromDIP(2)); m_change_filament_times_sizer->Add(m_txt_change_filament_times, 0, wxTOP, 0); + m_warn_when_drying_sizer = new wxBoxSizer(wxHORIZONTAL); + m_txt_warn_when_drying = new Label(m_scroll_area, wxEmptyString); + m_txt_warn_when_drying->SetFont(::Label::Body_13); + m_txt_warn_when_drying->SetForegroundColour(wxColour("#F09A17")); + m_txt_warn_when_drying->SetBackgroundColour(*wxWHITE); + m_txt_warn_when_drying->SetLabel(_L("To ensure print quality, the drying temperature will be lowered during printing.")); + m_warn_when_drying_sizer->Add(m_txt_warn_when_drying, 0, wxTOP, FromDIP(2)); + /*Advanced Options*/ wxBoxSizer* sizer_split_options = new wxBoxSizer(wxHORIZONTAL); auto m_split_options_line = new wxPanel(m_scroll_area, wxID_ANY); @@ -735,6 +743,8 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater) m_scroll_sizer->Add(m_change_filament_times_sizer, 0,wxLEFT|wxRIGHT, FromDIP(15)); // m_scroll_sizer->Add(m_link_edit_nozzle, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(suggestion_sizer, 0, wxLEFT|wxRIGHT|wxEXPAND, FromDIP(15)); + m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + m_scroll_sizer->Add(m_warn_when_drying_sizer, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(sizer_split_options, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); m_scroll_sizer->Add(m_options_other, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); @@ -1734,6 +1744,47 @@ bool SelectMachineDialog::CheckErrorSyncNozzleMappingResultV0(MachineObject* obj return true; } +bool SelectMachineDialog::is_ams_drying(MachineObject* obj) +{ + const auto& ams_list = obj->GetFilaSystem()->GetAmsList(); + for (auto ams = ams_list.begin(); ams != ams_list.end(); ams++) { + if (ams->second->AmsIsDrying()) { + return true; + } + } + + return false; +} + +bool SelectMachineDialog::is_selected_ams_drying(MachineObject* obj) +{ + if (!obj) return false; + + // If a UI material is selected, only when that material is mapped to an AMS + // and that AMS is currently drying. + for (const auto &kv : m_materialList) { + Material *mat = kv.second; + if (!mat || !mat->item) continue; + if (!mat->item->m_selected) continue; + + // find mapping entry for this material id + for (const FilamentInfo &f : m_ams_mapping_result) { + if (f.id != mat->id) continue; + + if (f.ams_id.empty()) return false; + + auto fila_system = obj->GetFilaSystem(); + if (!fila_system) return false; + DevAms* dev_ams = fila_system->GetAmsById(f.ams_id); + return (dev_ams && dev_ams->AmsIsDrying()); + } + + return false; + } + + return false; +} + void SelectMachineDialog::prepare(int print_plate_idx) { m_print_plate_idx = print_plate_idx; @@ -3888,6 +3939,16 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) m_check_ext_change_assist->Enable(false); } + { + const bool show_warn_when_drying = is_selected_ams_drying(obj_); + const bool is_currently_shown = (m_txt_warn_when_drying != nullptr) ? m_txt_warn_when_drying->IsShown() : false; + if (is_currently_shown != show_warn_when_drying) { + m_warn_when_drying_sizer->Show(show_warn_when_drying); + Layout(); + Fit(); + } + } + /*reading done*/ if (wxGetApp().app_config) { if (obj_->upgrade_force_upgrade) { @@ -4404,6 +4465,7 @@ void SelectMachineDialog::set_default() m_mapping_sugs_sizer->Show(false); m_change_filament_times_sizer->Show(false); m_txt_change_filament_times->Show(false); + m_warn_when_drying_sizer->Show(false); // rset status bar m_status_bar->reset(); @@ -4513,6 +4575,10 @@ void SelectMachineDialog::reset_and_sync_ams_list() m_filaments_map = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_real_filament_maps(project_config); } + bool selected_any = false; + MaterialItem *first_enabled = nullptr; + int first_enabled_id = -1; + for (auto i = 0; i < extruders.size(); i++) { auto extruder = extruders[i] - 1; auto colour = wxGetApp().preset_bundle->project_config.opt_string("filament_colour", (unsigned int) extruder); @@ -4542,7 +4608,20 @@ void SelectMachineDialog::reset_and_sync_ams_list() item = new MaterialItem(m_filament_panel, colour_rgb, _L(display_materials[extruder])); m_sizer_ams_mapping->Add(item, 0, wxALL, FromDIP(5)); } + + if (!item) continue; + item->SetToolTip(m_ams_tooltip); + + if (!selected_any && extruder == m_current_filament_id && item->m_enable) { + item->on_selected(); + selected_any = true; + } + if (!first_enabled && item->m_enable) { + first_enabled = item; + first_enabled_id = extruder; + } + item->Bind(wxEVT_LEFT_UP, [this, item, materials, extruder](wxMouseEvent &e) {}); item->Bind(wxEVT_LEFT_DOWN, [this, item, materials, extruder](wxMouseEvent &e) { if (!item->m_enable) {return;} @@ -4621,6 +4700,11 @@ void SelectMachineDialog::reset_and_sync_ams_list() } } + if (!selected_any && first_enabled) { + m_current_filament_id = first_enabled_id; + first_enabled->on_selected(); + } + if (use_double_extruder) { m_filament_left_panel->Show(); @@ -5031,6 +5115,10 @@ void SelectMachineDialog::set_default_from_sdcard() m_materialList.clear(); m_filaments.clear(); + bool selected_any = false; + MaterialItem *first_enabled = nullptr; + int first_enabled_id = -1; + for (auto i = 0; i < m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info.size(); i++) { FilamentInfo fo = m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info[i]; @@ -5053,6 +5141,17 @@ void SelectMachineDialog::set_default_from_sdcard() m_sizer_ams_mapping->Add(item, 0, wxALL, FromDIP(5)); } + if (!item) continue; + + if (!selected_any && fo.id == m_current_filament_id && item->m_enable) { + item->on_selected(); + selected_any = true; + } + if (!first_enabled && item->m_enable) { + first_enabled = item; + first_enabled_id = fo.id; + } + item->Bind(wxEVT_LEFT_UP, [this, item, materials](wxMouseEvent& e) {}); item->Bind(wxEVT_LEFT_DOWN, [this, obj_, item, materials, diameters_count, fo](wxMouseEvent& e) { if (!item->m_enable) {return;} @@ -5129,6 +5228,11 @@ void SelectMachineDialog::set_default_from_sdcard() wxSize screenSize = wxGetDisplaySize(); auto dialogSize = this->GetSize(); + if (!selected_any && first_enabled) { + m_current_filament_id = first_enabled_id; + first_enabled->on_selected(); + } + reset_ams_material(); // basic info diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index 5d51f17f4b..a62e8de3d2 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -28,6 +28,7 @@ #include "boost/bimap/bimap.hpp" #include "AmsMappingPopup.hpp" +#include "GUI_ObjectLayers.hpp" #include "ReleaseNote.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" @@ -354,6 +355,7 @@ protected: wxBoxSizer* m_sizer_autorefill{ nullptr }; wxBoxSizer* m_mapping_sugs_sizer{ nullptr }; wxBoxSizer* m_change_filament_times_sizer{ nullptr }; + wxBoxSizer* m_warn_when_drying_sizer{ nullptr }; Button* m_button_ensure{ nullptr }; wxStaticBitmap * m_rename_button{nullptr}; wxStaticBitmap* m_staticbitmap{ nullptr }; @@ -387,6 +389,8 @@ protected: CheckBox* m_check_ext_change_assist{ nullptr }; Label* m_label_ext_change_assist{ nullptr }; + Label* m_txt_warn_when_drying{ nullptr }; + PrinterInfoBox* m_printer_box { nullptr}; PrinterMsgPanel * m_text_printer_msg{nullptr}; Label* m_text_printer_msg_tips{ nullptr }; @@ -542,6 +546,9 @@ public: bool CheckErrorDynamicSwitchNozzle(MachineObject* obj_); void on_flow_cali_option_changed(); + bool is_ams_drying(MachineObject* obj); + bool is_selected_ams_drying(MachineObject* obj); + PrintFromType get_print_type() {return m_print_type;}; wxString format_steel_name(NozzleType type); PrintDialogStatus get_status() { return m_print_status; } diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 2be47207d6..1bd564b5e1 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -3392,6 +3392,7 @@ void StatusPanel::update_ams(MachineObject *obj) // must select a current can m_ams_control->UpdateAms(obj->get_printer_series_str(), obj->printer_type, ams_info, ext_info, *obj->GetExtderSystem(), obj->get_dev_id(), false); + m_ams_control->UpdateAmsDryControl(obj); last_tray_exist_bits = obj->tray_exist_bits; last_ams_exist_bits = obj->ams_exist_bits; diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index ea693fbd4c..7334cb960a 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -31,6 +31,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons , m_Humidity_tip_popup(AmsHumidityTipPopup(this)) , m_percent_humidity_dry_popup(new uiAmsPercentHumidityDryPopup(this)) , m_ams_introduce_popup(AmsIntroducePopup(this)) + , m_ams_dry_ctr_win(new AMSDryCtrWin(this)) { Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (dev) { @@ -252,6 +253,12 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons uiAmsHumidityInfo *info = (uiAmsHumidityInfo *) evt.GetClientData(); if (info) { + Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + MachineObject *obj = nullptr; + if (dev) { + obj = dev->get_selected_machine(); + } + if (info->ams_type == AMSModel::GENERIC_AMS) { wxPoint img_pos = ClientToScreen(wxPoint(0, 0)); @@ -261,9 +268,14 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons int humidity_value = info->humidity_display_idx; if (humidity_value > 0 && humidity_value <= 5) { m_Humidity_tip_popup.set_humidity_level(humidity_value); } m_Humidity_tip_popup.Popup(); - } - else - { + } else if (obj && obj->is_support_remote_dry && (info->ams_type == AMSModel::N3F_AMS || info->ams_type == AMSModel::N3S_AMS)){ + m_ams_dry_ctr_win->set_ams_id(info->ams_id); + + wxPoint img_pos = ClientToScreen(wxPoint(0, 0)); + wxPoint popup_pos(img_pos.x - m_ams_dry_ctr_win->GetSize().GetWidth() + FromDIP(150), img_pos.y - FromDIP(80)); + m_ams_dry_ctr_win->Move(popup_pos); + m_ams_dry_ctr_win->ShowModal(); + } else { m_percent_humidity_dry_popup->Update(info); wxPoint img_pos = ClientToScreen(wxPoint(0, 0)); @@ -283,7 +295,12 @@ void AMSControl::on_retry() post_event(wxCommandEvent(EVT_AMS_RETRY)); } -AMSControl::~AMSControl() {} +AMSControl::~AMSControl() +{ + if (m_ams_dry_ctr_win) { + delete m_ams_dry_ctr_win; + } +} std::string AMSControl::GetCurentAms() { return m_current_ams; @@ -501,6 +518,10 @@ void AMSControl::msw_rescale() m_percent_humidity_dry_popup->msw_rescale(); } + if (m_ams_dry_ctr_win) { + m_ams_dry_ctr_win->msw_rescale(); + } + m_Humidity_tip_popup.msw_rescale(); Layout(); @@ -824,6 +845,27 @@ void AMSControl::show_vams_kn_value(bool show) //m_vams_lib->show_kn_value(show); } +void AMSControl::UpdateAmsDryControl(MachineObject* obj) +{ + if (!m_ams_dry_ctr_win->IsShown()) { + return; + } + + if (!obj || !obj->GetFilaSystem()) { + m_ams_dry_ctr_win->Close(); + return; + } + + std::weak_ptr weak_fila_system = obj->GetFilaSystem(); + + if (auto locaked_fila_system = weak_fila_system.lock()) { + m_ams_dry_ctr_win->update(locaked_fila_system, obj); + } else { + m_ams_dry_ctr_win->Close(); + return; + } +} + std::vector AMSControl::GenerateSimulateData() { auto caninfo0_0 = Caninfo{ "0", (""), *wxRED, AMSCanType::AMS_CAN_TYPE_VIRTUAL }; auto caninfo0_1 = Caninfo{ "1", (""), *wxGREEN, AMSCanType::AMS_CAN_TYPE_VIRTUAL }; diff --git a/src/slic3r/GUI/Widgets/AMSControl.hpp b/src/slic3r/GUI/Widgets/AMSControl.hpp index 7616d269f2..1fe8876eba 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.hpp +++ b/src/slic3r/GUI/Widgets/AMSControl.hpp @@ -16,6 +16,7 @@ #include #include "slic3r/GUI/DeviceCore/DevExtruderSystem.h" +#include "slic3r/GUI/AMSDryControl.hpp" namespace Slic3r { namespace GUI { @@ -127,6 +128,7 @@ protected: AmsHumidityTipPopup m_Humidity_tip_popup; uiAmsPercentHumidityDryPopup* m_percent_humidity_dry_popup; + AMSDryCtrWin* m_ams_dry_ctr_win; std::string m_last_ams_id = ""; std::string m_last_tray_id = ""; @@ -170,6 +172,7 @@ public: void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type); void CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type); void ClearAms(); + void UpdateAmsDryControl(MachineObject* obj); void UpdateAms(const std::string &series_name, const std::string &printer_type, std::vector ams_info, diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index f1d892134b..b0ea653dd3 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -536,7 +536,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, print_json["ams"] = ams_json; // Call the parser to populate DevFilaSystem - DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false); + DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem().get(), false); BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays"; // Set printer_type so update_sync_status() can match it against the preset's printer type. diff --git a/tests/filament_group/fg_test_evaluator.hpp b/tests/filament_group/fg_test_evaluator.hpp index fcd7d6576e..00a2e1a6f5 100644 --- a/tests/filament_group/fg_test_evaluator.hpp +++ b/tests/filament_group/fg_test_evaluator.hpp @@ -210,13 +210,15 @@ inline FullEvalResult full_evaluate_map(const FilamentGroupContext& ctx, return result; } -inline TestResult run_and_evaluate(const FilamentGroupContext& ctx) { +inline TestResult run_and_evaluate(const FilamentGroupContext& ctx, + const ClusteringBudget& budget = {}) { TestResult result; auto start = std::chrono::high_resolution_clock::now(); int algo_cost = 0; FilamentGroup fg(ctx); + fg.set_clustering_budget(budget); result.filament_map = fg.calc_filament_group(&algo_cost); auto end = std::chrono::high_resolution_clock::now(); diff --git a/tests/filament_group/filament_group_regression_main.cpp b/tests/filament_group/filament_group_regression_main.cpp index f197eec002..b2cc4bd178 100644 --- a/tests/filament_group/filament_group_regression_main.cpp +++ b/tests/filament_group/filament_group_regression_main.cpp @@ -211,19 +211,23 @@ static std::vector& get_property_specs() { return specs; } -// Orca: a small number of config_c "stress" goldens run the nozzle-centric kmedoids clustering path, -// which is bounded by a 3000 ms wall-clock budget (FilamentGroup.cpp calc_group_by_kmedoids). On -// slower hardware the clustering explores fewer restarts and lands on a deterministically-worse-but- -// valid grouping than the stored golden. We regression-lock those against Orca's own deterministic -// score (bit-stable across runs on this machine — verified twice) so the gate stays green while the -// divergence is documented; every other golden is a true parity gate at 3% tolerance. -static std::optional orca_locked_base_score(const std::string& stem) { - if (stem == "stress_66") return 125103.0; // config_c 15-filament kmedoids case; golden 117843 - return std::nullopt; -} +// Under the default wall clock the result depends on how fast the machine is (see ClusteringBudget), +// so the goldens are graded under a fixed budget instead. Two restarts is the fewest that reaches +// parity with the reference on every golden, stress_79 being the last to get there. Four leaves +// margin, since the search follows a different path on each standard library (see below). +static constexpr ClusteringBudget FIXED_SEARCH_BUDGET{ + /*timeout_ms*/ 0, // no wall clock + /*max_restarts*/ 4}; // ============ Layer 1: Golden Regression (all configs) ============ +// Graded against the BambuStudio golden the harness was ported from, one-directional at 3%. +// +// The tolerance is a parity allowance, and it also covers a small spread across standard libraries. +// The k-medoids search seeds each restart with std::shuffle, whose algorithm the C++ standard leaves +// unspecified, so libstdc++, libc++ and the MSVC STL permute the same seed differently, start from +// different medoids, and settle on slightly different groupings, about 3e-4 apart on either side of +// the reference, and only on the goldens heavy enough to reach the k-medoids search. TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") { auto files = get_golden_files(); if (files.empty()) { @@ -238,29 +242,49 @@ TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") { auto tc = load_test_case(file_path); REQUIRE(tc.base_result.has_value()); - auto result = run_and_evaluate(tc.context); + auto result = run_and_evaluate(tc.context, FIXED_SEARCH_BUDGET); auto eval = full_evaluate_map(tc.context, result.filament_map); auto& base = *tc.base_result; - // Reference score: the stored golden by default; Orca's deterministic score for the - // documented heuristic-divergent config_c stress golden (see orca_locked_base_score). - std::string stem = fs::path(file_path).stem().string(); - double base_score = base.full_score; - if (auto locked = orca_locked_base_score(stem)) - base_score = *locked; - INFO("Case: " << tc.metadata.id); - INFO("Reference score: " << base_score << " (BBS golden " << base.full_score << ")"); + INFO("Golden score: " << base.full_score); INFO("Actual score: " << eval.full_score); - INFO("Flush cost: " << eval.flush_cost << " (BBS golden " << base.flush_cost << ")"); + INFO("Flush cost: " << eval.flush_cost << " (golden " << base.flush_cost << ")"); INFO("Elapsed: " << result.elapsed_ms << " ms"); - int tolerance = std::max(50, (int)(base_score * 0.03)); + int tolerance = std::max(50, (int)(base.full_score * 0.03)); REQUIRE(result.constraints_ok); - REQUIRE(eval.full_score <= base_score + tolerance); - // RelWithDebInfo runaway guard; the Release-calibrated 20 s limit is raised for the slower build. + REQUIRE(eval.full_score <= base.full_score + tolerance); + + // A slower search still scores the same above, since it searches just as far, but in slicing + // it would mean fewer restarts fit in the wall clock and so worse groupings. Loose on + // purpose, so it never becomes a proxy for how loaded the runner is. + const double throughput_ceiling_ms = 10.0 * ClusteringBudget{}.timeout_ms; + REQUIRE(result.elapsed_ms < throughput_ceiling_ms); + } +} + +// Covers the path real slicing takes, under the default wall clock. The score there depends on the +// runner rather than on the code (see FIXED_SEARCH_BUDGET), so the only things worth asserting are +// that the grouping comes back valid and that the search terminates. +TEST_CASE("FilamentGroup returns a valid grouping under the default budget", "[filament_group][budget]") { + auto files = get_golden_files(); + REQUIRE(!files.empty()); + + auto file_path = GENERATE_REF(from_range(files)); + + DYNAMIC_SECTION("Golden: " << fs::path(file_path).stem().string()) { + auto tc = load_test_case(file_path); + + auto result = run_and_evaluate(tc.context); // the default budget, as real slicing runs it + + INFO("Case: " << tc.metadata.id); + INFO("Elapsed: " << result.elapsed_ms << " ms"); + + REQUIRE(result.constraints_ok); + // A hang guard. The clock is only checked between swaps, so a sweep can overshoot. REQUIRE(result.elapsed_ms < 40000.0); } }