diff --git a/README.md b/README.md index 175426c8182..88d6624d073 100644 --- a/README.md +++ b/README.md @@ -1,237 +1,51 @@ -
+# OrcaSlicer-KX - - OrcaSlicer logo - +**OrcaSlicer builds with extra Anycubic Kobra X patches** -OrcaSlicer%2FOrcaSlicer | Trendshift +Pre-built [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) binaries that bundle in-progress PRs improving Kobra X support — recommended for use with [KX-Bridge](https://gitea.it-drui.de/viewit/KX-Bridge-Release). -[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml) +[![Download](https://img.shields.io/badge/Download-Releases-2EA043?style=for-the-badge&logo=gitea&logoColor=white)](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases) -OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints. -Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection. -

+--- -# Official links and community +## Custom Filament Presets & KX-Bridge -#### Official Website: +📄 [How to create, verify and import custom filament presets](https://gitea.it-drui.de/viewit/KX-Bridge-Release/src/branch/master/docs/filament-preset-bridge-guide.md) -OrcaSlicer.com +--- -#### Github Repository: +## Included patches -GitHub Logo +- **[PR #13372](https://github.com/SoftFever/OrcaSlicer/pull/13372)** — Moonraker / Happy-Hare AMS sync fix +- **[PR #13529](https://github.com/SoftFever/OrcaSlicer/pull/13529)** — Anycubic Kobra X printer profiles +- **[PR #13677](https://github.com/SoftFever/OrcaSlicer/pull/13677)** — Kobra X G-code fix +- **[PR #13719](https://github.com/SoftFever/OrcaSlicer/pull/13719)** — Vendor + name matching for Moonraker filament sync +- KX-specific filament-hint patches (unique `filament_id` for user presets, 3-pass matching, sub-brands) -#### Follow us: +--- -X Logo -YouTube Logo +## Download -#### Join our Discord community: +Get the latest binaries from the [Releases page](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases): -discord logo +- **Linux:** `OrcaSlicer_Linux_V*.AppImage` — `chmod +x` and run +- **Windows:** `OrcaSlicer-Windows-x64.zip` — extract and run `orca-slicer.exe` - - - - -
-⚠️ CAUTION:
-Several clickbait and malicious websites, such as orca-slicer[.]com and orcaslicer[.]net, are pretending to be the official OrcaSlicer site. These sites may redirect you to dangerous downloads or contain misleading information.
-Our only official website is www.orcaslicer.com.

-If you come across any of these in search results, please report them as unsafe or phishing to help keep the community secure with:
- - Google Safe Browsing
- - Microsoft Security Intelligence
- - IPThreat -
+Checksums in `SHA256SUMS.txt`. macOS is not provided here — build from upstream. -

