mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' into refactor/printer-agent-interface
This commit is contained in:
@@ -32,9 +32,17 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: OrcaSlicer Version
|
label: OrcaSlicer Version
|
||||||
description: Which version of Orca Slicer are you running? You can see the full version in `Help` -> `About Orca Slicer`.
|
description: Which version of Orca Slicer are you running? You can see the full version in `Help` -> `About Orca Slicer`.
|
||||||
placeholder: e.g. 1.9.0
|
placeholder: e.g. 2.5.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: working_version
|
||||||
|
attributes:
|
||||||
|
label: Regression compared to a previous version
|
||||||
|
description: Did it work in a previous version?
|
||||||
|
placeholder: e.g. 2.3.2
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: os_type
|
id: os_type
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -7781,19 +7781,19 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, possible-boost-format
|
#, possible-boost-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8361,21 +8361,21 @@ msgstr "No s'ha seleccionat el directori per a la substitució"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Substituït amb fitxers 3D del directori:\n"
|
msgstr "Substituït amb fitxers 3D del directori:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Omès %1%: mateix fitxer.\n"
|
msgstr "✖ Omès %s: mateix fitxer.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Omès %1%: el fitxer no existeix.\n"
|
msgstr "✖ Omès %s: el fitxer no existeix.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Omès %1%: la substitució ha fallat.\n"
|
msgstr "✖ Omès %s: la substitució ha fallat.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Substituït %1%.\n"
|
msgstr "✔ Substituït %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Volums substituïts"
|
msgstr "Volums substituïts"
|
||||||
|
|||||||
@@ -8320,21 +8320,21 @@ msgstr "Nebyla vybrána složka pro nahrazení"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Nahrazeno 3D soubory ze složky:\n"
|
msgstr "Nahrazeno 3D soubory ze složky:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Přeskočeno %1%: stejný soubor.\n"
|
msgstr "✖ Přeskočeno %s: stejný soubor.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Přeskočeno %1%: soubor neexistuje.\n"
|
msgstr "✖ Přeskočeno %s: soubor neexistuje.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Přeskočeno %1%: nahrazení se nezdařilo.\n"
|
msgstr "✖ Přeskočeno %s: nahrazení se nezdařilo.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Nahrazeno %1%.\n"
|
msgstr "✔ Nahrazeno %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Nahrazené objemy"
|
msgstr "Nahrazené objemy"
|
||||||
|
|||||||
@@ -8191,21 +8191,21 @@ msgstr "Verzeichnis um daraus zu ersetzen wurde nicht ausgewählt"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Ersetzt durch 3D-Dateien aus Verzeichnis:\n"
|
msgstr "Ersetzt durch 3D-Dateien aus Verzeichnis:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Übersprungen %1%: gleiche Datei.\n"
|
msgstr "✖ Übersprungen %s: gleiche Datei.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Übersprungen %1%: Datei existiert nicht.\n"
|
msgstr "✖ Übersprungen %s: Datei existiert nicht.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Übersprungen %1%: Ersetzen fehlgeschlagen.\n"
|
msgstr "✖ Übersprungen %s: Ersetzen fehlgeschlagen.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Ersetzt %1%.\n"
|
msgstr "✔ Ersetzt %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Ersetzte Volumen"
|
msgstr "Ersetzte Volumen"
|
||||||
|
|||||||
@@ -7776,20 +7776,20 @@ msgstr ""
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -7997,21 +7997,21 @@ msgstr "No se seleccionó el directorio para el reemplazo"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Reemplazado con archivos 3D desde el directorio:\n"
|
msgstr "Reemplazado con archivos 3D desde el directorio:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Omitido %1%: mismo archivo.\n"
|
msgstr "✖ Omitido %s: mismo archivo.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Omitido %1%: el archivo no existe.\n"
|
msgstr "✖ Omitido %s: el archivo no existe.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Omitido %1%: fallo al reemplazar.\n"
|
msgstr "✖ Omitido %s: fallo al reemplazar.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Reemplazado %1%.\n"
|
msgstr "✔ Reemplazado %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Volúmenes reemplazados"
|
msgstr "Volúmenes reemplazados"
|
||||||
|
|||||||
@@ -8064,21 +8064,21 @@ msgstr "Ez da ordezkatzeko direktoriorik hautatu"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Direktorio honetako 3D fitxategiekin ordeztuta:\n"
|
msgstr "Direktorio honetako 3D fitxategiekin ordeztuta:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ %1% saltatu da: fitxategi bera.\n"
|
msgstr "✖ %s saltatu da: fitxategi bera.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ %1% saltatu da: fitxategia ez da existitzen.\n"
|
msgstr "✖ %s saltatu da: fitxategia ez da existitzen.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ %1% saltatu da: ezin izan da ordeztu.\n"
|
msgstr "✖ %s saltatu da: ezin izan da ordeztu.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ %1% ordezkatu da.\n"
|
msgstr "✔ %s ordezkatu da.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Ordeztutako bolumenak"
|
msgstr "Ordeztutako bolumenak"
|
||||||
|
|||||||
@@ -8120,21 +8120,21 @@ msgstr "Le répertoire pour le remplacement n'a pas été sélectionné"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Remplacé par des fichiers 3D depuis le répertoire :\n"
|
msgstr "Remplacé par des fichiers 3D depuis le répertoire :\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Ignoré %1% : même fichier.\n"
|
msgstr "✖ Ignoré %s : même fichier.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Ignoré %1% : le fichier n'existe pas.\n"
|
msgstr "✖ Ignoré %s : le fichier n'existe pas.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Ignoré %1% : échec du remplacement.\n"
|
msgstr "✖ Ignoré %s : échec du remplacement.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Remplacé %1%.\n"
|
msgstr "✔ Remplacé %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Volumes remplacés"
|
msgstr "Volumes remplacés"
|
||||||
|
|||||||
@@ -8244,21 +8244,21 @@ msgstr "A cseréhez nem lett mappa kiválasztva"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Cserélve a mappából származó 3D fájlokra:\n"
|
msgstr "Cserélve a mappából származó 3D fájlokra:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Kihagyva %1%: azonos fájl.\n"
|
msgstr "✖ %s kihagyva: azonos fájl.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Kihagyva %1%: a fájl nem létezik.\n"
|
msgstr "✖ %s kihagyva: a fájl nem létezik.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Kihagyva %1%: a csere sikertelen.\n"
|
msgstr "✖ %s kihagyva: a csere sikertelen.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Lecserélve: %1%.\n"
|
msgstr "✔%s lecserélve.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Lecserélt térfogatok"
|
msgstr "Lecserélt térfogatok"
|
||||||
|
|||||||
@@ -8244,21 +8244,21 @@ msgstr "La directory per la sostituzione non è stata selezionata"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Sostituito con file 3D dalla directory:\n"
|
msgstr "Sostituito con file 3D dalla directory:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Saltato %1%: stesso file.\n"
|
msgstr "✖ Saltato %s: stesso file.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Saltato %1%: il file non esiste.\n"
|
msgstr "✖ Saltato %s: il file non esiste.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Saltato %1%: sostituzione fallita.\n"
|
msgstr "✖ Saltato %s: sostituzione fallita.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Sostituito %1%.\n"
|
msgstr "✔ Sostituito %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Volumi sostituiti"
|
msgstr "Volumi sostituiti"
|
||||||
|
|||||||
@@ -8262,21 +8262,21 @@ msgstr "置換用のディレクトリが選択されていません"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "ディレクトリの3Dファイルで置換しました:\n"
|
msgstr "ディレクトリの3Dファイルで置換しました:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ スキップ %1%: 同一ファイル。\n"
|
msgstr "✖ スキップ %s: 同一ファイル。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ スキップ %1%: ファイルが存在しません。\n"
|
msgstr "✖ スキップ %s: ファイルが存在しません。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ スキップ %1%: 置換に失敗しました。\n"
|
msgstr "✖ スキップ %s: 置換に失敗しました。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ 置換しました %1%。\n"
|
msgstr "✔ 置換しました %s。\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "置換されたボリューム"
|
msgstr "置換されたボリューム"
|
||||||
|
|||||||
@@ -8288,24 +8288,24 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr "다음 디렉터리의 3D 파일로 교체했습니다:\n"
|
msgstr "다음 디렉터리의 3D 파일로 교체했습니다:\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ 건너뜀 %1%: 동일한 파일입니다.\n"
|
msgstr "✖ 건너뜀 %s: 동일한 파일입니다.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ 건너뜀 %1%: 파일이 존재하지 않습니다.\n"
|
msgstr "✖ 건너뜀 %s: 파일이 존재하지 않습니다.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ 건너뜀 %1%: 교체하지 못했습니다.\n"
|
msgstr "✖ 건너뜀 %s: 교체하지 못했습니다.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ %1%을(를) 교체했습니다.\n"
|
msgstr "✔ %s을(를) 교체했습니다.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8239,21 +8239,21 @@ msgstr ""
|
|||||||
"Pakeista 3D failais iš katalogo:\n"
|
"Pakeista 3D failais iš katalogo:\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Praleistas %1%: tas pats failas.\n"
|
msgstr "✖ Praleistas %s: tas pats failas.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Praleistas %1%: failas neegzistuoja.\n"
|
msgstr "✖ Praleistas %s: failas neegzistuoja.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Praleistas %1%: nepavyko pakeisti.\n"
|
msgstr "✖ Praleistas %s: nepavyko pakeisti.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Pakeistas %1%.\n"
|
msgstr "✔ Pakeistas %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Pakeisti tūriai"
|
msgstr "Pakeisti tūriai"
|
||||||
|
|||||||
@@ -8999,24 +8999,24 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr "Vervangen door 3D-bestanden uit de map:\n"
|
msgstr "Vervangen door 3D-bestanden uit de map:\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Overgeslagen %1%: hetzelfde bestand.\n"
|
msgstr "✖ Overgeslagen %s: hetzelfde bestand.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Overgeslagen %1%: bestand bestaat niet.\n"
|
msgstr "✖ Overgeslagen %s: bestand bestaat niet.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Overgeslagen %1%: vervangen is mislukt.\n"
|
msgstr "✖ Overgeslagen %s: vervangen is mislukt.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Vervangen %1%.\n"
|
msgstr "✔ Vervangen %s.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8444,24 +8444,24 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr "Zastąpiono plikami 3D z katalogu:\n"
|
msgstr "Zastąpiono plikami 3D z katalogu:\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Pominięto %1%: ten sam plik.\n"
|
msgstr "✖ Pominięto %s: ten sam plik.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Pominięto %1%: plik nie istnieje.\n"
|
msgstr "✖ Pominięto %s: plik nie istnieje.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Pominięto %1%: nie udało się zastąpić.\n"
|
msgstr "✖ Pominięto %s: nie udało się zastąpić.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Zastąpiono %1%.\n"
|
msgstr "✔ Zastąpiono %s.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8026,21 +8026,21 @@ msgstr "Diretório para substituição não foi selecionado"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Substituído por arquivos 3D do diretório:\n"
|
msgstr "Substituído por arquivos 3D do diretório:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ %1% Ignorados: mesmo arquivo.\n"
|
msgstr "✖ %s Ignorados: mesmo arquivo.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ %1% Ignorados: arquivo não existe.\n"
|
msgstr "✖ %s Ignorados: arquivo não existe.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ %1% Ignorados: falha ao substituir.\n"
|
msgstr "✖ %s Ignorados: falha ao substituir.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ %1% Substituídos.\n"
|
msgstr "✔ %s Substituídos.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Volumes substiruídos"
|
msgstr "Volumes substiruídos"
|
||||||
|
|||||||
@@ -8413,21 +8413,21 @@ msgstr "Расположение для замены не указано"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Заменено файлами из расположения:\n"
|
msgstr "Заменено файлами из расположения:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Пропущен %1%: идентичный файл.\n"
|
msgstr "✖ Пропущен %s: идентичный файл.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Пропущен %1%: файл не существует.\n"
|
msgstr "✖ Пропущен %s: файл не существует.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Пропущен %1%: не удалось заменить.\n"
|
msgstr "✖ Пропущен %s: не удалось заменить.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Заменён %1%.\n"
|
msgstr "✔ Заменён %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Модели заменены"
|
msgstr "Модели заменены"
|
||||||
|
|||||||
@@ -9088,24 +9088,24 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr "Ersatt med 3D-filer från mappen:\n"
|
msgstr "Ersatt med 3D-filer från mappen:\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Hoppade över %1%: samma fil.\n"
|
msgstr "✖ Hoppade över %s: samma fil.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Hoppade över %1%: filen finns inte.\n"
|
msgstr "✖ Hoppade över %s: filen finns inte.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Hoppade över %1%: det gick inte att ersätta.\n"
|
msgstr "✖ Hoppade över %s: det gick inte att ersätta.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Ersatte %1%.\n"
|
msgstr "✔ Ersatte %s.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8199,21 +8199,21 @@ msgstr "ไม่ได้เลือกไดเรกทอรีสำหร
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "แทนที่ด้วยไฟล์ 3D จากไดเรกทอรี:\n"
|
msgstr "แทนที่ด้วยไฟล์ 3D จากไดเรกทอรี:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ ข้าม %1%: ไฟล์เดียวกัน\n"
|
msgstr "✖ ข้าม %s: ไฟล์เดียวกัน\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ ข้าม %1%: ไม่มีไฟล์อยู่\n"
|
msgstr "✖ ข้าม %s: ไม่มีไฟล์อยู่\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ ข้าม %1%: ไม่สามารถแทนที่ได้\n"
|
msgstr "✖ ข้าม %s: ไม่สามารถแทนที่ได้\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔แทนที่ %1%\n"
|
msgstr "✔แทนที่ %s\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "ปริมาณที่ถูกแทนที่"
|
msgstr "ปริมาณที่ถูกแทนที่"
|
||||||
|
|||||||
@@ -8335,21 +8335,21 @@ msgstr "Değiştirme için dizin seçilmedi"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Dizindeki 3D dosyalarla değiştirildi:\n"
|
msgstr "Dizindeki 3D dosyalarla değiştirildi:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ %1% atlandı: aynı dosya.\n"
|
msgstr "✖ %s atlandı: aynı dosya.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ %1% atlandı: dosya mevcut değil.\n"
|
msgstr "✖ %s atlandı: dosya mevcut değil.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ %1% atlandı: değiştirilemedi.\n"
|
msgstr "✖ %s atlandı: değiştirilemedi.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ %1% değiştirildi.\n"
|
msgstr "✔ %s değiştirildi.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Değiştirilen birimler"
|
msgstr "Değiştirilen birimler"
|
||||||
|
|||||||
@@ -8306,21 +8306,21 @@ msgstr "Каталог для заміни не вибрано"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "Замінено 3D-файлами з каталогу:\n"
|
msgstr "Замінено 3D-файлами з каталогу:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Пропущено %1%: той самий файл.\n"
|
msgstr "✖ Пропущено %s: той самий файл.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Пропущено %1%: файл не існує.\n"
|
msgstr "✖ Пропущено %s: файл не існує.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Пропущено %1%: не вдалося замінити.\n"
|
msgstr "✖ Пропущено %s: не вдалося замінити.\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Замінено %1%.\n"
|
msgstr "✔ Замінено %s.\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "Замінені обʼєми"
|
msgstr "Замінені обʼєми"
|
||||||
|
|||||||
@@ -8721,24 +8721,24 @@ msgid "Replaced with 3D files from directory:\n"
|
|||||||
msgstr "Đã thay thế bằng file 3D từ thư mục:\n"
|
msgstr "Đã thay thế bằng file 3D từ thư mục:\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ Đã bỏ qua %1%: cùng một file.\n"
|
msgstr "✖ Đã bỏ qua %s: cùng một file.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ Đã bỏ qua %1%: file không tồn tại.\n"
|
msgstr "✖ Đã bỏ qua %s: file không tồn tại.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ Đã bỏ qua %1%: thay thế thất bại.\n"
|
msgstr "✖ Đã bỏ qua %s: thay thế thất bại.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ Đã thay thế %1%.\n"
|
msgstr "✔ Đã thay thế %s.\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
|
|||||||
@@ -8028,21 +8028,21 @@ msgstr "未选择替换目录"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "替换为目录中的 3D 文件:\n"
|
msgstr "替换为目录中的 3D 文件:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ 跳过 %1%:同一文件。\n"
|
msgstr "✖ 跳过 %s:同一文件。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ 跳过%1%:文件不存在。\n"
|
msgstr "✖ 跳过%s:文件不存在。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ 跳过%1%:替换失败。\n"
|
msgstr "✖ 跳过%s:替换失败。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ 替换了 %1%。\n"
|
msgstr "✔ 替换了 %s。\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "替换的卷"
|
msgstr "替换的卷"
|
||||||
|
|||||||
@@ -8193,21 +8193,21 @@ msgstr "未選擇替換的目錄"
|
|||||||
msgid "Replaced with 3D files from directory:\n"
|
msgid "Replaced with 3D files from directory:\n"
|
||||||
msgstr "已從目錄替換為 3D 檔案:\n"
|
msgstr "已從目錄替換為 3D 檔案:\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: same file.\n"
|
msgid "✖ Skipped %s: same file.\n"
|
||||||
msgstr "✖ 已跳過 %1%:相同檔案。\n"
|
msgstr "✖ 已跳過 %s:相同檔案。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: file does not exist.\n"
|
msgid "✖ Skipped %s: file does not exist.\n"
|
||||||
msgstr "✖ 已跳過 %1%:檔案不存在。\n"
|
msgstr "✖ 已跳過 %s:檔案不存在。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✖ Skipped %1%: failed to replace.\n"
|
msgid "✖ Skipped %s: failed to replace.\n"
|
||||||
msgstr "✖ 已跳過 %1%:無法替換。\n"
|
msgstr "✖ 已跳過 %s:無法替換。\n"
|
||||||
|
|
||||||
#, boost-format
|
#, c-format
|
||||||
msgid "✔ Replaced %1%.\n"
|
msgid "✔ Replaced %s.\n"
|
||||||
msgstr "✔ 已替換 %1%。\n"
|
msgstr "✔ 已替換 %s。\n"
|
||||||
|
|
||||||
msgid "Replaced volumes"
|
msgid "Replaced volumes"
|
||||||
msgstr "已替換體積"
|
msgstr "已替換體積"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Creality",
|
"name": "Creality",
|
||||||
"version": "02.03.02.75",
|
"version": "02.03.02.76",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Creality configurations",
|
"description": "Creality configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -128,7 +128,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "1",
|
"enable_prime_tower": "1",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"draft_shield": "disabled",
|
"draft_shield": "disabled",
|
||||||
"elefant_foot_compensation": "0.15",
|
"elefant_foot_compensation": "0.15",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enable_prime_tower": "0",
|
"enable_prime_tower": "0",
|
||||||
"enable_support": "0",
|
"enable_support": "0",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@
|
|||||||
"detect_narrow_internal_solid_infill": "1",
|
"detect_narrow_internal_solid_infill": "1",
|
||||||
"dont_filter_internal_bridges": "disabled",
|
"dont_filter_internal_bridges": "disabled",
|
||||||
"elefant_foot_compensation_layers": "1",
|
"elefant_foot_compensation_layers": "1",
|
||||||
"enable_arc_fitting": "1",
|
"enable_arc_fitting": "0",
|
||||||
"enable_overhang_speed": "1",
|
"enable_overhang_speed": "1",
|
||||||
"enforce_support_layers": "0",
|
"enforce_support_layers": "0",
|
||||||
"ensure_vertical_shell_thickness": "ensure_all",
|
"ensure_vertical_shell_thickness": "ensure_all",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Custom Printer",
|
"name": "Custom Printer",
|
||||||
"version": "02.04.00.02",
|
"version": "02.04.00.03",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "My configurations",
|
"description": "My configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"instantiation": "false",
|
"instantiation": "false",
|
||||||
"gcode_flavor": "klipper",
|
"gcode_flavor": "klipper",
|
||||||
"single_extruder_multi_material": "0",
|
"single_extruder_multi_material": "0",
|
||||||
|
"wait_for_temp_on_wipe_tower": "1",
|
||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"Generic PLA @MyToolChanger"
|
"Generic PLA @MyToolChanger"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Qidi",
|
"name": "Qidi",
|
||||||
"version": "02.04.00.08",
|
"version": "02.04.00.09",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Qidi configurations",
|
"description": "Qidi configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"cooling_tube_length": "0",
|
"cooling_tube_length": "0",
|
||||||
"parking_pos_retraction": "0",
|
"parking_pos_retraction": "0",
|
||||||
"extra_loading_move": "5",
|
"extra_loading_move": "5",
|
||||||
"change_filament_gcode": "G1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nBUFFER_MONITORING ENABLE=0\nDISABLE_ALL_SENSOR\nM106 S255\nMOVE_TO_TRASH\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-10 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\n; FLUSH_START\nM106 S25\nG1 E30 F300\n; FLUSH_END\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\n{if flush_length_1 > 23.7}\nG1 E23.7 F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\n{else}\nG1 E{flush_length_1} F{old_filament_e_feedrate}\n{endif}\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 X85 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM400\nM106 S255\nM104 S[new_filament_temp]\nINIT_SYNC_BUFFER_STATE\nBUFFER_MONITORING ENABLE=1\nG1 E10 F25 \nM109 S[new_filament_temp]\nG1 E-5 F1800\nCLEAR_OOZE\nTOOL_CHANGE_END\nG1 Y270 F8000\nM106 S0\nG1 E2 F1800\nENABLE_ALL_SENSOR\n",
|
"change_filament_gcode": "{if current_extruder != next_extruder}\nG1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nBUFFER_MONITORING ENABLE=0\nDISABLE_ALL_SENSOR\nM106 S255\nMOVE_TO_TRASH\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-10 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\n; FLUSH_START\nM106 S25\nG1 E30 F300\n; FLUSH_END\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\n{if flush_length_1 > 23.7}\nG1 E23.7 F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\n{else}\nG1 E{flush_length_1} F{old_filament_e_feedrate}\n{endif}\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 X85 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM400\nM106 S255\nM104 S[new_filament_temp]\nINIT_SYNC_BUFFER_STATE\nBUFFER_MONITORING ENABLE=1\nG1 E10 F25 \nM109 S[new_filament_temp]\nG1 E-5 F1800\nCLEAR_OOZE\nTOOL_CHANGE_END\nG1 Y270 F8000\nM106 S0\nG1 E2 F1800\nENABLE_ALL_SENSOR\n{endif}\n",
|
||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"QIDI PLA Rapido @Qidi Q2 0.4 nozzle"
|
"QIDI PLA Rapido @Qidi Q2 0.4 nozzle"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"cooling_tube_length": "0",
|
"cooling_tube_length": "0",
|
||||||
"parking_pos_retraction": "0",
|
"parking_pos_retraction": "0",
|
||||||
"extra_loading_move": "5",
|
"extra_loading_move": "5",
|
||||||
"change_filament_gcode": "G1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nBUFFER_MONITORING ENABLE=0\nDISABLE_ALL_SENSOR\nM106 S255\nMOVE_TO_TRASH\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-10 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\n; FLUSH_START\nM106 S25\nG1 E30 F300\n; FLUSH_END\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\n{if flush_length_1 > 23.7}\nG1 E23.7 F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\n{else}\nG1 E{flush_length_1} F{old_filament_e_feedrate}\n{endif}\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 X85 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM400\nM106 S255\nM104 S[new_filament_temp]\nINIT_SYNC_BUFFER_STATE\nBUFFER_MONITORING ENABLE=1\nG1 E10 F25 \nM109 S[new_filament_temp]\nG1 E-5 F1800\nCLEAR_OOZE\nTOOL_CHANGE_END\nG1 Y270 F8000\nM106 S0\nG1 E2 F1800\nENABLE_ALL_SENSOR\n",
|
"change_filament_gcode": "{if current_extruder != next_extruder}\nG1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nBUFFER_MONITORING ENABLE=0\nDISABLE_ALL_SENSOR\nM106 S255\nMOVE_TO_TRASH\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-10 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\n; FLUSH_START\nM106 S25\nG1 E30 F300\n; FLUSH_END\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\n{if flush_length_1 > 23.7}\nG1 E23.7 F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{old_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\nG1 E{(flush_length_1 - 23.7) * 0.02} F50\nG1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate}\n{else}\nG1 E{flush_length_1} F{old_filament_e_feedrate}\n{endif}\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 X85 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 X92 F9000\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM400\nM106 S255\nM104 S[new_filament_temp]\nINIT_SYNC_BUFFER_STATE\nBUFFER_MONITORING ENABLE=1\nG1 E10 F25 \nM109 S[new_filament_temp]\nG1 E-5 F1800\nCLEAR_OOZE\nTOOL_CHANGE_END\nG1 Y270 F8000\nM106 S0\nG1 E2 F1800\nENABLE_ALL_SENSOR\n{endif}\n",
|
||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"QIDI PLA Rapido @Qidi Q2C 0.4 nozzle"
|
"QIDI PLA Rapido @Qidi Q2C 0.4 nozzle"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"bed_exclude_area": [
|
"bed_exclude_area": [
|
||||||
"0x0, 16x0, 16x13, 0x13, 0x0, 0x0, 0x0, 0x0, 0x13, 6x13, 6x23, 0x23, 0x13, 0x13, 0x13, 0x13, 0x387, 53x387, 53x390, 0x390, 0x387, 0x387, 0x397, 0x390, 338x390, 338x384, 390x384, 390x390, 0x390"
|
"0x0, 16x0, 16x13, 0x13, 0x0, 0x0, 0x0, 0x0, 0x13, 6x13, 6x23, 0x23, 0x13, 0x13, 0x13, 0x13, 0x387, 53x387, 53x390, 0x390, 0x387, 0x387, 0x397, 0x390, 338x390, 338x384, 390x384, 390x390, 0x390"
|
||||||
],
|
],
|
||||||
"change_filament_gcode": "G1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nDISABLE_ALL_SENSOR\nM104 S{old_filament_temp - 10}\nM106 S255\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-2 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\nM106 S0\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\nM109.0 S{(nozzle_temperature_range_high[current_extruder])-25}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\nM109.0 S{(nozzle_temperature_range_high[next_extruder])-25}\n{endif}\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\nG1 Y403.5 F2000\nG1 E{flush_length_1} F{old_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 E{flush_length_2} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 E{flush_length_3} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 E{flush_length_4} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\nM400\nM106 S180\nM104 S{new_filament_temp - 10}\nG1 E1 F10\nM109.1 S{new_filament_temp - 10}\nG1 E-4 F1000\nG4 P2000\nM204 S5000\nG1 Y403 F2000\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F3000\nG1 X145 F2000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F8000\nG1 Y380\nG1 X116\nG4 P2000\nG1 Y403 F3000\nG1 X130\nG1 X100 F8000\nG1 Y380\nG1 X116\nG1 Y403 F3000\nG1 X130 F3000\nG1 X100 F8000\nG1 Y380\nM104 S[new_filament_temp]\nTOOL_CHANGE_END\nG1 E{new_retract_length_toolchange + 1} F{new_filament_e_feedrate}\nENABLE_ALL_SENSOR\n",
|
"change_filament_gcode": "{if current_extruder != next_extruder}\nG1 Z{max_layer_z + 3.0} F1200\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nDISABLE_ALL_SENSOR\nM104 S{old_filament_temp - 10}\nM106 S255\n{if long_retractions_when_cut[previous_extruder]}\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{else}\nG1 E-2 F{old_filament_e_feedrate}\n{endif}\nM400\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM106 P2 S0\nUNLOAD_T[current_extruder]\nT[next_extruder]\nM106 S0\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\nM109.0 S{(nozzle_temperature_range_high[current_extruder])-25}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\nM109.0 S{(nozzle_temperature_range_high[next_extruder])-25}\n{endif}\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\n{if flush_length_1 > 1}\n; FLUSH_START\nG1 Y403.5 F2000\nG1 E{flush_length_1} F{old_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\n; FLUSH_START\nG1 E{flush_length_2} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\n; FLUSH_START\nG1 E{flush_length_3} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\n; FLUSH_START\nG1 E{flush_length_4} F{new_filament_e_feedrate * 0.5}\n; FLUSH_END\n{endif}\nM400\nM106 S180\nM104 S{new_filament_temp - 10}\nG1 E1 F10\nM109.1 S{new_filament_temp - 10}\nG1 E-4 F1000\nG4 P2000\nM204 S5000\nG1 Y403 F2000\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F3000\nG1 X145 F2000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F8000\nG1 Y380\nG1 X116\nG4 P2000\nG1 Y403 F3000\nG1 X130\nG1 X100 F8000\nG1 Y380\nG1 X116\nG1 Y403 F3000\nG1 X130 F3000\nG1 X100 F8000\nG1 Y380\nM104 S[new_filament_temp]\nTOOL_CHANGE_END\nG1 E{new_retract_length_toolchange + 1} F{new_filament_e_feedrate}\nENABLE_ALL_SENSOR\n{endif}\n",
|
||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"QIDI PLA Rapido @Qidi X-Max 4 0.4 nozzle"
|
"QIDI PLA Rapido @Qidi X-Max 4 0.4 nozzle"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"single_extruder_multi_material": "1",
|
"single_extruder_multi_material": "1",
|
||||||
"change_filament_gcode": "{if max_layer_z < 12}\nG1 Z15 F1200\n{else}\nG1 Z{max_layer_z + 3.0} F1200\n{endif}\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nDISABLE_ALL_SENSOR\n{if long_retractions_when_cut[previous_extruder]}\nMOVE_TO_TRASH\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\nM400\n{else}\nG1 E-5 F{old_filament_e_feedrate}\n{endif}\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM400\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\nM106 S0\nM106 P2 S0\nUNLOAD_T[current_extruder]\nG92 E0\nM83\nG1 E2 F50\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nSET_HEATER_TEMPERATURE HEATER=extruder TARGET={nozzle_temperature_range_high[current_extruder]} WAIT=1\n{else}\nSET_HEATER_TEMPERATURE HEATER=extruder TARGET={nozzle_temperature_range_high[next_extruder]} WAIT=1\n{endif}\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\nM400\nM106 S60\n; FLUSH_START\nG1 E1 F50\nG1 E{65.5 * 0.58} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E{65.5 * 0.18} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E{65.5 * 0.18} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{if flush_length_1 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_1 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E{flush_length_1 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E{flush_length_1 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM104 S[new_filament_temp]\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nM109 S[new_filament_temp]\nG92 E0\nM400\nCLEAR_FLUSH\nCLEAR_OOZE\nM400\nM106 S0\nTOOL_CHANGE_END\nG1 Y305 F9000\nENABLE_ALL_SENSOR",
|
"change_filament_gcode": "{if current_extruder != next_extruder}\n{if max_layer_z < 12}\nG1 Z15 F1200\n{else}\nG1 Z{max_layer_z + 3.0} F1200\n{endif}\nTOOL_CHANGE_START F=[current_extruder] T=[next_extruder]\nDISABLE_ALL_SENSOR\n{if long_retractions_when_cut[previous_extruder]}\nMOVE_TO_TRASH\nG1 E-{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\nM400\n{else}\nG1 E-5 F{old_filament_e_feedrate}\n{endif}\nCUT_FILAMENT T=[current_extruder]\nMOVE_TO_TRASH\nM400\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nM104 S{nozzle_temperature_range_high[current_extruder]}\n{else}\nM104 S{nozzle_temperature_range_high[next_extruder]}\n{endif}\nM106 S0\nM106 P2 S0\nUNLOAD_T[current_extruder]\nG92 E0\nM83\nG1 E2 F50\nT[next_extruder]\n{if nozzle_temperature_range_high[current_extruder] >= nozzle_temperature_range_high[next_extruder]}\nSET_HEATER_TEMPERATURE HEATER=extruder TARGET={nozzle_temperature_range_high[current_extruder]} WAIT=1\n{else}\nSET_HEATER_TEMPERATURE HEATER=extruder TARGET={nozzle_temperature_range_high[next_extruder]} WAIT=1\n{endif}\n{if long_retractions_when_cut[previous_extruder]}\nG1 E{retraction_distances_when_cut[previous_extruder]} F{old_filament_e_feedrate}\n{endif}\nM400\nM106 S60\n; FLUSH_START\nG1 E1 F50\nG1 E{65.5 * 0.58} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E{65.5 * 0.18} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E{65.5 * 0.18} F{old_filament_e_feedrate}\nG1 E{65.5 * 0.02} F50\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{if flush_length_1 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_1 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E{flush_length_1 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E{flush_length_1 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_1 * 0.02} F50\nG1 E-[old_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_2 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[old_retract_length_toolchange] F300\nG1 E{flush_length_2 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_2 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_3 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_3 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_3 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\n{if flush_length_4 > 1}\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nCLEAR_FLUSH\nM400\nM106 S60\n; FLUSH_START\nG1 E[new_retract_length_toolchange] F300\nG1 E{flush_length_4 * 0.58} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate}\nG1 E{flush_length_4 * 0.02} F50\nG1 E-[new_retract_length_toolchange] F1800\n; FLUSH_END\n{endif}\nM104 S[new_filament_temp]\nM400\nM106 S255\nG91\nG1 X-5 F60\nG1 X5 F60\nG90\nM109 S[new_filament_temp]\nG92 E0\nM400\nCLEAR_FLUSH\nCLEAR_OOZE\nM400\nM106 S0\nTOOL_CHANGE_END\nG1 Y305 F9000\nENABLE_ALL_SENSOR\n{endif}",
|
||||||
"is_support_multi_box": "0",
|
"is_support_multi_box": "0",
|
||||||
"machine_pause_gcode": "PAUSE",
|
"machine_pause_gcode": "PAUSE",
|
||||||
"thumbnails": [
|
"thumbnails": [
|
||||||
|
|||||||
+85
-49
@@ -768,30 +768,31 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clearance the tower-approach router keeps around the tower: the avoid box is
|
||||||
|
// inflated by this much before routing, and the inflated corners must stay on the
|
||||||
|
// bed for a route to be generated at all.
|
||||||
|
static constexpr float wipe_tower_routing_clearance = 2.f;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
// start_pos refers to the last position before the wipe_tower.
|
// start_pos refers to the last position before the wipe_tower.
|
||||||
// end_pos refers to the wipe tower's start_pos.
|
// end_pos refers to the wipe tower's start_pos.
|
||||||
// using the print coordinate system
|
// using the print coordinate system
|
||||||
Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const BoundingBox& printer_bbx) const
|
Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const Polygons& bed_polygons) const
|
||||||
{
|
{
|
||||||
Polyline res;
|
Polyline res;
|
||||||
coord_t alpha = scaled(2.f); // offset distance
|
coord_t alpha = scaled(wipe_tower_routing_clearance); // offset distance
|
||||||
BoundingBox avoid_polygon_inner = avoid_polygon;
|
BoundingBox avoid_polygon_inner = avoid_polygon;
|
||||||
avoid_polygon_inner.offset(alpha);
|
avoid_polygon_inner.offset(alpha);
|
||||||
coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0];
|
coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0];
|
||||||
Polygon bed_polygon = printer_bbx.polygon();
|
|
||||||
Vec2f v(1, 0); // the first print direction of end_pos.
|
Vec2f v(1, 0); // the first print direction of end_pos.
|
||||||
if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right.
|
if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right.
|
||||||
// Judge whether the avoid_polygon_inner is outside the printer_bbx.
|
// Judge whether the avoid_polygon_inner is outside the bed. The real printable
|
||||||
|
// outline is tested (not its bounding box), so on circular/custom beds corners
|
||||||
|
// hanging off the bed are rejected.
|
||||||
// If so, do nothing and just go directly to the end_pos.
|
// If so, do nothing and just go directly to the end_pos.
|
||||||
bool is_bbx_in_bed = true;
|
|
||||||
Points avoid_points = avoid_polygon_inner.polygon().points;
|
Points avoid_points = avoid_polygon_inner.polygon().points;
|
||||||
for (auto &wipe_tower_bbx_p : avoid_points) {
|
const bool is_bbx_in_bed = std::all_of(avoid_points.begin(), avoid_points.end(),
|
||||||
if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) {
|
[&bed_polygons](const Point &pt) { return contains(bed_polygons, pt, /*border_result=*/false); });
|
||||||
is_bbx_in_bed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!is_bbx_in_bed) {
|
if (!is_bbx_in_bed) {
|
||||||
res.points.push_back(end_pos);
|
res.points.push_back(end_pos);
|
||||||
return res;
|
return res;
|
||||||
@@ -898,27 +899,17 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
|
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printable-area bounds for tower-approach routing, in object coordinates (shared by
|
// Bed outline the tower-approach router plans against, in object coordinates. The real
|
||||||
// the BBL avoid-perimeter path in append_tcr and the Type2 skip-points router).
|
// outline is returned, not its bounding box, so the router's containment tests fail off
|
||||||
// Multi-nozzle: clamp the travel bounds to the region every extruder can reach
|
// the bed on circular/custom shapes; the multi-nozzle narrowing lives in the accessor.
|
||||||
// (get_extruder_shared_printable_polygon) instead of the full bed. Gated on the
|
Polygons WipeTowerIntegration::shared_printable_area(GCode &gcodegen) const
|
||||||
// multi-nozzle predicate so every existing single/dual printer keeps the historic
|
|
||||||
// full-printable_area routing byte-identical.
|
|
||||||
BoundingBox WipeTowerIntegration::printer_travel_bounds(GCode &gcodegen) const
|
|
||||||
{
|
{
|
||||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
// The frame change is a pure translation, so transform the origin once.
|
||||||
BoundingBox printer_bbx;
|
const Point offset = wipe_tower_point_to_object_point(gcodegen, Vec2f(m_plate_origin(0), m_plate_origin(1)));
|
||||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
Polygons bed_polygons = gcodegen.m_print->get_extruder_shared_printable_polygon();
|
||||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
for (Polygon &poly : bed_polygons)
|
||||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
poly.translate(offset);
|
||||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
return bed_polygons;
|
||||||
} else {
|
|
||||||
Points bed_points;
|
|
||||||
for (const auto& p : gcodegen.m_config.printable_area.values)
|
|
||||||
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
|
|
||||||
printer_bbx = BoundingBox(bed_points);
|
|
||||||
}
|
|
||||||
return printer_bbx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
||||||
@@ -933,15 +924,37 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
|
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
|
||||||
return {};
|
return {};
|
||||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||||
// Transform the tower-local bbx corners exactly like the tcr points; a rotated
|
// Transform tower-local corners exactly like the tcr points; a rotated tower gets a
|
||||||
// tower gets a conservative axis-aligned envelope.
|
// conservative axis-aligned envelope from the result.
|
||||||
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
|
auto tower_polygon = [&](const BoundingBoxf &bbx) {
|
||||||
for (auto& p : avoid_points.points)
|
Polygon poly = scaled(bbx).polygon();
|
||||||
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
|
for (Point &p : poly.points)
|
||||||
BoundingBox avoid_bbx(avoid_points.points);
|
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
|
||||||
if (avoid_bbx.contains(route_start))
|
return poly;
|
||||||
|
};
|
||||||
|
// The avoid envelope covers the first-layer brim (and rib flare), which a travel may
|
||||||
|
// cross freely: early-out only when the approach already starts over the tower body
|
||||||
|
// itself, so a start between the wall and the brim edge still gets routed in through
|
||||||
|
// the wall opening. Test the rotated polygon, not its bounding box — at angles off the
|
||||||
|
// axes the box's corner triangles cover most of the brim ring.
|
||||||
|
const float body_width = gcodegen.m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib ? m_wipe_tower_depth : m_right;
|
||||||
|
if (tower_polygon(BoundingBoxf(Vec2d(0., 0.), Vec2d(body_width, m_wipe_tower_depth))).contains(route_start))
|
||||||
return {};
|
return {};
|
||||||
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
|
|
||||||
|
const Polygons bed = shared_printable_area(gcodegen);
|
||||||
|
BoundingBox avoid_bbx = get_extents(tower_polygon(m_wipe_tower_bbx));
|
||||||
|
// The inflated corners must stay on the bed for the router to generate a route at all:
|
||||||
|
// clamp the box against the bed shrunk by the clearance the router adds, so a tower
|
||||||
|
// parked near the bed edge is still routed along the clamped side instead of always
|
||||||
|
// travelling straight across the tower.
|
||||||
|
BoundingBox clamp_bbx = get_extents(bed);
|
||||||
|
clamp_bbx.offset(-(scaled(wipe_tower_routing_clearance) + SCALED_EPSILON));
|
||||||
|
avoid_bbx.min = avoid_bbx.min.cwiseMax(clamp_bbx.min);
|
||||||
|
avoid_bbx.max = avoid_bbx.max.cwiseMin(clamp_bbx.max);
|
||||||
|
if (avoid_bbx.min.x() >= avoid_bbx.max.x() || avoid_bbx.min.y() >= avoid_bbx.max.y())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, bed);
|
||||||
std::string gcode;
|
std::string gcode;
|
||||||
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
||||||
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
||||||
@@ -1322,7 +1335,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
||||||
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
||||||
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
||||||
BoundingBox avoid_bbx, printer_bbx = printer_travel_bounds(gcodegen);
|
BoundingBox avoid_bbx;
|
||||||
{
|
{
|
||||||
// set avoid_bbx
|
// set avoid_bbx
|
||||||
avoid_bbx = scaled(m_wipe_tower_bbx);
|
avoid_bbx = scaled(m_wipe_tower_bbx);
|
||||||
@@ -1334,7 +1347,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
avoid_bbx = BoundingBox(avoid_points.points);
|
avoid_bbx = BoundingBox(avoid_points.points);
|
||||||
}
|
}
|
||||||
std::string travel_to_wipe_tower_gcode;
|
std::string travel_to_wipe_tower_gcode;
|
||||||
Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx);
|
Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, shared_printable_area(gcodegen));
|
||||||
|
|
||||||
for (size_t i = 0; i < travel_polyline.points.size(); ++i) {
|
for (size_t i = 0; i < travel_polyline.points.size(); ++i) {
|
||||||
const auto &p = travel_polyline.points[i];
|
const auto &p = travel_polyline.points[i];
|
||||||
@@ -1554,7 +1567,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id);
|
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id);
|
||||||
toolchange_temp_override = interface_temp;
|
toolchange_temp_override = interface_temp;
|
||||||
}
|
}
|
||||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override,
|
||||||
|
WipeTower2::wait_for_temp_enabled(gcodegen.m_config)); // TODO: toolchange_z vs print_z
|
||||||
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
|
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
|
||||||
// The tool changed in place (multi-tool printer without ramming), so the
|
// The tool changed in place (multi-tool printer without ramming), so the
|
||||||
// tower entry is the tcr's own positioning move — a straight line across
|
// tower entry is the tcr's own positioning move — a straight line across
|
||||||
@@ -1705,7 +1719,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
std::string trimmed = line;
|
std::string trimmed = line;
|
||||||
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
|
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
|
||||||
bool skip_line = false;
|
bool skip_line = false;
|
||||||
if (boost::starts_with(trimmed, "M109")) {
|
if (boost::starts_with(trimmed, "M109") && trimmed.find(WipeTower2::wait_for_temp_tag()) == std::string::npos) {
|
||||||
bool matches_extruder = true;
|
bool matches_extruder = true;
|
||||||
if (trimmed.find('T') != std::string::npos)
|
if (trimmed.find('T') != std::string::npos)
|
||||||
matches_extruder = trimmed.find(t_token) != std::string::npos;
|
matches_extruder = trimmed.find(t_token) != std::string::npos;
|
||||||
@@ -7636,8 +7650,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
if (sloped) {
|
if (sloped) {
|
||||||
speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed));
|
speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed));
|
||||||
}
|
}
|
||||||
}
|
} else if(path.role() == erInternalBridgeInfill) {
|
||||||
else if(path.role() == erInternalBridgeInfill) {
|
|
||||||
speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
|
speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
|
||||||
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
|
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
|
||||||
speed = NOZZLE_CONFIG(bridge_speed);
|
speed = NOZZLE_CONFIG(bridge_speed);
|
||||||
@@ -7648,7 +7661,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
} else if (path.role() == erTopSolidInfill) {
|
} else if (path.role() == erTopSolidInfill) {
|
||||||
speed = NOZZLE_CONFIG(top_surface_speed);
|
speed = NOZZLE_CONFIG(top_surface_speed);
|
||||||
} else if (path.role() == erIroning) {
|
} else if (path.role() == erIroning) {
|
||||||
speed = m_config.get_abs_value("ironing_speed");
|
const size_t filament_idx = get_filament_config_index(m_writer.filament()->id());
|
||||||
|
speed = m_config.filament_ironing_speed.is_nil(filament_idx)
|
||||||
|
? m_config.get_abs_value("ironing_speed")
|
||||||
|
: m_config.filament_ironing_speed.get_at(filament_idx);
|
||||||
} else if (path.role() == erBottomSurface) {
|
} else if (path.role() == erBottomSurface) {
|
||||||
speed = NOZZLE_CONFIG(initial_layer_infill_speed);
|
speed = NOZZLE_CONFIG(initial_layer_infill_speed);
|
||||||
} else if (path.role() == erGapFill) {
|
} else if (path.role() == erGapFill) {
|
||||||
@@ -8939,7 +8955,7 @@ void GCode::update_placeholder_parser_with_variant_params()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override)
|
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override, bool defer_temp_wait)
|
||||||
{
|
{
|
||||||
int new_extruder_id = get_extruder_id(new_filament_id);
|
int new_extruder_id = get_extruder_id(new_filament_id);
|
||||||
if (!m_writer.need_toolchange(new_filament_id))
|
if (!m_writer.need_toolchange(new_filament_id))
|
||||||
@@ -9046,6 +9062,24 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
|||||||
if (toolchange_temp_override > 0)
|
if (toolchange_temp_override > 0)
|
||||||
new_filament_temp = toolchange_temp_override;
|
new_filament_temp = toolchange_temp_override;
|
||||||
|
|
||||||
|
// With wait_for_temp_on_wipe_tower the blocking M109 is deferred to the wipe tower, so raise
|
||||||
|
// the incoming filament's target here — ahead of the tool change rather than after it — and
|
||||||
|
// let the heat-up overlap the change itself as well as the travel to the tower. The command
|
||||||
|
// always carries an explicit tool index (the option is off for single extruder MM, so the
|
||||||
|
// writer emits one), leaving the outgoing filament that pre_toolchange just dropped to its
|
||||||
|
// standby temperature alone. nozzle_temperature == 0 means "use the first layer temperature".
|
||||||
|
if (defer_temp_wait) {
|
||||||
|
// Target what the tower will wait on. It waits on the first layer temperature not only on
|
||||||
|
// the first layer but also while priming, which runs before any layer is set: there
|
||||||
|
// on_first_layer() is false and print_z is the initial layer height, so neither test above
|
||||||
|
// catches it. nozzle_temperature == 0 means "use the first layer temperature" as well.
|
||||||
|
int preheat_temp = new_filament_temp;
|
||||||
|
if (toolchange_temp_override <= 0 && (m_layer == nullptr || preheat_temp <= 0))
|
||||||
|
preheat_temp = m_config.nozzle_temperature_initial_layer.get_at(new_fi);
|
||||||
|
if (preheat_temp > 0)
|
||||||
|
gcode += m_writer.set_temperature(preheat_temp, false, new_filament_id);
|
||||||
|
}
|
||||||
|
|
||||||
Vec3d nozzle_pos = m_writer.get_position();
|
Vec3d nozzle_pos = m_writer.get_position();
|
||||||
float old_retract_length, old_retract_length_toolchange, wipe_volume;
|
float old_retract_length, old_retract_length_toolchange, wipe_volume;
|
||||||
int old_filament_temp, old_filament_e_feedrate;
|
int old_filament_temp, old_filament_e_feedrate;
|
||||||
@@ -9349,8 +9383,10 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
|||||||
}
|
}
|
||||||
check_add_eol(gcode);
|
check_add_eol(gcode);
|
||||||
}
|
}
|
||||||
// Set the new extruder to the operating temperature.
|
// Set the new extruder to the operating temperature. With defer_temp_wait the target was
|
||||||
if (m_ooze_prevention.enable)
|
// already raised before the tool change and the blocking wait belongs to the wipe tower
|
||||||
|
// generator, so there is nothing left to restore here.
|
||||||
|
if (m_ooze_prevention.enable && !defer_temp_wait)
|
||||||
gcode += m_ooze_prevention.post_toolchange(*this);
|
gcode += m_ooze_prevention.post_toolchange(*this);
|
||||||
|
|
||||||
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
|
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
|
||||||
|
|||||||
@@ -130,11 +130,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) const;
|
||||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||||
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
||||||
BoundingBox printer_travel_bounds(GCode &gcodegen) const;
|
Polygons shared_printable_area(GCode &gcodegen) const;
|
||||||
|
|
||||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||||
@@ -262,7 +262,7 @@ public:
|
|||||||
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
||||||
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
||||||
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
||||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
|
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
|
||||||
bool is_BBL_Printer();
|
bool is_BBL_Printer();
|
||||||
WipeTowerType wipe_tower_type();
|
WipeTowerType wipe_tower_type();
|
||||||
|
|
||||||
|
|||||||
@@ -1630,25 +1630,56 @@ float WipeTower::get_auto_brim_by_height(float max_height) {
|
|||||||
return 8.f;
|
return 8.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2f WipeTower::move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2,int scaled_offset)
|
Vec2f WipeTower::move_box_inside_polygon(const BoundingBox &box, const Polygons &polygons, coord_t offset)
|
||||||
{
|
{
|
||||||
Vec2f res{0, 0};
|
if (polygons.empty()) return Vec2f{0.f, 0.f};
|
||||||
if (box1.size()[0] >= box2.size()[0]- 2*scaled_offset || box1.size()[1] >= box2.size()[1]-2*scaled_offset) return res;
|
|
||||||
|
|
||||||
if (box1.max[0] > box2.max[0] - scaled_offset) {
|
const BoundingBox bed = get_extents(polygons);
|
||||||
res[0] = unscaled<float>((box2.max[0] - scaled_offset) - box1.max[0]);
|
// No position fits the footprint.
|
||||||
}
|
if (box.size().x() >= bed.size().x() - 2 * offset || box.size().y() >= bed.size().y() - 2 * offset)
|
||||||
else if (box1.min[0] < box2.min[0] + scaled_offset) {
|
return Vec2f{0.f, 0.f};
|
||||||
res[0] = unscaled<float>((box2.min[0] + scaled_offset) - box1.min[0]);
|
|
||||||
|
// Clamp against the bounding box first, moving only along the axis that is violated so a dragged
|
||||||
|
// prime tower slides along the bed edge instead of jumping inwards.
|
||||||
|
Point shift(0, 0);
|
||||||
|
for (int axis = 0; axis < 2; ++axis) {
|
||||||
|
if (box.max[axis] > bed.max[axis] - offset)
|
||||||
|
shift[axis] = (bed.max[axis] - offset) - box.max[axis];
|
||||||
|
else if (box.min[axis] < bed.min[axis] + offset)
|
||||||
|
shift[axis] = (bed.min[axis] + offset) - box.min[axis];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (box1.max[1] > box2.max[1] - scaled_offset) {
|
// A bed that fills its own bounding box is fully clamped by that, so every rectangular bed — all
|
||||||
res[1] = unscaled<float>((box2.max[1] - scaled_offset) - box1.max[1]);
|
// but the delta-style profiles — stops here and keeps its historic placement, including when a
|
||||||
|
// negative margin lets the footprint hang over the edge. The tolerance is relative because an
|
||||||
|
// exact rectangle loses a few ulps once the areas are squared world coordinates.
|
||||||
|
double area = 0.;
|
||||||
|
for (const Polygon &poly : polygons) area += std::abs(poly.area());
|
||||||
|
const double bed_area = double(bed.size().x()) * double(bed.size().y());
|
||||||
|
if (area >= bed_area * (1. - EPSILON)) return unscaled<float>(shift);
|
||||||
|
|
||||||
|
// Clamp a negative margin (an auto brim width that has not been resolved yet) to zero: padding by
|
||||||
|
// it would shrink the footprint and hand back a position the validation still rejects. The
|
||||||
|
// epsilon lets the move's round trip through millimeters land on the outline without counting as
|
||||||
|
// a violation.
|
||||||
|
BoundingBox padded = box.inflated(std::max<coord_t>(offset, 0) - SCALED_EPSILON);
|
||||||
|
padded.translate(shift);
|
||||||
|
auto fits = [&padded, &polygons](const Point &move) {
|
||||||
|
BoundingBox moved = padded;
|
||||||
|
moved.translate(move);
|
||||||
|
return diff(Polygons{moved.polygon()}, polygons).empty();
|
||||||
|
};
|
||||||
|
if (fits(Point(0, 0))) return unscaled<float>(shift);
|
||||||
|
|
||||||
|
// Walk towards the middle of the bed. On every non-rectangular bed we ship, the fitting positions
|
||||||
|
// form a convex region around it, so bisecting stops just inside the outline.
|
||||||
|
Point lo(0, 0), hi = bed.center() - padded.center();
|
||||||
|
if (!fits(hi)) return unscaled<float>(shift);
|
||||||
|
for (int i = 0; i < 12; ++i) {
|
||||||
|
const Point mid = (lo + hi) / 2;
|
||||||
|
if (fits(mid)) hi = mid; else lo = mid;
|
||||||
}
|
}
|
||||||
else if (box1.min[1] < box2.min[1] + scaled_offset) {
|
return unscaled<float>(Point(shift + hi));
|
||||||
res[1] = unscaled<float>((box2.min[1] + scaled_offset) - box1.min[1]);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygon WipeTower::rib_section(float width, float depth, float rib_length, float rib_width,bool fillet_wall)
|
Polygon WipeTower::rib_section(float width, float depth, float rib_length, float rib_width,bool fillet_wall)
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ public:
|
|||||||
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
|
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
|
||||||
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
|
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
|
||||||
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
|
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
|
||||||
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
|
// Translation that brings a footprint inside the printable outline, padded by offset. The prime
|
||||||
|
// tower is validated against the real outline (see layered_print_cleareance_valid), so clamping
|
||||||
|
// against the bounding box alone would leave it off a delta or hexagonal bed. box and polygons
|
||||||
|
// must share one scaled coordinate frame; the translation comes back in millimeters.
|
||||||
|
static Vec2f move_box_inside_polygon(const BoundingBox &box, const Polygons &polygons, coord_t offset = 0);
|
||||||
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
|
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
|
||||||
struct Extrusion
|
struct Extrusion
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ public:
|
|||||||
const Vec2f& pos() const { return m_current_pos; }
|
const Vec2f& pos() const { return m_current_pos; }
|
||||||
const Vec2f start_pos_rotated() const { return m_start_pos; }
|
const Vec2f start_pos_rotated() const { return m_start_pos; }
|
||||||
const Vec2f pos_rotated() const { return this->rotate(m_current_pos); }
|
const Vec2f pos_rotated() const { return this->rotate(m_current_pos); }
|
||||||
|
const Vec2f rotated(const Vec2f &pt) const { return this->rotate(pt); }
|
||||||
float elapsed_time() const { return m_elapsed_time; }
|
float elapsed_time() const { return m_elapsed_time; }
|
||||||
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
|
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
|
||||||
|
|
||||||
@@ -624,10 +625,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set extruder temperature, don't wait by default.
|
// Set extruder temperature, don't wait by default.
|
||||||
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false)
|
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false, const std::string& comment = std::string())
|
||||||
{
|
{
|
||||||
flush_planner_queue();
|
flush_planner_queue();
|
||||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature);
|
||||||
|
if (!comment.empty())
|
||||||
|
m_gcode += " " + comment;
|
||||||
|
m_gcode += "\n";
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,6 +1010,13 @@ bool WipeTower2::use_gap_wall(const PrintConfig& config)
|
|||||||
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
|
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WipeTower2::wait_for_temp_enabled(const PrintConfig& config)
|
||||||
|
{
|
||||||
|
// SEMM runs its own unload/load temperature sequence; the GUI hides the option
|
||||||
|
// there but a profile may still carry it set.
|
||||||
|
return config.wait_for_temp_on_wipe_tower.value && !config.single_extruder_multi_material.value;
|
||||||
|
}
|
||||||
|
|
||||||
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||||
m_semm(config.single_extruder_multi_material.value),
|
m_semm(config.single_extruder_multi_material.value),
|
||||||
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
||||||
@@ -1035,7 +1046,8 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
|||||||
m_wall_type((int)config.wipe_tower_wall_type),
|
m_wall_type((int)config.wipe_tower_wall_type),
|
||||||
m_use_gap_wall(use_gap_wall(config)),
|
m_use_gap_wall(use_gap_wall(config)),
|
||||||
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
||||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value),
|
||||||
|
m_wait_for_temp_on_wipe_tower(wait_for_temp_enabled(config))
|
||||||
{
|
{
|
||||||
// Read absolute value of first layer speed, if given as percentage,
|
// Read absolute value of first layer speed, if given as percentage,
|
||||||
// it is taken over following default. Speeds from config are not
|
// it is taken over following default. Speeds from config are not
|
||||||
@@ -1085,6 +1097,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
|||||||
m_bed_bottom_left = m_bed_shape == RectangularBed
|
m_bed_bottom_left = m_bed_shape == RectangularBed
|
||||||
? Vec2f(bed_points.front().x(), bed_points.front().y())
|
? Vec2f(bed_points.front().x(), bed_points.front().y())
|
||||||
: Vec2f::Zero();
|
: Vec2f::Zero();
|
||||||
|
m_bed_polygon = Polygon::new_scale(bed_points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1236,7 +1249,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
|
|||||||
|
|
||||||
unsigned int tool = tools[idx_tool];
|
unsigned int tool = tools[idx_tool];
|
||||||
m_left_to_right = true;
|
m_left_to_right = true;
|
||||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials.
|
toolchange_Change(writer, tool, m_filpar[tool].material, m_filpar[tool].first_layer_temperature, false); // Select the tool, set a speed override for soluble and flex materials.
|
||||||
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
||||||
if (idx_tool + 1 == tools.size()) {
|
if (idx_tool + 1 == tools.size()) {
|
||||||
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
||||||
@@ -1355,13 +1368,19 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
|||||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||||
new_tool_temp);
|
new_tool_temp);
|
||||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
|
// Wait-at-tower target: the interface temp when an interface boost applies on this layer,
|
||||||
|
// otherwise the print temp (nozzle_temperature == 0 means "use the first layer temp").
|
||||||
|
int wait_for_temp = interface_layer && m_filpar[tool].interface_print_temperature > 0 ?
|
||||||
|
m_filpar[tool].interface_print_temperature :
|
||||||
|
(is_first_layer() || m_filpar[tool].temperature == 0 ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
|
||||||
|
toolchange_Change(writer, tool, m_filpar[tool].material, wait_for_temp, true); // Change the tool, set a speed override for soluble and flex materials.
|
||||||
toolchange_Load(writer, cleaning_box);
|
toolchange_Load(writer, cleaning_box);
|
||||||
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||||
int base_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
int base_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
||||||
if (interface_layer) {
|
if (interface_layer) {
|
||||||
int interface_temp = m_filpar[tool].interface_print_temperature;
|
int interface_temp = m_filpar[tool].interface_print_temperature;
|
||||||
if (interface_temp > 0 && interface_temp != base_temp)
|
// With wait-for-temp-on-wipe-tower the toolchange already blocked for the interface temp.
|
||||||
|
if (interface_temp > 0 && interface_temp != base_temp && !m_wait_for_temp_on_wipe_tower)
|
||||||
writer.set_extruder_temp(interface_temp, true);
|
writer.set_extruder_temp(interface_temp, true);
|
||||||
if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
||||||
writer.set_extruder_temp(base_temp, false);
|
writer.set_extruder_temp(base_temp, false);
|
||||||
@@ -1671,7 +1690,9 @@ void WipeTower2::toolchange_Unload(
|
|||||||
void WipeTower2::toolchange_Change(
|
void WipeTower2::toolchange_Change(
|
||||||
WipeTowerWriter2 &writer,
|
WipeTowerWriter2 &writer,
|
||||||
const size_t new_tool,
|
const size_t new_tool,
|
||||||
const std::string& new_material)
|
const std::string& new_material,
|
||||||
|
const int wait_for_temp,
|
||||||
|
const bool wait_beside_tower)
|
||||||
{
|
{
|
||||||
// Ask the writer about how much of the old filament we consumed:
|
// Ask the writer about how much of the old filament we consumed:
|
||||||
if (m_current_tool < m_used_filament_length.size())
|
if (m_current_tool < m_used_filament_length.size())
|
||||||
@@ -1685,6 +1706,90 @@ void WipeTower2::toolchange_Change(
|
|||||||
if (m_is_mk4mmu3)
|
if (m_is_mk4mmu3)
|
||||||
writer.switch_filament_monitoring(true);
|
writer.switch_filament_monitoring(true);
|
||||||
|
|
||||||
|
const bool wait_for_temp_here = m_wait_for_temp_on_wipe_tower && wait_for_temp > 0;
|
||||||
|
|
||||||
|
// The Tn above was issued without a blocking temperature wait (GCode::set_extruder only raises
|
||||||
|
// the target, ahead of the Tn); block here, before the deretraction below, which must not
|
||||||
|
// extrude on a cold nozzle. Like the Bambu H2C, park beside the tower for the heat-up so drool lands
|
||||||
|
// next to it instead of on its top surface — nearest x side first, then that side clamped
|
||||||
|
// toward the bed edge, then the far side, in place if all would leave the bed. Raw
|
||||||
|
// pre-rotated moves (see the repositioning move below) keep the writer's tracked position
|
||||||
|
// at the tower entry. The tag keeps the
|
||||||
|
// interface-temp deduplication pass in append_tcr2 from stripping the M109.
|
||||||
|
if (wait_for_temp_here && wait_beside_tower) {
|
||||||
|
// The rib wall and the stabilization cone bulge past the nominal width rectangle
|
||||||
|
// (widest near the bottom), and the first-layer brim is printed around the wall later
|
||||||
|
// in the layer — clear the widest of them, not just the rectangle, so the park point
|
||||||
|
// and its drool stay off the tower.
|
||||||
|
float min_x = 0.f, max_x = m_wipe_tower_width;
|
||||||
|
if (m_wall_type == (int)wtwRib) {
|
||||||
|
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||||
|
const BoundingBox rib_bbox = get_extents(generate_rib_polygon(wt_box)); // the fillet stays within this bbox
|
||||||
|
min_x = std::min(min_x, unscaled<float>(rib_bbox.min.x()));
|
||||||
|
max_x = std::max(max_x, unscaled<float>(rib_bbox.max.x()));
|
||||||
|
} else if (m_wall_type == (int)wtwCone) {
|
||||||
|
const double support_scale = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth,
|
||||||
|
m_wipe_tower_cone_angle).second;
|
||||||
|
const double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z;
|
||||||
|
const double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle / 2.f)) * (m_wipe_tower_height - z);
|
||||||
|
const double w = m_layer_info->depth + m_perimeter_width;
|
||||||
|
if (r > 0.5 * w + 0.01) { // same guard as generate_support_cone_wall
|
||||||
|
const float bulge = float(std::sqrt(r * r - 0.25 * w * w) / support_scale);
|
||||||
|
min_x = std::min(min_x, m_wipe_tower_width / 2.f - bulge);
|
||||||
|
max_x = std::max(max_x, m_wipe_tower_width / 2.f + bulge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_first_layer()) {
|
||||||
|
const float brim = m_wipe_tower_brim_width < 0.f ? WipeTower::get_auto_brim_by_height(m_wipe_tower_height) :
|
||||||
|
m_wipe_tower_brim_width;
|
||||||
|
min_x -= brim;
|
||||||
|
max_x += brim;
|
||||||
|
}
|
||||||
|
constexpr float gap = 2.f;
|
||||||
|
constexpr float min_gap = 0.5f;
|
||||||
|
const bool on_left = writer.x() < m_wipe_tower_width / 2.f;
|
||||||
|
const float near_x = on_left ? min_x - gap : max_x + gap;
|
||||||
|
const float far_x = on_left ? max_x + gap : min_x - gap;
|
||||||
|
const Eigen::Rotation2Df to_bed(float(Geometry::deg2rad(m_wipe_tower_rotation_angle)));
|
||||||
|
auto park_pt_on_bed = [this, &writer, to_bed](float side_x) {
|
||||||
|
const Vec2f bed_pt = to_bed * (writer.rotated(Vec2f(side_x, writer.y())) + m_rib_offset) + m_wipe_tower_pos;
|
||||||
|
return m_bed_polygon.contains(Point::new_scale(bed_pt.x(), bed_pt.y()));
|
||||||
|
};
|
||||||
|
float park_x = near_x;
|
||||||
|
bool have_park = park_pt_on_bed(near_x);
|
||||||
|
if (!have_park) {
|
||||||
|
// The ideal near point hangs off the bed: pull it back to the bed edge as long
|
||||||
|
// as that still clears the tower envelope by min_gap (the BBL tower clamps its
|
||||||
|
// stop_pos against the bed the same way in append_tcr). Bisection, because with
|
||||||
|
// tower rotation and non-rectangular beds the bed edge is not axis-aligned.
|
||||||
|
const float limit_x = on_left ? min_x - min_gap : max_x + min_gap;
|
||||||
|
if (park_pt_on_bed(limit_x)) {
|
||||||
|
float on = limit_x, off = near_x;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
const float mid = 0.5f * (on + off);
|
||||||
|
if (park_pt_on_bed(mid))
|
||||||
|
on = mid;
|
||||||
|
else
|
||||||
|
off = mid;
|
||||||
|
}
|
||||||
|
park_x = on;
|
||||||
|
have_park = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!have_park && park_pt_on_bed(far_x)) {
|
||||||
|
park_x = far_x;
|
||||||
|
have_park = true;
|
||||||
|
}
|
||||||
|
if (have_park) {
|
||||||
|
const Vec2f stop = writer.rotated(Vec2f(park_x, writer.y()));
|
||||||
|
writer.feedrate(m_travel_speed * 60.f)
|
||||||
|
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(stop.x())
|
||||||
|
+ " Y" + Slic3r::float_to_string_decimal_point(stop.y())
|
||||||
|
+ never_skip_tag() + "\n");
|
||||||
|
}
|
||||||
|
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
|
||||||
|
}
|
||||||
|
|
||||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||||
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
||||||
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
|
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
|
||||||
@@ -1695,6 +1800,10 @@ void WipeTower2::toolchange_Change(
|
|||||||
+ never_skip_tag() + "\n"
|
+ never_skip_tag() + "\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Priming has no tower to park beside — wait right at the priming line instead.
|
||||||
|
if (wait_for_temp_here && !wait_beside_tower)
|
||||||
|
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
|
||||||
|
|
||||||
writer.append("[deretraction_from_wipe_tower_generator]");
|
writer.append("[deretraction_from_wipe_tower_generator]");
|
||||||
|
|
||||||
// The toolchange Tn command will be inserted later, only in case that the user does
|
// The toolchange Tn command will be inserted later, only in case that the user does
|
||||||
@@ -2021,31 +2130,68 @@ std::pair<double, double> WipeTower2::get_wipe_tower_cone_base(double width, dou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static method to extract wipe_volumes[from][to] from the configuration.
|
// Static method to extract wipe_volumes[from][to] from the configuration.
|
||||||
std::vector<std::vector<float>> WipeTower2::extract_wipe_volumes(const PrintConfig& config)
|
// Takes a ConfigBase so the GUI's wipe tower size estimate can pass the plate's
|
||||||
|
// DynamicPrintConfig directly instead of materializing a full PrintConfig per call.
|
||||||
|
std::vector<std::vector<float>> WipeTower2::extract_wipe_volumes(const ConfigBase& config)
|
||||||
{
|
{
|
||||||
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
|
// flush_volumes_matrix holds one filaments x filaments block per nozzle (written by
|
||||||
std::vector<float> wiping_matrix(cast<float>(config.flush_volumes_matrix.values));
|
// PresetBundle::update_multi_material_filament_presets), so the filament count is
|
||||||
auto scale = config.flush_multiplier.get_at(0);
|
// sqrt(size / nozzles). One tower serves every nozzle and the filament to nozzle assignment is
|
||||||
|
// only decided later by ToolOrdering, so fold the blocks with std::max: the depth reserved here
|
||||||
|
// has to cover the worst nozzle. With a single nozzle the fold has one term.
|
||||||
|
const std::vector<double> &raw_matrix = config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
|
||||||
|
const auto *nozzle_diameter = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||||
|
size_t nozzle_nums = (nozzle_diameter == nullptr || nozzle_diameter->values.empty()) ? 1 : nozzle_diameter->values.size();
|
||||||
|
unsigned int number_of_extruders = (unsigned int)(sqrt(raw_matrix.size() / nozzle_nums) + EPSILON);
|
||||||
|
if (size_t(number_of_extruders) * number_of_extruders * nozzle_nums != raw_matrix.size()) {
|
||||||
|
// Saved for a different nozzle count (older project, or the printer was just switched):
|
||||||
|
// fall back to reading the whole option as one block, as this did before.
|
||||||
|
nozzle_nums = 1;
|
||||||
|
number_of_extruders = (unsigned int)(sqrt(raw_matrix.size()) + EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
// The values shall only be used when SEMM is enabled. The purging for other printers
|
// The values shall only be used when SEMM is enabled. The purging for other printers
|
||||||
// is determined by filament_minimal_purge_on_wipe_tower.
|
// is determined by filament_minimal_purge_on_wipe_tower.
|
||||||
if (! config.purge_in_prime_tower.value || ! config.single_extruder_multi_material.value)
|
const bool purge = config.option<ConfigOptionBool>("purge_in_prime_tower")->value
|
||||||
std::fill(wiping_matrix.begin(), wiping_matrix.end(), 0.f);
|
&& config.option<ConfigOptionBool>("single_extruder_multi_material")->value;
|
||||||
|
|
||||||
// Extract purging volumes for each extruder pair:
|
// Extract purging volumes for each extruder pair, each nozzle's block scaled by its own multiplier:
|
||||||
std::vector<std::vector<float>> wipe_volumes;
|
std::vector<std::vector<float>> wipe_volumes(number_of_extruders, std::vector<float>(number_of_extruders, 0.f));
|
||||||
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
|
if (purge) {
|
||||||
for (size_t i = 0; i<number_of_extruders; ++i)
|
const auto *multiplier = config.option<ConfigOptionFloats>("flush_multiplier");
|
||||||
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
|
for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
|
||||||
|
const std::vector<double> block = get_flush_volumes_matrix(raw_matrix, nozzle_id, nozzle_nums);
|
||||||
|
const double scale = multiplier->get_at(nozzle_id);
|
||||||
|
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||||
|
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
||||||
|
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], float(block[size_t(i) * number_of_extruders + j]) * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
|
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
|
||||||
|
const auto *minimal_purge = config.option<ConfigOptionFloats>("filament_minimal_purge_on_wipe_tower");
|
||||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||||
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
||||||
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j] * scale, config.filament_minimal_purge_on_wipe_tower.get_at(j));
|
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], minimal_purge->get_at(j));
|
||||||
|
|
||||||
return wipe_volumes;
|
return wipe_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float WipeTower2::estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt)
|
||||||
|
{
|
||||||
|
const std::vector<std::vector<float>> wipe_volumes = extract_wipe_volumes(config);
|
||||||
|
if (wipe_volumes.empty()) // an empty flush matrix would make the average below 0/0
|
||||||
|
return 0.f;
|
||||||
|
float maximum = 0.f;
|
||||||
|
for (const std::vector<float> &v : wipe_volumes)
|
||||||
|
maximum += *std::max_element(v.begin(), v.end());
|
||||||
|
maximum = maximum * filaments_cnt / wipe_volumes.size();
|
||||||
|
|
||||||
|
// Orca: it's overshooting a bit, so let's reduce it a bit
|
||||||
|
maximum *= 0.6;
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
|
||||||
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
|
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
|
||||||
{
|
{
|
||||||
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
|
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
|
||||||
|
|||||||
@@ -17,13 +17,20 @@ namespace Slic3r
|
|||||||
|
|
||||||
class WipeTowerWriter2;
|
class WipeTowerWriter2;
|
||||||
class PrintRegionConfig;
|
class PrintRegionConfig;
|
||||||
|
class ConfigBase;
|
||||||
|
|
||||||
class WipeTower2
|
class WipeTower2
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
|
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
|
||||||
|
// Marks the wait-for-temp-on-wipe-tower M109 so the interface-temp deduplication pass
|
||||||
|
// in WipeTowerIntegration::append_tcr2 does not strip it.
|
||||||
|
static const std::string wait_for_temp_tag() { return ";_WAIT_FOR_TEMP_ON_WIPE_TOWER"; }
|
||||||
static std::pair<double, double> get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg);
|
static std::pair<double, double> get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg);
|
||||||
static std::vector<std::vector<float>> extract_wipe_volumes(const PrintConfig& config);
|
static std::vector<std::vector<float>> extract_wipe_volumes(const ConfigBase& config);
|
||||||
|
// Estimated total flush volume of a SEMM print with the given number of filaments,
|
||||||
|
// used to reserve wipe tower space before the tower is generated.
|
||||||
|
static float estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt);
|
||||||
|
|
||||||
|
|
||||||
// Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2.
|
// Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2.
|
||||||
@@ -38,6 +45,11 @@ public:
|
|||||||
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
||||||
static bool use_gap_wall(const PrintConfig& config);
|
static bool use_gap_wall(const PrintConfig& config);
|
||||||
|
|
||||||
|
// Whether the blocking toolchange temperature wait moves onto the wipe tower.
|
||||||
|
// Shared with the defer flag in GCode.cpp append_tcr2 so the deferral and the
|
||||||
|
// tower's tagged M109 can never disagree.
|
||||||
|
static bool wait_for_temp_enabled(const PrintConfig& config);
|
||||||
|
|
||||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||||
@@ -227,6 +239,7 @@ private:
|
|||||||
size_t m_first_layer_idx = size_t(-1);
|
size_t m_first_layer_idx = size_t(-1);
|
||||||
bool m_enable_tower_interface_features = false;
|
bool m_enable_tower_interface_features = false;
|
||||||
bool m_enable_tower_interface_cooldown_during_tower = false;
|
bool m_enable_tower_interface_cooldown_during_tower = false;
|
||||||
|
bool m_wait_for_temp_on_wipe_tower = false;
|
||||||
bool m_prev_layer_had_interface = false;
|
bool m_prev_layer_had_interface = false;
|
||||||
bool m_current_layer_has_interface = false;
|
bool m_current_layer_has_interface = false;
|
||||||
|
|
||||||
@@ -263,6 +276,7 @@ private:
|
|||||||
} m_bed_shape;
|
} m_bed_shape;
|
||||||
float m_bed_width; // width of the bed bounding box
|
float m_bed_width; // width of the bed bounding box
|
||||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||||
|
Polygon m_bed_polygon; // printable_area contour (scaled)
|
||||||
|
|
||||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||||
@@ -385,7 +399,9 @@ private:
|
|||||||
void toolchange_Change(
|
void toolchange_Change(
|
||||||
WipeTowerWriter2 &writer,
|
WipeTowerWriter2 &writer,
|
||||||
const size_t new_tool,
|
const size_t new_tool,
|
||||||
const std::string& new_material);
|
const std::string& new_material,
|
||||||
|
const int wait_for_temp,
|
||||||
|
const bool wait_beside_tower);
|
||||||
|
|
||||||
void toolchange_Load(
|
void toolchange_Load(
|
||||||
WipeTowerWriter2 &writer,
|
WipeTowerWriter2 &writer,
|
||||||
|
|||||||
@@ -1423,7 +1423,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||||||
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
|
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
|
||||||
"grab_length", "support_object_skip_flush", "physical_extruder_map",
|
"grab_length", "support_object_skip_flush", "physical_extruder_map",
|
||||||
"cooling_tube_retraction",
|
"cooling_tube_retraction",
|
||||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
|
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", "wait_for_temp_on_wipe_tower",
|
||||||
"z_offset",
|
"z_offset",
|
||||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
||||||
"bed_temperature_formula", "nozzle_flush_dataset",
|
"bed_temperature_formula", "nozzle_flush_dataset",
|
||||||
|
|||||||
+39
-21
@@ -282,6 +282,14 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "wipe_tower_x"
|
|| opt_key == "wipe_tower_x"
|
||||||
|| opt_key == "wipe_tower_y"
|
|| opt_key == "wipe_tower_y"
|
||||||
|| opt_key == "wipe_tower_rotation_angle") {
|
|| opt_key == "wipe_tower_rotation_angle") {
|
||||||
|
// The tower gcode itself is position-independent (position and rotation are applied
|
||||||
|
// at export), except that the wait_for_temp_on_wipe_tower park bakes a bed-relative
|
||||||
|
// side choice into it (WipeTower2::toolchange_Change) — regenerate it when the tower
|
||||||
|
// moves. Gating on the old config is safe: both inputs of wait_for_temp_enabled
|
||||||
|
// invalidate psWipeTower themselves when they are part of the same diff.
|
||||||
|
if ((opt_key == "wipe_tower_x" || opt_key == "wipe_tower_y" || opt_key == "wipe_tower_rotation_angle")
|
||||||
|
&& WipeTower2::wait_for_temp_enabled(m_config))
|
||||||
|
steps.emplace_back(psWipeTower);
|
||||||
steps.emplace_back(psSkirtBrim);
|
steps.emplace_back(psSkirtBrim);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "slicing_pipeline_plugin"
|
opt_key == "slicing_pipeline_plugin"
|
||||||
@@ -382,6 +390,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "wiping_volumes_extruders"
|
|| opt_key == "wiping_volumes_extruders"
|
||||||
|| opt_key == "enable_filament_ramming"
|
|| opt_key == "enable_filament_ramming"
|
||||||
|| opt_key == "tool_change_on_wipe_tower"
|
|| opt_key == "tool_change_on_wipe_tower"
|
||||||
|
|| opt_key == "wait_for_temp_on_wipe_tower"
|
||||||
|| opt_key == "purge_in_prime_tower"
|
|| opt_key == "purge_in_prime_tower"
|
||||||
|| opt_key == "z_offset"
|
|| opt_key == "z_offset"
|
||||||
|| opt_key == "support_multi_bed_types"
|
|| opt_key == "support_multi_bed_types"
|
||||||
@@ -1039,13 +1048,14 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
|||||||
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y));
|
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y));
|
||||||
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth));
|
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth));
|
||||||
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth));
|
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth));
|
||||||
wipe_tower_convex_hull.rotate(a);
|
wipe_tower_convex_hull.rotate(Geometry::deg2rad(a), Point(scale_(x), scale_(y)));
|
||||||
convex_hulls_temp.push_back(wipe_tower_convex_hull);
|
convex_hulls_temp.push_back(wipe_tower_convex_hull);
|
||||||
} else {
|
} else {
|
||||||
//here, wipe_tower_polygon is not always convex.
|
//here, wipe_tower_polygon is not always convex.
|
||||||
Polygon wipe_tower_polygon;
|
Polygon wipe_tower_polygon;
|
||||||
if (print.wipe_tower_data().wipe_tower_mesh_data)
|
if (print.wipe_tower_data().wipe_tower_mesh_data)
|
||||||
wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom;
|
wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom;
|
||||||
|
wipe_tower_polygon.rotate(Geometry::deg2rad(a));
|
||||||
wipe_tower_polygon.translate(Point(scale_(x), scale_(y)));
|
wipe_tower_polygon.translate(Point(scale_(x), scale_(y)));
|
||||||
convex_hulls_temp.push_back(wipe_tower_polygon);
|
convex_hulls_temp.push_back(wipe_tower_polygon);
|
||||||
}
|
}
|
||||||
@@ -1064,6 +1074,22 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
|||||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
||||||
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
||||||
}
|
}
|
||||||
|
// Skip the containment check for towers that will never be printed (single-filament
|
||||||
|
// prints without smooth timelapse keep the config's tower position but emit nothing).
|
||||||
|
// Pre-generation only the body square is tested — the auto-brim estimate can overshoot
|
||||||
|
// the generated brim by several mm and must not hard-fail a print that physically fits.
|
||||||
|
// Post-generation the mesh bottom already includes the real brim, so the exact
|
||||||
|
// footprint is tested.
|
||||||
|
if (filaments_count > 1 || print.enable_timelapse_print()) {
|
||||||
|
// The shared printable polygon is plate-local, while the tower polygons above are
|
||||||
|
// already shifted by the plate origin.
|
||||||
|
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
|
||||||
|
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
|
||||||
|
for (Polygon &p : printable_polys)
|
||||||
|
p.translate(plate_shift);
|
||||||
|
if (!diff(convex_hulls_temp, printable_polys).empty())
|
||||||
|
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3918,6 +3944,12 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
|||||||
double volume = wipe_volume * filament_depth_count;
|
double volume = wipe_volume * filament_depth_count;
|
||||||
if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2);
|
if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2);
|
||||||
|
|
||||||
|
// Sizing should take into account currently set wiping volumes.
|
||||||
|
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
|
||||||
|
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
|
||||||
|
const bool semm_flush = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
|
||||||
|
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(m_config, filaments_cnt);
|
||||||
|
|
||||||
if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) {
|
if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) {
|
||||||
double depth = std::sqrt(volume / layer_height * extra_spacing);
|
double depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||||
if (need_wipe_tower || filaments_cnt > 1) {
|
if (need_wipe_tower || filaments_cnt > 1) {
|
||||||
@@ -3929,30 +3961,16 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
double width = m_config.prime_tower_width;
|
double width = m_config.prime_tower_width;
|
||||||
if (m_config.purge_in_prime_tower && m_config.single_extruder_multi_material) {
|
double depth = volume / (layer_height * width);
|
||||||
// Calculating depth should take into account currently set wiping volumes.
|
// The flush volumes already hold the spacing between wipes.
|
||||||
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
|
if (!semm_flush) depth *= extra_spacing;
|
||||||
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
|
if (need_wipe_tower || depth > EPSILON) {
|
||||||
std::vector<std::vector<float>> wipe_volumes = WipeTower2::extract_wipe_volumes(m_config);
|
|
||||||
std::vector<float> max_wipe_volumes;
|
|
||||||
for (const std::vector<float> &v : wipe_volumes)
|
|
||||||
max_wipe_volumes.emplace_back(*std::max_element(v.begin(), v.end()));
|
|
||||||
float maximum = std::accumulate(max_wipe_volumes.begin(), max_wipe_volumes.end(), 0.f);
|
|
||||||
maximum = maximum * filaments_cnt / max_wipe_volumes.size();
|
|
||||||
|
|
||||||
// Orca: it's overshooting a bit, so let's reduce it a bit
|
|
||||||
maximum *= 0.6;
|
|
||||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = maximum / (layer_height * width);
|
|
||||||
} else {
|
|
||||||
double depth = volume / (layer_height * width) * extra_spacing;
|
|
||||||
if (need_wipe_tower || m_wipe_tower_data.depth > EPSILON) {
|
|
||||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
depth = std::max((double) min_wipe_tower_depth, depth);
|
||||||
}
|
}
|
||||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
||||||
}
|
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||||
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
|
||||||
}
|
}
|
||||||
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
|
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6570,6 +6570,17 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("wait_for_temp_on_wipe_tower", coBool);
|
||||||
|
def->label = L("Wait for temperature on wipe tower");
|
||||||
|
def->tooltip = L("Pick up the new tool without waiting for it to reach printing temperature, travel to the wipe "
|
||||||
|
"tower, and wait for the temperature there, right before purging. Ooze from the heat-up lands on "
|
||||||
|
"the tower instead of the model, and the travel overlaps with the heating. "
|
||||||
|
"Only relevant for multi-extruder (multi-toolhead) printers using a Type 2 wipe tower. "
|
||||||
|
"The firmware or tool change macro must not wait for the temperature itself. "
|
||||||
|
"When disabled, the temperature wait is issued right after the tool change command.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
|
||||||
def = this->add("wipe_tower_no_sparse_layers", coBool);
|
def = this->add("wipe_tower_no_sparse_layers", coBool);
|
||||||
def->label = L("No sparse layers (beta)");
|
def->label = L("No sparse layers (beta)");
|
||||||
|
|||||||
@@ -1660,6 +1660,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
((ConfigOptionBool, purge_in_prime_tower))
|
((ConfigOptionBool, purge_in_prime_tower))
|
||||||
((ConfigOptionBool, enable_filament_ramming))
|
((ConfigOptionBool, enable_filament_ramming))
|
||||||
((ConfigOptionBool, tool_change_on_wipe_tower))
|
((ConfigOptionBool, tool_change_on_wipe_tower))
|
||||||
|
((ConfigOptionBool, wait_for_temp_on_wipe_tower))
|
||||||
((ConfigOptionBool, support_multi_bed_types))
|
((ConfigOptionBool, support_multi_bed_types))
|
||||||
((ConfigOptionBool, use_3mf))
|
((ConfigOptionBool, use_3mf))
|
||||||
|
|
||||||
|
|||||||
@@ -2871,6 +2871,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
|
if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
|
||||||
|
// The tower size estimate reads printer- and filament-scope keys, which the print preset
|
||||||
|
// does not carry; built once here rather than per plate.
|
||||||
|
const DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
|
||||||
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
||||||
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
||||||
PartPlate* part_plate = ppl.get_plate(plate_id);
|
PartPlate* part_plate = ppl.get_plate(plate_id);
|
||||||
@@ -2895,51 +2898,26 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
if (part_plate->get_objects_on_this_plate().empty()) continue;
|
if (part_plate->get_objects_on_this_plate().empty()) continue;
|
||||||
|
|
||||||
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
|
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
|
||||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
||||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||||
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
|
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
|
||||||
|
|
||||||
{
|
// The stored position is already clamped onto the bed, by
|
||||||
const float margin = WIPE_TOWER_MARGIN + brim_width;
|
// set_default_wipe_tower_pos_for_plate and again on every drag.
|
||||||
BoundingBoxf3 plate_bbox = part_plate->get_bounding_box();
|
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
|
||||||
BoundingBoxf plate_bbox_2d(Vec2d(plate_bbox.min(0), plate_bbox.min(1)), Vec2d(plate_bbox.max(0), plate_bbox.max(1)));
|
// update for wipe tower position
|
||||||
const std::vector<Pointfs> &extruder_areas = part_plate->get_extruder_areas();
|
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||||
for (Pointfs points : extruder_areas) {
|
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
|
||||||
BoundingBoxf bboxf(points);
|
a,
|
||||||
plate_bbox_2d.min = plate_bbox_2d.min(0) >= bboxf.min(0) ? plate_bbox_2d.min : bboxf.min;
|
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
|
||||||
plate_bbox_2d.max = plate_bbox_2d.max(0) <= bboxf.max(0) ? plate_bbox_2d.max : bboxf.max;
|
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||||
}
|
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||||
|
} else {
|
||||||
coordf_t plate_bbox_x_min_local_coord = plate_bbox_2d.min(0) - plate_origin(0);
|
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||||
coordf_t plate_bbox_x_max_local_coord = plate_bbox_2d.max(0) - plate_origin(0);
|
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
|
||||||
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
|
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
|
||||||
|
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
|
||||||
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
|
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||||
// update for wipe tower position
|
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||||
{
|
|
||||||
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
|
||||||
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
|
|
||||||
a,
|
|
||||||
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
|
|
||||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
|
||||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const float margin = 2.f;
|
|
||||||
auto tower_bottom = current_print->wipe_tower_data().wipe_tower_mesh_data->bottom;
|
|
||||||
tower_bottom.translate(scaled(Vec2d{x, y}));
|
|
||||||
tower_bottom.translate(scaled(Vec2d{plate_origin[0], plate_origin[1]}));
|
|
||||||
auto tower_bottom_bbox = get_extents(tower_bottom);
|
|
||||||
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_id)->get_build_volume(true);
|
|
||||||
BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1])));
|
|
||||||
Vec2f offset = WipeTower::move_box_inside_box(tower_bottom_bbox, plate_bbox2d, scaled(margin));
|
|
||||||
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
|
||||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
|
|
||||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
|
|
||||||
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
|
|
||||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
|
||||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2262,6 +2262,12 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
|
|||||||
}
|
}
|
||||||
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
|
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
|
||||||
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
|
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
|
||||||
|
// Read from the passed plate config — m_print may not have been applied yet
|
||||||
|
// (fresh plates, CLI), in which case its PrintConfig still holds defaults.
|
||||||
|
const auto *purge_opt = config.option<ConfigOptionBool>("purge_in_prime_tower");
|
||||||
|
const auto *semm_opt = config.option<ConfigOptionBool>("single_extruder_multi_material");
|
||||||
|
const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value;
|
||||||
|
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size);
|
||||||
if (use_rib_wall) {
|
if (use_rib_wall) {
|
||||||
depth = std::sqrt(volume / layer_height * extra_spacing);
|
depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||||
if (need_wipe_tower || plate_extruder_size > 1) {
|
if (need_wipe_tower || plate_extruder_size > 1) {
|
||||||
@@ -2274,7 +2280,9 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
depth = volume/ (layer_height * w) *extra_spacing;
|
depth = volume / (layer_height * w);
|
||||||
|
// The flush volumes already hold the spacing between wipes.
|
||||||
|
if (!semm_flush) depth *= extra_spacing;
|
||||||
if (need_wipe_tower || depth > EPSILON) {
|
if (need_wipe_tower || depth > EPSILON) {
|
||||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||||
depth = std::max((double)min_wipe_tower_depth, depth);
|
depth = std::max((double)min_wipe_tower_depth, depth);
|
||||||
@@ -3337,6 +3345,11 @@ BoundingBoxf3 PartPlate::get_build_volume(bool use_share)
|
|||||||
return plate_box;
|
return plate_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Polygon PartPlate::get_shared_printable_polygon() const
|
||||||
|
{
|
||||||
|
return m_extruder_areas.empty() ? Polygon::new_scale(m_shape) : get_shared_poly(m_extruder_areas);
|
||||||
|
}
|
||||||
|
|
||||||
bool PartPlate::contains(const Vec3d& point) const
|
bool PartPlate::contains(const Vec3d& point) const
|
||||||
{
|
{
|
||||||
return m_bounding_box.contains(point);
|
return m_bounding_box.contains(point);
|
||||||
@@ -4375,22 +4388,21 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
|||||||
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
||||||
}
|
}
|
||||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
||||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
float w = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_tower_width"))->value;
|
||||||
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
|
|
||||||
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
|
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
|
||||||
bool enable_wrapping = false;
|
bool enable_wrapping = false;
|
||||||
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
|
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
|
||||||
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
|
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
|
||||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||||
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
|
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
|
||||||
|
|
||||||
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
|
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
|
||||||
wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 2, false, enable_wrapping);
|
wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute brim-aware margin: brim extends outward from tower position
|
// Compute brim-aware margin: brim extends outward from tower position
|
||||||
float brim_width = 0.f;
|
float brim_width = 0.f;
|
||||||
const ConfigOptionFloat *brim_opt = print_cfg.option<ConfigOptionFloat>("prime_tower_brim_width");
|
const ConfigOptionFloat *brim_opt = full_config.option<ConfigOptionFloat>("prime_tower_brim_width");
|
||||||
if (brim_opt) {
|
if (brim_opt) {
|
||||||
brim_width = brim_opt->value;
|
brim_width = brim_opt->value;
|
||||||
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
|
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
|
||||||
@@ -4412,6 +4424,18 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The bounding box above still allows a corner a delta or hexagonal bed does not have, and the
|
||||||
|
// prime tower is validated against the real outline — pull it onto the bed before storing.
|
||||||
|
{
|
||||||
|
Polygons bed{part_plate->get_shared_printable_polygon()};
|
||||||
|
bed.front().translate(Point(-scaled(plate_origin.x()), -scaled(plate_origin.y()))); // into the frame x/y live in
|
||||||
|
const BoundingBox tower(Point::new_scale(x, y),
|
||||||
|
Point::new_scale(x + wipe_tower_size(0), y + wipe_tower_size(1)));
|
||||||
|
const Vec2f move = WipeTower::move_box_inside_polygon(tower, bed, scaled<coord_t>(margin));
|
||||||
|
x += move.x();
|
||||||
|
y += move.y();
|
||||||
|
}
|
||||||
|
|
||||||
ConfigOptionFloat wt_x_opt(x);
|
ConfigOptionFloat wt_x_opt(x);
|
||||||
ConfigOptionFloat wt_y_opt(y);
|
ConfigOptionFloat wt_y_opt(y);
|
||||||
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0);
|
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0);
|
||||||
|
|||||||
@@ -425,6 +425,9 @@ public:
|
|||||||
const BoundingBox get_bounding_box_crd();
|
const BoundingBox get_bounding_box_crd();
|
||||||
BoundingBoxf3 get_plate_box() {return get_build_volume();}
|
BoundingBoxf3 get_plate_box() {return get_build_volume();}
|
||||||
BoundingBoxf3 get_build_volume(bool use_share = false);
|
BoundingBoxf3 get_build_volume(bool use_share = false);
|
||||||
|
// Polygon counterpart of get_build_volume(true), in scaled world coordinates. The bounding box
|
||||||
|
// that one returns hides the corners a non-rectangular bed does not have.
|
||||||
|
Polygon get_shared_printable_polygon() const;
|
||||||
|
|
||||||
const std::vector<BoundingBoxf3>& get_exclude_areas() { return m_exclude_bounding_box; }
|
const std::vector<BoundingBoxf3>& get_exclude_areas() { return m_exclude_bounding_box; }
|
||||||
|
|
||||||
|
|||||||
@@ -9503,7 +9503,7 @@ void Plater::priv::replace_all_with_stl()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string status = _L("Replaced with 3D files from directory:\n").ToStdString() + out_path.string() + "\n\n";
|
wxString status = _L("Replaced with 3D files from directory:\n") + from_u8(out_path.string()) + "\n\n";
|
||||||
|
|
||||||
for (unsigned int idx : volume_idxs) {
|
for (unsigned int idx : volume_idxs) {
|
||||||
const GLVolume* v = selection.get_volume(idx);
|
const GLVolume* v = selection.get_volume(idx);
|
||||||
@@ -9523,13 +9523,13 @@ void Plater::priv::replace_all_with_stl()
|
|||||||
std::string volume_name = volume->name;
|
std::string volume_name = volume->name;
|
||||||
|
|
||||||
if (new_path == input_path) {
|
if (new_path == input_path) {
|
||||||
status += boost::str(boost::format(_L("✖ Skipped %1%: same file.\n").ToStdString()) % volume_name);
|
status += wxString::Format(_L("✖ Skipped %s: same file.\n"), from_u8(volume_name));
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " skipping replace volume : same filename " << new_path;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " skipping replace volume : same filename " << new_path;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs::exists(new_path)) {
|
if (!fs::exists(new_path)) {
|
||||||
status += boost::str(boost::format(_L("✖ Skipped %1%: file does not exist.\n").ToStdString()) % volume_name);
|
status += wxString::Format(_L("✖ Skipped %s: file does not exist.\n"), from_u8(volume_name));
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : filen does not exist " << new_path;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : filen does not exist " << new_path;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -9537,12 +9537,12 @@ void Plater::priv::replace_all_with_stl()
|
|||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " replacing volume : " << input_path << " with " << new_path;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " replacing volume : " << input_path << " with " << new_path;
|
||||||
|
|
||||||
if (!replace_volume_with_stl(object_idx, volume_idx, new_path, _u8L("Replace with 3D file"))) {
|
if (!replace_volume_with_stl(object_idx, volume_idx, new_path, _u8L("Replace with 3D file"))) {
|
||||||
status += boost::str(boost::format(_L("✖ Skipped %1%: failed to replace.\n").ToStdString()) % volume_name);
|
status += wxString::Format(_L("✖ Skipped %s: failed to replace.\n"), from_u8(volume_name));
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : failed to replace with " << new_path;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : failed to replace with " << new_path;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
status += boost::str(boost::format(_L("✔ Replaced %1%.\n").ToStdString()) % volume_name);
|
status += wxString::Format(_L("✔ Replaced %s.\n"), from_u8(volume_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update 3D scene
|
// update 3D scene
|
||||||
|
|||||||
@@ -111,13 +111,46 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
|
|||||||
|
|
||||||
sizer->Add(m_radio_group, 0, wxEXPAND | wxTOP | wxLEFT, BORDER_W);
|
sizer->Add(m_radio_group, 0, wxEXPAND | wxTOP | wxLEFT, BORDER_W);
|
||||||
|
|
||||||
if (parent->m_mode == comDevelop) {
|
std::string inherits_str = sel_preset.inherits();
|
||||||
m_detach_checkbox = new wxCheckBox(parent, wxID_ANY, _L("Detach from parent"));
|
if (parent->m_mode == comDevelop && !inherits_str.empty()) {
|
||||||
sizer->Add(m_detach_checkbox, 0, wxALIGN_LEFT | wxALL, BORDER_W);
|
wxBoxSizer *detach_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
|
auto detach_tooltip = _L("Copies all inherited values from the parent preset into this preset and removes the connection with the parent preset.");
|
||||||
|
|
||||||
|
auto detach_checkbox = new ::CheckBox(parent);
|
||||||
|
detach_checkbox->SetToolTip(detach_tooltip);
|
||||||
|
|
||||||
|
auto detach_label = new wxStaticText(parent, wxID_ANY, _L("Detach from parent"));
|
||||||
|
detach_label->SetFont(::Label::Body_14);
|
||||||
|
detach_label->SetForegroundColour(wxColour("#363636"));
|
||||||
|
detach_label->SetToolTip(detach_tooltip);
|
||||||
|
|
||||||
|
detach_sizer->Add(detach_checkbox, 0, wxALIGN_LEFT | wxLEFT, BORDER_W);
|
||||||
|
detach_sizer->Add(detach_label , 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, FromDIP(5));
|
||||||
|
sizer->Add(detach_sizer, 0, wxEXPAND | wxTOP, BORDER_W);
|
||||||
|
sizer->AddSpacer(FromDIP(5));
|
||||||
|
|
||||||
|
auto parent_label = new wxStaticText(parent, wxID_ANY, inherits_str);
|
||||||
|
parent_label->SetFont(::Label::Body_12);
|
||||||
|
parent_label->SetForegroundColour(wxColour("#6B6B6B"));
|
||||||
|
parent_label->SetToolTip(_L("Parent preset"));
|
||||||
|
sizer->Add(parent_label, 0, wxEXPAND | wxLEFT, BORDER_W + FromDIP(24));
|
||||||
|
|
||||||
|
sizer->AddSpacer(FromDIP(5));
|
||||||
|
|
||||||
// Set initial state (unchecked by default)
|
// Set initial state (unchecked by default)
|
||||||
m_detach_checkbox->SetValue(m_detach);
|
detach_checkbox->SetValue(m_detach);
|
||||||
// Bind the checkbox event to update the detach state for this item
|
// Bind the checkbox event to update the detach state for this item
|
||||||
m_detach_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_detach = m_detach_checkbox->GetValue(); });
|
detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent&) { m_detach = detach_checkbox->GetValue(); });
|
||||||
|
|
||||||
|
auto on_toggle = [this, detach_checkbox]() {
|
||||||
|
detach_checkbox->SetValue(!detach_checkbox->GetValue());
|
||||||
|
wxCommandEvent ev(wxEVT_TOGGLEBUTTON, detach_checkbox->GetId());
|
||||||
|
ev.SetEventObject(detach_checkbox);
|
||||||
|
detach_checkbox->GetEventHandler()->ProcessEvent(ev);
|
||||||
|
};
|
||||||
|
detach_label->Bind(wxEVT_LEFT_DOWN, [on_toggle](wxMouseEvent& e) {if(!e.LeftDClick()) on_toggle();});
|
||||||
|
detach_label->Bind(wxEVT_LEFT_DCLICK, [on_toggle](wxMouseEvent& e) {on_toggle();});
|
||||||
}
|
}
|
||||||
|
|
||||||
m_radio_group->Bind(wxEVT_COMMAND_RADIOBOX_SELECTED, [this](wxCommandEvent &e) {
|
m_radio_group->Bind(wxEVT_COMMAND_RADIOBOX_SELECTED, [this](wxCommandEvent &e) {
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ class SavePresetDialog : public DPIDialog
|
|||||||
bool m_save_to_project {false};
|
bool m_save_to_project {false};
|
||||||
RadioGroup* m_radio_group; // ORCA
|
RadioGroup* m_radio_group; // ORCA
|
||||||
bool m_detach{false};
|
bool m_detach{false};
|
||||||
wxCheckBox* m_detach_checkbox{nullptr};
|
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1270,9 +1270,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
|
|||||||
} else {
|
} else {
|
||||||
if (v.is_wipe_tower) {//in world cs
|
if (v.is_wipe_tower) {//in world cs
|
||||||
int plate_idx = v.object_idx() - 1000;
|
int plate_idx = v.object_idx() - 1000;
|
||||||
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_build_volume(true);
|
const Polygons bed_polys{wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_shared_printable_polygon()};
|
||||||
BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1])));
|
|
||||||
Vec3d tower_size = v.bounding_box().size();
|
|
||||||
Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position();
|
Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position();
|
||||||
Vec3d actual_displacement = displacement;
|
Vec3d actual_displacement = displacement;
|
||||||
bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower);
|
bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower);
|
||||||
@@ -1287,18 +1285,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
|
|||||||
BoundingBoxf3 tower_bbox = v.bounding_box();
|
BoundingBoxf3 tower_bbox = v.bounding_box();
|
||||||
tower_bbox.translate(actual_displacement + tower_origin);
|
tower_bbox.translate(actual_displacement + tower_origin);
|
||||||
BoundingBox tower_bbox2d = BoundingBox(scaled(Vec2f(tower_bbox.min[0], tower_bbox.min[1])), scaled(Vec2f(tower_bbox.max[0], tower_bbox.max[1])));
|
BoundingBox tower_bbox2d = BoundingBox(scaled(Vec2f(tower_bbox.min[0], tower_bbox.min[1])), scaled(Vec2f(tower_bbox.max[0], tower_bbox.max[1])));
|
||||||
Vec2f offset = WipeTower::move_box_inside_box(tower_bbox2d, plate_bbox2d,scaled(margin));
|
const Vec2f offset = WipeTower::move_box_inside_polygon(tower_bbox2d, bed_polys, scaled(margin));
|
||||||
//if (tower_origin(0) + actual_displacement(0) - margin < plate_bbox.min(0)) {
|
|
||||||
// actual_displacement(0) = plate_bbox.min(0) - tower_origin(0) + margin;
|
|
||||||
//} else if (tower_origin(0) + actual_displacement(0) + tower_size(0) + margin > plate_bbox.max(0)) {
|
|
||||||
// actual_displacement(0) = plate_bbox.max(0) - tower_origin(0) - tower_size(0) - margin;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//if (tower_origin(1) + actual_displacement(1) - margin < plate_bbox.min(1)) {
|
|
||||||
// actual_displacement(1) = plate_bbox.min(1) - tower_origin(1) + margin;
|
|
||||||
//} else if (tower_origin(1) + actual_displacement(1) + tower_size(1) + margin > plate_bbox.max(1)) {
|
|
||||||
// actual_displacement(1) = plate_bbox.max(1) - tower_origin(1) - tower_size(1) - margin;
|
|
||||||
//}
|
|
||||||
actual_displacement += Vec3d(offset[0], offset[1],0);
|
actual_displacement += Vec3d(offset[0], offset[1],0);
|
||||||
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement);
|
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5627,6 +5627,7 @@ if (is_marlin_flavor)
|
|||||||
optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower");
|
optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower");
|
||||||
optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming");
|
optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming");
|
||||||
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
|
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
|
||||||
|
optgroup->append_single_option_line("wait_for_temp_on_wipe_tower", "printer_multimaterial_wipe_tower#wait-for-temperature-on-wipe-tower");
|
||||||
|
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings");
|
optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings");
|
||||||
@@ -6160,6 +6161,7 @@ void TabPrinter::toggle_options()
|
|||||||
// so the option is irrelevant there.
|
// so the option is irrelevant there.
|
||||||
const size_t extruders_count = m_config->option<ConfigOptionFloats>("nozzle_diameter")->size();
|
const size_t extruders_count = m_config->option<ConfigOptionFloats>("nozzle_diameter")->size();
|
||||||
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
|
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
|
||||||
|
toggle_option("wait_for_temp_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
|
||||||
}
|
}
|
||||||
wxString extruder_number;
|
wxString extruder_number;
|
||||||
long val = 1;
|
long val = 1;
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# Temperature and tool-change commands of a wait_for_temp_on_wipe_tower-off slice,
|
||||||
|
# captured from the main branch at a10d9e77cf. Regeneration is described
|
||||||
|
# at the test that reads this file: "Toolchange temperature commands are unchanged
|
||||||
|
# when the wipe tower wait is off" in tests/fff_print/test_multifilament.cpp.
|
||||||
|
M104 S215 T0 ; set nozzle temperature
|
||||||
|
M104 S215 T1 ; set nozzle temperature
|
||||||
|
; CP PRIMING START
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S215 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
M104 S175 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S215 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP PRIMING END
|
||||||
|
M104 S215 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S175 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S215 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; set nozzle temperature
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.0s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.4s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 31s lead 30.9s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 31s lead 30.7s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 31s lead 30.6s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.3s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 31s lead 30.6s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.0s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.2s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 31s lead 30.7s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.4s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.4s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T0 ; preheat T0 time: 30s lead 30.0s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T1 ; set nozzle temperature ;cooldown
|
||||||
|
T0 ; change extruder
|
||||||
|
M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S240 T1 ; preheat T1 time: 30s lead 30.0s
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||||
|
T1 ; change extruder
|
||||||
|
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
; CP TOOLCHANGE START
|
||||||
|
; CP TOOLCHANGE END
|
||||||
|
M104 S0 ; turn off temperature
|
||||||
@@ -1,12 +1,24 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "libslic3r/GCodeReader.hpp"
|
#include "libslic3r/GCodeReader.hpp"
|
||||||
|
|
||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace Slic3r::Test;
|
using namespace Slic3r::Test;
|
||||||
@@ -27,6 +39,156 @@ static std::set<int> tools_for_role(const std::string& gcode, const std::string&
|
|||||||
return tools;
|
return tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X where the nozzle sits while each tagged _WAIT_FOR_TEMP_ON_WIPE_TOWER M109 blocks:
|
||||||
|
// the nearest preceding G1 carrying an X (the park travel emitted just before the wait).
|
||||||
|
static std::vector<double> wait_park_xs(const std::string& gcode)
|
||||||
|
{
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::istringstream stream(gcode);
|
||||||
|
for (std::string line; std::getline(stream, line);)
|
||||||
|
lines.emplace_back(std::move(line));
|
||||||
|
std::vector<double> xs;
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
if (lines[i].rfind("M109", 0) != 0 || lines[i].find("_WAIT_FOR_TEMP_ON_WIPE_TOWER") == std::string::npos)
|
||||||
|
continue;
|
||||||
|
for (size_t j = i; j-- > 0;) {
|
||||||
|
if (lines[j].rfind("G1 ", 0) != 0)
|
||||||
|
continue;
|
||||||
|
const size_t x_pos = lines[j].find('X');
|
||||||
|
if (x_pos == std::string::npos)
|
||||||
|
continue;
|
||||||
|
xs.push_back(std::stod(lines[j].substr(x_pos + 1)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimated print time at each 1-based line of an exported G-code file, from a second
|
||||||
|
// GCodeProcessor pass over it. MoveVertex::time is the duration of one move and gcode_id is the
|
||||||
|
// line it came from (already rebased past the M73 insertions), so the running sum before the first
|
||||||
|
// move of a line is the elapsed time at that line. The file carries its own config footer, so
|
||||||
|
// process_file configures the processor -- including the shared s_IsBBLPrinter static that other
|
||||||
|
// tests in this binary mutate -- from the settings the export itself used.
|
||||||
|
static std::vector<double> elapsed_time_by_line(const std::string& gcode)
|
||||||
|
{
|
||||||
|
ScopedTemporaryFile temp_gcode(".gcode");
|
||||||
|
{
|
||||||
|
std::ofstream os(temp_gcode.string());
|
||||||
|
os << gcode;
|
||||||
|
}
|
||||||
|
GCodeProcessor processor;
|
||||||
|
processor.process_file(temp_gcode.string());
|
||||||
|
|
||||||
|
constexpr size_t NORMAL = size_t(PrintEstimatedStatistics::ETimeMode::Normal);
|
||||||
|
const size_t n_lines = size_t(std::count(gcode.begin(), gcode.end(), '\n')) + 2;
|
||||||
|
std::vector<double> elapsed(n_lines, 0.);
|
||||||
|
double running = 0.;
|
||||||
|
size_t next = 0;
|
||||||
|
for (const auto& move : processor.get_result().moves) {
|
||||||
|
const size_t id = std::min<size_t>(move.gcode_id, n_lines - 1);
|
||||||
|
while (next <= id)
|
||||||
|
elapsed[next++] = running;
|
||||||
|
running += move.time[NORMAL];
|
||||||
|
}
|
||||||
|
while (next < n_lines)
|
||||||
|
elapsed[next++] = running;
|
||||||
|
return elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The temperature-relevant projection of `gcode`: every M104/M109/Tn line, plus the toolchange and
|
||||||
|
// priming markers that anchor them, in order. A preheat -- an M104 the GCodeProcessor backtrace
|
||||||
|
// inserts mid-object, outside any block, naming a tool other than the one currently loaded -- also
|
||||||
|
// carries "lead <n>s", the estimated time from there to the tool change it heats for, which is the
|
||||||
|
// property preheat_time controls. No other temperature command gets one: for an M104 retargeting
|
||||||
|
// the active tool (the first-layer-to-other-layers bump) or one inside a block, the distance to the
|
||||||
|
// next Tn is a layer time or a handful of moves and says nothing about preheat_time. Everything
|
||||||
|
// else is dropped, so the trace does not move when travel, tower geometry or line numbering do.
|
||||||
|
static std::vector<std::string> temperature_trace(const std::string& gcode)
|
||||||
|
{
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::istringstream stream(gcode);
|
||||||
|
for (std::string line; std::getline(stream, line);) {
|
||||||
|
line.erase(0, line.find_first_not_of(" \t"));
|
||||||
|
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
|
||||||
|
line.pop_back();
|
||||||
|
lines.emplace_back(std::move(line));
|
||||||
|
}
|
||||||
|
const std::vector<double> elapsed = elapsed_time_by_line(gcode);
|
||||||
|
|
||||||
|
const auto is_tool = [](const std::string& l) { return l.size() >= 2 && l[0] == 'T' && std::isdigit((unsigned char) l[1]); };
|
||||||
|
const auto is_temp = [](const std::string& l) { return l.rfind("M104", 0) == 0 || l.rfind("M109", 0) == 0; };
|
||||||
|
const auto marker = [](const std::string& l) -> const char* {
|
||||||
|
for (const char* m : { "; CP TOOLCHANGE START", "; CP TOOLCHANGE END", "; CP PRIMING START", "; CP PRIMING END" })
|
||||||
|
if (l.find(m) != std::string::npos)
|
||||||
|
return m;
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tool a "T<n>" line, or the "T<n>" argument of an M104, names -- or -1 when it names none.
|
||||||
|
const auto tool_of = [&is_tool](const std::string& l) -> int {
|
||||||
|
size_t t = std::string::npos; // index of the 'T'
|
||||||
|
if (is_tool(l))
|
||||||
|
t = 0;
|
||||||
|
else if (l.rfind("M104", 0) == 0 && l.find(" T") != std::string::npos)
|
||||||
|
t = l.find(" T") + 1;
|
||||||
|
if (t == std::string::npos || t + 1 >= l.size() || !std::isdigit((unsigned char) l[t + 1]))
|
||||||
|
return -1;
|
||||||
|
return std::stoi(l.substr(t + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> trace;
|
||||||
|
bool in_block = false;
|
||||||
|
int current_tool = -1;
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
if (const char* m = marker(lines[i])) {
|
||||||
|
in_block = std::string(m).find("START") != std::string::npos;
|
||||||
|
trace.emplace_back(m); // the marker alone: some carry a trailing tool id, some do not
|
||||||
|
} else if (is_tool(lines[i]) || is_temp(lines[i])) {
|
||||||
|
std::string entry = lines[i];
|
||||||
|
const int named = tool_of(lines[i]);
|
||||||
|
if (!in_block && lines[i].rfind("M104", 0) == 0 && current_tool != -1 && named != -1 && named != current_tool) {
|
||||||
|
size_t tn = i;
|
||||||
|
while (tn < lines.size() && !is_tool(lines[tn]))
|
||||||
|
++tn;
|
||||||
|
if (tn < lines.size()) {
|
||||||
|
char lead[32];
|
||||||
|
std::snprintf(lead, sizeof(lead), "\tlead %.1fs", elapsed[tn + 1] - elapsed[i + 1]);
|
||||||
|
entry += lead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_tool(lines[i]))
|
||||||
|
current_tool = named;
|
||||||
|
trace.emplace_back(std::move(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits a trace entry into its command text and the lead time appended after a tab, if any.
|
||||||
|
static std::pair<std::string, std::optional<double>> split_lead(const std::string& entry)
|
||||||
|
{
|
||||||
|
const size_t tab = entry.find('\t');
|
||||||
|
if (tab == std::string::npos)
|
||||||
|
return { entry, std::nullopt };
|
||||||
|
const std::string tail = entry.substr(tab + 1); // "lead 30.2s"
|
||||||
|
return { entry.substr(0, tab), std::stod(tail.substr(tail.find(' ') + 1)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same command, and a lead time within half a second. The lead is an estimate summed over every
|
||||||
|
// move before it, so it drifts slightly with unrelated changes to travel or tower geometry; half a
|
||||||
|
// second is far below the tens of seconds a preheat leaving its backtrace position would shift it.
|
||||||
|
static bool trace_entries_match(const std::string& a, const std::string& b)
|
||||||
|
{
|
||||||
|
const auto x = split_lead(a);
|
||||||
|
const auto y = split_lead(b);
|
||||||
|
if (x.first != y.first)
|
||||||
|
return false;
|
||||||
|
if (x.second.has_value() != y.second.has_value())
|
||||||
|
return false;
|
||||||
|
return !x.second.has_value() || std::abs(*x.second - *y.second) <= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
// Tool index = filament id - 1; brim and skirt follow the wall filament.
|
// Tool index = filament id - 1; brim and skirt follow the wall filament.
|
||||||
TEST_CASE("Each feature prints with its assigned filament", "[MultiFilament]")
|
TEST_CASE("Each feature prints with its assigned filament", "[MultiFilament]")
|
||||||
{
|
{
|
||||||
@@ -86,6 +248,389 @@ TEST_CASE("Per-object wall filament override is honored", "[MultiFilament]")
|
|||||||
CHECK(tools_for_role(gcode, "infill") == std::set<int>{ 0 }); // infill not overridden: stays on F1
|
CHECK(tools_for_role(gcode, "infill") == std::set<int>{ 0 }); // infill not overridden: stays on F1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With wait_for_temp_on_wipe_tower the blocking M109 moves from right after the Tn command to
|
||||||
|
// a stop point parked beside the wipe tower (heat-up drool falls next to the tower, not onto
|
||||||
|
// its top): tagged with _WAIT_FOR_TEMP_ON_WIPE_TOWER, after the toolchange and before the
|
||||||
|
// repositioning move and the first extrusion of the purge. The restore that used to block there
|
||||||
|
// demotes to a non-blocking M104 and moves ahead of the Tn, so the incoming tool heats up over
|
||||||
|
// the change itself. Ordering and the off-tower stop are the contract here.
|
||||||
|
TEST_CASE("Toolchange temperature wait moves to the wipe tower when enabled", "[MultiFilament]")
|
||||||
|
{
|
||||||
|
const bool wait_on_tower = GENERATE(false, true);
|
||||||
|
DYNAMIC_SECTION("wait_for_temp_on_wipe_tower " << (wait_on_tower ? 1 : 0)) {
|
||||||
|
const std::string gcode = slice_with_object_overrides(
|
||||||
|
{ cube(20), cube(20) },
|
||||||
|
multifilament_config(2, {
|
||||||
|
{ "nozzle_diameter", "0.4,0.4" },
|
||||||
|
{ "printer_extruder_id", "1,2" },
|
||||||
|
{ "printer_extruder_variant", "Direct Drive Standard,Direct Drive Standard" },
|
||||||
|
{ "extruder_printable_height", "0,0" },
|
||||||
|
{ "single_extruder_multi_material", 0 },
|
||||||
|
{ "enable_prime_tower", 1 },
|
||||||
|
{ "prime_tower_width", 35 },
|
||||||
|
{ "wipe_tower_x", "50" },
|
||||||
|
{ "wipe_tower_y", "50" },
|
||||||
|
{ "ooze_prevention", 1 },
|
||||||
|
{ "standby_temperature_delta", -40 },
|
||||||
|
// The post-processor's own preheat pass also inserts an M104 for the incoming
|
||||||
|
// filament ahead of the Tn; switch it off so the temperature commands under test
|
||||||
|
// are the only ones in the toolchange block.
|
||||||
|
{ "preheat_time", 0 },
|
||||||
|
{ "wait_for_temp_on_wipe_tower", wait_on_tower ? 1 : 0 },
|
||||||
|
}),
|
||||||
|
// One filament per object -> a toolchange on every layer. Assigned at the object
|
||||||
|
// level: the used-filament count that gates the prime tower is derived from
|
||||||
|
// object/volume configs on the harness's single apply (region filament ids such
|
||||||
|
// as sparse_infill_filament_id are not counted there and the tower would be
|
||||||
|
// silently disabled).
|
||||||
|
{ { { "extruder", 1 } }, { { "extruder", 2 } } });
|
||||||
|
|
||||||
|
// Split into lines and scan the "; CP TOOLCHANGE START".."; CP TOOLCHANGE END" blocks.
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::istringstream gcode_stream(gcode);
|
||||||
|
for (std::string line; std::getline(gcode_stream, line);)
|
||||||
|
lines.emplace_back(std::move(line));
|
||||||
|
const auto is_tool_line = [](const std::string& l) { return l.size() >= 2 && l[0] == 'T' && std::isdigit((unsigned char)l[1]); };
|
||||||
|
const auto is_m109_line = [](const std::string& l) { return l.rfind("M109", 0) == 0; };
|
||||||
|
// A non-blocking set-temperature naming one specific tool, e.g. "M104 S255 T1".
|
||||||
|
const auto is_m104_for_tool = [](const std::string& l, int tool) {
|
||||||
|
if (l.rfind("M104", 0) != 0)
|
||||||
|
return false;
|
||||||
|
const std::string token = " T" + std::to_string(tool);
|
||||||
|
const size_t at = l.find(token);
|
||||||
|
return at != std::string::npos && !std::isdigit((unsigned char)l[at + token.size()]);
|
||||||
|
};
|
||||||
|
const auto is_tagged_wait = [](const std::string& l) { return l.find("_WAIT_FOR_TEMP_ON_WIPE_TOWER") != std::string::npos; };
|
||||||
|
const auto is_extruding = [](const std::string& l) {
|
||||||
|
if (l.rfind("G1 ", 0) != 0)
|
||||||
|
return false;
|
||||||
|
const size_t e = l.find(" E");
|
||||||
|
return e != std::string::npos && l.find_first_of("XY") != std::string::npos && l[e + 2] != '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
int checked_blocks = 0;
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
if (lines[i].find("; CP TOOLCHANGE START") == std::string::npos)
|
||||||
|
continue;
|
||||||
|
size_t block_end = i;
|
||||||
|
while (block_end < lines.size() && lines[block_end].find("; CP TOOLCHANGE END") == std::string::npos)
|
||||||
|
++block_end;
|
||||||
|
size_t tool_line = block_end;
|
||||||
|
for (size_t j = i; j < block_end; ++j)
|
||||||
|
if (is_tool_line(lines[j])) { tool_line = j; break; }
|
||||||
|
if (tool_line == block_end)
|
||||||
|
continue; // final unload block, no toolchange
|
||||||
|
++checked_blocks;
|
||||||
|
|
||||||
|
// Where the incoming tool's target temperature is raised, relative to its Tn.
|
||||||
|
const int new_tool = std::stoi(lines[tool_line].substr(1));
|
||||||
|
size_t preheat = tool_line, restore = block_end;
|
||||||
|
for (size_t j = i; j < tool_line; ++j)
|
||||||
|
if (is_m104_for_tool(lines[j], new_tool)) { preheat = j; break; }
|
||||||
|
for (size_t j = tool_line + 1; j < block_end; ++j)
|
||||||
|
if (is_m104_for_tool(lines[j], new_tool)) { restore = j; break; }
|
||||||
|
|
||||||
|
size_t tagged_wait = block_end, untagged_m109 = block_end, first_extrusion = block_end;
|
||||||
|
for (size_t j = tool_line + 1; j < block_end; ++j) {
|
||||||
|
if (is_m109_line(lines[j]) && tagged_wait == block_end && is_tagged_wait(lines[j]))
|
||||||
|
tagged_wait = j;
|
||||||
|
if (is_m109_line(lines[j]) && untagged_m109 == block_end && !is_tagged_wait(lines[j]))
|
||||||
|
untagged_m109 = j;
|
||||||
|
if (first_extrusion == block_end && is_extruding(lines[j]))
|
||||||
|
first_extrusion = j;
|
||||||
|
}
|
||||||
|
INFO("toolchange block at line " << i + 1);
|
||||||
|
if (wait_on_tower) {
|
||||||
|
// The only blocking wait is the tagged one, parked beside the tower before the purge.
|
||||||
|
REQUIRE(tagged_wait < block_end);
|
||||||
|
CHECK(untagged_m109 == block_end);
|
||||||
|
// The target is raised ahead of the toolchange, so the incoming tool heats up
|
||||||
|
// while it is picked up, and nothing sets it again afterwards.
|
||||||
|
CHECK(preheat < tool_line);
|
||||||
|
CHECK(restore == block_end);
|
||||||
|
REQUIRE(first_extrusion < block_end);
|
||||||
|
CHECK(tagged_wait < first_extrusion);
|
||||||
|
// The travel preceding the wait parks outside the tower footprint. The tower
|
||||||
|
// auto-sizes, so derive its extent from the purge extrusions of this block.
|
||||||
|
size_t stop_line = block_end;
|
||||||
|
for (size_t j = tagged_wait; j-- > tool_line;)
|
||||||
|
if (lines[j].rfind("G1 ", 0) == 0 && lines[j].find('X') != std::string::npos) { stop_line = j; break; }
|
||||||
|
REQUIRE(stop_line < block_end);
|
||||||
|
const double stop_x = std::stod(lines[stop_line].substr(lines[stop_line].find('X') + 1));
|
||||||
|
double purge_min_x = std::numeric_limits<double>::max(), purge_max_x = std::numeric_limits<double>::lowest();
|
||||||
|
for (size_t j = tagged_wait; j < block_end; ++j) {
|
||||||
|
const size_t x_pos = lines[j].find('X');
|
||||||
|
if (!is_extruding(lines[j]) || x_pos == std::string::npos)
|
||||||
|
continue;
|
||||||
|
const double x = std::stod(lines[j].substr(x_pos + 1));
|
||||||
|
purge_min_x = std::min(purge_min_x, x);
|
||||||
|
purge_max_x = std::max(purge_max_x, x);
|
||||||
|
}
|
||||||
|
REQUIRE(purge_min_x <= purge_max_x);
|
||||||
|
INFO("stop travel: " << lines[stop_line] << " purge x range: " << purge_min_x << ".." << purge_max_x);
|
||||||
|
const bool beside_tower = stop_x < purge_min_x - 0.5 || stop_x > purge_max_x + 0.5;
|
||||||
|
CHECK(beside_tower);
|
||||||
|
} else {
|
||||||
|
// Stock behavior: the blocking wait follows the toolchange command directly, and
|
||||||
|
// nothing raises the incoming tool's target before it.
|
||||||
|
REQUIRE(untagged_m109 < block_end);
|
||||||
|
CHECK(tagged_wait == block_end);
|
||||||
|
CHECK(preheat == tool_line);
|
||||||
|
if (first_extrusion < block_end)
|
||||||
|
CHECK(untagged_m109 < first_extrusion);
|
||||||
|
}
|
||||||
|
i = block_end;
|
||||||
|
}
|
||||||
|
REQUIRE(checked_blocks > 0);
|
||||||
|
if (!wait_on_tower)
|
||||||
|
CHECK(gcode.find("_WAIT_FOR_TEMP_ON_WIPE_TOWER") == std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priming runs before the first layer is set up, so set_extruder sees no layer at all: its
|
||||||
|
// on_first_layer() test is false and print_z is the initial layer height rather than 0. The
|
||||||
|
// tower nonetheless blocks on the first layer temperature there, so the pre-heat raised ahead
|
||||||
|
// of each priming Tn has to name that same temperature — pre-heating to the "other layers"
|
||||||
|
// value instead leaves the tagged M109 asking the firmware to cool back down before the
|
||||||
|
// priming lines are extruded.
|
||||||
|
TEST_CASE("Wipe tower priming pre-heats to the first layer temperature", "[MultiFilament]")
|
||||||
|
{
|
||||||
|
const std::string gcode = slice_with_object_overrides(
|
||||||
|
{ cube(20), cube(20) },
|
||||||
|
multifilament_config(2, {
|
||||||
|
{ "nozzle_diameter", "0.4,0.4" },
|
||||||
|
{ "printer_extruder_id", "1,2" },
|
||||||
|
{ "printer_extruder_variant", "Direct Drive Standard,Direct Drive Standard" },
|
||||||
|
{ "extruder_printable_height", "0,0" },
|
||||||
|
{ "single_extruder_multi_material", 0 },
|
||||||
|
{ "single_extruder_multi_material_priming", 1 },
|
||||||
|
{ "enable_prime_tower", 1 },
|
||||||
|
{ "prime_tower_width", 35 },
|
||||||
|
{ "wipe_tower_x", "50" },
|
||||||
|
{ "wipe_tower_y", "50" },
|
||||||
|
{ "preheat_time", 0 }, // see the wait test above
|
||||||
|
// Distinct enough that picking the wrong one is unambiguous.
|
||||||
|
{ "nozzle_temperature_initial_layer", "215,215" },
|
||||||
|
{ "nozzle_temperature", "240,240" },
|
||||||
|
{ "wait_for_temp_on_wipe_tower", 1 },
|
||||||
|
}),
|
||||||
|
{ { { "extruder", 1 } }, { { "extruder", 2 } } });
|
||||||
|
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::istringstream gcode_stream(gcode);
|
||||||
|
for (std::string line; std::getline(gcode_stream, line);)
|
||||||
|
lines.emplace_back(std::move(line));
|
||||||
|
// Temperature of an M104/M109, or -1 when the line is neither.
|
||||||
|
const auto temp_of = [](const std::string& l) {
|
||||||
|
if (l.rfind("M104", 0) != 0 && l.rfind("M109", 0) != 0)
|
||||||
|
return -1;
|
||||||
|
const size_t s = l.find('S');
|
||||||
|
return s == std::string::npos ? -1 : std::stoi(l.substr(s + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t start = lines.size(), end = lines.size();
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
if (start == lines.size() && lines[i].find("; CP PRIMING START") != std::string::npos)
|
||||||
|
start = i;
|
||||||
|
else if (start < lines.size() && lines[i].find("; CP PRIMING END") != std::string::npos) {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUIRE(start < end);
|
||||||
|
|
||||||
|
int checked_waits = 0;
|
||||||
|
for (size_t i = start; i < end; ++i) {
|
||||||
|
if (lines[i].find("_WAIT_FOR_TEMP_ON_WIPE_TOWER") == std::string::npos)
|
||||||
|
continue;
|
||||||
|
++checked_waits;
|
||||||
|
INFO("priming wait at line " << i + 1 << ": " << lines[i]);
|
||||||
|
CHECK(temp_of(lines[i]) == 215); // the tower waits on the first layer temperature
|
||||||
|
// The most recent set-temperature before it is the pre-heat, and must agree with it.
|
||||||
|
int preheat = -1;
|
||||||
|
for (size_t j = i; j-- > start;)
|
||||||
|
if ((preheat = temp_of(lines[j])) != -1)
|
||||||
|
break;
|
||||||
|
CHECK(preheat == 215);
|
||||||
|
}
|
||||||
|
REQUIRE(checked_waits > 0); // the feature under test is active
|
||||||
|
}
|
||||||
|
|
||||||
|
// The temperature-wait park picks its side of the tower by testing bed containment with the
|
||||||
|
// tower position at psWipeTower generation time, while WipeTowerIntegration shifts the cached
|
||||||
|
// moves by the CURRENT position at export. Moving the tower normally invalidates only
|
||||||
|
// psSkirtBrim (tower gcode is position-independent), but the park makes it bed-relative, so a
|
||||||
|
// GUI-style move-and-reslice on the same Print must regenerate the tower — otherwise the stale
|
||||||
|
// park prints outside the bed. Contract: every tagged wait parks inside the printable area.
|
||||||
|
TEST_CASE("Wipe tower temperature-wait park is regenerated when the tower moves", "[MultiFilament]")
|
||||||
|
{
|
||||||
|
// Two objects, one filament each: a toolchange (and a tagged wait) on every layer, like
|
||||||
|
// the wait test above — but on a single-extruder machine profile: the synthetic
|
||||||
|
// dual-extruder keys would drag in the extruder-variant expansion, which is not
|
||||||
|
// idempotent on the default machine profile and would pollute the re-apply diff below.
|
||||||
|
// Rectangle wall and no brim keep the tower-local footprint inside [0, 35], so the park
|
||||||
|
// sits at the generator's 2mm side gap: local -2 or 37.
|
||||||
|
DynamicPrintConfig config = multifilament_config(2, {
|
||||||
|
{ "single_extruder_multi_material", 0 },
|
||||||
|
{ "enable_prime_tower", 1 },
|
||||||
|
{ "prime_tower_width", 35 },
|
||||||
|
{ "wipe_tower_wall_type", "rectangle" }, // the default rib bulges past the width
|
||||||
|
{ "prime_tower_brim_width", 0 }, // the default 3 widens the first-layer envelope
|
||||||
|
{ "printable_area", "0x0,200x0,200x200,0x200" },
|
||||||
|
{ "wipe_tower_x", "0" },
|
||||||
|
{ "wipe_tower_y", "50" },
|
||||||
|
{ "ooze_prevention", 1 },
|
||||||
|
{ "standby_temperature_delta", -40 },
|
||||||
|
{ "wait_for_temp_on_wipe_tower", 1 },
|
||||||
|
});
|
||||||
|
// init_print force-sets this on its own copy; set it here too so the re-apply below
|
||||||
|
// diffs in wipe_tower_x ONLY — the exact GUI increment under test.
|
||||||
|
config.set_key_value("gcode_comments", new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{
|
||||||
|
{ { "extruder", 1 } }, { { "extruder", 2 } } }; // object-level, see the wait test above
|
||||||
|
init_print(std::vector<TriangleMesh>{ cube(20), cube(20) }, print, model, config, &overrides);
|
||||||
|
|
||||||
|
const std::string at_edge = gcode(print);
|
||||||
|
const std::vector<double> at_edge_parks = wait_park_xs(at_edge);
|
||||||
|
REQUIRE(!at_edge_parks.empty()); // the feature under test is active
|
||||||
|
for (double x : at_edge_parks) {
|
||||||
|
INFO("wait park X " << x << " with the tower at x=0 on a 200mm bed");
|
||||||
|
CHECK(x >= -0.05);
|
||||||
|
CHECK(x <= 200.05);
|
||||||
|
}
|
||||||
|
REQUIRE(print.is_step_done(psWipeTower));
|
||||||
|
|
||||||
|
// Move the tower to the right bed edge (164 + 35 = 199 keeps the body printable) and
|
||||||
|
// re-apply on the SAME Print, as the GUI does. Base the re-apply on the print's own
|
||||||
|
// resolved config so the diff is wipe_tower_x alone — re-applying the caller's config
|
||||||
|
// would also diff the apply-time extruder normalization write-backs, and those keys
|
||||||
|
// regenerate the tower for the wrong reason. The cached right-side park would export
|
||||||
|
// at 164 + 37 = 201, off the bed; regeneration clamps the park against the bed edge.
|
||||||
|
// Assemble the moved config exactly the way init_print assembled the first one — the
|
||||||
|
// apply-time normalization is only idempotent when both applies start from the same
|
||||||
|
// derivation, and any stray diff key would regenerate the tower for the wrong reason.
|
||||||
|
config.set_deserialize_strict({ { "wipe_tower_x", "164" } });
|
||||||
|
DynamicPrintConfig moved_config = DynamicPrintConfig::full_print_config();
|
||||||
|
moved_config.apply(config);
|
||||||
|
moved_config.set_key_value("gcode_comments", new ConfigOptionBool(true));
|
||||||
|
print.apply(model, moved_config);
|
||||||
|
CHECK_FALSE(print.is_step_done(psWipeTower)); // the move must re-generate the tower
|
||||||
|
|
||||||
|
const std::string moved = gcode(print);
|
||||||
|
const std::vector<double> moved_parks = wait_park_xs(moved);
|
||||||
|
REQUIRE(!moved_parks.empty()); // the waits must survive the re-slice
|
||||||
|
for (double x : moved_parks) {
|
||||||
|
INFO("wait park X " << x << " with the tower at x=164 on a 200mm bed");
|
||||||
|
CHECK(x >= -0.05);
|
||||||
|
CHECK(x <= 200.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The flag-off half of the three tests above. Every site wait_for_temp_on_wipe_tower touches is
|
||||||
|
// guarded -- set_extruder's pre-toolchange preheat block and its post_toolchange skip,
|
||||||
|
// toolchange_Change's park, the interface-temp guard in WipeTower2::tool_change, and append_tcr2's
|
||||||
|
// tagged-M109 filter -- so with the option off the feature has to be inert and temperature emission
|
||||||
|
// has to stay exactly as it was before the option existed. That is pinned against a trace captured
|
||||||
|
// from main rather than against expectations written from the current code, which would be
|
||||||
|
// re-derived from the very code they are meant to guard.
|
||||||
|
//
|
||||||
|
// Note what main emits here, since it is easy to misread as a missing wait: with preheat_time set,
|
||||||
|
// the toolchange carries no blocking M109 at all. GCodeProcessor's backtrace moves the heat-up to
|
||||||
|
// an M104 preheat_time seconds earlier and demotes the in-place command, which is the entire point
|
||||||
|
// of preheating. The lead times below are what pin that placement.
|
||||||
|
TEST_CASE("Toolchange temperature commands are unchanged when the wipe tower wait is off", "[MultiFilament][Regression]")
|
||||||
|
{
|
||||||
|
// 20x20x5 cubes at the default 0.2mm layer height are 25 layers, one filament each, so there is
|
||||||
|
// a toolchange -- and a preheat ahead of it -- on every layer.
|
||||||
|
const std::string gcode = slice_with_object_overrides(
|
||||||
|
{ make_cube(20., 20., 5.), make_cube(20., 20., 5.) },
|
||||||
|
multifilament_config(2, {
|
||||||
|
{ "nozzle_diameter", "0.4,0.4" },
|
||||||
|
{ "printer_extruder_id", "1,2" },
|
||||||
|
{ "printer_extruder_variant", "Direct Drive Standard,Direct Drive Standard" },
|
||||||
|
{ "extruder_printable_height", "0,0" },
|
||||||
|
{ "single_extruder_multi_material", 0 },
|
||||||
|
{ "single_extruder_multi_material_priming", 1 }, // reaches toolchange_Change's priming path
|
||||||
|
{ "enable_prime_tower", 1 },
|
||||||
|
{ "prime_tower_width", 35 },
|
||||||
|
{ "wipe_tower_x", "50" },
|
||||||
|
{ "wipe_tower_y", "50" },
|
||||||
|
// GCodeProcessor::apply_config enables the preheat backtrace on
|
||||||
|
// ooze_prevention && preheat_time > 0 && !SEMM && filaments > 1. That is what puts an
|
||||||
|
// M104 preheat_time seconds ahead of every Tn, and it also gives set_extruder's
|
||||||
|
// standby/restore pair, which the option demotes and moves when it is on.
|
||||||
|
{ "ooze_prevention", 1 },
|
||||||
|
{ "standby_temperature_delta", -40 },
|
||||||
|
{ "preheat_time", 30 },
|
||||||
|
{ "preheat_steps", 1 },
|
||||||
|
// enable_tower_interface_features is deliberately left off: the interface temperature
|
||||||
|
// is observable only through a change_filament_gcode template that reads
|
||||||
|
// new_filament_temp, since append_tcr2 strips the tower's own M109 for it, and the
|
||||||
|
// default template here has none. The option's interface-temp guard is covered by the
|
||||||
|
// enabled-path tests above instead.
|
||||||
|
//
|
||||||
|
// Distinct enough that a wrong pick between the two is unambiguous in the trace.
|
||||||
|
{ "nozzle_temperature_initial_layer", "215,215" },
|
||||||
|
{ "nozzle_temperature", "240,240" },
|
||||||
|
{ "wait_for_temp_on_wipe_tower", 0 },
|
||||||
|
}),
|
||||||
|
// Object-level, so the used-filament count that gates the prime tower is derived from it.
|
||||||
|
{ { { "extruder", 1 } }, { { "extruder", 2 } } });
|
||||||
|
|
||||||
|
const std::vector<std::string> trace = temperature_trace(gcode);
|
||||||
|
REQUIRE(trace.size() > 1);
|
||||||
|
CHECK(gcode.find("_WAIT_FOR_TEMP_ON_WIPE_TOWER") == std::string::npos);
|
||||||
|
|
||||||
|
const std::string golden_path = std::string(TEST_DATA_DIR PATH_SEPARATOR "wipe_tower_temperature_trace_main.txt");
|
||||||
|
|
||||||
|
// Regenerate by appending this test and its helpers to the same file on main (dropping the
|
||||||
|
// wait_for_temp_on_wipe_tower key, which main's config does not know), rebuilding
|
||||||
|
// fff_print_tests there, running it with ORCA_UPDATE_WIPE_TOWER_TEMP_TRACE=1, copying the file
|
||||||
|
// it writes back here, and filling in the commit it was captured from.
|
||||||
|
if (std::getenv("ORCA_UPDATE_WIPE_TOWER_TEMP_TRACE") != nullptr) {
|
||||||
|
std::ofstream out(golden_path);
|
||||||
|
REQUIRE(out.good());
|
||||||
|
out << "# Temperature and tool-change commands of a wait_for_temp_on_wipe_tower-off slice,\n"
|
||||||
|
"# captured from the main branch at <fill in the commit>. Regeneration is described\n"
|
||||||
|
"# at the test that reads this file: \"Toolchange temperature commands are unchanged\n"
|
||||||
|
"# when the wipe tower wait is off\" in tests/fff_print/test_multifilament.cpp.\n";
|
||||||
|
for (const std::string& entry : trace)
|
||||||
|
out << entry << "\n";
|
||||||
|
WARN("Rewrote " << golden_path << " from this run; it no longer reflects main.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> golden;
|
||||||
|
{
|
||||||
|
std::ifstream in(golden_path);
|
||||||
|
INFO("reading " << golden_path);
|
||||||
|
REQUIRE(in.good());
|
||||||
|
for (std::string line; std::getline(in, line);) {
|
||||||
|
if (!line.empty() && line.back() == '\r')
|
||||||
|
line.pop_back();
|
||||||
|
if (!line.empty() && line[0] != '#')
|
||||||
|
golden.push_back(std::move(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUIRE(!golden.empty());
|
||||||
|
|
||||||
|
const size_t common = std::min(trace.size(), golden.size());
|
||||||
|
for (size_t i = 0; i < common; ++i) {
|
||||||
|
if (trace_entries_match(trace[i], golden[i]))
|
||||||
|
continue;
|
||||||
|
// Report the first difference only: past it the two are misaligned and every later entry
|
||||||
|
// would be reported as a difference too.
|
||||||
|
INFO("first difference at trace entry " << i + 1);
|
||||||
|
INFO(" main: " << golden[i]);
|
||||||
|
INFO(" branch: " << trace[i]);
|
||||||
|
FAIL("temperature emission differs from main with wait_for_temp_on_wipe_tower off");
|
||||||
|
}
|
||||||
|
CHECK(trace.size() == golden.size());
|
||||||
|
}
|
||||||
|
|
||||||
// max_layer_height can be shorter than the extruder count (normalization sizes it to the
|
// max_layer_height can be shorter than the extruder count (normalization sizes it to the
|
||||||
// filament count under single_extruder_multi_material). calc_max_layer_height() in ToolOrdering
|
// filament count under single_extruder_multi_material). calc_max_layer_height() in ToolOrdering
|
||||||
// indexed it per-nozzle and read past the end. Shortened directly here to isolate that read;
|
// indexed it per-nozzle and read past the end. Shortened directly here to isolate that read;
|
||||||
@@ -104,3 +649,4 @@ TEST_CASE("Multi-extruder slice stays in bounds with a short max_layer_height",
|
|||||||
init_and_process_print({ cube(20) }, print, config);
|
init_and_process_print({ cube(20) }, print, config);
|
||||||
REQUIRE_FALSE(print.objects().front()->layers().empty());
|
REQUIRE_FALSE(print.objects().front()->layers().empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower.hpp"
|
#include "libslic3r/GCode/WipeTower.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
@@ -53,6 +55,65 @@ TEST_CASE("Other flavors wait in the wipe tower with a seconds dwell", "[WipeTow
|
|||||||
CHECK(wait_command(flavor, 1.5f) == "G4 S1.500\n");
|
CHECK(wait_command(flavor, 1.5f) == "G4 S1.500\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The prime tower is validated against the real printable outline, so the placement clamps have to
|
||||||
|
// agree with it wherever that outline is not a rectangle. A regular hexagon inscribed in a 200mm
|
||||||
|
// circle stands in for the shipped delta beds.
|
||||||
|
TEST_CASE("The wipe tower placement clamp follows a non-rectangular bed outline", "[WipeTower]")
|
||||||
|
{
|
||||||
|
const coord_t margin = scaled<coord_t>(1.);
|
||||||
|
auto square_at = [](double x, double y, double side) {
|
||||||
|
return BoundingBox(Point::new_scale(x, y), Point::new_scale(x + side, y + side));
|
||||||
|
};
|
||||||
|
// Does the footprint, padded by pad, sit inside the outline once the returned move is applied?
|
||||||
|
auto lands_inside = [](BoundingBox box, const Polygons &bed, const Vec2f &move, coord_t pad) {
|
||||||
|
box.translate(Point::new_scale(move.x(), move.y()));
|
||||||
|
return diff(Polygons{box.inflated(pad).polygon()}, bed).empty();
|
||||||
|
};
|
||||||
|
|
||||||
|
const Polygons hex_bed{make_circle_num_segments(scaled<double>(100.), 6)};
|
||||||
|
const Polygons square_bed{Polygon::new_scale(Pointfs{{0., 0.}, {200., 0.}, {200., 200.}, {0., 200.}})};
|
||||||
|
|
||||||
|
SECTION("a rectangular bed is left to the bounding box clamp") {
|
||||||
|
const Vec2f move = WipeTower::move_box_inside_polygon(square_at(50., 50., 30.), square_bed, margin);
|
||||||
|
CHECK_THAT(move.x(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
CHECK_THAT(move.y(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dragging the tower off one edge may not pull it away from the other, or it would jump out from
|
||||||
|
// under the cursor instead of sliding along the edge.
|
||||||
|
SECTION("only the violated axis is clamped") {
|
||||||
|
const Vec2f move = WipeTower::move_box_inside_polygon(square_at(185., 50., 30.), square_bed, margin);
|
||||||
|
CHECK_THAT(move.x(), Catch::Matchers::WithinAbs(-16., 1e-6));
|
||||||
|
CHECK_THAT(move.y(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a footprint already inside the outline is left alone") {
|
||||||
|
const Vec2f move = WipeTower::move_box_inside_polygon(square_at(-15., -15., 30.), hex_bed, margin);
|
||||||
|
CHECK_THAT(move.x(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
CHECK_THAT(move.y(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a footprint in the bounding box corner is pulled onto the bed") {
|
||||||
|
const BoundingBox box = square_at(55., 50., 30.);
|
||||||
|
REQUIRE_FALSE(lands_inside(box, hex_bed, Vec2f::Zero(), margin)); // in the bbox, off the hexagon
|
||||||
|
CHECK(lands_inside(box, hex_bed, WipeTower::move_box_inside_polygon(box, hex_bed, margin), margin));
|
||||||
|
}
|
||||||
|
|
||||||
|
// An unresolved auto brim width reaches the drag clamp as a negative margin. Padding by it would
|
||||||
|
// shrink the footprint and hand back a position the slice validation still rejects.
|
||||||
|
SECTION("a negative margin still lands the footprint inside the outline") {
|
||||||
|
const BoundingBox box = square_at(55., 50., 30.);
|
||||||
|
const coord_t brim = scaled<coord_t>(-0.5);
|
||||||
|
CHECK(lands_inside(box, hex_bed, WipeTower::move_box_inside_polygon(box, hex_bed, brim), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a footprint too large for the bed is left alone") {
|
||||||
|
const Vec2f move = WipeTower::move_box_inside_polygon(square_at(-200., -200., 400.), hex_bed, margin);
|
||||||
|
CHECK_THAT(move.x(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
CHECK_THAT(move.y(), Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The cases above only exercise the helpers in isolation. The one below slices a real
|
// The cases above only exercise the helpers in isolation. The one below slices a real
|
||||||
// two-filament print, so it also covers the binding constraint of both changes: that the
|
// two-filament print, so it also covers the binding constraint of both changes: that the
|
||||||
// configured `gcode_flavor` reaches the wipe tower writer and lands in the exported G-code.
|
// configured `gcode_flavor` reaches the wipe tower writer and lands in the exported G-code.
|
||||||
|
|||||||
Reference in New Issue
Block a user