mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
Bug fixes and test cases for import filament of published 3MF. Update translations
This commit is contained in:
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -5945,7 +5945,7 @@ msgstr ""
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
@@ -8043,6 +8043,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2025-03-15 10:55+0100\n"
|
"PO-Revision-Date: 2025-03-15 10:55+0100\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6406,8 +6406,8 @@ msgstr "Desa el projecte com a"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Desar el projecte actual com"
|
msgstr "Desar el projecte actual com"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publicar"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8632,6 +8632,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publicar"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "El tipus de broquet no està establert. Establiu el broquet i torneu-ho a provar."
|
msgstr "El tipus de broquet no està establert. Establiu el broquet i torneu-ho a provar."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: Jakub Hencl\n"
|
"Last-Translator: Jakub Hencl\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6367,8 +6367,8 @@ msgstr "Uložit projekt jako"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Uložit aktuální projekt jako"
|
msgstr "Uložit aktuální projekt jako"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publikovat"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8592,6 +8592,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publikovat"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Typ trysky není nastaven. Nastavte prosím trysku a zkuste to znovu."
|
msgstr "Typ trysky není nastaven. Nastavte prosím trysku a zkuste to znovu."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: Heiko Liebscher <hliebschergmail.com>\n"
|
"Last-Translator: Heiko Liebscher <hliebschergmail.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6259,8 +6259,8 @@ msgstr "Projekt speichern als"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Aktuelles Projekt speichern als"
|
msgstr "Aktuelles Projekt speichern als"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Veröffentlichen"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8464,6 +8464,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Veröffentlichen"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Die Düsenart ist nicht eingestellt. Bitte stellen Sie die Düse ein und versuchen Sie es erneut."
|
msgstr "Die Düsenart ist nicht eingestellt. Bitte stellen Sie die Düse ein und versuchen Sie es erneut."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -5941,7 +5941,7 @@ msgstr ""
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
@@ -8039,6 +8039,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: Ian A. Bassi <>\n"
|
"Last-Translator: Ian A. Bassi <>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6116,8 +6116,8 @@ msgstr "Guardar proyecto como"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Guardar el proyecto actual como"
|
msgstr "Guardar el proyecto actual como"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publicar"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8267,6 +8267,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publicar"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "El tipo de boquilla no está establecido. Configure la boquilla e inténtelo de nuevo."
|
msgstr "El tipo de boquilla no está establecido. Configure la boquilla e inténtelo de nuevo."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-07-20 13:33+0200\n"
|
"PO-Revision-Date: 2026-07-20 13:33+0200\n"
|
||||||
"Last-Translator: Manu Goiogana <mgoiogana@gmail.com>\n"
|
"Last-Translator: Manu Goiogana <mgoiogana@gmail.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6161,8 +6161,8 @@ msgstr "Gorde proiektua honela"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Gorde uneko proiektua honela"
|
msgstr "Gorde uneko proiektua honela"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Argitaratu"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8334,6 +8334,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Argitaratu"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Pita mota ez dago ezarrita. Ezarri pita eta saiatu berriro."
|
msgstr "Pita mota ez dago ezarrita. Ezarri pita eta saiatu berriro."
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Guislain Cyril, Thomas Lété\n"
|
"Language-Team: Guislain Cyril, Thomas Lété\n"
|
||||||
@@ -6208,8 +6208,8 @@ msgstr "Enregistrer le projet sous"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Enregistrer le projet actuel sous"
|
msgstr "Enregistrer le projet actuel sous"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publier"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8390,6 +8390,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publier"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Le type de buse n'est pas défini. Veuillez définir la buse et réessayer."
|
msgstr "Le type de buse n'est pas défini. Veuillez définir la buse et réessayer."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"Language: hu\n"
|
"Language: hu\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -6310,8 +6310,8 @@ msgstr "Projekt mentése másként"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Jelenlegi projekt mentése másként"
|
msgstr "Jelenlegi projekt mentése másként"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Közzététel"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8516,6 +8516,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Közzététel"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "A fúvókatípus nincs beállítva. Állítsd be a fúvókát, majd próbáld újra."
|
msgstr "A fúvókatípus nincs beállítva. Állítsd be a fúvókát, majd próbáld újra."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6312,8 +6312,8 @@ msgstr "Salva progetto con nome"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Salva progetto corrente con nome"
|
msgstr "Salva progetto corrente con nome"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Pubblica"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8515,6 +8515,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Pubblica"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Il tipo di ugello non è impostato. Impostare l'ugello e riprovare."
|
msgstr "Il tipo di ugello non è impostato. Impostare l'ugello e riprovare."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6321,8 +6321,8 @@ msgstr "プロジェクトを名前を付けて保存"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "プロジェクトを名前を付けて保存"
|
msgstr "プロジェクトを名前を付けて保存"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "公開する"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8534,6 +8534,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "公開する"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "ノズルタイプが設定されていません。ノズルを設定して再試行してください。"
|
msgstr "ノズルタイプが設定されていません。ノズルを設定して再試行してください。"
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2025-06-02 17:12+0900\n"
|
"PO-Revision-Date: 2025-06-02 17:12+0900\n"
|
||||||
"Last-Translator: crwusiz <crwusiz@gmail.com>\n"
|
"Last-Translator: crwusiz <crwusiz@gmail.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6335,8 +6335,8 @@ msgstr "프로젝트 다른 이름으로 저장"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "현재 프로젝트 다른 이름으로 저장"
|
msgstr "현재 프로젝트 다른 이름으로 저장"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "게시"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8567,6 +8567,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "게시"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "노즐 유형이 설정되지 않았습니다. 노즐을 설정하고 다시 시도하세요."
|
msgstr "노즐 유형이 설정되지 않았습니다. 노즐을 설정하고 다시 시도하세요."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-07-02 14:13+0300\n"
|
"PO-Revision-Date: 2026-07-02 14:13+0300\n"
|
||||||
"Last-Translator: Gintaras Kučinskas <sharanchius@gmail.com>\n"
|
"Last-Translator: Gintaras Kučinskas <sharanchius@gmail.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6298,8 +6298,8 @@ msgstr "Įrašyti projektą kaip"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Įrašyti dabartinį projektą kaip"
|
msgstr "Įrašyti dabartinį projektą kaip"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Talpinti"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8510,6 +8510,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Talpinti"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Purkštuko tipas nenustatytas. Nustatykite purkštuką ir bandykite dar kartą."
|
msgstr "Purkštuko tipas nenustatytas. Nustatykite purkštuką ir bandykite dar kartą."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6873,8 +6873,8 @@ msgstr "Bewaar project als"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Bewaar huidig project als"
|
msgstr "Bewaar huidig project als"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publiceren"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9284,6 +9284,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publiceren"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Het mondstuktype is niet ingesteld. Stel het mondstuk in en probeer het opnieuw."
|
msgstr "Het mondstuktype is niet ingesteld. Stel het mondstuk in en probeer het opnieuw."
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: OrcaSlicer 2.3.0-rc\n"
|
"Project-Id-Version: OrcaSlicer 2.3.0-rc\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: \n"
|
"PO-Revision-Date: \n"
|
||||||
"Last-Translator: Krzysztof Morga <<tlumaczeniebs@gmail.com>>\n"
|
"Last-Translator: Krzysztof Morga <<tlumaczeniebs@gmail.com>>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6454,8 +6454,8 @@ msgstr "Zapisz projekt jako"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Zapisz bieżący projekt jako"
|
msgstr "Zapisz bieżący projekt jako"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Opublikuj"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8724,6 +8724,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Opublikuj"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Nie ustawiono typu dyszy Wprowadź ustawienia dyszy i spróbuj ponownie."
|
msgstr "Nie ustawiono typu dyszy Wprowadź ustawienia dyszy i spróbuj ponownie."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-07-26 11:14-0300\n"
|
"PO-Revision-Date: 2026-07-26 11:14-0300\n"
|
||||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||||
"Language-Team: Portuguese, Brazilian\n"
|
"Language-Team: Portuguese, Brazilian\n"
|
||||||
@@ -6130,8 +6130,8 @@ msgstr "Salvar projeto como"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Salvar o projeto atual como"
|
msgstr "Salvar o projeto atual como"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publicar"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8290,6 +8290,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publicar"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "O tipo de bico não está configurado. Configure o bico e tente novamente."
|
msgstr "O tipo de bico não está configurado. Configure o bico e tente novamente."
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: OrcaSlicer V2.5.0\n"
|
"Project-Id-Version: OrcaSlicer V2.5.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-02-25 13:38+0300\n"
|
"PO-Revision-Date: 2026-02-25 13:38+0300\n"
|
||||||
"Last-Translator: Felix14_v2\n"
|
"Last-Translator: Felix14_v2\n"
|
||||||
"Language-Team: Felix14_v2 (ДС/ТГ: @felix14_v2, почта: aleks111001@list.ru), Andylg <andylg@yandex.ru>\n"
|
"Language-Team: Felix14_v2 (ДС/ТГ: @felix14_v2, почта: aleks111001@list.ru), Andylg <andylg@yandex.ru>\n"
|
||||||
@@ -6363,8 +6363,8 @@ msgstr "Сохранить проект как"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Сохранить текущий проект как"
|
msgstr "Сохранить текущий проект как"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Опубликовать"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8569,6 +8569,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Опубликовать"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Не указан тип сопла. Укажите его и попробуйте ещё раз."
|
msgstr "Не указан тип сопла. Укажите его и попробуйте ещё раз."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -6957,8 +6957,8 @@ msgstr "Spara Projekt som"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Spara nuvarande projekt som"
|
msgstr "Spara nuvarande projekt som"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Publicera"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9377,6 +9377,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Publicera"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Nozzeltypen är inte angiven. Ange nozzeln och försök igen."
|
msgstr "Nozzeltypen är inte angiven. Ange nozzeln och försök igen."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-06-19 13:40+0700\n"
|
"PO-Revision-Date: 2026-06-19 13:40+0700\n"
|
||||||
"Last-Translator: Icezaza\n"
|
"Last-Translator: Icezaza\n"
|
||||||
"Language-Team: Thai\n"
|
"Language-Team: Thai\n"
|
||||||
@@ -6289,8 +6289,8 @@ msgstr "บันทึกโปรเจกต์เป็น"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "บันทึกโครงการปัจจุบันเป็น"
|
msgstr "บันทึกโครงการปัจจุบันเป็น"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "เผยแพร่"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8470,6 +8470,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "เผยแพร่"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "ไม่ได้ตั้งค่าประเภทหัวฉีด โปรดตั้งหัวฉีดแล้วลองอีกครั้ง"
|
msgstr "ไม่ได้ตั้งค่าประเภทหัวฉีด โปรดตั้งหัวฉีดแล้วลองอีกครั้ง"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-08-01 20:32+0300\n"
|
"PO-Revision-Date: 2026-08-01 20:32+0300\n"
|
||||||
"Last-Translator: GlauTech\n"
|
"Last-Translator: GlauTech\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6389,8 +6389,8 @@ msgstr "Projeyi farklı kaydet"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Mevcut projeyi farklı kaydet"
|
msgstr "Mevcut projeyi farklı kaydet"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Yayınla"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8606,6 +8606,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Yayınla"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Nozul tipi ayarlanmamış. Lütfen nozulu ayarlayın ve tekrar deneyin."
|
msgstr "Nozul tipi ayarlanmamış. Lütfen nozulu ayarlayın ve tekrar deneyin."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: orcaslicerua\n"
|
"Project-Id-Version: orcaslicerua\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-07-17 16:25+0300\n"
|
"PO-Revision-Date: 2026-07-17 16:25+0300\n"
|
||||||
"Last-Translator: Andrij Mizyk <andm1zyk@proton.me>\n"
|
"Last-Translator: Andrij Mizyk <andm1zyk@proton.me>\n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
@@ -6326,8 +6326,8 @@ msgstr "Зберегти проєкт як"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Зберегти поточний проєкт як"
|
msgstr "Зберегти поточний проєкт як"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Публікувати"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8580,6 +8580,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Публікувати"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Тип сопла не встановлений. Будь ласка, оберіть сопло та спробуйте ще раз."
|
msgstr "Тип сопла не встановлений. Будь ласка, оберіть сопло та спробуйте ще раз."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2025-10-02 17:43+0700\n"
|
"PO-Revision-Date: 2025-10-02 17:43+0700\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: hainguyen.ts13@gmail.com\n"
|
"Language-Team: hainguyen.ts13@gmail.com\n"
|
||||||
@@ -6674,8 +6674,8 @@ msgstr "Lưu dự án thành"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "Lưu dự án hiện tại thành"
|
msgstr "Lưu dự án hiện tại thành"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "Xuất bản"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9001,6 +9001,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "Xuất bản"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "Chưa đặt loại đầu phun. Vui lòng đặt đầu phun rồi thử lại."
|
msgstr "Chưa đặt loại đầu phun. Vui lòng đặt đầu phun rồi thử lại."
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Slic3rPE\n"
|
"Project-Id-Version: Slic3rPE\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2026-06-11 12:37-0300\n"
|
"PO-Revision-Date: 2026-06-11 12:37-0300\n"
|
||||||
"Last-Translator: Handle <mail@bysb.net>\n"
|
"Last-Translator: Handle <mail@bysb.net>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6144,8 +6144,8 @@ msgstr "项目另存为"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "项目另存为"
|
msgstr "项目另存为"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "发布"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8299,6 +8299,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "发布"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "未设置喷嘴类型。请设置喷嘴并重试。"
|
msgstr "未设置喷嘴类型。请设置喷嘴并重试。"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-08-18 16:25+0800\n"
|
"POT-Creation-Date: 2026-08-19 14:59+0800\n"
|
||||||
"PO-Revision-Date: 2025-11-28 13:48-0600\n"
|
"PO-Revision-Date: 2025-11-28 13:48-0600\n"
|
||||||
"Last-Translator: tntchn <15895303+tntchn@users.noreply.github.com>\n"
|
"Last-Translator: tntchn <15895303+tntchn@users.noreply.github.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
@@ -6274,8 +6274,8 @@ msgstr "另存專案為"
|
|||||||
msgid "Save current project as"
|
msgid "Save current project as"
|
||||||
msgstr "將目前專案另存為"
|
msgstr "將目前專案另存為"
|
||||||
|
|
||||||
msgid "Publish"
|
msgid "Publish 3MF"
|
||||||
msgstr "發布"
|
msgstr ""
|
||||||
|
|
||||||
msgid "Export a 3MF file with the selected settings embedded"
|
msgid "Export a 3MF file with the selected settings embedded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8465,6 +8465,9 @@ msgid ""
|
|||||||
"Please check whether the folder exists online or if other programs have the file open."
|
"Please check whether the folder exists online or if other programs have the file open."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Publish"
|
||||||
|
msgstr "發布"
|
||||||
|
|
||||||
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
msgid "The nozzle type is not set. Please set the nozzle and try again."
|
||||||
msgstr "噴嘴類型尚未設定。請設定噴嘴後再試一次。"
|
msgstr "噴嘴類型尚未設定。請設定噴嘴後再試一次。"
|
||||||
|
|
||||||
|
|||||||
+206
-336
@@ -4752,9 +4752,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
}
|
}
|
||||||
} // !is_published
|
} // !is_published
|
||||||
|
|
||||||
// 4) Load the project config values (the per extruder wipe matrix etc).
|
// Load the project config values. In published mode only the plate/bed geometry keys
|
||||||
// In published mode the receiver must not inherit the author's filament/purge data,
|
// cross over (the receiver must not inherit the author's filament/purge data).
|
||||||
// so only the plate/bed geometry project keys are applied.
|
|
||||||
this->project_config.apply_only(config, is_published ? s_project_options_published : s_project_options);
|
this->project_config.apply_only(config, is_published ? s_project_options_published : s_project_options);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -4774,29 +4773,23 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||||
this->update_multi_material_filament_presets();
|
this->update_multi_material_filament_presets();
|
||||||
|
|
||||||
// A "published" 3MF project overlays only the author-selected published keys onto the
|
// A "published" 3MF project overlays the author-selected published keys onto the user's
|
||||||
// user's currently-selected (edited) process preset. Scalar keys are applied directly;
|
// currently-selected (edited) process preset. Keys that cannot be applied are collected for
|
||||||
// vector (multi-extruder) keys are applied only when the edited preset has a matching
|
// notification; filament-class keys in published_keys (which only the material pass knows
|
||||||
// vector size. Keys that cannot be applied are collected for notification; legacy
|
// how to apply) fall through into skipped_keys.
|
||||||
// filament/printer keys in a published file fall through into skipped_keys.
|
|
||||||
if (is_published) {
|
if (is_published) {
|
||||||
std::vector<std::string> skipped_keys;
|
std::vector<std::string> skipped_keys;
|
||||||
std::set<std::string> applied_keys;
|
std::set<std::string> applied_keys;
|
||||||
// Set whenever the material overlay actually modifies a receiver filament preset
|
// Only re-select the edited filament preset when the material overlay changed
|
||||||
// (applied key, colour or slot replacement). Only then must the edited preset be
|
// something: re-selecting unconditionally would discard the user's unsaved in-memory
|
||||||
// re-snapshotted: re-selecting unconditionally would discard the user's unsaved
|
// filament edits when the published file touches nothing.
|
||||||
// in-memory filament edits when the published file touches nothing.
|
|
||||||
bool material_applied = false;
|
bool material_applied = false;
|
||||||
// Structural keys must never be applied to the user's presets: doing so would
|
// Structural keys are never applied (they would rewrite the user's preset
|
||||||
// rewrite their preset inheritance/structure. This is the single source of truth
|
// inheritance/structure). Defense-in-depth: a hand-crafted 3MF could list them despite
|
||||||
// shared with PublishSettingsDialog.cpp (publish_structural_keys in
|
// the dialog, so skip them here too.
|
||||||
// PublishSettings.hpp). Defense-in-depth: a hand-crafted 3MF could set
|
|
||||||
// published_keys to these regardless of the dialog, so skip them here too.
|
|
||||||
const std::set<std::string> &structural_keys = publish_structural_keys();
|
const std::set<std::string> &structural_keys = publish_structural_keys();
|
||||||
// The printer overlay is restricted to the publishable retraction/z-hop allowlist.
|
// The printer overlay is restricted to the publishable retraction/z-hop allowlist;
|
||||||
// Printer-class keys outside it are contract-excluded: never applied and never
|
// printer-class keys outside it are contract-excluded (never applied, never reported).
|
||||||
// reported as skipped (a hand-crafted 3MF listing machine_start_gcode or
|
|
||||||
// nozzle_diameter must not apply them and must not spam the warning).
|
|
||||||
const std::set<std::string> &printer_allowlist = publishable_printer_keys();
|
const std::set<std::string> &printer_allowlist = publishable_printer_keys();
|
||||||
const std::vector<std::string> &printer_options = Preset::printer_options();
|
const std::vector<std::string> &printer_options = Preset::printer_options();
|
||||||
const std::set<std::string> printer_option_set(printer_options.begin(), printer_options.end());
|
const std::set<std::string> printer_option_set(printer_options.begin(), printer_options.end());
|
||||||
@@ -4805,10 +4798,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
for (const std::string &key : published_config->published_keys) {
|
for (const std::string &key : published_config->published_keys) {
|
||||||
if (applied_keys.count(key) != 0)
|
if (applied_keys.count(key) != 0)
|
||||||
continue; // already applied
|
continue; // already applied
|
||||||
// A '#' suffix denotes a variant (per-extruder/per-filament) key; resolve the base key.
|
// A '#' suffix denotes a variant key; resolve the base key.
|
||||||
const std::string base_key = key.substr(0, key.find('#'));
|
const std::string base_key = key.substr(0, key.find('#'));
|
||||||
// Structural keys are intentionally never applied (not "skipped due to
|
// Structural keys are never applied (not "skipped due to mismatch"), so bail
|
||||||
// mismatch"), so bail out before the applied/skipped bookkeeping.
|
// out before the applied/skipped bookkeeping.
|
||||||
if (structural_keys.count(base_key) != 0)
|
if (structural_keys.count(base_key) != 0)
|
||||||
continue;
|
continue;
|
||||||
if (allowlist != nullptr &&
|
if (allowlist != nullptr &&
|
||||||
@@ -4822,23 +4815,29 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
if (src_opt == nullptr)
|
if (src_opt == nullptr)
|
||||||
continue; // key not present in the loaded config; record later
|
continue; // key not present in the loaded config; record later
|
||||||
if (src_opt->is_vector()) {
|
if (src_opt->is_vector()) {
|
||||||
// Vector key: apply only when the edited preset has a matching vector size.
|
|
||||||
const ConfigOption *dst_opt = target.option(base_key);
|
const ConfigOption *dst_opt = target.option(base_key);
|
||||||
if (dst_opt == nullptr || !dst_opt->is_vector() ||
|
if (dst_opt == nullptr || !dst_opt->is_vector())
|
||||||
static_cast<const ConfigOptionVectorBase*>(src_opt)->size() != static_cast<const ConfigOptionVectorBase*>(dst_opt)->size())
|
|
||||||
continue; // cannot apply; will be reported as skipped
|
continue; // cannot apply; will be reported as skipped
|
||||||
// A '#' variant index must be in range: ConfigOptionVector::set_at would
|
// A '#N' variant key (e.g. per-extruder retraction_length#2) applies one
|
||||||
// otherwise resize the destination vector, corrupting the receiver's preset.
|
// element, so the index only needs to be in range on both sides - the
|
||||||
|
// receiver may have a different extruder count than the author. Out-of-range
|
||||||
|
// indices are skipped (set_at would otherwise resize the receiver's vector).
|
||||||
if (key.size() > base_key.size()) {
|
if (key.size() > base_key.size()) {
|
||||||
const size_t idx = static_cast<size_t>(std::atoi(key.c_str() + base_key.size() + 1));
|
const size_t idx = static_cast<size_t>(std::atoi(key.c_str() + base_key.size() + 1));
|
||||||
if (idx >= static_cast<const ConfigOptionVectorBase*>(src_opt)->size())
|
if (idx >= static_cast<const ConfigOptionVectorBase*>(src_opt)->size() ||
|
||||||
|
idx >= static_cast<const ConfigOptionVectorBase*>(dst_opt)->size())
|
||||||
continue; // out-of-range variant: cannot apply; reported as skipped
|
continue; // out-of-range variant: cannot apply; reported as skipped
|
||||||
|
} else if (static_cast<const ConfigOptionVectorBase*>(src_opt)->size() !=
|
||||||
|
static_cast<const ConfigOptionVectorBase*>(dst_opt)->size()) {
|
||||||
|
// Whole-vector base key: the receiver must have a matching vector size,
|
||||||
|
// otherwise applying would overwrite a different number of elements.
|
||||||
|
continue; // cannot apply; will be reported as skipped
|
||||||
}
|
}
|
||||||
target.apply_only(config, {key}, true);
|
target.apply_only(config, {key}, true);
|
||||||
applied_keys.insert(key);
|
applied_keys.insert(key);
|
||||||
} else {
|
} else {
|
||||||
// A scalar key cannot carry a '#N' variant suffix; a hand-crafted file
|
// A scalar key cannot carry a '#N' suffix; a hand-crafted file listing one
|
||||||
// listing one is reported as skipped instead of being silently marked applied.
|
// is reported as skipped instead of being silently marked applied.
|
||||||
if (key.find('#') != std::string::npos)
|
if (key.find('#') != std::string::npos)
|
||||||
continue;
|
continue;
|
||||||
// Scalar key: apply only if present on the user's machine.
|
// Scalar key: apply only if present on the user's machine.
|
||||||
@@ -4852,229 +4851,87 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
apply_published(this->prints.get_edited_preset().config, nullptr);
|
apply_published(this->prints.get_edited_preset().config, nullptr);
|
||||||
apply_published(this->printers.get_edited_preset().config, &printer_allowlist);
|
apply_published(this->printers.get_edited_preset().config, &printer_allowlist);
|
||||||
|
|
||||||
// Material pass: apply the author's material-qualified keys onto the receiver's
|
// Material pass: positional per-slot entries. The author published, per slot, either the
|
||||||
// matching filament presets. The file config carries the author's per-slot identity
|
// entire filament (full) or specific keys plus optionally a curated type and/or colour.
|
||||||
// (filament_type / filament_vendor remain in config; filament_ids was moved into a
|
// The receiver's slot is matched positionally against the published type:
|
||||||
// local earlier) and the per-slot material retraction values.
|
|
||||||
if (!published_config->material_keys.empty()) {
|
|
||||||
const ConfigOptionStrings *file_types = config.option<ConfigOptionStrings>("filament_type");
|
|
||||||
const ConfigOptionStrings *file_vendors = config.option<ConfigOptionStrings>("filament_vendor");
|
|
||||||
auto identity_matches = [](const std::string &id, const std::string &type, const std::string &vendor,
|
|
||||||
const std::string &slot_id, const std::string &slot_type, const std::string &slot_vendor) {
|
|
||||||
// When both sides carry a filament_id, equality is required; otherwise fall
|
|
||||||
// back to filament_type, with filament_vendor as an additional qualifier only
|
|
||||||
// when both sides have a non-empty vendor.
|
|
||||||
if (!id.empty() && !slot_id.empty())
|
|
||||||
return id == slot_id;
|
|
||||||
if (type.empty() || type != slot_type)
|
|
||||||
return false;
|
|
||||||
if (!vendor.empty() && !slot_vendor.empty())
|
|
||||||
return vendor == slot_vendor;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
|
||||||
// Entries using the filament-publishing-v2 features (full dump, published type or
|
|
||||||
// colour) are handled by the positional per-slot pass below; the legacy identity
|
|
||||||
// matching here applies only to files that predate those features.
|
|
||||||
if (entry.full || entry.publish_type || entry.publish_color)
|
|
||||||
continue;
|
|
||||||
// Resolve the author's source slot and its ordinal among the author slots
|
|
||||||
// carrying this entry's identity. A slotted entry (slot >= 0) names the exact
|
|
||||||
// author slot and targets the receiver's Nth matching preset (N = ordinal);
|
|
||||||
// a legacy entry (slot -1) uses the first matching author slot and applies to
|
|
||||||
// every matching receiver preset.
|
|
||||||
auto slot_identity = [&filament_ids, file_types, file_vendors](size_t slot, std::string &id, std::string &type, std::string &vendor) {
|
|
||||||
id = (slot < filament_ids.size()) ? filament_ids[slot] : std::string();
|
|
||||||
type = (file_types && slot < file_types->size()) ? file_types->get_at(slot) : std::string();
|
|
||||||
vendor = (file_vendors && slot < file_vendors->size()) ? file_vendors->get_at(slot) : std::string();
|
|
||||||
};
|
|
||||||
bool author_found = false;
|
|
||||||
size_t author_slot = 0;
|
|
||||||
size_t author_ordinal = 0;
|
|
||||||
if (entry.slot >= 0) {
|
|
||||||
// Collect every author slot carrying this identity, in slot order; the
|
|
||||||
// entry's slot must be among them, and its position is the ordinal used
|
|
||||||
// to pick the receiver's matching preset.
|
|
||||||
std::vector<size_t> matching_author_slots;
|
|
||||||
for (size_t slot = 0; slot < filament_ids.size(); ++slot) {
|
|
||||||
std::string slot_id, slot_type, slot_vendor;
|
|
||||||
slot_identity(slot, slot_id, slot_type, slot_vendor);
|
|
||||||
if (identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
|
|
||||||
slot_id, slot_type, slot_vendor))
|
|
||||||
matching_author_slots.emplace_back(slot);
|
|
||||||
}
|
|
||||||
const auto ordinal_it = std::find(matching_author_slots.begin(), matching_author_slots.end(), size_t(entry.slot));
|
|
||||||
if (ordinal_it != matching_author_slots.end()) {
|
|
||||||
author_slot = size_t(entry.slot);
|
|
||||||
author_ordinal = size_t(ordinal_it - matching_author_slots.begin());
|
|
||||||
author_found = true;
|
|
||||||
}
|
|
||||||
// Out of range, or the slot does not carry this identity: silent skip below.
|
|
||||||
} else {
|
|
||||||
// Legacy: the first author slot whose identity matches.
|
|
||||||
for (size_t slot = 0; slot < filament_ids.size(); ++slot) {
|
|
||||||
std::string slot_id, slot_type, slot_vendor;
|
|
||||||
slot_identity(slot, slot_id, slot_type, slot_vendor);
|
|
||||||
if (identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
|
|
||||||
slot_id, slot_type, slot_vendor)) {
|
|
||||||
author_slot = slot;
|
|
||||||
author_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!author_found)
|
|
||||||
// No author slot carries this material: nothing to apply, nothing to report.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const std::string material_label = entry.filament_id.empty() ? entry.filament_type : entry.filament_id;
|
|
||||||
auto report_skipped = [&skipped_keys, &material_label](const std::string &key, const std::string &slot_qualifier = std::string()) {
|
|
||||||
skipped_keys.emplace_back("material:" + material_label +
|
|
||||||
(slot_qualifier.empty() ? std::string() : " " + slot_qualifier) +
|
|
||||||
" (" + key + ")");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collect the receiver's matching filament presets (distinct by preset name).
|
|
||||||
std::vector<std::string> matched_preset_names;
|
|
||||||
std::set<std::string> fallback_matched_names;
|
|
||||||
for (const std::string &preset_name : this->filament_presets) {
|
|
||||||
Preset *preset = this->filaments.find_preset(preset_name);
|
|
||||||
if (preset == nullptr)
|
|
||||||
continue;
|
|
||||||
const std::string slot_id = preset->filament_id;
|
|
||||||
// Null-guard the identity reads: a malformed user preset may lack
|
|
||||||
// filament_type / filament_vendor entirely (hand-edited preset file).
|
|
||||||
const ConfigOptionStrings *slot_types = preset->config.option<ConfigOptionStrings>("filament_type");
|
|
||||||
const ConfigOptionStrings *slot_vendors = preset->config.option<ConfigOptionStrings>("filament_vendor");
|
|
||||||
const std::string slot_type = (slot_types && !slot_types->values.empty()) ? slot_types->get_at(0) : std::string();
|
|
||||||
const std::string slot_vendor = (slot_vendors && !slot_vendors->values.empty()) ? slot_vendors->get_at(0) : std::string();
|
|
||||||
if (!identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
|
|
||||||
slot_id, slot_type, slot_vendor))
|
|
||||||
continue;
|
|
||||||
if (std::find(matched_preset_names.begin(), matched_preset_names.end(), preset_name) == matched_preset_names.end())
|
|
||||||
matched_preset_names.emplace_back(preset_name);
|
|
||||||
if (entry.filament_id.empty() || slot_id.empty())
|
|
||||||
fallback_matched_names.insert(preset_name);
|
|
||||||
}
|
|
||||||
if (matched_preset_names.empty()) {
|
|
||||||
// No receiver material matches this entry: report each key as skipped.
|
|
||||||
for (const std::string &key : entry.keys)
|
|
||||||
report_skipped(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (fallback_matched_names.size() > 1) {
|
|
||||||
// The type fallback matched more than one distinct receiver preset: never
|
|
||||||
// guess which one the author meant.
|
|
||||||
for (const std::string &key : entry.keys)
|
|
||||||
report_skipped(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slotted entries target the receiver's matching preset at the author's
|
|
||||||
// ordinal; legacy entries apply to every matching receiver preset.
|
|
||||||
std::vector<std::string> apply_to_preset_names;
|
|
||||||
if (entry.slot >= 0) {
|
|
||||||
if (author_ordinal >= matched_preset_names.size()) {
|
|
||||||
// The receiver has fewer matching presets than the author's ordinal:
|
|
||||||
// this slot's values cannot be placed, report each key.
|
|
||||||
const std::string slot_qualifier = "slot " + std::to_string(entry.slot);
|
|
||||||
for (const std::string &key : entry.keys)
|
|
||||||
report_skipped(key, slot_qualifier);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
apply_to_preset_names.emplace_back(matched_preset_names[author_ordinal]);
|
|
||||||
} else {
|
|
||||||
apply_to_preset_names = matched_preset_names;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string &key : entry.keys) {
|
|
||||||
const std::string base_key = key.substr(0, key.find('#'));
|
|
||||||
if (structural_keys.count(base_key) != 0)
|
|
||||||
continue; // structural: silent
|
|
||||||
const ConfigOption *src_opt = config.option(base_key);
|
|
||||||
if (src_opt == nullptr || !src_opt->is_vector() ||
|
|
||||||
author_slot >= static_cast<const ConfigOptionVectorBase*>(src_opt)->size()) {
|
|
||||||
report_skipped(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const std::string &preset_name : apply_to_preset_names) {
|
|
||||||
Preset *preset = this->filaments.find_preset(preset_name);
|
|
||||||
if (preset == nullptr)
|
|
||||||
continue;
|
|
||||||
ConfigOption *dst_opt = preset->config.option(base_key);
|
|
||||||
// Per-slot scalar copy: the receiver's filament preset holds a single
|
|
||||||
// value per key (vector of size 1), the file holds the per-slot vector.
|
|
||||||
if (dst_opt == nullptr || !dst_opt->is_vector() ||
|
|
||||||
static_cast<const ConfigOptionVectorBase*>(dst_opt)->empty() ||
|
|
||||||
dst_opt->type() != src_opt->type()) {
|
|
||||||
report_skipped(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
static_cast<ConfigOptionVectorBase*>(dst_opt)->set_at(src_opt, 0, author_slot);
|
|
||||||
material_applied = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filament-publishing-v2: positional per-slot entries. The author published, per slot,
|
|
||||||
// either the entire filament (full) or specific keys plus optionally a curated type
|
|
||||||
// and/or colour. The receiver's slot is matched positionally against the published type:
|
|
||||||
// - colour: always applied to the slot, independent of the type gate;
|
// - colour: always applied to the slot, independent of the type gate;
|
||||||
// - type match: a full dump is intentionally ignored (the receiver keeps its material),
|
// - type match: the full dump still applies wholesale (every setting, as if the slot's
|
||||||
// a partial entry's keys are applied as usual;
|
// filament had been loaded from a normal save); a partial entry's keys are applied
|
||||||
// - type mismatch: the slot is replaced with the first visible same-type filament from
|
// as usual;
|
||||||
// the receiver's library; the author's values are applied on top of it (full) or the
|
// - type mismatch: the slot is replaced with the best visible candidate, scored by the
|
||||||
// published keys are applied (partial);
|
// published identity (exact filament_id, then vendor+type, then type only); a
|
||||||
// - no replacement available: a full entry falls back to applying the author's values
|
// preset no other slot references wins on equal scores, and a shared exact-material
|
||||||
// in-memory onto the receiver's current preset (no library import); a partial entry
|
// preset is taken even though mutating it also affects the other slot; the author's
|
||||||
// keeps the receiver's material and reports its keys as skipped.
|
// values are applied on top of it (full) or the published keys are applied (partial);
|
||||||
|
// - no replacement available: a full entry falls back to the first available visible
|
||||||
|
// preset, applying the author's values on top of it; a partial entry keeps the
|
||||||
|
// receiver's material and reports its keys as skipped.
|
||||||
|
// All applied values (colour and keys) are written onto the slot's stored preset
|
||||||
|
// directly (mutate in place): the receiver's material keeps its identity and is simply
|
||||||
|
// overridden. To keep slot-to-slot aliasing (several slots referencing one preset) from
|
||||||
|
// leaking one slot's values into another, published slots sharing a preset with another
|
||||||
|
// slot are re-pointed at distinct presets before the values are applied.
|
||||||
{
|
{
|
||||||
// Slot growth is tied to the author slots that carry published content (full,
|
// Grow the receiver's slots only as far as the highest published slot (never
|
||||||
// type or colour): the file's total filament count is irrelevant, and a slot the
|
// shrink, never pull filler materials for unpublished slots).
|
||||||
// author left unpublished must not pull a filler material into the receiver's
|
bool has_published_entries = false;
|
||||||
// setup. Grow only as far as the highest published slot (never shrink, never
|
|
||||||
// remove the receiver's existing materials).
|
|
||||||
bool has_new_semantics = false;
|
|
||||||
size_t target_slots = this->filament_presets.size();
|
size_t target_slots = this->filament_presets.size();
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
||||||
if (!entry.full && !entry.publish_type && !entry.publish_color)
|
has_published_entries = true;
|
||||||
continue; // legacy entry, handled above
|
|
||||||
has_new_semantics = true;
|
|
||||||
if (entry.slot >= 0)
|
if (entry.slot >= 0)
|
||||||
target_slots = std::max(target_slots, size_t(entry.slot) + 1);
|
target_slots = std::max(target_slots, size_t(entry.slot) + 1);
|
||||||
}
|
}
|
||||||
if (has_new_semantics) {
|
if (has_published_entries) {
|
||||||
// Defensive cap: never exceed the file's own filament count.
|
// Defensive cap: never exceed the file's own filament count.
|
||||||
target_slots = std::min(target_slots, num_filaments);
|
target_slots = std::min(target_slots, num_filaments);
|
||||||
// Slots that carry published content (full/type/colour) must reference a stored
|
// Slots carrying published content, steering the initial preset selection of
|
||||||
// preset that no other slot shares: the overlay mutates stored presets in place
|
// newly grown slots.
|
||||||
// (colour and keys), so a shared preset would leak one slot's published values
|
|
||||||
// into every slot that references it.
|
|
||||||
std::set<int> published_slots;
|
std::set<int> published_slots;
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys)
|
for (const PublishedMaterialEntry &entry : published_config->material_keys)
|
||||||
if ((entry.full || entry.publish_type || entry.publish_color) && entry.slot >= 0)
|
if (entry.slot >= 0)
|
||||||
published_slots.insert(entry.slot);
|
published_slots.insert(entry.slot);
|
||||||
std::set<std::string> used_preset_names(this->filament_presets.begin(), this->filament_presets.end());
|
std::set<std::string> used_preset_names(this->filament_presets.begin(), this->filament_presets.end());
|
||||||
// Mirror first_visible_idx()'s start index so suppressed default presets are
|
// Mirror first_visible_idx()'s start index so suppressed default presets are
|
||||||
// never picked as a slot material.
|
// never picked as a slot material.
|
||||||
const size_t first_candidate = this->filaments.is_default_suppressed() ? this->filaments.num_default_presets() : 0;
|
const size_t first_candidate = this->filaments.is_default_suppressed() ? this->filaments.num_default_presets() : 0;
|
||||||
|
// Candidate preference for a published entry: exact setting_id (variant-level,
|
||||||
|
// since "Generic PLA" and "Generic PLA Matte" share filament_id), then exact
|
||||||
|
// filament_id, then vendor+type, then type only (a type-only pick may surface an
|
||||||
|
// unrelated preset, e.g. a different vendor's PLA).
|
||||||
|
auto candidate_score = [](const Preset &candidate, const PublishedMaterialEntry &entry) -> int {
|
||||||
|
if (!entry.setting_id.empty() && candidate.setting_id == entry.setting_id)
|
||||||
|
return 3;
|
||||||
|
const ConfigOptionStrings *types = candidate.config.opt<ConfigOptionStrings>("filament_type");
|
||||||
|
const ConfigOptionStrings *vendors = candidate.config.opt<ConfigOptionStrings>("filament_vendor");
|
||||||
|
const std::string type = (types != nullptr && !types->values.empty()) ? types->get_at(0) : std::string();
|
||||||
|
const std::string vendor = (vendors != nullptr && !vendors->values.empty()) ? vendors->get_at(0) : std::string();
|
||||||
|
if (!entry.filament_id.empty() && candidate.filament_id == entry.filament_id)
|
||||||
|
return 2;
|
||||||
|
if (normalize_filament_type(type) == entry.publish_type_value) {
|
||||||
|
if (!entry.filament_vendor.empty() && vendor == entry.filament_vendor)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
while (this->filament_presets.size() < target_slots) {
|
while (this->filament_presets.size() < target_slots) {
|
||||||
const size_t new_slot_idx = this->filament_presets.size();
|
const size_t new_slot_idx = this->filament_presets.size();
|
||||||
std::string initial_preset;
|
std::string initial_preset;
|
||||||
if (published_slots.count(static_cast<int>(new_slot_idx)) != 0) {
|
if (published_slots.count(static_cast<int>(new_slot_idx)) != 0) {
|
||||||
// Proactively assign a distinct matching candidate preset if this slot
|
// Prefer the best distinct candidate for the slot's published material
|
||||||
// carries a published type...
|
// (exact id, then vendor+type, then type only)...
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
||||||
if (entry.slot != static_cast<int>(new_slot_idx) || !entry.publish_type || entry.publish_type_value.empty())
|
if (entry.slot != static_cast<int>(new_slot_idx) || !entry.publish_type || entry.publish_type_value.empty())
|
||||||
continue;
|
continue;
|
||||||
for (size_t i = 0; i < this->filaments.size(); ++i) {
|
int best_score = -1;
|
||||||
|
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||||
const Preset &candidate = this->filaments.preset(i);
|
const Preset &candidate = this->filaments.preset(i);
|
||||||
if (!candidate.is_visible || used_preset_names.count(candidate.name) != 0)
|
if (!candidate.is_visible || used_preset_names.count(candidate.name) != 0)
|
||||||
continue;
|
continue;
|
||||||
if (normalize_filament_type(candidate.config.opt_string("filament_type", 0u)) == entry.publish_type_value) {
|
const int score = candidate_score(candidate, entry);
|
||||||
|
if (score > best_score) {
|
||||||
|
best_score = score;
|
||||||
initial_preset = candidate.name;
|
initial_preset = candidate.name;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -5091,62 +4948,65 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (initial_preset.empty())
|
if (initial_preset.empty())
|
||||||
// Unpublished filler slot, or every visible preset is already used: repeat
|
// Unpublished filler slot, or every visible preset is already used:
|
||||||
// the receiver's last preset, mirroring the "Add one filament" behaviour
|
// repeat the receiver's last preset ("Add one filament" behaviour).
|
||||||
// (PresetBundle::set_num_filaments).
|
|
||||||
initial_preset = this->filament_presets.empty() ? this->filaments.first_visible().name
|
initial_preset = this->filament_presets.empty() ? this->filaments.first_visible().name
|
||||||
: this->filament_presets.back();
|
: this->filament_presets.back();
|
||||||
this->filament_presets.emplace_back(initial_preset);
|
this->filament_presets.emplace_back(initial_preset);
|
||||||
used_preset_names.insert(initial_preset);
|
used_preset_names.insert(initial_preset);
|
||||||
}
|
}
|
||||||
// Slots that were grown before this block (e.g. by update_multi_material_filament_presets
|
// Published slots that alias another slot (multi-extruder with one filament)
|
||||||
// matching the extruder count) may still alias another slot; re-point them at a
|
// get re-pointed at distinct presets: the overlay mutates stored presets in
|
||||||
// distinct preset. Slot 0, the receiver's own material, is never re-assigned.
|
// place, so a shared preset would leak one slot's published values into every
|
||||||
|
// aliased slot. Slot 0 (the receiver's own material) is never re-assigned;
|
||||||
|
// when no unused candidate exists the aliasing stays (unavoidable).
|
||||||
|
auto referenced_elsewhere = [&](const std::string &preset_name, size_t except_slot) {
|
||||||
|
for (size_t s = 0; s < this->filament_presets.size(); ++s)
|
||||||
|
if (s != except_slot && this->filament_presets[s] == preset_name)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
for (size_t slot = 1; slot < this->filament_presets.size(); ++slot) {
|
for (size_t slot = 1; slot < this->filament_presets.size(); ++slot) {
|
||||||
if (published_slots.count(static_cast<int>(slot)) == 0)
|
if (published_slots.count(static_cast<int>(slot)) == 0 ||
|
||||||
continue;
|
!referenced_elsewhere(this->filament_presets[slot], slot))
|
||||||
bool shared = false;
|
|
||||||
for (size_t other = 0; other < this->filament_presets.size(); ++other)
|
|
||||||
if (other != slot && this->filament_presets[other] == this->filament_presets[slot]) {
|
|
||||||
shared = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!shared)
|
|
||||||
continue;
|
continue;
|
||||||
|
// Prefer the best distinct candidate for the slot's published material
|
||||||
|
// (exact id, then vendor+type, then type only)...
|
||||||
std::string replacement;
|
std::string replacement;
|
||||||
|
int best_score = -1;
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
||||||
if (entry.slot != static_cast<int>(slot) || !entry.publish_type || entry.publish_type_value.empty())
|
if (entry.slot != static_cast<int>(slot) || !entry.publish_type || entry.publish_type_value.empty())
|
||||||
continue;
|
continue;
|
||||||
for (size_t i = 0; i < this->filaments.size(); ++i) {
|
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||||
const Preset &candidate = this->filaments.preset(i);
|
const Preset &candidate = this->filaments.preset(i);
|
||||||
if (!candidate.is_visible || used_preset_names.count(candidate.name) != 0)
|
if (!candidate.is_visible || referenced_elsewhere(candidate.name, size_t(-1)))
|
||||||
continue;
|
continue;
|
||||||
if (normalize_filament_type(candidate.config.opt_string("filament_type", 0u)) == entry.publish_type_value) {
|
const int score = candidate_score(candidate, entry);
|
||||||
|
if (score > best_score) {
|
||||||
|
best_score = score;
|
||||||
replacement = candidate.name;
|
replacement = candidate.name;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// ...otherwise any distinct visible preset not referenced by another slot.
|
||||||
if (replacement.empty()) {
|
if (replacement.empty()) {
|
||||||
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||||
const Preset &candidate = this->filaments.preset(i);
|
const Preset &candidate = this->filaments.preset(i);
|
||||||
if (!candidate.is_visible || used_preset_names.count(candidate.name) != 0)
|
if (candidate.is_visible && !referenced_elsewhere(candidate.name, size_t(-1))) {
|
||||||
continue;
|
|
||||||
replacement = candidate.name;
|
replacement = candidate.name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (replacement.empty())
|
|
||||||
continue; // every visible preset is used: aliasing is unavoidable
|
|
||||||
used_preset_names.erase(this->filament_presets[slot]);
|
|
||||||
this->filament_presets[slot] = replacement;
|
|
||||||
used_preset_names.insert(replacement);
|
|
||||||
}
|
}
|
||||||
// Mirror set_num_filaments' project_config vector handling ("Add one filament"):
|
if (replacement.empty())
|
||||||
// resize the per-slot colour/type/map vectors to the grown slot count and seed the
|
continue; // every visible preset is referenced: aliasing is unavoidable
|
||||||
// new entries so the slots render with colours instead of blank chips. Only the
|
this->filament_presets[slot] = replacement;
|
||||||
// new entries are seeded; the receiver's existing values are left untouched.
|
material_applied = true;
|
||||||
|
}
|
||||||
|
// Grow the per-slot colour/type/map project vectors to the new slot count and
|
||||||
|
// seed the new entries so the slots render with colours instead of blank chips
|
||||||
|
// (mirrors set_num_filaments; existing values are left untouched).
|
||||||
ConfigOptionStrings *proj_colour = this->project_config.opt<ConfigOptionStrings>("filament_colour");
|
ConfigOptionStrings *proj_colour = this->project_config.opt<ConfigOptionStrings>("filament_colour");
|
||||||
ConfigOptionStrings *proj_multi_colour = this->project_config.opt<ConfigOptionStrings>("filament_multi_colour");
|
ConfigOptionStrings *proj_multi_colour = this->project_config.opt<ConfigOptionStrings>("filament_multi_colour");
|
||||||
ConfigOptionStrings *proj_colour_type = this->project_config.opt<ConfigOptionStrings>("filament_colour_type");
|
ConfigOptionStrings *proj_colour_type = this->project_config.opt<ConfigOptionStrings>("filament_colour_type");
|
||||||
@@ -5184,8 +5044,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
if (proj_colour_type && slot < proj_colour_type->values.size())
|
if (proj_colour_type && slot < proj_colour_type->values.size())
|
||||||
proj_colour_type->values[slot] = "1"; // default colour type
|
proj_colour_type->values[slot] = "1"; // default colour type
|
||||||
}
|
}
|
||||||
// Rebuild the flush volumes for the grown slot count (set_num_filaments does the
|
// Rebuild the flush volumes for the grown slot count (as set_num_filaments does).
|
||||||
// same; without it the matrix would stay at the receiver's old size).
|
|
||||||
this->update_multi_material_filament_presets();
|
this->update_multi_material_filament_presets();
|
||||||
|
|
||||||
auto apply_slot_keys = [&](Preset &preset, const std::vector<std::string> &slot_keys, int author_slot,
|
auto apply_slot_keys = [&](Preset &preset, const std::vector<std::string> &slot_keys, int author_slot,
|
||||||
@@ -5207,23 +5066,22 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
skipped_keys.emplace_back("material:" + material_label + " (" + key + ")");
|
skipped_keys.emplace_back("material:" + material_label + " (" + key + ")");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Per-slot scalar copy: the receiver's filament preset holds a single
|
// Per-slot scalar copy: the receiver preset holds one value per key
|
||||||
// value per key (vector of size 1), the file holds the per-slot vector.
|
// (vector of size 1), the file holds the per-slot vector.
|
||||||
static_cast<ConfigOptionVectorBase*>(dst_opt)->set_at(src_opt, 0, author_slot);
|
static_cast<ConfigOptionVectorBase*>(dst_opt)->set_at(src_opt, 0, author_slot);
|
||||||
material_applied = true;
|
material_applied = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The slot's values are applied directly onto the slot's stored preset (mutate
|
||||||
|
// in place); the per-entry type gate below may re-point the slot first.
|
||||||
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
|
||||||
if (!entry.full && !entry.publish_type && !entry.publish_color)
|
|
||||||
continue; // legacy entry, handled above
|
|
||||||
if (entry.slot < 0 || size_t(entry.slot) >= this->filament_presets.size())
|
if (entry.slot < 0 || size_t(entry.slot) >= this->filament_presets.size())
|
||||||
continue; // out of range: nothing to do for this slot
|
continue; // out of range: nothing to do for this slot
|
||||||
const size_t slot = size_t(entry.slot);
|
const size_t slot = size_t(entry.slot);
|
||||||
// Modify the stored preset itself (real=true), never the edited snapshot:
|
// Resolve the stored preset itself (real=true), never the edited snapshot:
|
||||||
// find_preset would return &m_edited_preset for the currently selected slot,
|
// find_preset would return &m_edited_preset for the selected slot, and the
|
||||||
// and the re-select at the end of this block re-snapshots from the stored
|
// re-select at the end re-snapshots from the stored preset.
|
||||||
// preset, silently discarding any values applied to the snapshot.
|
|
||||||
Preset *recv = this->filaments.find_preset(this->filament_presets[slot], false, true);
|
Preset *recv = this->filaments.find_preset(this->filament_presets[slot], false, true);
|
||||||
if (recv == nullptr)
|
if (recv == nullptr)
|
||||||
continue;
|
continue;
|
||||||
@@ -5234,83 +5092,96 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
|
|
||||||
bool apply_slot = true;
|
bool apply_slot = true;
|
||||||
if (entry.publish_type && !entry.publish_type_value.empty()) {
|
if (entry.publish_type && !entry.publish_type_value.empty()) {
|
||||||
const std::string recv_type = normalize_filament_type(recv->config.opt_string("filament_type", 0u));
|
// Null-guard: a malformed receiver preset may lack filament_type.
|
||||||
if (recv_type == entry.publish_type_value) {
|
const ConfigOptionStrings *recv_types = recv->config.opt<ConfigOptionStrings>("filament_type");
|
||||||
// Type match: the receiver keeps its material. A full dump is
|
const std::string recv_type = (recv_types != nullptr && !recv_types->values.empty()) ? recv_types->get_at(0) : std::string();
|
||||||
// intentionally ignored for this slot; partial keys still apply.
|
if (normalize_filament_type(recv_type) != entry.publish_type_value) {
|
||||||
if (entry.full)
|
// Type mismatch: replace the slot with the best matching preset,
|
||||||
apply_slot = false;
|
// scored by the published identity (exact filament_id, then
|
||||||
} else {
|
// vendor+type, then type only). A preset no other slot references
|
||||||
// Type mismatch: replace the slot with the first visible same-type
|
// wins on equal scores; a shared exact-material preset is taken even
|
||||||
// filament from the receiver's library, preferring one that no other
|
// though mutating it also affects the other slot.
|
||||||
// slot references (a shared stored preset would leak this slot's
|
auto find_best = [&](bool unreferenced_only) -> std::pair<int, std::string> {
|
||||||
// published values into that slot).
|
int best_score = -1;
|
||||||
std::string replacement, first_same_type;
|
std::string best_name;
|
||||||
for (size_t i = 0; i < this->filaments.size(); ++i) {
|
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||||
const Preset &candidate = this->filaments.preset(i);
|
const Preset &candidate = this->filaments.preset(i);
|
||||||
if (!candidate.is_visible)
|
if (!candidate.is_visible)
|
||||||
continue;
|
continue;
|
||||||
if (normalize_filament_type(candidate.config.opt_string("filament_type", 0u)) != entry.publish_type_value)
|
const int score = candidate_score(candidate, entry);
|
||||||
|
if (score <= best_score)
|
||||||
continue;
|
continue;
|
||||||
if (first_same_type.empty())
|
if (unreferenced_only) {
|
||||||
first_same_type = candidate.name;
|
bool used = false;
|
||||||
bool used_elsewhere = false;
|
|
||||||
for (size_t s = 0; s < this->filament_presets.size(); ++s)
|
for (size_t s = 0; s < this->filament_presets.size(); ++s)
|
||||||
if (s != slot && this->filament_presets[s] == candidate.name) {
|
if (s != slot && this->filament_presets[s] == candidate.name) {
|
||||||
used_elsewhere = true;
|
used = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!used_elsewhere) {
|
if (used)
|
||||||
replacement = candidate.name;
|
continue;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
best_score = score;
|
||||||
|
best_name = candidate.name;
|
||||||
|
}
|
||||||
|
return { best_score, best_name };
|
||||||
|
};
|
||||||
|
const auto [strict_score, strict_name] = find_best(true);
|
||||||
|
const auto [relaxed_score, relaxed_name] = find_best(false);
|
||||||
|
int score = strict_score;
|
||||||
|
std::string replacement = strict_name;
|
||||||
|
if (relaxed_score > strict_score) {
|
||||||
|
score = relaxed_score;
|
||||||
|
replacement = relaxed_name;
|
||||||
}
|
}
|
||||||
if (replacement.empty())
|
|
||||||
replacement = first_same_type;
|
|
||||||
if (!replacement.empty()) {
|
if (!replacement.empty()) {
|
||||||
const std::string old_name = recv->name;
|
const std::string old_name = recv->name;
|
||||||
this->filament_presets[slot] = replacement;
|
this->filament_presets[slot] = replacement;
|
||||||
recv = this->filaments.find_preset(replacement, false, true);
|
recv = this->filaments.find_preset(replacement, false, true);
|
||||||
material_applied = true;
|
material_applied = true;
|
||||||
published_config->material_replacements.emplace_back(
|
std::string replacement_line = "slot " + std::to_string(slot) + ": " + old_name + " -> " + replacement;
|
||||||
"slot " + std::to_string(slot) + ": " + old_name + " -> " + replacement);
|
// A pick that is not the exact published material is a substitute;
|
||||||
|
// an entry without identity fields cannot be judged, so it stays plain.
|
||||||
|
if (score < 2 && (!entry.filament_id.empty() || !entry.filament_vendor.empty()))
|
||||||
|
replacement_line += " (substitute: no exact material match)";
|
||||||
|
published_config->material_replacements.emplace_back(std::move(replacement_line));
|
||||||
} else if (entry.full) {
|
} else if (entry.full) {
|
||||||
// No library match: create a temporary project-embedded custom preset
|
// No same-type library preset: fall back to the first available
|
||||||
// populated with default settings and overlaid with the author's values.
|
// visible preset, preferring one no other slot references, and
|
||||||
std::string custom_name = entry.publish_type_value + " (Published)";
|
// apply the author's full values on top of it (the dump carries
|
||||||
for (size_t idx = 1; this->filaments.find_preset(custom_name, false) != nullptr; ++idx)
|
// filament_type, so the preset takes the author's type).
|
||||||
custom_name = entry.publish_type_value + " (Published " + std::to_string(idx) + ")";
|
std::string fallback;
|
||||||
|
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||||
// Capture the slot's current name BEFORE load_preset: the custom
|
const Preset &candidate = this->filaments.preset(i);
|
||||||
// name sorts ahead of the slot's material, so the deque insertion
|
if (!candidate.is_visible)
|
||||||
// relocates it and recv would dangle after the call.
|
continue;
|
||||||
const std::string old_name = recv->name;
|
if (fallback.empty())
|
||||||
|
fallback = candidate.name;
|
||||||
DynamicPrintConfig custom_cfg = this->filaments.default_preset_for(config).config;
|
bool referenced = false;
|
||||||
// filament_type is a per-slot vector option: set it via the strings
|
for (size_t s = 0; s < this->filament_presets.size(); ++s)
|
||||||
// accessor. opt_string(key, bool) would ask for the scalar
|
if (this->filament_presets[s] == candidate.name) {
|
||||||
// ConfigOptionString, fail the cast and dereference nullptr.
|
referenced = true;
|
||||||
if (ConfigOptionStrings *type_opt = custom_cfg.opt<ConfigOptionStrings>("filament_type", true)) {
|
break;
|
||||||
if (type_opt->values.empty())
|
|
||||||
type_opt->values.emplace_back();
|
|
||||||
type_opt->values[0] = entry.publish_type_value;
|
|
||||||
}
|
}
|
||||||
if (ConfigOptionStrings *id_opt = custom_cfg.opt<ConfigOptionStrings>("filament_settings_id", true))
|
if (!referenced) {
|
||||||
if (!id_opt->values.empty())
|
fallback = candidate.name;
|
||||||
id_opt->values[0] = custom_name;
|
break;
|
||||||
|
}
|
||||||
Preset &created = this->filaments.load_preset("", custom_name, std::move(custom_cfg), false, file_version);
|
}
|
||||||
created.is_project_embedded = true;
|
if (!fallback.empty() && fallback != recv->name) {
|
||||||
created.is_visible = true;
|
const std::string old_name = recv->name;
|
||||||
|
this->filament_presets[slot] = fallback;
|
||||||
this->filament_presets[slot] = custom_name;
|
recv = this->filaments.find_preset(fallback, false, true);
|
||||||
recv = &created;
|
|
||||||
material_applied = true;
|
material_applied = true;
|
||||||
published_config->material_replacements.emplace_back(
|
published_config->material_replacements.emplace_back(
|
||||||
"slot " + std::to_string(slot) + ": " + old_name + " -> " + custom_name);
|
"slot " + std::to_string(slot) + ": " + old_name + " -> " + fallback +
|
||||||
|
" (substitute: no " + entry.publish_type_value + " available)");
|
||||||
|
}
|
||||||
|
// No visible preset at all: keep the receiver's material and let
|
||||||
|
// the full dump mutate it below.
|
||||||
} else {
|
} else {
|
||||||
// Partial publish with no replacement available: keep the
|
// Partial publish with no replacement: keep the receiver's
|
||||||
// receiver's material and report this slot's keys as skipped.
|
// material and report the slot's keys as skipped.
|
||||||
for (const std::string &key : entry.keys)
|
for (const std::string &key : entry.keys)
|
||||||
skipped_keys.emplace_back("material:" + material_label + " (" + key + ")");
|
skipped_keys.emplace_back("material:" + material_label + " (" + key + ")");
|
||||||
apply_slot = false;
|
apply_slot = false;
|
||||||
@@ -5318,14 +5189,15 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colour is slot-scoped and independent of the type gate: it is applied to
|
// The values below are applied onto whatever stored preset the slot ended up
|
||||||
// whichever material ends up in the slot (original, replacement or the
|
// on (original, type replacement or the full-publish fallback), in place.
|
||||||
// in-memory fallback), and synced into project_config for GUI rendering.
|
|
||||||
|
// Colour is slot-scoped and independent of the type gate; it is also synced
|
||||||
|
// into project_config for GUI rendering.
|
||||||
if (entry.publish_color && !entry.color.empty()) {
|
if (entry.publish_color && !entry.color.empty()) {
|
||||||
if (recv != nullptr) {
|
if (recv != nullptr) {
|
||||||
// Create the key when the target preset lacks it (e.g. a replacement
|
// Create the key when the target preset lacks it: the colour is a
|
||||||
// built from the static defaults): the colour is a requirement, not
|
// requirement, not an override.
|
||||||
// an optional override.
|
|
||||||
if (ConfigOptionStrings *colour = recv->config.opt<ConfigOptionStrings>("filament_colour", true)) {
|
if (ConfigOptionStrings *colour = recv->config.opt<ConfigOptionStrings>("filament_colour", true)) {
|
||||||
if (colour->values.empty())
|
if (colour->values.empty())
|
||||||
colour->values.emplace_back();
|
colour->values.emplace_back();
|
||||||
@@ -5364,11 +5236,9 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
}
|
}
|
||||||
published_config->skipped_keys = std::move(skipped_keys);
|
published_config->skipped_keys = std::move(skipped_keys);
|
||||||
|
|
||||||
// The material overlay above modified the filament collection presets in place, but
|
// The material overlay mutates the collection presets in place, but the edited preset
|
||||||
// the edited preset (what the GUI displays) is a snapshot taken when the preset was
|
// (what the GUI displays) is a snapshot taken when the preset was last selected.
|
||||||
// last selected. Re-select the first slot's filament (mirroring a normal project load)
|
// Re-select the first slot's filament so the applied values surface in the GUI.
|
||||||
// so the applied values (colour, type, keys and slot replacements) surface in the GUI;
|
|
||||||
// selecting any other slot's filament afterwards snapshots its modified preset too.
|
|
||||||
if (material_applied && !this->filament_presets.empty())
|
if (material_applied && !this->filament_presets.empty())
|
||||||
this->filaments.select_preset_by_name(this->filament_presets.front(), true);
|
this->filaments.select_preset_by_name(this->filament_presets.front(), true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,22 +167,21 @@ struct PresetBundleMetadata
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configuration describing a "published" 3MF project: the file carries a flag plus a list of
|
// A "published" 3MF project: keeps the user's currently-selected presets and overlays only the
|
||||||
// author-selected setting keys. When loading such a project the user's currently-selected
|
// author-selected published keys onto the edited presets.
|
||||||
// presets are kept and only the published keys are overlaid onto the edited presets.
|
|
||||||
struct PublishedConfig
|
struct PublishedConfig
|
||||||
{
|
{
|
||||||
bool published = false;
|
bool published = false;
|
||||||
std::vector<std::string> published_keys;
|
std::vector<std::string> published_keys;
|
||||||
// Material-qualified published keys chosen by the author for the materials used in the
|
// Per-slot published material keys, applied positionally (author slot N -> receiver slot N),
|
||||||
// project; applied on load only to the receiver's filament presets whose material
|
// gated by the author's optional type requirement and written onto the slot's stored preset
|
||||||
// identity matches (see PublishedMaterialEntry in PublishSettings.hpp).
|
// in place (see PublishedMaterialEntry in PublishSettings.hpp).
|
||||||
std::vector<PublishedMaterialEntry> material_keys;
|
std::vector<PublishedMaterialEntry> material_keys;
|
||||||
// Keys that could not be applied (missing on the user's machine or vector size mismatch),
|
// Keys that could not be applied (missing on the user's machine or vector size mismatch),
|
||||||
// filled in by load_config_file_config for notification purposes.
|
// filled in by load_config_file_config for notification purposes.
|
||||||
std::vector<std::string> skipped_keys;
|
std::vector<std::string> skipped_keys;
|
||||||
// Human-readable notices of the slot material replacements performed while loading a
|
// Human-readable notices of the slot material replacements performed while loading a
|
||||||
// published project (e.g. "Slot 2: replaced PETG with PLA"), for the load notification.
|
// published project, for the load notification.
|
||||||
std::vector<std::string> material_replacements;
|
std::vector<std::string> material_replacements;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,9 @@ std::string normalize_filament_type(const std::string& type)
|
|||||||
|
|
||||||
const std::set<std::string>& publish_structural_keys()
|
const std::set<std::string>& publish_structural_keys()
|
||||||
{
|
{
|
||||||
// Structural / non-publishable keys. The *_settings_id keys are also part of
|
// Non-publishable keys: the *_settings_id keys are also in PresetCollection::skipped_in_dirty
|
||||||
// PresetCollection::skipped_in_dirty (Preset.cpp) and are excluded there too.
|
// (Preset.cpp) / stripped from configs (profile_print_params_same); publishing them would
|
||||||
// This mirrors the structural keys stripped from configs in Preset.cpp
|
// rewrite the user's preset inheritance/structure.
|
||||||
// (profile_print_params_same) plus other keys that must never be published
|
|
||||||
// because they would rewrite the user's preset inheritance/structure.
|
|
||||||
static const std::set<std::string> structural_keys = {
|
static const std::set<std::string> structural_keys = {
|
||||||
"printer_settings_id", "filament_settings_id", "print_settings_id",
|
"printer_settings_id", "filament_settings_id", "print_settings_id",
|
||||||
"sla_print_settings_id", "sla_material_settings_id",
|
"sla_print_settings_id", "sla_material_settings_id",
|
||||||
@@ -88,9 +86,8 @@ const std::vector<PublishablePrinterOption>& publishable_printer_z_hop_options()
|
|||||||
|
|
||||||
const std::set<std::string>& publishable_printer_keys()
|
const std::set<std::string>& publishable_printer_keys()
|
||||||
{
|
{
|
||||||
// The union of the printer tab's "Retraction" and "Z-Hop" optgroups. The "Retraction when
|
// Union of the two optgroups; "Retraction when switching material" keys are excluded
|
||||||
// switching material" keys are intentionally excluded: toolchange retraction is
|
// (toolchange retraction is device/profile territory, not a publishable behavior tweak).
|
||||||
// device/profile territory, not a publishable behavior tweak.
|
|
||||||
static const std::set<std::string> printer_keys = [] {
|
static const std::set<std::string> printer_keys = [] {
|
||||||
std::set<std::string> keys;
|
std::set<std::string> keys;
|
||||||
for (const PublishablePrinterOption &opt : publishable_printer_retraction_options())
|
for (const PublishablePrinterOption &opt : publishable_printer_retraction_options())
|
||||||
@@ -113,9 +110,8 @@ std::vector<std::string> collect_dirty_settings_keys(const PresetBundle& bundle)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print and printer presets each track a single edited preset; filaments may span
|
// Union the dirty keys of each collection's edited preset (filaments may span multiple
|
||||||
// multiple slots (multi-material). Union the dirty keys of each collection's edited
|
// slots); feeds only the Publish dialog's pre-check.
|
||||||
// preset; this feeds only the Publish dialog's pre-check.
|
|
||||||
append_dirty(bundle.prints.current_dirty_options(true));
|
append_dirty(bundle.prints.current_dirty_options(true));
|
||||||
append_dirty(bundle.printers.current_dirty_options(true));
|
append_dirty(bundle.printers.current_dirty_options(true));
|
||||||
append_dirty(bundle.filaments.current_dirty_options(true));
|
append_dirty(bundle.filaments.current_dirty_options(true));
|
||||||
@@ -131,12 +127,11 @@ DynamicPrintConfig filter_published_config(
|
|||||||
DynamicPrintConfig filtered;
|
DynamicPrintConfig filtered;
|
||||||
|
|
||||||
std::set<std::string> base_keys_to_include;
|
std::set<std::string> base_keys_to_include;
|
||||||
// Base keys that must never be masked: identity, plate geometry, process/printer keys and
|
// Never masked (whole-vector serialization): identity, plate geometry, process/printer
|
||||||
// partially-published material keys keep today's whole-vector serialization (all slots).
|
// keys and partially-published material keys.
|
||||||
std::set<std::string> mask_exempt_keys;
|
std::set<std::string> mask_exempt_keys;
|
||||||
// For keys carried only by "full" entries: base key -> author slots whose values must
|
// "Full" entries only: base key -> author slots whose values must survive; other slots are
|
||||||
// survive; the other slots are masked to their defaults so a full publish does not leak
|
// masked to their defaults so a full publish does not leak unrelated slot data.
|
||||||
// the author's unrelated slot data.
|
|
||||||
std::map<std::string, std::set<int>> full_slot_map;
|
std::map<std::string, std::set<int>> full_slot_map;
|
||||||
|
|
||||||
// 1. Mandatory material identity & slot count keys for 3MF validation/normalization
|
// 1. Mandatory material identity & slot count keys for 3MF validation/normalization
|
||||||
@@ -183,8 +178,7 @@ DynamicPrintConfig filter_published_config(
|
|||||||
mask_exempt_keys.insert(base_key);
|
mask_exempt_keys.insert(base_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 4b. "Full publish" entries carry the entire slot; the values of the covered keys are
|
// Full-publish keys: mask to the author's slot on export (see the copy loop below).
|
||||||
// masked to the author's slot on export (see the copy loop below).
|
|
||||||
for (const std::string &key : entry.full_keys) {
|
for (const std::string &key : entry.full_keys) {
|
||||||
const std::string base_key = key.substr(0, key.find('#'));
|
const std::string base_key = key.substr(0, key.find('#'));
|
||||||
if (base_key.empty())
|
if (base_key.empty())
|
||||||
@@ -195,9 +189,8 @@ DynamicPrintConfig filter_published_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mask a vector option's slots that are not author-published: copy the option default over
|
// Mask non-published vector slots with the option default; keys without a default stay
|
||||||
// each non-published index. Keys without an option default are left unmasked (the file then
|
// unmasked (whole vector, matching partial-publish behavior).
|
||||||
// carries the whole vector, matching the partial-publish behavior).
|
|
||||||
auto mask_slots = [](ConfigOption &opt, const ConfigOptionDef *def, const std::set<int> &keep_slots) {
|
auto mask_slots = [](ConfigOption &opt, const ConfigOptionDef *def, const std::set<int> &keep_slots) {
|
||||||
auto *vec = dynamic_cast<ConfigOptionVectorBase*>(&opt);
|
auto *vec = dynamic_cast<ConfigOptionVectorBase*>(&opt);
|
||||||
if (vec == nullptr || vec->size() == 0 || def == nullptr || !def->default_value)
|
if (vec == nullptr || vec->size() == 0 || def == nullptr || !def->default_value)
|
||||||
@@ -212,7 +205,7 @@ DynamicPrintConfig filter_published_config(
|
|||||||
vec->set_at(def->default_value.get(), idx, 0);
|
vec->set_at(def->default_value.get(), idx, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Copy selected options from full_config into filtered config
|
// Copy the selected options from full_config into the filtered config.
|
||||||
for (const std::string &key : base_keys_to_include) {
|
for (const std::string &key : base_keys_to_include) {
|
||||||
if (const ConfigOption *opt = full_config.option(key)) {
|
if (const ConfigOption *opt = full_config.option(key)) {
|
||||||
ConfigOption *cloned = opt->clone();
|
ConfigOption *cloned = opt->clone();
|
||||||
|
|||||||
@@ -6,78 +6,63 @@
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
class PresetBundle;
|
class PresetBundle;
|
||||||
|
|
||||||
// Structural / non-publishable setting keys, shared by the Publish dialog and the published-3MF
|
// Structural keys that must never be published (single source of truth for the denylist):
|
||||||
// overlay path in PresetBundle::load_config_file_config. These keys must never be published
|
// publishing them would rewrite the user's preset inheritance/structure.
|
||||||
// because they would rewrite the user's preset inheritance/structure. This is the single
|
|
||||||
// source of truth for the denylist.
|
|
||||||
const std::set<std::string>& publish_structural_keys();
|
const std::set<std::string>& publish_structural_keys();
|
||||||
|
|
||||||
// One option row of the printer tab's "Retraction" / "Z-Hop" optgroups (TabPrinter::build_fff,
|
// One row of the printer tab's "Retraction" / "Z-Hop" optgroups (key + tab icon id), kept
|
||||||
// Tab.cpp). Key and icon id are kept together so the tab can later be migrated onto these
|
// together so the tab can later be migrated onto these lists.
|
||||||
// lists; publishable_printer_keys() is their union, and the published-3MF loader/dialog must
|
|
||||||
// never accept printer keys outside it.
|
|
||||||
struct PublishablePrinterOption {
|
struct PublishablePrinterOption {
|
||||||
const char *key; // config key, e.g. "retraction_length"
|
const char *key; // config key, e.g. "retraction_length"
|
||||||
const char *icon; // tab icon id, e.g. "printer_extruder_retraction#length"
|
const char *icon; // tab icon id, e.g. "printer_extruder_retraction#length"
|
||||||
};
|
};
|
||||||
|
|
||||||
// The printer tab's "Retraction" optgroup options, in tab order.
|
// The printer tab's "Retraction" / "Z-Hop" optgroup options, in tab order.
|
||||||
const std::vector<PublishablePrinterOption>& publishable_printer_retraction_options();
|
const std::vector<PublishablePrinterOption>& publishable_printer_retraction_options();
|
||||||
// The printer tab's "Z-Hop" optgroup options, in tab order.
|
|
||||||
const std::vector<PublishablePrinterOption>& publishable_printer_z_hop_options();
|
const std::vector<PublishablePrinterOption>& publishable_printer_z_hop_options();
|
||||||
|
|
||||||
// Printer-class retraction / z-hop keys that are publishable: the union of
|
// Union of the two optgroup option lists; the published-3MF overlay applies printer keys only
|
||||||
// publishable_printer_retraction_options() and publishable_printer_z_hop_options(). The
|
// when their base key is in this allowlist (anything else is contract-excluded).
|
||||||
// published-3MF overlay applies printer keys only when their base key is in this allowlist;
|
|
||||||
// any other printer-class key in a published file is contract-excluded (never applied, never
|
|
||||||
// reported as skipped).
|
|
||||||
const std::set<std::string>& publishable_printer_keys();
|
const std::set<std::string>& publishable_printer_keys();
|
||||||
|
|
||||||
// Returns the union of setting keys that differ from the base/system preset across the current
|
// Union of setting keys differing from the base/system preset across the current print,
|
||||||
// print, printer and filament presets (feeds the Publish dialog's pre-check).
|
// printer and filament presets (feeds the Publish dialog's pre-check).
|
||||||
std::vector<std::string> collect_dirty_settings_keys(const PresetBundle& bundle);
|
std::vector<std::string> collect_dirty_settings_keys(const PresetBundle& bundle);
|
||||||
|
|
||||||
// A material-qualified set of published setting keys, chosen by the author for one of the
|
// Per-slot published material keys, applied positionally (author slot N -> receiver slot N).
|
||||||
// materials used in the project. The identity fields let the receiver apply the keys only
|
// The identity fields are carried for reference/notification labels only; the type gate
|
||||||
// when a matching material is selected: filament_id is the most precise (stable across
|
// (publish_type) is the author's explicit opt-in for requiring a material type.
|
||||||
// machines/vendors when present, empty for user presets); filament_type + filament_vendor
|
|
||||||
// are the fallback. Keys are base keys (no "#N" variant suffix).
|
|
||||||
struct PublishedMaterialEntry {
|
struct PublishedMaterialEntry {
|
||||||
std::string filament_type; // material family, e.g. "PLA" (may be empty)
|
std::string filament_type; // material family, e.g. "PLA" (may be empty)
|
||||||
std::string filament_vendor; // e.g. "Generic", "Bambu" (may be empty)
|
std::string filament_vendor; // e.g. "Generic", "Bambu" (may be empty)
|
||||||
std::string filament_id; // stable material id, e.g. "GFL99" (may be empty)
|
std::string filament_id; // stable material id, e.g. "GFL99" (may be empty)
|
||||||
// 0-based author filament slot this entry's values came from; -1 = legacy/unspecified
|
// Unique preset id of the author's slot preset (e.g. Orca Filament Library "setting_id");
|
||||||
// (files written before the slot field). Slotted entries apply to the receiver's Nth
|
// used on load to match the exact published variant, which filament_id alone cannot
|
||||||
// matching preset (N = the slot's ordinal among the author's matching slots); legacy
|
// distinguish ("Generic PLA" and "Generic PLA Matte" share their inherited id).
|
||||||
// entries apply to every matching receiver preset.
|
std::string setting_id;
|
||||||
|
// 0-based author filament slot; -1 (hand-crafted files) is skipped.
|
||||||
int slot{-1};
|
int slot{-1};
|
||||||
std::vector<std::string> keys;
|
std::vector<std::string> keys;
|
||||||
// "Full Publish": the entire filament preset of this slot is serialized (see full_keys),
|
// "Full Publish": serialize the whole filament preset (full_keys); the type gate then
|
||||||
// not just the individually selected keys. On load the type gate (publish_type_value)
|
// decides whether the receiver keeps its material (type match) or is replaced.
|
||||||
// decides whether the receiver keeps its material (type match) or is replaced; a full
|
|
||||||
// entry carries no partial keys.
|
|
||||||
bool full{false};
|
bool full{false};
|
||||||
// All non-structural filament keys of the author's slot preset, present when full is true.
|
// All non-structural filament keys of the author's slot preset; values travel in the file
|
||||||
// Values travel in the file config, masked to the author's slot index.
|
// config, masked to the author's slot index.
|
||||||
std::vector<std::string> full_keys;
|
std::vector<std::string> full_keys;
|
||||||
// Vendor-agnostic, curated (MaterialType) filament type the author requires for this slot.
|
// Vendor-agnostic (MaterialType) filament type the author requires for this slot; on
|
||||||
// On load the receiver's slot material is matched against it; on mismatch the slot is
|
// mismatch the slot is replaced with a same-type filament from the receiver's library.
|
||||||
// replaced with a same-type filament from the receiver's library.
|
|
||||||
bool publish_type{false};
|
bool publish_type{false};
|
||||||
std::string publish_type_value;
|
std::string publish_type_value;
|
||||||
// Required filament colour for this slot, applied on load regardless of the type match.
|
// Required filament colour, applied on load regardless of the type match.
|
||||||
bool publish_color{false};
|
bool publish_color{false};
|
||||||
std::string color;
|
std::string color;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalizes a filament type string against the curated MaterialType list: an exact match
|
// "PLA High Speed" -> "PLA" (strip a space modifier); dash types like "PA-CF" are kept intact.
|
||||||
// wins, then the value is stripped after its first space ("PLA High Speed" -> "PLA"); a
|
|
||||||
// value still not recognized is returned unchanged. Shared by the Publish dialog's type row
|
|
||||||
// default and by the published-3MF loader's type matching.
|
|
||||||
std::string normalize_filament_type(const std::string& type);
|
std::string normalize_filament_type(const std::string& type);
|
||||||
|
|
||||||
// Constructs a minimal DynamicPrintConfig for a published 3MF export containing only the
|
// Minimal DynamicPrintConfig for a published 3MF export: only the selected published keys,
|
||||||
// author-selected published keys, material keys, material identity fields, and plate geometry keys.
|
// material keys, identity fields and plate geometry keys.
|
||||||
class DynamicPrintConfig;
|
class DynamicPrintConfig;
|
||||||
DynamicPrintConfig filter_published_config(
|
DynamicPrintConfig filter_published_config(
|
||||||
const DynamicPrintConfig &full_config,
|
const DynamicPrintConfig &full_config,
|
||||||
|
|||||||
@@ -11,18 +11,16 @@ class DynamicPrintConfig;
|
|||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
// Return the value of the given option (identified by opt_key, which may contain
|
// Human-readable value of opt_key (may carry a "#<index>" suffix) in config.
|
||||||
// a "#<index>" suffix) formatted as a human readable string.
|
|
||||||
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config);
|
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config);
|
||||||
|
|
||||||
// Return the full label of the given option (identified by opt_key, which may contain
|
// Full label of opt_key; "N/A" when the option is not set.
|
||||||
// a "#<index>" suffix). Returns "N/A" when the option is not set.
|
|
||||||
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config);
|
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config);
|
||||||
|
|
||||||
// Strip the "#<index>" suffix (if any) from the given option key.
|
// Strip the "#<index>" suffix (if any) from the option key.
|
||||||
std::string get_pure_opt_key(const std::string& opt_key);
|
std::string get_pure_opt_key(const std::string& opt_key);
|
||||||
|
|
||||||
// Return the localized label of the currently selected value of an enum option.
|
// Localized label of the currently selected value of an enum option.
|
||||||
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1);
|
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1);
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
|
|||||||
@@ -2837,11 +2837,11 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
auto publish_handler = [this](wxCommandEvent&) { publish_project(); };
|
auto publish_handler = [this](wxCommandEvent&) { publish_project(); };
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||||
publish_handler, "menu_publish", nullptr,
|
publish_handler, "menu_publish", nullptr,
|
||||||
[this](){return can_export_model(); }, this);
|
[this](){return can_export_model(); }, this);
|
||||||
#else
|
#else
|
||||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||||
publish_handler, "", nullptr,
|
publish_handler, "", nullptr,
|
||||||
[this](){return can_export_model(); }, this);
|
[this](){return can_export_model(); }, this);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+23
-25
@@ -7208,9 +7208,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BBS: a "published" 3MF project carries a flag plus a list of author-selected
|
// BBS: a "published" 3MF carries a flag plus the author-selected setting keys;
|
||||||
// setting keys. When present, keep the user's currently-selected presets and
|
// on load keep the user's current presets and overlay only those keys.
|
||||||
// overlay only the published keys onto the edited presets on load.
|
|
||||||
PublishedConfig published_config;
|
PublishedConfig published_config;
|
||||||
if (model.model_info != nullptr) {
|
if (model.model_info != nullptr) {
|
||||||
auto published_it = model.model_info->metadata_items.find("published");
|
auto published_it = model.model_info->metadata_items.find("published");
|
||||||
@@ -7249,6 +7248,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
entry.filament_vendor = mat["filament_vendor"].get<std::string>();
|
entry.filament_vendor = mat["filament_vendor"].get<std::string>();
|
||||||
if (mat.contains("filament_id") && mat["filament_id"].is_string())
|
if (mat.contains("filament_id") && mat["filament_id"].is_string())
|
||||||
entry.filament_id = mat["filament_id"].get<std::string>();
|
entry.filament_id = mat["filament_id"].get<std::string>();
|
||||||
|
if (mat.contains("setting_id") && mat["setting_id"].is_string())
|
||||||
|
entry.setting_id = mat["setting_id"].get<std::string>();
|
||||||
}
|
}
|
||||||
if (m.contains("slot") && m["slot"].is_number_integer())
|
if (m.contains("slot") && m["slot"].is_number_integer())
|
||||||
entry.slot = m["slot"].get<int>();
|
entry.slot = m["slot"].get<int>();
|
||||||
@@ -7257,7 +7258,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
for (const auto &k : *entry_keys_it)
|
for (const auto &k : *entry_keys_it)
|
||||||
if (k.is_string())
|
if (k.is_string())
|
||||||
entry.keys.emplace_back(k.get<std::string>());
|
entry.keys.emplace_back(k.get<std::string>());
|
||||||
// Filament-publishing-v2 fields; absent in legacy files.
|
// Fields always written by the current exporter.
|
||||||
if (m.contains("full") && m["full"].is_boolean())
|
if (m.contains("full") && m["full"].is_boolean())
|
||||||
entry.full = m["full"].get<bool>();
|
entry.full = m["full"].get<bool>();
|
||||||
const auto entry_full_keys_it = m.find("full_keys");
|
const auto entry_full_keys_it = m.find("full_keys");
|
||||||
@@ -7282,10 +7283,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BBS: a "published" 3MF behaves like a new project once loaded: the file's path
|
// BBS: a "published" 3MF loads as a new project: its path must not become the
|
||||||
// must not become the project filename (Save/Ctrl-S would otherwise overwrite the
|
// project filename (Save/Ctrl-S would overwrite the shared file), and the
|
||||||
// shared file), and the published metadata is consumed by the overlay above and
|
// published metadata is consumed above and stripped so a later save is a normal
|
||||||
// stripped so a later save produces a normal, unpublished 3MF.
|
// unpublished 3MF.
|
||||||
if (published_out != nullptr && published_config.published)
|
if (published_out != nullptr && published_config.published)
|
||||||
*published_out = true;
|
*published_out = true;
|
||||||
if (published_config.published && load_config && this->model.model_info != nullptr) {
|
if (published_config.published && load_config && this->model.model_info != nullptr) {
|
||||||
@@ -13309,10 +13310,10 @@ void Plater::load_project(wxString const& filename2,
|
|||||||
p->set_project_filename(filename);
|
p->set_project_filename(filename);
|
||||||
}
|
}
|
||||||
else if (loaded_published) {
|
else if (loaded_published) {
|
||||||
// A "published" 3MF loads as a new project: the shared file's path must not become
|
// A "published" 3MF loads as a new project: its path must not become the project
|
||||||
// the project filename, so Save/Ctrl-S prompts for a destination instead of
|
// filename (Save/Ctrl-S prompts for a destination instead of overwriting it);
|
||||||
// overwriting the published file. reset() above already cleared the project name
|
// reset() already cleared the project name, so restore the default title and keep
|
||||||
// and folder; restore the default new-project title and keep the file in recents.
|
// the file in recents.
|
||||||
p->set_project_name(_L("Untitled"));
|
p->set_project_name(_L("Untitled"));
|
||||||
if (!filename.IsEmpty())
|
if (!filename.IsEmpty())
|
||||||
wxGetApp().mainframe->add_to_recent_projects(filename);
|
wxGetApp().mainframe->add_to_recent_projects(filename);
|
||||||
@@ -16225,10 +16226,9 @@ void Plater::export_core_3mf()
|
|||||||
export_3mf(path_u8, SaveStrategy::Silence);
|
export_3mf(path_u8, SaveStrategy::Silence);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export the current project as a "published" 3MF. This is a pure export: unlike save_project(),
|
// Export the current project as a "published" 3MF: a pure export that never touches the
|
||||||
// it never touches the project's file name, dirty state, backup path or title, and the
|
// project's file name, dirty state, backup path or title, and attaches the published metadata
|
||||||
// published metadata is attached to the model only for the duration of the export so the
|
// to the model only for the duration of the export (a later Save Project is a normal 3MF).
|
||||||
// in-memory project stays exactly as it was (a later Save Project produces a normal 3MF).
|
|
||||||
int Plater::export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys)
|
int Plater::export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys)
|
||||||
{
|
{
|
||||||
wxString path = p->get_export_file(FT_3MF, _L("Publish 3MF file as:"));
|
wxString path = p->get_export_file(FT_3MF, _L("Publish 3MF file as:"));
|
||||||
@@ -16240,14 +16240,14 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
|||||||
j.push_back(key);
|
j.push_back(key);
|
||||||
nlohmann::json jm = nlohmann::json::array();
|
nlohmann::json jm = nlohmann::json::array();
|
||||||
for (const Slic3r::PublishedMaterialEntry& e : material_keys)
|
for (const Slic3r::PublishedMaterialEntry& e : material_keys)
|
||||||
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}}}, {"slot", e.slot}, {"keys", e.keys},
|
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}, {"setting_id", e.setting_id}}}, {"slot", e.slot}, {"keys", e.keys},
|
||||||
{"full", e.full}, {"full_keys", e.full_keys},
|
{"full", e.full}, {"full_keys", e.full_keys},
|
||||||
{"publish_type", e.publish_type}, {"type", e.publish_type_value},
|
{"publish_type", e.publish_type}, {"type", e.publish_type_value},
|
||||||
{"publish_color", e.publish_color}, {"color", e.color} });
|
{"publish_color", e.publish_color}, {"color", e.color} });
|
||||||
|
|
||||||
Model& model = this->model();
|
Model& model = this->model();
|
||||||
// Remember the previous metadata state so it can be restored after the export, keeping the
|
// Save the previous metadata so it can be restored after the export, keeping the in-memory
|
||||||
// in-memory project pristine (the published flag lives only in the exported file).
|
// project pristine (the published flag lives only in the exported file).
|
||||||
const bool had_model_info = (model.model_info != nullptr);
|
const bool had_model_info = (model.model_info != nullptr);
|
||||||
const bool had_published = had_model_info && (model.model_info->metadata_items.find("published") != model.model_info->metadata_items.end());
|
const bool had_published = had_model_info && (model.model_info->metadata_items.find("published") != model.model_info->metadata_items.end());
|
||||||
const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find("published_keys") != model.model_info->metadata_items.end());
|
const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find("published_keys") != model.model_info->metadata_items.end());
|
||||||
@@ -16261,15 +16261,13 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
|||||||
model.model_info->metadata_items["published_keys"] = j.dump();
|
model.model_info->metadata_items["published_keys"] = j.dump();
|
||||||
model.model_info->metadata_items["published_material_keys"] = jm.dump();
|
model.model_info->metadata_items["published_material_keys"] = jm.dump();
|
||||||
|
|
||||||
// Minimal published export: filter full_config to only the published keys, material keys,
|
// Minimal published export: filter full_config to the published keys, material keys,
|
||||||
// identity fields, and plate geometry keys, and omit project-embedded preset dumps.
|
// identity fields and plate geometry keys, and omit project-embedded preset dumps.
|
||||||
DynamicPrintConfig full_cfg = wxGetApp().preset_bundle->full_config_secure();
|
DynamicPrintConfig full_cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||||
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
||||||
|
|
||||||
// Same file layout save_project() uses for its project files, plus SaveStrategy::Silence and SaveStrategy::MinimalPublished:
|
// Same file layout as save_project(), plus Silence (so export_3mf does not set the project
|
||||||
// without it export_3mf() calls set_project_filename() on success, which would make this
|
// filename on success, keeping this a pure export like export_core_3mf()) and MinimalPublished.
|
||||||
// pure export the current project file. Silence keeps the project state untouched, exactly
|
|
||||||
// like export_core_3mf().
|
|
||||||
auto save_strategy = SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence | SaveStrategy::MinimalPublished;
|
auto save_strategy = SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence | SaveStrategy::MinimalPublished;
|
||||||
bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames");
|
bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames");
|
||||||
if (full_pathnames)
|
if (full_pathnames)
|
||||||
|
|||||||
@@ -495,9 +495,8 @@ public:
|
|||||||
void export_gcode_3mf(bool export_all = false);
|
void export_gcode_3mf(bool export_all = false);
|
||||||
void send_gcode_finish(wxString name);
|
void send_gcode_finish(wxString name);
|
||||||
void export_core_3mf();
|
void export_core_3mf();
|
||||||
// Export the current project as a "published" 3MF: embeds the author-selected settings
|
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
|
||||||
// (published_keys / published_material_keys) into the file's metadata. A pure export: the
|
// pure export that leaves the in-memory project untouched.
|
||||||
// in-memory project (filename, dirty state, model_info metadata) is left untouched.
|
|
||||||
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
||||||
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
|
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
|
||||||
void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false, FileType file_type = FT_STL);
|
void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false, FileType file_type = FT_STL);
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Menu ids for show_menu(). Dedicated range above the standard ids so the popup cannot
|
// Menu ids for show_menu(): dedicated range so the popup cannot collide with application-level
|
||||||
// collide with application-level bindings (e.g. MainFrame's recent-files wxID_FILE1.. range).
|
// bindings (e.g. MainFrame's recent-files wxID_FILE1.. range).
|
||||||
enum {
|
enum {
|
||||||
kPublishSelectAll = wxID_HIGHEST + 1,
|
kPublishSelectAll = wxID_HIGHEST + 1,
|
||||||
kPublishDeselectAll,
|
kPublishDeselectAll,
|
||||||
@@ -49,9 +49,7 @@ PublishMaterialIdentity material_identity(size_t slot, const DynamicPrintConfig&
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Generic PLA @System" -> "Generic PLA"; mirrors the alias derivation in
|
// "Generic PLA @System" -> "Generic PLA"; mirrors the alias derivation in PresetBundle.cpp.
|
||||||
// PresetBundle::load_vendor_configs_from_json (PresetBundle.cpp) and
|
|
||||||
// PresetCollection::set_custom_preset_alias (Preset.cpp).
|
|
||||||
std::string material_display_name(const std::string& preset_name)
|
std::string material_display_name(const std::string& preset_name)
|
||||||
{
|
{
|
||||||
const size_t at = preset_name.find_first_of('@');
|
const size_t at = preset_name.find_first_of('@');
|
||||||
@@ -62,8 +60,8 @@ std::string material_display_name(const std::string& preset_name)
|
|||||||
return bare.empty() ? preset_name : bare;
|
return bare.empty() ? preset_name : bare;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Human-readable section title for a filament slot: the resolved preset name,
|
// Section title for a filament slot: the resolved preset name, then the filament type, then
|
||||||
// falling back to the filament type, then to the generic "Material".
|
// the generic "Material".
|
||||||
wxString material_title(size_t slot, const PresetBundle* bundle, const DynamicPrintConfig& full)
|
wxString material_title(size_t slot, const PresetBundle* bundle, const DynamicPrintConfig& full)
|
||||||
{
|
{
|
||||||
if (slot < bundle->filament_presets.size()) {
|
if (slot < bundle->filament_presets.size()) {
|
||||||
@@ -165,8 +163,7 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent)
|
|||||||
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
|
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
|
||||||
|
|
||||||
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||||
// Publish is always allowed: no settings selected means a publish with
|
// Publish is always allowed: no settings selected means no settings override.
|
||||||
// no settings override.
|
|
||||||
EndModal(wxID_OK);
|
EndModal(wxID_OK);
|
||||||
});
|
});
|
||||||
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
||||||
@@ -183,12 +180,11 @@ PublishSettingsDialog::~PublishSettingsDialog() {}
|
|||||||
|
|
||||||
void PublishSettingsDialog::build_option_model()
|
void PublishSettingsDialog::build_option_model()
|
||||||
{
|
{
|
||||||
// Structural / non-publishable keys, shared with the published-3MF overlay
|
// Structural / non-publishable keys, shared with the published-3MF overlay path.
|
||||||
// path (see libslic3r/PublishSettings.hpp).
|
|
||||||
const std::set<std::string>& denylist = publish_structural_keys();
|
const std::set<std::string>& denylist = publish_structural_keys();
|
||||||
// Base keys already added in the print/printer sections. Printer rows share
|
// Base keys already added in the print/printer sections. Printer rows share this set:
|
||||||
// this set: a base key appears once (per-extruder "#N" variants collapse to
|
// per-extruder "#N" variants collapse to the first occurrence (acceptable MVP; the
|
||||||
// the first occurrence - acceptable MVP; the per-extruder context is lost).
|
// per-extruder context is lost in the UI).
|
||||||
std::set<std::string> added;
|
std::set<std::string> added;
|
||||||
|
|
||||||
PresetBundle* bundle = wxGetApp().preset_bundle;
|
PresetBundle* bundle = wxGetApp().preset_bundle;
|
||||||
@@ -198,17 +194,17 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
m_info_allsel = _L("All items selected...");
|
m_info_allsel = _L("All items selected...");
|
||||||
m_info_empty = _L("No matching items...");
|
m_info_empty = _L("No matching items...");
|
||||||
|
|
||||||
// Keep the tab order explicit: Section's enum order is Print, Printer,
|
// Tab order differs from Section's enum order (Print, Printer, Material): the dialog
|
||||||
// Material, while the dialog presents Printer, Filament, Process.
|
// presents Printer, Filament, Process.
|
||||||
m_sections.reserve(3);
|
m_sections.reserve(3);
|
||||||
const Section tab_order[] = {Section::Printer, Section::Material, Section::Print};
|
const Section tab_order[] = {Section::Printer, Section::Material, Section::Print};
|
||||||
for (Section kind : tab_order)
|
for (Section kind : tab_order)
|
||||||
section_group_for(kind);
|
section_group_for(kind);
|
||||||
bind_tab_events();
|
bind_tab_events();
|
||||||
|
|
||||||
// Shared per-option label/value computation; returns false when the option
|
// Shared per-option label/value computation; returns false when the option must be skipped
|
||||||
// must be skipped (denylisted / unknown / empty label). value is the pure
|
// (denylisted / unknown / empty label). value is the stringified value; unit the translated
|
||||||
// stringified value; unit is the translated sidetext (may be empty).
|
// sidetext (may be empty).
|
||||||
auto option_text = [&denylist, &full](const std::string& opt_id, const std::string& pure_key, wxString& label, wxString& value,
|
auto option_text = [&denylist, &full](const std::string& opt_id, const std::string& pure_key, wxString& label, wxString& value,
|
||||||
wxString& unit) -> bool {
|
wxString& unit) -> bool {
|
||||||
if (denylist.count(pure_key) > 0)
|
if (denylist.count(pure_key) > 0)
|
||||||
@@ -224,9 +220,8 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Phase 1: printer per-extruder retraction settings (displayed first,
|
// --- Phase 1: printer per-extruder retraction settings (first, mirroring the sidebar's
|
||||||
// mirroring the sidebar's Printer group). The printer tab's
|
// Printer group), from the printer tab's "Extruder"/"Extruder N" pages.
|
||||||
// "Extruder"/"Extruder N" pages carry the per-extruder retraction options.
|
|
||||||
{
|
{
|
||||||
size_t g = section_group_for(Section::Printer);
|
size_t g = section_group_for(Section::Printer);
|
||||||
category_index_for(_L("Extruder"), Section::Printer, "custom-gcode_extruder", g, 0);
|
category_index_for(_L("Extruder"), Section::Printer, "custom-gcode_extruder", g, 0);
|
||||||
@@ -238,18 +233,17 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
continue;
|
continue;
|
||||||
const wxString page_title = Tab::translate_category(page->title(), tab->m_type);
|
const wxString page_title = Tab::translate_category(page->title(), tab->m_type);
|
||||||
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
|
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
|
||||||
// Allowlist on the untranslated optgroup title; the "Retraction
|
// Allowlist on the untranslated optgroup title; the "Retraction when
|
||||||
// when switching material" group is intentionally skipped.
|
// switching material" group is intentionally skipped.
|
||||||
if (optgroup->title != "Retraction" && optgroup->title != "Z-Hop")
|
if (optgroup->title != "Retraction" && optgroup->title != "Z-Hop")
|
||||||
continue;
|
continue;
|
||||||
const wxString subcategory = _(optgroup->title);
|
const wxString subcategory = _(optgroup->title);
|
||||||
for (const auto& opt : optgroup->opt_map()) {
|
for (const auto& opt : optgroup->opt_map()) {
|
||||||
const std::string& opt_id = opt.first;
|
const std::string& opt_id = opt.first;
|
||||||
const std::string& pure_key = opt.second.first;
|
const std::string& pure_key = opt.second.first;
|
||||||
// Per-extruder "#N" variants collapse to the first base key. The row stores
|
// Per-extruder "#N" variants collapse to the first base key. The row
|
||||||
// the BASE key (whole-vector semantics on load: the size-guarded apply
|
// stores the base key; GetPublishedKeys() later expands it back to one
|
||||||
// copies the author's full vector), while the "#0" opt_id is only used to
|
// "#N" entry per extruder so the load side can apply per-extruder values.
|
||||||
// display the first extruder's value.
|
|
||||||
if (!added.insert(pure_key).second)
|
if (!added.insert(pure_key).second)
|
||||||
continue;
|
continue;
|
||||||
wxString label, value, unit;
|
wxString label, value, unit;
|
||||||
@@ -264,8 +258,8 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Phase 2: per-material sections synthesized from the filament tab's
|
// --- Phase 2: per-material sections synthesized from the filament tab's "Setting
|
||||||
// "Setting Overrides" page, under the Filament group.
|
// Overrides" page, under the Filament group.
|
||||||
{
|
{
|
||||||
size_t g = section_group_for(Section::Material);
|
size_t g = section_group_for(Section::Material);
|
||||||
Tab* filament_tab = nullptr;
|
Tab* filament_tab = nullptr;
|
||||||
@@ -283,17 +277,16 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (overrides_page != nullptr) {
|
if (overrides_page != nullptr) {
|
||||||
// One section per filament slot: a 4-slot printer (e.g. 1 PLA +
|
// One section per filament slot (a 4-slot printer shows 4 pages), each
|
||||||
// 3 PETG) shows 4 separate pages, each disambiguated internally by
|
// disambiguated by its colour chip and slot identity while showing the bare name.
|
||||||
// its colour chip and slot identity while displaying the bare name.
|
|
||||||
for (size_t slot = 0; slot < bundle->filament_presets.size(); ++slot) {
|
for (size_t slot = 0; slot < bundle->filament_presets.size(); ++slot) {
|
||||||
const PublishMaterialIdentity identity = material_identity(slot, full);
|
const PublishMaterialIdentity identity = material_identity(slot, full);
|
||||||
const wxString title = material_title(slot, bundle, full);
|
const wxString title = material_title(slot, bundle, full);
|
||||||
const size_t category_index = category_index_for(title, Section::Material, "custom-gcode_filament", g, slot, identity);
|
const size_t category_index = category_index_for(title, Section::Material, "custom-gcode_filament", g, slot, identity);
|
||||||
|
|
||||||
// Filament-publishing-v2 rows: the author may require a filament colour and/or
|
// Material requirement rows: an optional filament colour and/or a
|
||||||
// a vendor-agnostic material type for this slot. They live in their own
|
// vendor-agnostic material type for this slot, in their own optgroup so they
|
||||||
// optgroup so they stay visually separated from the setting rows.
|
// stay visually separated from the setting rows.
|
||||||
{
|
{
|
||||||
const size_t req_sub = subcategory_index_for(category_index, _L("Material"), "custom-gcode_filament");
|
const size_t req_sub = subcategory_index_for(category_index, _L("Material"), "custom-gcode_filament");
|
||||||
std::string hex;
|
std::string hex;
|
||||||
@@ -309,18 +302,16 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
RowKind::Type);
|
RowKind::Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A material section must not repeat a key; the same key may
|
// A material section must not repeat a key; the same key may appear in other
|
||||||
// appear in other material sections - that is intended.
|
// material sections - that is intended.
|
||||||
std::set<std::string> material_added;
|
std::set<std::string> material_added;
|
||||||
|
|
||||||
for (const ConfigOptionsGroupShp& optgroup : overrides_page->m_optgroups) {
|
for (const ConfigOptionsGroupShp& optgroup : overrides_page->m_optgroups) {
|
||||||
// Allowlist on the untranslated optgroup title; the
|
// Allowlist on the untranslated optgroup title; "Ironing" is skipped.
|
||||||
// "Ironing" group is intentionally skipped.
|
|
||||||
if (optgroup->title != "Retraction" && optgroup->title != "Retraction when switching material")
|
if (optgroup->title != "Retraction" && optgroup->title != "Retraction when switching material")
|
||||||
continue;
|
continue;
|
||||||
for (const auto& opt : optgroup->opt_map()) {
|
for (const auto& opt : optgroup->opt_map()) {
|
||||||
// Row keys are base keys (no "#N"): the load side
|
// Row keys are base keys; the load side applies them positionally.
|
||||||
// matches the material and uses the author's slot.
|
|
||||||
const std::string& opt_id = opt.first;
|
const std::string& opt_id = opt.first;
|
||||||
std::string base = opt_id.substr(0, opt_id.find('#'));
|
std::string base = opt_id.substr(0, opt_id.find('#'));
|
||||||
if (!material_added.insert(base).second)
|
if (!material_added.insert(base).second)
|
||||||
@@ -381,17 +372,15 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-check the dirty (modified) settings and mark them bold. The base-key
|
// Pre-check the dirty (modified) settings and mark them bold (base-key match, across all
|
||||||
// match covers all sections; collect_dirty_settings_keys already unions the
|
// sections; collect_dirty_settings_keys unions the prints, printers and filaments).
|
||||||
// prints, printers and filaments of the bundle.
|
|
||||||
std::set<std::string> dirty_base;
|
std::set<std::string> dirty_base;
|
||||||
for (const std::string& key : collect_dirty_settings_keys(*wxGetApp().preset_bundle)) {
|
for (const std::string& key : collect_dirty_settings_keys(*wxGetApp().preset_bundle)) {
|
||||||
auto n = key.find('#');
|
auto n = key.find('#');
|
||||||
dirty_base.insert(n == std::string::npos ? key : key.substr(0, n));
|
dirty_base.insert(n == std::string::npos ? key : key.substr(0, n));
|
||||||
}
|
}
|
||||||
for (Row& row : m_rows) {
|
for (Row& row : m_rows) {
|
||||||
// The Color/Type requirement rows are not "dirty overrides": they are never
|
// The Color/Type requirement rows are not "dirty overrides": never auto-checked.
|
||||||
// auto-checked by the dirty pre-check.
|
|
||||||
if (row.kind != RowKind::Setting)
|
if (row.kind != RowKind::Setting)
|
||||||
continue;
|
continue;
|
||||||
std::string base = row.key.substr(0, row.key.find('#'));
|
std::string base = row.key.substr(0, row.key.find('#'));
|
||||||
@@ -402,8 +391,8 @@ void PublishSettingsDialog::build_option_model()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire the "Full Publish" checkboxes: toggling one disables/enables the material's
|
// Wire the "Full Publish" checkboxes: toggling one disables/enables the material's rows.
|
||||||
// rows. Bind by index so the lambda stays valid even if the vector is reallocated later.
|
// Bind by index so the lambda stays valid even if the vector is reallocated later.
|
||||||
for (size_t c = 0; c < m_categories.size(); ++c)
|
for (size_t c = 0; c < m_categories.size(); ++c)
|
||||||
if (m_categories[c].full_check != nullptr)
|
if (m_categories[c].full_check != nullptr)
|
||||||
m_categories[c].full_check->Bind(wxEVT_CHECKBOX, [this, c](wxCommandEvent&) { on_full_toggle(c); });
|
m_categories[c].full_check->Bind(wxEVT_CHECKBOX, [this, c](wxCommandEvent&) { on_full_toggle(c); });
|
||||||
@@ -449,6 +438,7 @@ size_t PublishSettingsDialog::section_group_for(Section kind)
|
|||||||
section.icon_name = "process";
|
section.icon_name = "process";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
section.icon_bmp = ScalableBitmap(this, section.icon_name, 16);
|
||||||
|
|
||||||
constexpr long tab_style = wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS |
|
constexpr long tab_style = wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS |
|
||||||
wxTR_FULL_ROW_HIGHLIGHT;
|
wxTR_FULL_ROW_HIGHLIGHT;
|
||||||
@@ -466,6 +456,9 @@ size_t PublishSettingsDialog::section_group_for(Section kind)
|
|||||||
page_sizer->Add(section.page_host, 1, wxEXPAND | wxTOP, FromDIP(4));
|
page_sizer->Add(section.page_host, 1, wxEXPAND | wxTOP, FromDIP(4));
|
||||||
section.page->SetSizer(page_sizer);
|
section.page->SetSizer(page_sizer);
|
||||||
|
|
||||||
|
if (section.icon_bmp.bmp().IsOk())
|
||||||
|
m_outer_tabs->AppendItem(section.title, section.icon_bmp.bmp());
|
||||||
|
else
|
||||||
m_outer_tabs->AppendItem(section.title);
|
m_outer_tabs->AppendItem(section.title);
|
||||||
m_outer_host_sizer->Add(section.page, 1, wxEXPAND);
|
m_outer_host_sizer->Add(section.page, 1, wxEXPAND);
|
||||||
section.page->Hide();
|
section.page->Hide();
|
||||||
@@ -610,7 +603,7 @@ void PublishSettingsDialog::add_row_ui(const std::string& key,
|
|||||||
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL);
|
row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL);
|
||||||
// The value is read-only text (incl. the Type row: the published type is the slot's
|
// The value is read-only text (incl. the Type row: the published type is the slot's
|
||||||
// normalized type, the author cannot pick a different one here).
|
// normalized type, not author-editable).
|
||||||
current.value_label = new wxStaticText(category.scroll, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
|
current.value_label = new wxStaticText(category.scroll, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
|
||||||
current.value_label->SetFont(Label::Body_13);
|
current.value_label->SetFont(Label::Body_13);
|
||||||
current.value_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
|
current.value_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
|
||||||
@@ -643,8 +636,7 @@ void PublishSettingsDialog::on_full_toggle(size_t category_index)
|
|||||||
|
|
||||||
void PublishSettingsDialog::set_row_bold(Row& row, bool bold)
|
void PublishSettingsDialog::set_row_bold(Row& row, bool bold)
|
||||||
{
|
{
|
||||||
// Real set/clear: rebase on the dialog's body font so that clearing bold
|
// Rebase on the dialog's body font so clearing bold restores the exact original font.
|
||||||
// restores the exact original font (the old CheckList::SetBold was one-way).
|
|
||||||
row.check->SetFont(bold ? Label::Body_13.Bold() : Label::Body_13);
|
row.check->SetFont(bold ? Label::Body_13.Bold() : Label::Body_13);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,12 +710,11 @@ void PublishSettingsDialog::apply_filter(const wxString& filter_text)
|
|||||||
Freeze();
|
Freeze();
|
||||||
wxString filter = filter_text.Lower();
|
wxString filter = filter_text.Lower();
|
||||||
|
|
||||||
// Pseudo filters (menu only): show only checked ("::sel") or only
|
// Pseudo filters (menu only): show only checked ("::sel") or only unchecked ("::nonsel").
|
||||||
// unchecked ("::nonsel") rows.
|
|
||||||
const bool pseudo = (filter == "::sel" || filter == "::nonsel");
|
const bool pseudo = (filter == "::sel" || filter == "::nonsel");
|
||||||
m_fb_sizer->Show(!pseudo);
|
m_fb_sizer->Show(!pseudo);
|
||||||
|
|
||||||
// Update row matches first; page and optgroup visibility is applied below.
|
// Row matches are computed first; page and optgroup visibility is applied below.
|
||||||
if (pseudo) {
|
if (pseudo) {
|
||||||
if (m_filter_ctrl->GetValue().Lower() != filter) {
|
if (m_filter_ctrl->GetValue().Lower() != filter) {
|
||||||
m_filter_ctrl->ChangeValue(filter);
|
m_filter_ctrl->ChangeValue(filter);
|
||||||
@@ -808,8 +799,7 @@ void PublishSettingsDialog::apply_visibility()
|
|||||||
|
|
||||||
void PublishSettingsDialog::select_all(bool value)
|
void PublishSettingsDialog::select_all(bool value)
|
||||||
{
|
{
|
||||||
// "All" does not auto-enable gated material sections; "None" leaves a gated
|
// "All" skips disabled (gated) rows; "None" leaves a gated row's preserved value.
|
||||||
// row's preserved value untouched.
|
|
||||||
for (Row& row : m_rows)
|
for (Row& row : m_rows)
|
||||||
if (row.check->IsEnabled())
|
if (row.check->IsEnabled())
|
||||||
row.check->SetValue(value);
|
row.check->SetValue(value);
|
||||||
@@ -829,19 +819,18 @@ bool PublishSettingsDialog::row_is_visible(const Row& row) const
|
|||||||
void PublishSettingsDialog::select_visible(bool value)
|
void PublishSettingsDialog::select_visible(bool value)
|
||||||
{
|
{
|
||||||
wxString filter = m_filter_ctrl->GetValue().Lower();
|
wxString filter = m_filter_ctrl->GetValue().Lower();
|
||||||
// In a pseudo-filter view the rows being toggled would all disappear;
|
// In a pseudo-filter view the rows being toggled would all disappear; drop the filter
|
||||||
// drop the filter afterwards so the result stays visible.
|
// afterwards so the result stays visible.
|
||||||
bool clear_pseudo = (!value && filter == "::nonsel") || (value && filter == "::sel");
|
bool clear_pseudo = (!value && filter == "::nonsel") || (value && filter == "::sel");
|
||||||
|
|
||||||
// Toggle the rows that are visible under the *current* filter.
|
// Toggle the rows visible under the *current* filter.
|
||||||
for (Row& row : m_rows)
|
for (Row& row : m_rows)
|
||||||
if (row_is_visible(row))
|
if (row_is_visible(row))
|
||||||
row.check->SetValue(value);
|
row.check->SetValue(value);
|
||||||
|
|
||||||
if (clear_pseudo) {
|
if (clear_pseudo) {
|
||||||
// Note: SetValue() may fire wxEVT_TEXT on some platforms, which
|
// Note: SetValue() may fire wxEVT_TEXT on some platforms, re-entering apply_filter() -
|
||||||
// re-enters apply_filter() - that is fine, the rows above were already
|
// that is fine; the rows above were already toggled and the trailing call is idempotent.
|
||||||
// toggled and the trailing call below is idempotent.
|
|
||||||
m_filter_ctrl->ChangeValue("");
|
m_filter_ctrl->ChangeValue("");
|
||||||
apply_filter(""); // resync visibility and the All/None bar
|
apply_filter(""); // resync visibility and the All/None bar
|
||||||
}
|
}
|
||||||
@@ -895,12 +884,31 @@ void PublishSettingsDialog::show_menu(wxMouseEvent& evt)
|
|||||||
std::vector<std::string> PublishSettingsDialog::GetPublishedKeys() const
|
std::vector<std::string> PublishSettingsDialog::GetPublishedKeys() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> out;
|
std::vector<std::string> out;
|
||||||
// Process and printer sections both travel through published_keys (the load-side
|
// Process and printer sections both travel through published_keys (the load-side overlay
|
||||||
// overlay applies process keys to the prints edited preset and the allowlisted
|
// applies process keys to the prints edited preset and the allowlisted printer keys to the
|
||||||
// printer keys to the printers edited preset). Material keys use a separate API.
|
// printers edited preset); material keys use a separate API.
|
||||||
for (const Row& row : m_rows)
|
const DynamicPrintConfig full = wxGetApp().preset_bundle->full_config();
|
||||||
if ((row.section == Section::Print || row.section == Section::Printer) && row.check->GetValue())
|
for (const Row& row : m_rows) {
|
||||||
|
if ((row.section != Section::Print && row.section != Section::Printer) || !row.check->GetValue())
|
||||||
|
continue;
|
||||||
|
if (row.section == Section::Printer) {
|
||||||
|
// Printer rows store the base key (per-extruder "#N" variants collapsed during
|
||||||
|
// build). Publish every extruder element so the load side can apply per-extruder
|
||||||
|
// values even when the receiver has a different extruder count; a scalar printer
|
||||||
|
// key is published as-is.
|
||||||
|
const std::string base_key = row.key.substr(0, row.key.find('#'));
|
||||||
|
if (const ConfigOption* opt = full.option(base_key)) {
|
||||||
|
if (const auto* vec = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
|
||||||
|
for (size_t i = 0; i < vec->size(); ++i)
|
||||||
|
out.push_back(base_key + "#" + std::to_string(i));
|
||||||
|
} else {
|
||||||
|
out.push_back(base_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
out.push_back(row.key);
|
out.push_back(row.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,8 +923,15 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
|||||||
entry.filament_vendor = cat.filament_vendor;
|
entry.filament_vendor = cat.filament_vendor;
|
||||||
entry.filament_id = cat.filament_id;
|
entry.filament_id = cat.filament_id;
|
||||||
entry.slot = static_cast<int>(cat.filament_slot);
|
entry.slot = static_cast<int>(cat.filament_slot);
|
||||||
// "Full Publish": the entire filament preset of the slot is embedded; type and color
|
// The author's preset id distinguishes exact variants that share filament_id
|
||||||
// are implicitly published, and the per-key rows are disabled and their state is ignored.
|
// ("Generic PLA" vs "Generic PLA Matte"), so the receiver can match precisely.
|
||||||
|
PresetBundle *bundle = wxGetApp().preset_bundle;
|
||||||
|
if (bundle != nullptr && cat.filament_slot < bundle->filament_presets.size()) {
|
||||||
|
if (const Preset *preset = bundle->filaments.find_preset(bundle->filament_presets[cat.filament_slot], false, true))
|
||||||
|
entry.setting_id = preset->setting_id;
|
||||||
|
}
|
||||||
|
// "Full Publish": the whole filament preset is embedded; type and colour are implicitly
|
||||||
|
// published, and the per-key rows are disabled / their state ignored.
|
||||||
if (cat.full_check != nullptr && cat.full_check->GetValue()) {
|
if (cat.full_check != nullptr && cat.full_check->GetValue()) {
|
||||||
entry.full = true;
|
entry.full = true;
|
||||||
entry.full_keys = full_keys_for_slot();
|
entry.full_keys = full_keys_for_slot();
|
||||||
@@ -946,8 +961,7 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
|||||||
entry.keys.push_back(row.key);
|
entry.keys.push_back(row.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A material with only setting keys but none checked, or with nothing selected at all,
|
// Nothing checked at all -> nothing to write.
|
||||||
// carries no information for the writer.
|
|
||||||
if (!entry.keys.empty() || entry.publish_type || entry.publish_color)
|
if (!entry.keys.empty() || entry.publish_type || entry.publish_color)
|
||||||
out.push_back(std::move(entry));
|
out.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
@@ -956,9 +970,9 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
|||||||
|
|
||||||
std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
|
std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
|
||||||
{
|
{
|
||||||
// The canonical filament preset keys, minus the structural keys the published overlay must
|
// The canonical filament preset keys minus the structural keys the published overlay must
|
||||||
// never touch (inherits, compatibility, *_settings_id, ...), plus filament_colour (not a
|
// never touch (inherits, compatibility, *_settings_id, ...), plus filament_colour (not a
|
||||||
// member of Preset::filament_options). The values travel in the exported config, masked to
|
// member of Preset::filament_options). Values travel in the exported config, masked to
|
||||||
// this slot, and are applied on load onto the receiver's slot.
|
// this slot, and are applied on load onto the receiver's slot.
|
||||||
const std::set<std::string>& denylist = publish_structural_keys();
|
const std::set<std::string>& denylist = publish_structural_keys();
|
||||||
std::vector<std::string> keys;
|
std::vector<std::string> keys;
|
||||||
@@ -971,7 +985,7 @@ std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
|
|||||||
|
|
||||||
void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||||
{
|
{
|
||||||
// Rescale toolbar bitmaps and icons; collapse chevrons are vector-drawn and repaint themselves.
|
// Rescale toolbar bitmaps and icons; collapse chevrons are vector-drawn and repaint.
|
||||||
m_search.msw_rescale();
|
m_search.msw_rescale();
|
||||||
m_menu.msw_rescale();
|
m_menu.msw_rescale();
|
||||||
m_filter_box->SetIcon(m_search.bmp());
|
m_filter_box->SetIcon(m_search.bmp());
|
||||||
@@ -1000,8 +1014,13 @@ void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
|||||||
cat.list_sizer->Layout();
|
cat.list_sizer->Layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SectionGroup& section : m_sections)
|
for (size_t s = 0; s < m_sections.size(); ++s) {
|
||||||
|
SectionGroup& section = m_sections[s];
|
||||||
|
section.icon_bmp.msw_rescale();
|
||||||
|
if (section.icon_bmp.bmp().IsOk())
|
||||||
|
m_outer_tabs->SetItemBitmap(s, section.icon_bmp.bmp());
|
||||||
section.tabs->Rescale();
|
section.tabs->Rescale();
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh the per-row Color chips at the new DPI.
|
// Refresh the per-row Color chips at the new DPI.
|
||||||
for (Row& row : m_rows) {
|
for (Row& row : m_rows) {
|
||||||
|
|||||||
@@ -28,25 +28,21 @@ struct PublishMaterialIdentity
|
|||||||
std::string id;
|
std::string id;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dialog that lets a model author select which settings get embedded in a 3MF.
|
// Dialog letting a model author select which settings get embedded in a 3MF. Nested tab layout
|
||||||
// Settings are grouped into the same nested custom tab layout used by the
|
// mirroring the Process settings (Printer / Filament / Process outer tabs, category or material
|
||||||
// Process settings: Printer, Filament, and Process outer tabs, with category or
|
// tabs inside each). Dirty settings are pre-checked and shown bold; on OK the print rows become
|
||||||
// material tabs inside each section. Optgroups are ordinary grouped headers.
|
// "published_keys" and the material rows become "published_material_keys".
|
||||||
// Modified (dirty) settings are pre-checked and shown bold. On OK, the print
|
|
||||||
// rows become the "published_keys" list and the material rows become the
|
|
||||||
// per-material "published_material_keys".
|
|
||||||
class PublishSettingsDialog : public DPIDialog
|
class PublishSettingsDialog : public DPIDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PublishSettingsDialog(wxWindow* parent = nullptr);
|
PublishSettingsDialog(wxWindow* parent = nullptr);
|
||||||
~PublishSettingsDialog();
|
~PublishSettingsDialog();
|
||||||
|
|
||||||
// The selected print-section setting keys (in display order). Keys may
|
// The selected print/printer setting keys (in display order); printer keys carry a '#N'
|
||||||
// contain '#'.
|
// per-extruder suffix.
|
||||||
std::vector<std::string> GetPublishedKeys() const;
|
std::vector<std::string> GetPublishedKeys() const;
|
||||||
|
|
||||||
// The selected keys grouped per material: one entry per material section
|
// The selected keys grouped per material section (base keys, no '#N' suffix).
|
||||||
// with at least one checked key. Keys are base keys (no "#N" suffix).
|
|
||||||
std::vector<Slic3r::PublishedMaterialEntry> GetPublishedMaterialKeys() const;
|
std::vector<Slic3r::PublishedMaterialEntry> GetPublishedMaterialKeys() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -56,9 +52,9 @@ private:
|
|||||||
// Which part of the settings the row/category came from.
|
// Which part of the settings the row/category came from.
|
||||||
enum class Section { Print, Printer, Material };
|
enum class Section { Print, Printer, Material };
|
||||||
|
|
||||||
// One selectable setting row: a checkbox (setting name) plus a value label
|
// One selectable setting row: a checkbox (setting name) plus a value label and an optional
|
||||||
// and a (optional) grey unit label. key is the full config key and may carry
|
// grey unit label. key is the full config key, possibly with a "#N" variant suffix
|
||||||
// a "#N" variant suffix (print/printer rows); material rows carry the base key.
|
// (print/printer rows); material rows carry the base key.
|
||||||
enum class RowKind {
|
enum class RowKind {
|
||||||
Setting, // a regular setting key
|
Setting, // a regular setting key
|
||||||
Color, // material colour requirement (filament_colour)
|
Color, // material colour requirement (filament_colour)
|
||||||
@@ -113,9 +109,9 @@ private:
|
|||||||
ScalableBitmap icon_bmp; // scalable bitmap for DPI changes
|
ScalableBitmap icon_bmp; // scalable bitmap for DPI changes
|
||||||
wxStaticBitmap* icon{nullptr};
|
wxStaticBitmap* icon{nullptr};
|
||||||
wxStaticBitmap* filament_color_chip{nullptr};
|
wxStaticBitmap* filament_color_chip{nullptr};
|
||||||
wxStaticText* title_label{nullptr}; // material title (static text, Full Publish carries the label elsewhere)
|
wxStaticText* title_label{nullptr}; // material title (static text; Full Publish carries the label elsewhere)
|
||||||
// "Full Publish": serializing the entire filament preset of this slot. While checked,
|
// "Full Publish": while checked, the whole slot preset is serialized and its rows
|
||||||
// the slot's rows (incl. Color/Type) are disabled.
|
// (incl. Color/Type) are disabled.
|
||||||
bool full{false};
|
bool full{false};
|
||||||
wxCheckBox* full_check{nullptr};
|
wxCheckBox* full_check{nullptr};
|
||||||
// Material identity, only for Section::Material categories.
|
// Material identity, only for Section::Material categories.
|
||||||
@@ -134,6 +130,7 @@ private:
|
|||||||
wxString title; // _L("Printer") / _L("Filament") / _L("Process")
|
wxString title; // _L("Printer") / _L("Filament") / _L("Process")
|
||||||
Section kind{Section::Print}; // maps 1:1 to the display group
|
Section kind{Section::Print}; // maps 1:1 to the display group
|
||||||
std::string icon_name; // "printer" / "filament" / "process"
|
std::string icon_name; // "printer" / "filament" / "process"
|
||||||
|
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
|
||||||
wxPanel* page{nullptr};
|
wxPanel* page{nullptr};
|
||||||
TabCtrl* tabs{nullptr};
|
TabCtrl* tabs{nullptr};
|
||||||
wxPanel* page_host{nullptr};
|
wxPanel* page_host{nullptr};
|
||||||
|
|||||||
@@ -501,12 +501,10 @@ SCENARIO("Nozzle-group metadata .3mf round-trip", "[3mf][MultiNozzle]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "Publish" feature stores a published flag plus a JSON array of author-selected setting keys
|
// Locks the serialization contract of the "Publish" metadata: the published flag and the
|
||||||
// in model.model_info->metadata_items. This locks the serialization contract: both keys must survive
|
// published_keys JSON array in model.model_info->metadata_items must survive a store_bbs_3mf ->
|
||||||
// a store_bbs_3mf -> load_bbs_3mf round-trip unchanged. (The full preset-preservation behavior —
|
// load_bbs_3mf round-trip unchanged. (The full preset-preservation behavior is exercised
|
||||||
// keeping the user's currently-selected presets and overlaying only the published keys onto them —
|
// headlessly in test_preset_bundle_loading.cpp.)
|
||||||
// is exercised headlessly in "Published 3MF overlays only the author-selected process keys onto the
|
|
||||||
// edited preset" in test_preset_bundle_loading.cpp.)
|
|
||||||
SCENARIO("Published 3MF round-trips the published flag and published_keys metadata", "[3mf]") {
|
SCENARIO("Published 3MF round-trips the published flag and published_keys metadata", "[3mf]") {
|
||||||
GIVEN("a model carrying published metadata") {
|
GIVEN("a model carrying published metadata") {
|
||||||
Model model;
|
Model model;
|
||||||
@@ -518,8 +516,8 @@ SCENARIO("Published 3MF round-trips the published flag and published_keys metada
|
|||||||
model.model_info->metadata_items["published"] = "1";
|
model.model_info->metadata_items["published"] = "1";
|
||||||
model.model_info->metadata_items["published_keys"] = R"(["layer_height","wall_thickness"])";
|
model.model_info->metadata_items["published_keys"] = R"(["layer_height","wall_thickness"])";
|
||||||
|
|
||||||
// store_bbs_3mf stages Metadata/project_settings.config through the model's backup path;
|
// store_bbs_3mf stages project_settings.config through the model's backup path; point
|
||||||
// point it at a writable temp dir (the default lives under a read-only root in CI).
|
// it at a writable temp dir (the default lives under a read-only root in CI).
|
||||||
ScopedTemporaryDir backup_dir("orca_pub");
|
ScopedTemporaryDir backup_dir("orca_pub");
|
||||||
model.set_backup_path(backup_dir.string());
|
model.set_backup_path(backup_dir.string());
|
||||||
|
|
||||||
@@ -564,8 +562,8 @@ SCENARIO("Published 3MF round-trips the published flag and published_keys metada
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A project saved without the Publish metadata (i.e. a normal 3MF) must load identically: the
|
// A normal 3MF (no Publish metadata) must load identically: the loader must not fabricate a
|
||||||
// loader must not fabricate a "published" flag or published_keys for files that never carried them.
|
// "published" flag or published_keys for files that never carried them.
|
||||||
SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") {
|
SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") {
|
||||||
GIVEN("a model without any published metadata") {
|
GIVEN("a model without any published metadata") {
|
||||||
Model model;
|
Model model;
|
||||||
@@ -610,10 +608,8 @@ SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "Publish" feature can also store material-qualified setting keys, one entry per material
|
// Locks the serialization contract of the published_material_keys metadata: the per-entry JSON
|
||||||
// the author uses, in model.model_info->metadata_items. This locks the serialization contract
|
// must survive a store_bbs_3mf -> load_bbs_3mf round-trip verbatim, exactly like published_keys.
|
||||||
// for that entry list: the JSON must survive a store_bbs_3mf -> load_bbs_3mf round-trip
|
|
||||||
// verbatim, exactly like the plain published_keys array.
|
|
||||||
SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf]") {
|
SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf]") {
|
||||||
GIVEN("a model carrying published material keys metadata") {
|
GIVEN("a model carrying published material keys metadata") {
|
||||||
Model model;
|
Model model;
|
||||||
@@ -658,8 +654,7 @@ SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf
|
|||||||
REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json);
|
REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json);
|
||||||
|
|
||||||
// The value must parse back to one material entry carrying the nested identity
|
// The value must parse back to one material entry carrying the nested identity
|
||||||
// object, the author slot ordinal and the key list, so the loader can match it
|
// object, the author slot ordinal and the key list.
|
||||||
// to the receiver's filaments.
|
|
||||||
nlohmann::json entries = nlohmann::json::parse(material_keys_json);
|
nlohmann::json entries = nlohmann::json::parse(material_keys_json);
|
||||||
REQUIRE(entries.is_array());
|
REQUIRE(entries.is_array());
|
||||||
REQUIRE(entries.size() == 1);
|
REQUIRE(entries.size() == 1);
|
||||||
@@ -689,19 +684,19 @@ SCENARIO("Minimal published 3MF serialization filters config and omits embedded
|
|||||||
|
|
||||||
const std::vector<std::string> published_keys = { "layer_height", "retraction_length" };
|
const std::vector<std::string> published_keys = { "layer_height", "retraction_length" };
|
||||||
const std::vector<PublishedMaterialEntry> material_keys = {
|
const std::vector<PublishedMaterialEntry> material_keys = {
|
||||||
{ "PLA", "Generic", "GFL99", 0, { "filament_retraction_length" } }
|
{ "PLA", "Generic", "GFL99", "", 0, { "filament_retraction_length" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
||||||
|
|
||||||
// Filtered config must contain the published keys and identity keys
|
// Filtered config keeps the published and identity keys...
|
||||||
REQUIRE(filtered_cfg.option("layer_height") != nullptr);
|
REQUIRE(filtered_cfg.option("layer_height") != nullptr);
|
||||||
REQUIRE(filtered_cfg.option("retraction_length") != nullptr);
|
REQUIRE(filtered_cfg.option("retraction_length") != nullptr);
|
||||||
REQUIRE(filtered_cfg.option("filament_colour") != nullptr);
|
REQUIRE(filtered_cfg.option("filament_colour") != nullptr);
|
||||||
REQUIRE(filtered_cfg.option("filament_type") != nullptr);
|
REQUIRE(filtered_cfg.option("filament_type") != nullptr);
|
||||||
REQUIRE(filtered_cfg.option("wipe_tower_x") != nullptr);
|
REQUIRE(filtered_cfg.option("wipe_tower_x") != nullptr);
|
||||||
|
|
||||||
// Non-published settings should NOT be in filtered_cfg
|
// ...and drops everything else.
|
||||||
REQUIRE(filtered_cfg.option("sparse_infill_density") == nullptr);
|
REQUIRE(filtered_cfg.option("sparse_infill_density") == nullptr);
|
||||||
REQUIRE(filtered_cfg.option("machine_start_gcode") == nullptr);
|
REQUIRE(filtered_cfg.option("machine_start_gcode") == nullptr);
|
||||||
|
|
||||||
@@ -716,7 +711,7 @@ SCENARIO("Minimal published 3MF serialization filters config and omits embedded
|
|||||||
ScopedTemporaryFile temp(".3mf");
|
ScopedTemporaryFile temp(".3mf");
|
||||||
const std::string test_file = temp.string();
|
const std::string test_file = temp.string();
|
||||||
|
|
||||||
// Create a fake project preset to verify it gets omitted with MinimalPublished
|
// Create a fake project preset to verify MinimalPublished omits it.
|
||||||
Preset preset(Preset::TYPE_PRINT, "TestPrintPreset");
|
Preset preset(Preset::TYPE_PRINT, "TestPrintPreset");
|
||||||
preset.config = full_cfg;
|
preset.config = full_cfg;
|
||||||
std::vector<Preset*> project_presets = { &preset };
|
std::vector<Preset*> project_presets = { &preset };
|
||||||
@@ -753,9 +748,9 @@ SCENARIO("Minimal published 3MF serialization filters config and omits embedded
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filament-publishing v2: a "full publish" entry carries the whole slot's key list. Its vector
|
// A "full publish" entry carries the whole slot's key list: its vector options keep only the
|
||||||
// options keep only the author's slot value; the other slots are masked to their defaults so a
|
// author's slot value, the other slots are masked to their defaults so a slot-1 full publish
|
||||||
// slot-1 full publish does not leak slot 0's data into the file.
|
// does not leak slot 0's data into the file.
|
||||||
SCENARIO("Full-publish entries filter the whole slot and mask the other slots", "[3mf]") {
|
SCENARIO("Full-publish entries filter the whole slot and mask the other slots", "[3mf]") {
|
||||||
GIVEN("a full print configuration with two filament slots") {
|
GIVEN("a full print configuration with two filament slots") {
|
||||||
DynamicPrintConfig full_cfg = DynamicPrintConfig::full_print_config();
|
DynamicPrintConfig full_cfg = DynamicPrintConfig::full_print_config();
|
||||||
@@ -787,9 +782,9 @@ SCENARIO("Full-publish entries filter the whole slot and mask the other slots",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filament-publishing v2: the extended per-entry fields (full dump list, published type and
|
// The extended per-entry fields (full dump list, published type and colour) travel inside the
|
||||||
// colour) travel inside the published_material_keys metadata and round-trip unchanged.
|
// published_material_keys metadata and round-trip unchanged.
|
||||||
SCENARIO("Published 3MF round-trips the filament-publishing-v2 material metadata", "[3mf]") {
|
SCENARIO("Published 3MF round-trips the extended material metadata", "[3mf]") {
|
||||||
GIVEN("a model carrying extended published material keys metadata") {
|
GIVEN("a model carrying extended published material keys metadata") {
|
||||||
Model model;
|
Model model;
|
||||||
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
||||||
@@ -797,7 +792,7 @@ SCENARIO("Published 3MF round-trips the filament-publishing-v2 material metadata
|
|||||||
model.add_default_instances();
|
model.add_default_instances();
|
||||||
|
|
||||||
const std::string material_keys_json =
|
const std::string material_keys_json =
|
||||||
R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99"},"slot":1,"keys":[],"full":true,"full_keys":["filament_retraction_length","filament_colour"],"publish_type":true,"type":"PLA","publish_color":false,"color":""}])";
|
R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99","setting_id":"RFs9eCKYOMUSmvZf"},"slot":1,"keys":[],"full":true,"full_keys":["filament_retraction_length","filament_colour"],"publish_type":true,"type":"PLA","publish_color":false,"color":""}])";
|
||||||
|
|
||||||
model.model_info = std::make_shared<ModelInfo>();
|
model.model_info = std::make_shared<ModelInfo>();
|
||||||
model.model_info->metadata_items["published_material_keys"] = material_keys_json;
|
model.model_info->metadata_items["published_material_keys"] = material_keys_json;
|
||||||
@@ -832,7 +827,7 @@ SCENARIO("Published 3MF round-trips the filament-publishing-v2 material metadata
|
|||||||
REQUIRE(dst_model.model_info != nullptr);
|
REQUIRE(dst_model.model_info != nullptr);
|
||||||
REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json);
|
REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json);
|
||||||
|
|
||||||
// The value must parse back with every filament-publishing-v2 field intact.
|
// The value must parse back with every extended field intact.
|
||||||
nlohmann::json entries = nlohmann::json::parse(material_keys_json);
|
nlohmann::json entries = nlohmann::json::parse(material_keys_json);
|
||||||
REQUIRE(entries.is_array());
|
REQUIRE(entries.is_array());
|
||||||
REQUIRE(entries.size() == 1);
|
REQUIRE(entries.size() == 1);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user