merge: KX-Aenderungen (filament_id-Fixes, Patches, Docs, Version) in OrcaSlicer release/v2.4

# Conflicts:
#	version.inc
This commit is contained in:
thysson2701
2026-06-09 13:22:48 +02:00
10 changed files with 806 additions and 41 deletions

View File

@@ -0,0 +1,356 @@
# Eigene Filament-Presets anlegen, prüfen und mit KX-Bridge verknüpfen
> **Gilt für:** OrcaSlicer-KX v2.4.0-alpha-kx2 oder neuer
---
## Was ist die `filament_id` und warum ist sie wichtig?
Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wird von der KX-Bridge genutzt, um beim AMS-Sync das richtige Preset zuzuordnen.
- System-Presets (z.B. "Polymaker PolyTerra PLA") haben eine feste ID wie `GFL99` oder `OGFL04`.
- **Eigene (User-)Presets** bekommen in OrcaSlicer-KX automatisch eine eindeutige ID, die mit `P` beginnt (z.B. `P3a7f2c1`).
Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert.
> **Achtung — abgeleitete Presets (Issue #52):** Wenn du dein Preset von einem **Hersteller-Preset** ableitest (z.B. "Anycubic PLA Matte"), übernimmt es zunächst die feste Hersteller-ID (z.B. `GFA001`). Beim Sync wird dann fälschlicherweise das Hersteller-Preset statt deines eigenen ausgewählt.
> Ab OrcaSlicer-KX **v2.4.0-alpha-kx3** wird beim Speichern automatisch eine eigene `P...`-ID vergeben — auch für abgeleitete Presets. Hast du das Preset mit einer älteren Version angelegt, **öffne es einmal und speichere es erneut** (Save), damit die `P`-ID generiert wird.
---
## 1. Eigenes Filament-Preset anlegen
1. OrcaSlicer-KX starten
2. Rechts oben im **Filament-Dropdown** ein passendes Basis-Preset wählen (z.B. "Generic PLA" oder ein Hersteller-Preset)
3. Einstellungen nach Wunsch anpassen (Temperaturen, Kühlung, etc.)
4. Auf das **Speichern-Symbol** (Diskette) klicken → **"Save as new preset"**
5. Namen eingeben — z.B. `SUNLU PLA+ 2.0`
> Der Name muss später exakt so in der Bridge eingetragen werden.
6. Drucker auswählen: **Anycubic Kobra X 0.4 nozzle** — wichtig für die Kompatibilität!
7. **Speichern** klicken
8. OrcaSlicer **einmal neu starten** — erst dann wird die `filament_id` dauerhaft gespeichert.
---
## 2. Eindeutige ID prüfen
Nach dem Neustart prüfen, ob die ID korrekt gesetzt wurde:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Die Datei öffnen und nach `filament_id` suchen:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Korrekt: ID beginnt mit `P` gefolgt von 7 Hex-Zeichen
❌ Fehlt oder leer: OrcaSlicer-KX zu alt — Update auf v2.4.0-alpha-kx2 oder neuer
---
## 3. Preset auf einen anderen PC übertragen (Import)
### Exportieren (Quell-PC)
Die Preset-Datei einfach kopieren:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importieren (Ziel-PC)
**Methode A — Datei direkt kopieren:**
1. Die `.json`-Datei in das gleiche Verzeichnis auf dem Ziel-PC kopieren
2. OrcaSlicer neu starten → Preset erscheint im Dropdown
**Methode B — OrcaSlicer Import-Funktion:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Die `.json`-Datei auswählen
3. OrcaSlicer neu starten
> **Wichtig:** Die `filament_id` in der Datei bleibt erhalten — das Preset wird auf dem Ziel-PC genauso erkannt wie auf dem Quell-PC.
---
## 4. Preset in KX-Bridge verknüpfen
1. KX-Bridge UI öffnen
2. **Filament-Verwaltung** → AMS-Slot auswählen
3. Im Feld **Filament-Name** exakt den OrcaSlicer-Preset-Namen eintragen:
```
SUNLU PLA+ 2.0
```
4. Speichern
Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer findet das Preset anhand von Name und `filament_id` → zeigt es korrekt an.
---
## Wichtige Hinweise
| Was | Warum |
|-----|-------|
| Name in OrcaSlicer und Bridge müssen **exakt** übereinstimmen | Groß-/Kleinschreibung und Sonderzeichen werden verglichen |
| Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen |
| Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben |
| **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets |
| Bei von Hersteller-Presets abgeleiteten Presets: **v2.4.0-alpha-kx3** oder neuer | Erst ab dieser Version wird die geerbte Hersteller-ID beim Speichern durch eine eigene `P`-ID ersetzt (Issue #52) |
---
---
# How to Create, Verify and Import Custom Filament Presets for KX-Bridge
> **Requires:** OrcaSlicer-KX v2.4.0-alpha-kx2 or newer
---
## What is the `filament_id` and why does it matter?
Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge uses this ID to match the correct preset during AMS sync.
- System presets (e.g. "Polymaker PolyTerra PLA") have a fixed ID like `GFL99` or `OGFL04`.
- **Custom (user) presets** automatically receive a unique ID starting with `P` (e.g. `P3a7f2c1`) in OrcaSlicer-KX.
Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists.
> **Caution — derived presets (Issue #52):** If you derive your preset from a **vendor preset** (e.g. "Anycubic PLA Matte"), it initially inherits the fixed vendor ID (e.g. `GFA001`). During sync the vendor preset is then incorrectly selected instead of your own.
> As of OrcaSlicer-KX **v2.4.0-alpha-kx3**, a unique `P...` ID is generated automatically on save — including for derived presets. If you created the preset with an older version, **open it once and save it again** so the `P` ID gets generated.
---
## 1. Create a Custom Filament Preset
1. Launch OrcaSlicer-KX
2. Select a suitable base preset from the **filament dropdown** (e.g. "Generic PLA" or a vendor preset)
3. Adjust settings as needed (temperatures, cooling, etc.)
4. Click the **save icon** (floppy disk) → **"Save as new preset"**
5. Enter a name — e.g. `SUNLU PLA+ 2.0`
> This name must be entered in the bridge exactly as typed here.
6. Select printer: **Anycubic Kobra X 0.4 nozzle** — required for compatibility!
7. Click **Save**
8. **Restart OrcaSlicer once** — the `filament_id` is only written permanently after a restart.
---
## 2. Verify the Unique ID
After restarting, check that the ID was set correctly:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Open the file and look for `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correct: ID starts with `P` followed by 7 hex characters
❌ Missing or empty: Your OrcaSlicer-KX version is too old — update to v2.4.0-alpha-kx2 or newer
---
## 3. Transfer a Preset to Another PC (Import)
### Export (source PC)
Simply copy the preset file:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Import (target PC)
**Method A — Copy file directly:**
1. Copy the `.json` file to the same directory on the target PC
2. Restart OrcaSlicer → preset appears in the dropdown
**Method B — OrcaSlicer import function:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Select the `.json` file
3. Restart OrcaSlicer
> **Note:** The `filament_id` inside the file is preserved — the preset will be recognized on the target PC exactly as on the source PC.
---
## 4. Link the Preset in KX-Bridge
1. Open the KX-Bridge UI
2. Go to **Filament Management** → select the AMS slot
3. In the **Filament Name** field, enter the OrcaSlicer preset name exactly:
```
SUNLU PLA+ 2.0
```
4. Save
The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer matches by name and `filament_id` → displays the preset correctly.
---
## Quick Reference
| What | Why |
|------|-----|
| Name in OrcaSlicer and Bridge must match **exactly** | Case and special characters are compared |
| Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving |
| **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart |
| Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets |
| For presets derived from vendor presets: **v2.4.0-alpha-kx3** or newer | Only from this version is the inherited vendor ID replaced with a unique `P` ID on save (Issue #52) |
---
---
# Cómo crear, verificar e importar perfiles de filamento personalizados para KX-Bridge
> **Requiere:** OrcaSlicer-KX v2.4.0-alpha-kx2 o superior
---
## ¿Qué es el `filament_id` y por qué es importante?
Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge usa este ID para asignar el perfil correcto durante la sincronización AMS.
- Los perfiles del sistema (p. ej. "Polymaker PolyTerra PLA") tienen un ID fijo como `GFL99` o `OGFL04`.
- Los **perfiles personalizados (usuario)** reciben automáticamente un ID único que empieza por `P` (p. ej. `P3a7f2c1`) en OrcaSlicer-KX.
Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista.
> **Atención — perfiles derivados (Issue #52):** Si derivas tu perfil de un **perfil de fabricante** (p. ej. "Anycubic PLA Matte"), inicialmente hereda el ID fijo del fabricante (p. ej. `GFA001`). Durante la sincronización se selecciona entonces por error el perfil del fabricante en lugar del tuyo.
> A partir de OrcaSlicer-KX **v2.4.0-alpha-kx3**, se genera automáticamente un ID `P...` único al guardar — también para perfiles derivados. Si creaste el perfil con una versión anterior, **ábrelo una vez y vuelve a guardarlo** (Save) para que se genere el ID `P`.
---
## 1. Crear un perfil de filamento personalizado
1. Iniciar OrcaSlicer-KX
2. Seleccionar un perfil base adecuado en el **menú desplegable de filamento** (p. ej. "Generic PLA" o un perfil de fabricante)
3. Ajustar la configuración según sea necesario (temperaturas, refrigeración, etc.)
4. Hacer clic en el **icono de guardar** (disquete) → **"Save as new preset"**
5. Introducir un nombre — p. ej. `SUNLU PLA+ 2.0`
> Este nombre debe introducirse en la bridge exactamente igual.
6. Seleccionar impresora: **Anycubic Kobra X 0.4 nozzle** — ¡necesario para la compatibilidad!
7. Hacer clic en **Guardar**
8. **Reiniciar OrcaSlicer una vez** — el `filament_id` solo se escribe de forma permanente tras un reinicio.
---
## 2. Verificar el ID único
Tras reiniciar, comprobar que el ID se ha establecido correctamente:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Abrir el archivo y buscar `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correcto: el ID empieza por `P` seguido de 7 caracteres hexadecimales
❌ Falta o está vacío: la versión de OrcaSlicer-KX es demasiado antigua — actualizar a v2.4.0-alpha-kx2 o superior
---
## 3. Transferir un perfil a otro PC (importar)
### Exportar (PC de origen)
Simplemente copiar el archivo del perfil:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importar (PC de destino)
**Método A — Copiar el archivo directamente:**
1. Copiar el archivo `.json` al mismo directorio en el PC de destino
2. Reiniciar OrcaSlicer → el perfil aparece en el menú desplegable
**Método B — Función de importación de OrcaSlicer:**
1. En OrcaSlicer: **File → Import → Import Configs...**
2. Seleccionar el archivo `.json`
3. Reiniciar OrcaSlicer
> **Nota:** El `filament_id` dentro del archivo se conserva — el perfil se reconocerá en el PC de destino exactamente igual que en el de origen.
---
## 4. Vincular el perfil en KX-Bridge
1. Abrir la interfaz de KX-Bridge
2. Ir a **Gestión de filamentos** → seleccionar la ranura AMS
3. En el campo **Nombre de filamento**, introducir el nombre exacto del perfil de OrcaSlicer:
```
SUNLU PLA+ 2.0
```
4. Guardar
La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización → OrcaSlicer busca por nombre y `filament_id` → muestra el perfil correctamente.
---
## Referencia rápida
| Qué | Por qué |
|-----|---------|
| El nombre en OrcaSlicer y en Bridge debe coincidir **exactamente** | Se comparan mayúsculas, minúsculas y caracteres especiales |
| El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar |
| **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio |
| Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario |
| Para perfiles derivados de perfiles de fabricante: **v2.4.0-alpha-kx3** o superior | Solo a partir de esta versión se reemplaza el ID heredado del fabricante por un ID `P` único al guardar (Issue #52) |

View File

@@ -41,6 +41,8 @@
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <boost/uuid/detail/md5.hpp>
#include <boost/algorithm/hex.hpp>
#include "libslic3r.h"
#include "Utils.hpp"
@@ -636,6 +638,31 @@ void Preset::save(DynamicPrintConfig* parent_config)
//BBS: add project embedded preset logic
if (this->is_project_embedded)
return;
// Generate a unique filament_id for user filament presets that don't have one yet.
// Inherited presets (e.g. "My PLA" inheriting "Generic PLA @System") previously had
// no filament_id which caused AMS sync to fall back to the parent's Generic ID.
// Also generate a new ID if filament_id is inherited from the parent (== base_id).
// This happens when a user preset is saved for the first time without having its own ID.
// A user preset needs its own filament_id if:
// - it has no filament_id at all, OR
// - its filament_id does not start with "P" (user preset IDs start with "P",
// system IDs start with GFL/OGFL/GFA/etc.)
bool needs_unique_filament_id = this->is_user() && !this->name.empty() &&
this->type == Preset::TYPE_FILAMENT &&
(this->filament_id.empty() || this->filament_id.front() != 'P');
if (needs_unique_filament_id) {
boost::uuids::detail::md5 hash;
boost::uuids::detail::md5::digest_type digest;
hash.process_bytes(this->name.data(), this->name.size());
hash.get_digest(digest);
const auto char_digest = reinterpret_cast<const char *>(&digest);
std::string result;
boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result));
this->filament_id = "P" + result.substr(0, 7);
BOOST_LOG_TRIVIAL(info) << "Preset::save: generated filament_id='" << this->filament_id << "' for user preset '" << this->name << "'";
}
//BBS: change to json format
//this->config.save(this->file);
std::string from_str;
@@ -691,6 +718,8 @@ void Preset::save(DynamicPrintConfig* parent_config)
opt_dst->set(opt_src);
}
}
if (!filament_id.empty())
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
} else if (!filament_id.empty() && inherits().empty()) {
DynamicPrintConfig temp_config = config;
@@ -1643,7 +1672,10 @@ void PresetCollection::load_presets(
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
// Only inherit filament_id from parent if this preset has no own ID in JSON.
// User presets with a P-prefix ID (generated by Preset::save) must keep their own ID.
if (preset.filament_id.empty())
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
@@ -2825,8 +2857,20 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
if (m_type == Preset::TYPE_PRINT)
preset.config.option<ConfigOptionString>("print_settings_id", true)->value = new_name;
else if (m_type == Preset::TYPE_FILAMENT)
else if (m_type == Preset::TYPE_FILAMENT) {
preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = new_name;
// Generate a unique filament_id for user presets that don't have one yet (PR #13315).
if (preset.filament_id.empty() || preset.filament_id.front() != 'P') {
boost::uuids::detail::md5 hash;
boost::uuids::detail::md5::digest_type digest;
hash.process_bytes(new_name.data(), new_name.size());
hash.get_digest(digest);
const auto char_digest = reinterpret_cast<const char *>(&digest);
std::string result;
boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result));
preset.filament_id = "P" + result.substr(0, 7);
}
}
else if (m_type == Preset::TYPE_PRINTER)
preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = new_name;
final_inherits = preset.inherits();