+> ⚠ This is an **alpha build** (OrcaSlicer 2.4.0-alpha). Use at your own risk. -# Main features +--- -- **[Advanced Calibration Tools](https://www.orcaslicer.com/wiki/calibration_guide)** - Comprehensive suite: temperature towers, flow rate, retraction & more for optimal performance. -- **[Precise Wall](https://www.orcaslicer.com/wiki/quality_settings_precision#precise-wall) and [Seam Control](https://www.orcaslicer.com/wiki/quality_settings_seam)** - Adjust outer wall spacing and apply scarf seams to enhance print accuracy. -- **[Sandwich Mode](https://www.orcaslicer.com/wiki/quality_settings_wall_and_surfaces#innerouterinner) and [Polyholes](https://www.orcaslicer.com/wiki/quality_settings_precision#polyholes) Support** - Use varied infill [patterns](https://www.orcaslicer.com/wiki/strength_settings_patterns) and accurate hole shapes for improved clarity. -- **[Overhang](https://www.orcaslicer.com/wiki/quality_settings_overhangs) and [Support Optimization](https://www.orcaslicer.com/wiki#support-settings)** - Modify geometry for printable overhangs with precise support placement. -- **[Granular Controls and Customization](https://www.orcaslicer.com/wiki#process-settings)** - Fine-tune print speed, layer height, pressure, and temperature with precision. -- **Network Printer Support** - Seamless integration with Klipper, PrusaLink, and OctoPrint for remote control. -- **[Mouse Ear Brims](https://www.orcaslicer.com/wiki/others_settings_brim) & [Adaptive Bed Mesh](https://www.orcaslicer.com/wiki/printer_basic_information_adaptive_bed_mesh)** - Automatic brims and adaptive mesh calibration ensure consistent adhesion. -- **User-Friendly Interface** - Intuitive drag-and-drop design with pre-made profiles for popular printers. -- **[Open-Source](https://github.com/OrcaSlicer/OrcaSlicer) & [Community Driven](https://discord.gg/P4VE9UY9gJ)** - Regular updates fueled by continuous community contributions. -- **Wide Printer Compatibility** - Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more. -- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/). +## Why a separate repo? -# Wiki +KX-Bridge itself is hosted at [viewit/KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release). +OrcaSlicer is a different project under a different license (AGPL-3.0) — kept separate so licenses, source provenance and update cycles do not get tangled. -The [wiki](https://www.orcaslicer.com/wiki) aims to provide a detailed explanation of the slicer settings, including how to maximize their use and how to calibrate and set up your printer. +--- -- **[Access the wiki here](https://www.orcaslicer.com/wiki)** -- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/how_to_wiki)** +## License & source -# Download +These builds are derivatives of [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer), licensed under **GNU AGPL-3.0**. +The KX-specific patches live on branch [`build-alpha-pr-kx-filament-hint`](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/build-alpha-pr-kx-filament-hint) of this repository. -## Stable Release - -📥 **[Download the Latest Stable Release](https://github.com/OrcaSlicer/OrcaSlicer/releases/latest)** -Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users. - -## Nightly Builds - -🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds)** -Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated. - -# How to install - -## Windows - -Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases). - -- *For convenience there is also a portable build available.* -
- Troubleshooting - - - *If you have troubles to run the build, you might need to install following runtimes:* - - [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe) - - [Details of this runtime](https://aka.ms/webview2) - - [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703) - - [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe) - - [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe) - - This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142` -
- -Windows Package Manager - -```shell -winget install --id=SoftFever.OrcaSlicer -e -``` - -## Mac - -1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU. -2. Drag OrcaSlicer.app to Application folder. -3. *If you want to run a build from a PR, you also need to follow the instructions below:* - -
- Quarantine - - - Option 1 (You only need to do this once. After that the app can be opened normally.): - - Step 1: Hold _cmd_ and right click the app, from the context menu choose **Open**. - - Step 2: A warning window will pop up, click _Open_ - - - Option 2: - Execute this command in terminal: - - ```shell - xattr -dr com.apple.quarantine /Applications/OrcaSlicer.app - ``` - - - Option 3: - - Step 1: open the app, a warning window will pop up - ![mac_cant_open](./SoftFever_doc/mac_cant_open.png) - - Step 2: in `System Settings` -> `Privacy & Security`, click `Open Anyway`: - ![mac_security_setting](./SoftFever_doc/mac_security_setting.png) -
- -## Linux - -### Flathub (Recommended) - -OrcaSlicer is available through FlatHub: - -Download on Flathub - -Install from the command line: - -```shell -flatpak install flathub com.orcaslicer.OrcaSlicer -flatpak run com.orcaslicer.OrcaSlicer -``` - -It can also be installed through graphical software managers (KDE Discover, GNOME Software, etc.) when Flathub is enabled. Search for **OrcaSlicer** in your software center. - -### AppImage - -AppImages are published for both **x86_64** and **aarch64** (ARM64). Pick the file matching your CPU — the ARM64 build has `aarch64` in its name (e.g. `OrcaSlicer_Linux_AppImage_Ubuntu2404_aarch64_*.AppImage`). - - 1. Download App image from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases). - 2. Double click the downloaded file to run it. - - 3. If you run into trouble executing it, try this command in the terminal: - `chmod +x /path_to_appimage/OrcaSlicer_Linux.AppImage` - -# How to Compile - -All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://www.orcaslicer.com/wiki/how_to_build) page. - -Please refer to the wiki to ensure you're following the latest and most accurate steps for your platform. - -# Klipper Note - -If you're running Klipper, it's recommended to add the following configuration to your `printer.cfg` file. - -```gcode -# Enable object exclusion -[exclude_object] - -# Enable arcs support -[gcode_arcs] -resolution: 0.1 -``` - -# Supports - -**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers. -Their generous support enables me to purchase filaments and other essential 3D printing materials for the project. -Thank you! :) - -## Sponsors - - - - - - -
- - QIDI - - - - BIGTREE TECH - -
- -## Backers: - -**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv) - -## Support me - - - - - -## Some Background - -Open-source slicing has always been built on a tradition of collaboration and attribution. [Slic3r](https://github.com/Slic3r/Slic3r), created by Alessandro Ranellucci and the RepRap community, laid the foundation. [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research built on Slic3r and acknowledged that heritage. [Bambu Studio](https://github.com/bambulab/BambuStudio) in turn forked from PrusaSlicer, and [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill extended PrusaSlicer with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before. - -OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and ideas inspired by CuraSlicer and SuperSlicer. But it has since grown far beyond its origins. Through relentless innovation — introducing advanced calibration tools, precise wall and seam control, tree supports, adaptive slicing, and hundreds of other features — OrcaSlicer has become 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. - -The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co). - -# License - -- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3. -- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license. -- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3. -- The **Bambu networking plugin** is based on non-free libraries from BambuLab. It is optional to the OrcaSlicer and provides extended functionalities for Bambulab printer users. diff --git a/docs/filament-preset-bridge-guide.md b/docs/filament-preset-bridge-guide.md new file mode 100644 index 00000000000..c2933f7b33a --- /dev/null +++ b/docs/filament-preset-bridge-guide.md @@ -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) | diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 948c779137d..9e87a34299c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #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(&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; @@ -1005,9 +1034,6 @@ static std::vector s_Preset_print_options{ "lateral_lattice_angle_1", "lateral_lattice_angle_2", "infill_overhang_angle", - "lightning_overhang_angle", - "lightning_prune_angle", - "lightning_straightening_angle", "top_surface_pattern", "bottom_surface_pattern", "infill_direction", @@ -1072,13 +1098,10 @@ static std::vector s_Preset_print_options{ "print_order", "support_remove_small_overhang", "filename_format", - "outer_wall_filament_id", - "inner_wall_filament_id", + "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament_id", - "internal_solid_filament_id", - "top_surface_filament_id", - "bottom_surface_filament_id", + "sparse_infill_filament", + "solid_infill_filament", "support_filament", "support_interface_filament", "support_interface_not_for_body", @@ -1100,7 +1123,6 @@ static std::vector s_Preset_print_options{ "infill_wall_overlap", "top_bottom_infill_wall_overlap", "bridge_flow", - "bridge_line_width", "internal_bridge_flow", "elefant_foot_compensation", "elefant_foot_compensation_layers", @@ -1165,7 +1187,6 @@ static std::vector s_Preset_print_options{ "small_perimeter_threshold", "bridge_angle", "internal_bridge_angle", - "relative_bridge_angle", "filter_out_gap_fill", "travel_acceleration", "inner_wall_acceleration", @@ -1326,7 +1347,7 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "printer_technology", - "printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", + "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs", "single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", @@ -1337,7 +1358,7 @@ static std::vector s_Preset_printer_options { "scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode", "nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure", "best_object_pos", "head_wrap_detect_zone", - "host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent", + "host_type", "print_host", "printhost_apikey", "bbl_use_printhost", "printer_agent", "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format", @@ -1643,7 +1664,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); } @@ -2833,8 +2857,20 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det if (m_type == Preset::TYPE_PRINT) preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) + else if (m_type == Preset::TYPE_FILAMENT) { preset.config.option("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(&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("printer_settings_id", true)->value = new_name; final_inherits = preset.inherits(); @@ -3855,7 +3891,6 @@ static std::vector s_PhysicalPrinter_opts { "print_host", "print_host_webui", "printhost_apikey", - "flashforge_serial_number", "printhost_cafile", "printhost_port", "printhost_authorization_type", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 1ace4db6856..a78dac2748c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3211,9 +3211,28 @@ unsigned int PresetBundle::sync_ams_list(std::vectorAdd( 0, 0, 0, wxTOP, FromDIP(33)); bool is_zh = wxGetApp().app_config->get("language") == "zh_CN"; diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index 8f35ccf4548..02a3ab34381 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -612,9 +612,10 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS { curr_tray->remain = -1; } - if (tray_it->contains("tray_slot_placeholder")) { - curr_tray->is_slot_placeholder = true; - } + // Reset the placeholder flag every update so a slot that + // was empty before but is now loaded does not stay marked + // as a placeholder (and vice versa). + curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder"); int ams_id_int = 0; int tray_id_int = 0; try @@ -638,6 +639,21 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS catch (...) { } + // An empty slot (placeholder or exist-bit not set) must not + // keep the previously loaded filament's name/type/color from + // an earlier update. The Moonraker/AMS-Lite bridge reports + // empty slots with a placeholder + cleared fields, but the + // tray object is merged (not rebuilt) between updates, so the + // stale values would otherwise survive and the slot would + // render as loaded. Reset them explicitly. + if (curr_tray->is_slot_placeholder || !curr_tray->is_exists) + { + curr_tray->setting_id = ""; + curr_tray->m_fila_type = ""; + curr_tray->color = ""; + curr_tray->sub_brands = ""; + curr_tray->cols.clear(); + } if (tray_it->contains("setting_id")) { curr_tray->filament_setting_id = (*tray_it)["setting_id"].get(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3262d3fe021..e66522d1ad8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3360,6 +3360,9 @@ std::map Sidebar::build_filament_ams_list(MachineObject 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}); + // KX: pass the human-readable sub-brand/filament name through to the + // preset matcher (consumed in PresetBundle, see filament_sub_brands). + tray_config.set_key_value("filament_sub_brands", new ConfigOptionStrings{tray.sub_brands}); std::optional info; if (wxGetApp().preset_bundle) { info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index edec9f903d4..86a7c399ed3 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -267,6 +267,13 @@ int PresetComboBox::update_ams_color() auto color_pack = static_cast(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament) auto color_type = static_cast(cfg->option("filament_colour_type")->clone()); // color type + // KX: guard against out-of-range filament indices (e.g. AMS slots beyond the + // current project's filament count) and default an empty 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 diff --git a/src/slic3r/GUI/Widgets/AMSItem.cpp b/src/slic3r/GUI/Widgets/AMSItem.cpp index 129eec9c618..12282a1bbda 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.cpp +++ b/src/slic3r/GUI/Widgets/AMSItem.cpp @@ -69,7 +69,12 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag, auto it = ams->GetTrays().find(std::to_string(i)); Caninfo info; // tray is exists - if (it != ams->GetTrays().end() && it->second->is_exists) { + // A placeholder slot is explicitly empty (reported by the Moonraker/AMS-Lite + // bridge): treat it as empty regardless of any stale is_exists/filament data + // left over from a previous update, so it renders grey at its position + // instead of showing the previously loaded filament. + if (it != ams->GetTrays().end() && it->second->is_exists + && !it->second->is_slot_placeholder) { if (it->second->is_tray_info_ready()) { info.can_id = it->second->id; info.ctype = it->second->ctype; diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index f1d892134b5..ffa278b04ae 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -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,166 @@ 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(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(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 vendor_match_candidates(std::string vendor) +{ + std::vector 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& 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 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 + } + // Skip the vendor filter for user presets: a preset derived from a + // vendor-specific parent (e.g. "Anycubic PLA Matte @...") resolves + // filament_vendor from the PARENT ("Anycubic"), not from the user + // preset itself ("Tinmorry"), so the filter would wrongly drop it + // and the AMS sync falls back to the vendor preset (Issue #52). The + // strict name match below is discriminating enough for user presets. + if (!preset.is_user() && !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 +609,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& trays) +void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays, int active_lane_index) { // Look up MachineObject via DeviceManager @@ -499,6 +660,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 +692,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 +704,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 +738,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) { std::vector 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 +747,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 +970,51 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments.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 +1029,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index) +bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& 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 +1101,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& 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 +1133,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& 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 +1149,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& 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); diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index a37a6c3fd67..2eeb1099df3 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -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& trays); + void build_ams_payload(int ams_count, int max_lane_index, const std::vector& 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& trays, int& max_lane_index); + bool fetch_hh_filament_info(std::vector& trays, int& max_lane_index, int& active_lane_index); bool fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index); // JSON helper methods diff --git a/tools/verify_build.sh b/tools/verify_build.sh new file mode 100755 index 00000000000..6576bdd299d --- /dev/null +++ b/tools/verify_build.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# Prüft ob ein OrcaSlicer-KX Build-Artefakt die erwarteten Strings enthält. +# Verwendung: +# ./tools/verify_build.sh linux +# ./tools/verify_build.sh windows +# +# 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 " + 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" | head -1 | grep -oP 'SoftFever_VERSION\s+"\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 + # Bevorzugt die unkomprimierte Binary aus dem build/-Baum prüfen (kein FUSE, + # keine AppImage-Extraktion nötig). Fällt nur zurück auf --appimage-extract, + # wenn keine lose Binary gefunden wird. + # Echte ELF-Binary suchen, nicht die 4K-Shell-Wrapper im build/-Root. + BUILD_ROOT="$(dirname "$ARTIFACT")" + BINARY="" + while IFS= read -r cand; do + [[ -z "$cand" ]] && continue + if file -b "$cand" 2>/dev/null | grep -q "ELF"; then + BINARY="$cand" + break + fi + done < <(find "$BUILD_ROOT" -name "orca-slicer" -type f -not -path "*/squashfs-root/*" 2>/dev/null) + + if [[ -z "$BINARY" ]]; then + echo "Keine lose Binary gefunden — extrahiere AppImage..." + chmod +x "$ARTIFACT" + cd "$TMPDIR" + if ! "$ARTIFACT" --appimage-extract bin/orca-slicer >/dev/null 2>&1; then + "$ARTIFACT" --appimage-extract >/dev/null 2>&1 || true + fi + BINARY=$(find "$TMPDIR/squashfs-root" -name "orca-slicer" -type f 2>/dev/null | head -1) + else + echo "Prüfe lose Binary aus build/: $BINARY" + fi + + if [[ -z "$BINARY" || ! -f "$BINARY" ]]; then + echo "FEHLER: orca-slicer Binary weder im build/-Baum noch 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 diff --git a/version.inc b/version.inc index b5c5c07d4cf..aa3ee4609a7 100644 --- a/version.inc +++ b/version.inc @@ -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") +set(SoftFever_VERSION "2.4.0-kx1") string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" SoftFever_VERSION_MATCH ${SoftFever_VERSION}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})