View File

@@ -3211,9 +3211,28 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
bool has_type = false;
auto filament_type = ams.opt_string("filament_type", 0u);
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
auto sub_brands = ams.opt_string("filament_sub_brands", 0u);
// If sub_brands (filament brand name) is known, prefer a preset whose name starts with it
// to resolve ID collisions where two different vendors share the same filament_id.
auto iter = filaments.end();
if (!sub_brands.empty() && !filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type, &sub_brands](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id
&& boost::algorithm::istarts_with(f.name, sub_brands); });
}
if (iter == filaments.end()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
}
// Also search user presets (not base presets) with matching filament_id.
// User presets have P-prefix IDs and are not returned by get_preset_base(f) == &f.
if (iter == filaments.end() && !filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && !f.is_system && f.filament_id == filament_id; });
}
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
if (!filament_type.empty()) {

View File

@@ -269,6 +269,7 @@ AboutDialog::AboutDialog()
text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before."));
text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features."));
text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry."));
text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). GNU AGPL-3.0: upstream https://github.com/SoftFever/OrcaSlicer + KX patches https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/build-alpha-pr-kx-filament-hint"));
text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33));
bool is_zh = wxGetApp().app_config->get("language") == "zh_CN";

View File

@@ -3293,11 +3293,13 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
tray_config.set_key_value("slot_id", new ConfigOptionStrings{slot_id});
tray_config.set_key_value("filament_type", new ConfigOptionStrings{tray.m_fila_type});
tray_config.set_key_value("tray_name", new ConfigOptionStrings{ name });
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX))});
const std::string filament_color = into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX));
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{filament_color});
tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)});
tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists});
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder});
tray_config.set_key_value("filament_sub_brands", new ConfigOptionStrings{tray.sub_brands});
std::optional<FilamentBaseInfo> info;
if (wxGetApp().preset_bundle) {
info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id);
@@ -3306,12 +3308,19 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
for (int i = 0; i < tray.cols.size(); ++i) {
tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.push_back(into_u8(wxColour("#" + tray.cols[i]).GetAsString(wxC2S_HTML_SYNTAX)));
}
if (tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.empty() && !filament_color.empty()) {
tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.push_back(filament_color);
}
return tray_config;
};
if (obj->ams_support_virtual_tray) {
int extruder = 0x10000; // Main (first) extruder at right
for (auto & vt_tray : obj->vt_slot) {
if (!vt_tray.is_exists && vt_tray.setting_id.empty() && vt_tray.m_fila_type.empty() && vt_tray.color.empty()) {
extruder = 0;
continue;
}
filament_ams_list.emplace(extruder + stoi(vt_tray.id), build_tray_config(vt_tray, "Ext",vt_tray.id, "0"));//254 or 255
extruder = 0;
}
@@ -3468,7 +3477,9 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
p->plater->pop_warning_and_go_to_device_page("", Plater::PrinterWarningType::EMPTY_FILAMENT, _L("Sync printer information"));
return;
}
if (!wxGetApp().plater()->is_same_printer_for_connected_and_selected()) {
auto* agent = wxGetApp().getDeviceManager()->get_agent();
const bool direct_pull_sync = agent && agent->get_filament_sync_mode() == FilamentSyncMode::pull;
if (!direct_pull_sync && !wxGetApp().plater()->is_same_printer_for_connected_and_selected()) {
return;
}
std::string ams_filament_ids = wxGetApp().app_config->get("ams_filament_ids", p->ams_list_device);
@@ -3476,31 +3487,39 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
if (!ams_filament_ids.empty()) {
boost::algorithm::split(list2, ams_filament_ids, boost::algorithm::is_any_of(","));
}
wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg
SyncAmsInfoDialog::SyncInfo temp_info;
temp_info.use_dialog_pos = false;
temp_info.cancel_text_to_later = is_from_big_sync_btn;
if (m_sync_dlg == nullptr) {
m_sync_dlg = new SyncAmsInfoDialog(this, temp_info);
SyncAmsInfoDialog::SyncResult sync_result;
int dlg_res{(int) wxID_YES};
if (direct_pull_sync) {
sync_result.direct_sync = true;
sync_result.is_same_printer = true;
} else {
m_sync_dlg->set_info(temp_info);
}
int dlg_res{(int) wxID_CANCEL};
if (m_sync_dlg->is_need_show()) {
m_sync_dlg->deal_only_exist_ext_spool(obj);
if (m_sync_dlg->is_dirty_filament()) {
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true);
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
dynamic_filament_list.update();
wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg
SyncAmsInfoDialog::SyncInfo temp_info;
temp_info.use_dialog_pos = false;
temp_info.cancel_text_to_later = is_from_big_sync_btn;
if (m_sync_dlg == nullptr) {
m_sync_dlg = new SyncAmsInfoDialog(this, temp_info);
} else {
m_sync_dlg->set_info(temp_info);
}
dlg_res = (int) wxID_CANCEL;
if (m_sync_dlg->is_need_show()) {
m_sync_dlg->deal_only_exist_ext_spool(obj);
if (m_sync_dlg->is_dirty_filament()) {
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true);
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
dynamic_filament_list.update();
}
m_sync_dlg->set_check_dirty_fialment(false);
dlg_res = m_sync_dlg->ShowModal();
} else {
dlg_res =(int) wxID_YES;
}
m_sync_dlg->set_check_dirty_fialment(false);
dlg_res = m_sync_dlg->ShowModal();
} else {
dlg_res =(int) wxID_YES;
}
if (dlg_res == wxID_CANCEL)
return;
auto sync_result = m_sync_dlg->get_result();
if (!direct_pull_sync)
sync_result = m_sync_dlg->get_result();
if (!sync_result.is_same_printer) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "check error: sync_result.is_same_printer value is false";
return;

View File

@@ -267,6 +267,11 @@ int PresetComboBox::update_ams_color()
auto color_pack = static_cast<ConfigOptionStrings *>(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament)
auto color_type = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour_type")->clone()); // color type
if (m_filament_idx >= color_head->values.size()) color_head->values.resize(m_filament_idx + 1);
if (m_filament_idx >= color_pack->values.size()) color_pack->values.resize(m_filament_idx + 1);
if (m_filament_idx >= color_type->values.size()) color_type->values.resize(m_filament_idx + 1);
if (ctype.empty()) ctype = "1";
color_head->values[m_filament_idx] = color;
color_type->values[m_filament_idx] = ctype;
std::string color_str = ""; // Translate multi color info to config storage format

View File

@@ -4,6 +4,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "../GUI/DeviceCore/DevStorage.h"
#include "../GUI/DeviceCore/DevFirmware.h"
@@ -88,6 +89,160 @@ std::string map_moonraker_state(std::string state)
return "IDLE";
}
std::string normalize_filament_name_for_match(const std::string& input)
{
std::string normalized = input;
boost::trim(normalized);
// Ignore profile suffixes like " @0.4 nozzle" for name matching.
if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) {
normalized = normalized.substr(0, suffix_pos);
}
// Remove non-name symbols (e.g. trademark signs) while preserving separators
// commonly used in filament names.
std::string cleaned;
cleaned.reserve(normalized.size());
for (unsigned char c : normalized) {
if (std::isalnum(c) || c == '-' || c == '+' || c == '/' || std::isspace(c)) {
cleaned.push_back(static_cast<char>(std::toupper(c)));
} else {
cleaned.push_back(' ');
}
}
// Collapse repeated whitespace.
std::string collapsed;
collapsed.reserve(cleaned.size());
bool prev_space = true;
for (unsigned char c : cleaned) {
if (std::isspace(c)) {
if (!prev_space) {
collapsed.push_back(' ');
}
prev_space = true;
} else {
collapsed.push_back(static_cast<char>(c));
prev_space = false;
}
}
boost::trim(collapsed);
return collapsed;
}
bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate)
{
if (wanted == candidate) {
return true;
}
// Allow lane names with trailing color descriptors, e.g.:
// "ELEGOO RAPID PETG GREY" -> "ELEGOO RAPID PETG".
if (!candidate.empty() && boost::starts_with(wanted, candidate + " ")) {
return true;
}
return false;
}
std::vector<std::string> vendor_match_candidates(std::string vendor)
{
std::vector<std::string> candidates;
boost::trim(vendor);
if (vendor.empty()) {
return candidates;
}
candidates.push_back(vendor);
// Also try first token (e.g. "Bambu Lab" -> "Bambu") without hardcoded aliases.
const auto first_space = vendor.find_first_of(" \t");
if (first_space != std::string::npos) {
std::string first = vendor.substr(0, first_space);
boost::trim(first);
if (!first.empty() && !boost::iequals(first, vendor)) {
candidates.push_back(first);
}
}
return candidates;
}
std::string filament_id_by_name(const Slic3r::PresetCollection& filaments,
const std::string& filament_name,
const std::vector<std::string>& vendor_filters = {})
{
if (filament_name.empty()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name";
return "";
}
const std::string wanted = normalize_filament_name_for_match(filament_name);
std::vector<std::string> normalized_vendor_filters;
normalized_vendor_filters.reserve(vendor_filters.size());
for (const auto& vendor_filter : vendor_filters) {
const std::string normalized_vendor = normalize_filament_name_for_match(vendor_filter);
if (!normalized_vendor.empty()) {
normalized_vendor_filters.push_back(normalized_vendor);
}
}
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name
<< "' normalized='" << wanted << "' vendor_filters=" << normalized_vendor_filters.size();
// Two-pass search: Pass 1 = compatible presets only (ideal), Pass 2 = all visible presets
// (fallback for vendors like eSUN/Eryone that have no printer-specific Kobra X profile).
for (int pass = 1; pass <= 3; ++pass) {
for (size_t i = 0; i < filaments.size(); ++i) {
const auto& preset = filaments.preset(i);
// Pass 1: compatible + visible only
// Pass 2: visible but not necessarily compatible (vendors without printer-specific profile)
// Pass 3: any preset including invisible (vendors not installed as printer)
if (pass <= 2 && !preset.is_visible) {
continue;
}
// User presets (created via "Save As") may have no filament_id yet.
// We still match them by name and use their name as the identifier.
const std::string preset_id = preset.filament_id.empty() ? preset.name : preset.filament_id;
if (pass == 1 && !preset.is_compatible) {
continue; // Pass 1: only compatible presets
}
if (!normalized_vendor_filters.empty()) {
const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u));
bool vendor_match = false;
for (const auto& vendor_filter : normalized_vendor_filters) {
if (preset_vendor == vendor_filter) {
vendor_match = true;
break;
}
}
if (!vendor_match) {
if (pass == 1) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name
<< "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'";
}
continue;
}
}
const std::string candidate = normalize_filament_name_for_match(preset.name);
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare (pass=" << pass << ") preset='" << preset.name
<< "' normalized='" << candidate << "' preset_id='" << preset_id << "'";
if (filament_name_match_relaxed(wanted, candidate)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched (pass=" << pass << ") requested='" << filament_name
<< "' normalized='" << wanted << "' to preset='" << preset.name
<< "' preset_id='" << preset_id << "'";
return preset_id;
}
}
if (pass == 1) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 1 (compatible) found no match for '"
<< filament_name << "', trying pass 2 (all visible)";
} else if (pass == 2) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 2 (all visible) found no match for '"
<< filament_name << "', trying pass 3 (all presets incl. invisible)";
}
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name
<< "' normalized='" << wanted << "'";
return "";
}
} // namespace
namespace Slic3r {
@@ -448,7 +603,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
return BAMBU_NETWORK_SUCCESS;
}
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays)
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index)
{
// Look up MachineObject via DeviceManager
@@ -499,6 +654,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
tray_json["tray_info_idx"] = tray->tray_info_idx;
tray_json["tray_type"] = tray->tray_type;
tray_json["tray_sub_brands"] = tray->tray_sub_brands.empty() ? tray->tray_type : tray->tray_sub_brands;
tray_json["tray_color"] = normalize_color_value(tray->tray_color);
// Add temperature data if provided
@@ -530,6 +686,11 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
ams_json["ams"] = ams_array;
ams_json["ams_exist_bits"] = ams_exist_ss.str();
ams_json["tray_exist_bits"] = tray_exist_ss.str();
if (active_lane_index >= 0) {
const std::string active_lane = std::to_string(active_lane_index);
ams_json["tray_now"] = active_lane;
ams_json["tray_tar"] = active_lane;
}
// Wrap in the expected structure for ParseV1_0
nlohmann::json print_json = nlohmann::json::object();
@@ -537,6 +698,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
// Call the parser to populate DevFilaSystem
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false);
ExtderSystemParser::ParseV1_0(print_json, obj->GetExtderSystem());
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
// Set printer_type so update_sync_status() can match it against the preset's printer type.
@@ -570,6 +732,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
{
std::vector<AmsTrayData> trays;
int max_lane_index = 0;
int active_lane_index = -1;
// Try Moonraker filament data (more generic, supports any filament changer
// software that reports lane data to Moonraker like AFC and recent Happy
@@ -578,16 +741,16 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with "
<< (max_lane_index + 1) << " lanes";
int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays);
build_ams_payload(ams_count, max_lane_index, trays, active_lane_index);
return true;
}
// Attempt Happy Hare first (more widely adopted, supports more filament changers)
if (fetch_hh_filament_info(trays, max_lane_index)) {
if (fetch_hh_filament_info(trays, max_lane_index, active_lane_index)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
<< (max_lane_index + 1) << " gates";
int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays);
build_ams_payload(ams_count, max_lane_index, trays, active_lane_index);
return true;
}
@@ -801,13 +964,51 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
tray.slot_index = lane_index;
tray.tray_color = safe_json_string(lane_obj, "color");
tray.tray_type = safe_json_string(lane_obj, "material");
const std::string lane_name = safe_json_string(lane_obj, "name");
const std::string lane_vendor = safe_json_string(lane_obj, "vendor_name");
tray.tray_sub_brands = lane_name;
tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp");
tray.has_filament = !tray.tray_type.empty();
auto* bundle = GUI::wxGetApp().preset_bundle;
tray.tray_info_idx = bundle
? bundle->filaments.filament_id_by_type(tray.tray_type)
: map_filament_type_to_generic_id(tray.tray_type);
if (bundle) {
const auto vendor_candidates = vendor_match_candidates(lane_vendor);
auto match_with_vendor_prefix = [&](const std::string& suffix) -> std::string {
if (suffix.empty()) {
return "";
}
for (const auto& vendor_candidate : vendor_candidates) {
const std::string requested = vendor_candidate + " " + suffix;
std::string match_id = filament_id_by_name(bundle->filaments, requested, vendor_candidates);
if (!match_id.empty()) {
return match_id;
}
}
return "";
};
// Prefer the most specific lane identity first, then broader vendor/material mapping.
tray.tray_info_idx = match_with_vendor_prefix(lane_name);
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = match_with_vendor_prefix(tray.tray_type);
}
if (tray.tray_info_idx.empty() && !lane_name.empty()) {
tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name, vendor_candidates);
}
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' vendor='" << lane_vendor << "' vendor_candidates=" << vendor_candidates.size()
<< "' name='" << lane_name
<< "' mapped_by='preset_bundle' tray_info_idx='" << tray.tray_info_idx << "'";
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' mapped_by='generic_fallback' tray_info_idx='" << tray.tray_info_idx << "'";
}
max_lane_index = std::max(max_lane_index, lane_index);
trays.push_back(tray);
@@ -822,7 +1023,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
}
// Fetch filament info from Happy Hare MMU
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index)
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index)
{
// Query Happy Hare MMU status
std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
@@ -894,8 +1095,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Get arrays
const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array();
const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array();
const auto& gate_filament_name = mmu.contains("gate_filament_name") ? mmu["gate_filament_name"] : nlohmann::json::array();
const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array();
const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array();
const int active_gate = safe_json_int(mmu, "gate");
active_lane_index = active_gate;
std::string active_filament_name;
if (mmu.contains("active_filament") && mmu["active_filament"].is_object()) {
active_filament_name = safe_json_string(mmu["active_filament"], "filament_name");
if (active_filament_name.empty()) {
active_filament_name = safe_json_string(mmu["active_filament"], "material");
}
}
if (!gate_status.is_array() || !gate_material.is_array() ||
!gate_color.is_array() || !gate_temperature.is_array()) {
@@ -916,8 +1127,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Extract gate data
std::string material = safe_array_string(gate_material, gate_idx);
std::string filament_name = safe_array_string(gate_filament_name, gate_idx);
std::string color = safe_array_string(gate_color, gate_idx);
int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
// For the active gate, prefer active_filament from MMU state.
if (gate_idx == active_gate && !active_filament_name.empty()) {
filament_name = active_filament_name;
}
// Skip if no material type (empty gate)
if (material.empty()) {
@@ -927,15 +1143,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
AmsTrayData tray;
tray.slot_index = gate_idx;
tray.tray_type = material;
tray.tray_sub_brands = filament_name;
tray.tray_color = color;
tray.nozzle_temp = nozzle_temp;
tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
tray.has_filament = true;
auto* bundle = GUI::wxGetApp().preset_bundle;
tray.tray_info_idx = bundle
? bundle->filaments.filament_id_by_type(tray.tray_type)
: map_filament_type_to_generic_id(tray.tray_type);
if (bundle) {
tray.tray_info_idx = filament_id_by_name(bundle->filaments, filament_name);
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
}
max_lane_index = std::max(max_lane_index, gate_idx);
trays.push_back(tray);

View File

@@ -92,14 +92,16 @@ protected:
int slot_index = 0; // 0-based slot index
bool has_filament = false;
std::string tray_type; // Material type (e.g., "PLA", "ASA")
std::string tray_sub_brands; // Human-readable filament name
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
std::string tray_info_idx; // Setting ID (optional)
std::string filament_vendor; // Vendor hint from bridge (optional, KX-Bridge sendet das)
int bed_temp = 0; // Optional
int nozzle_temp = 0; // Optional
};
// Build ams JSON and call parser
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index = -1);
// Methods that derived classes may need to override or access
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
@@ -161,7 +163,7 @@ private:
uint64_t generation);
// System-specific filament fetch methods
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index);
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
// JSON helper methods

97
tools/verify_build.sh Executable file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Prüft ob ein OrcaSlicer-KX Build-Artefakt die erwarteten Strings enthält.
# Verwendung:
# ./tools/verify_build.sh linux <path/to/OrcaSlicer.AppImage>
# ./tools/verify_build.sh windows <path/to/OrcaSlicer-package.zip>
#
# Gibt Exit-Code 0 bei Erfolg, 1 bei Fehler zurück.
set -euo pipefail
PLATFORM="${1:-}"
ARTIFACT="${2:-}"
if [[ -z "$PLATFORM" || -z "$ARTIFACT" ]]; then
echo "Verwendung: $0 <linux|windows> <artefakt-pfad>"
exit 1
fi
if [[ ! -f "$ARTIFACT" ]]; then
echo "FEHLER: Artefakt nicht gefunden: $ARTIFACT"
exit 1
fi
# Versionstring aus version.inc lesen
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
VERSION=$(grep 'set(SoftFever_VERSION' "$REPO_ROOT/version.inc" | grep -oP '"\K[^"]+')
if [[ -z "$VERSION" ]]; then
echo "FEHLER: Konnte VERSION nicht aus version.inc lesen"
exit 1
fi
echo "=== OrcaSlicer-KX Build-Verifikation ==="
echo "Plattform : $PLATFORM"
echo "Artefakt : $ARTIFACT"
echo "Erwartet : $VERSION"
echo "========================================"
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
CHECKS=(
"$VERSION"
"KX-Bridge"
"filament_sub_brands"
)
BINARY=""
if [[ "$PLATFORM" == "linux" ]]; then
echo "Extrahiere AppImage..."
chmod +x "$ARTIFACT"
cd "$TMPDIR"
"$ARTIFACT" --appimage-extract bin/orca-slicer 2>/dev/null || \
"$ARTIFACT" --appimage-extract 2>/dev/null
BINARY=$(find "$TMPDIR/squashfs-root" -name "orca-slicer" -type f | head -1)
if [[ -z "$BINARY" ]]; then
echo "FEHLER: orca-slicer Binary nicht im AppImage gefunden"
exit 1
fi
elif [[ "$PLATFORM" == "windows" ]]; then
echo "Extrahiere Windows ZIP..."
unzip -q "$ARTIFACT" "OrcaSlicer.dll" -d "$TMPDIR" 2>/dev/null || true
BINARY="$TMPDIR/OrcaSlicer.dll"
if [[ ! -f "$BINARY" ]]; then
echo "FEHLER: OrcaSlicer.dll nicht im ZIP gefunden"
exit 1
fi
else
echo "FEHLER: Unbekannte Plattform '$PLATFORM' (erwartet: linux oder windows)"
exit 1
fi
echo "Binary : $BINARY ($(du -sh "$BINARY" | cut -f1))"
echo ""
FAILED=0
for needle in "${CHECKS[@]}"; do
if grep -qaF "$needle" "$BINARY" 2>/dev/null; then
echo " [OK] $needle"
else
echo " [FAIL] $needle — NICHT GEFUNDEN"
FAILED=1
fi
done
echo ""
if [[ $FAILED -eq 0 ]]; then
echo "=== VERIFIKATION ERFOLGREICH ==="
exit 0
else
echo "=== VERIFIKATION FEHLGESCHLAGEN — Upload abgebrochen ==="
exit 1
fi

View File

@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0")
endif()
set(SoftFever_VERSION "2.4.0-beta")
set(SoftFever_VERSION "2.4.0-beta-kx3")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})