Compare commits

...
Author SHA1 Message Date
Argo 7e63fce706 Fix 3MF import crash for silent-mode machine limits with legacy vector sizes (#12289)
Normalize printer_options_with_variant_2 (stride=2) machine limit vectors during preset merge to handle legacy 3MF/projects that store only a single (normal,silent) pair despite multiple printer variants, preventing set_with_restore() size-mismatch crashes.

Crash error message during 3MF import as project:

<img width="833" height="274" alt="image" src="https://github.com/user-attachments/assets/f92148a9-98c6-47b7-a0ab-d5ac8b1f2be4" />

3MF attached that causes the bug. 
[cube.3mf.zip](https://github.com/user-attachments/files/25318309/cube.3mf.zip)
2026-02-15 20:50:17 +08:00
Matthias BlaicherandSoftFever 380f4b4a18 Fix EGL/GLX mismatch causing blank 3D preview on Linux (#12308)
- Add configurable GLEW_USE_EGL option (default OFF) to match wxWidgets
- Explicitly set wxUSE_GLCANVAS_EGL=OFF for vendored wxWidgets build
- Add compile-time check to detect EGL/GLX backend mismatch between
  GLEW and wxWidgets, preventing silent rendering failures

The bug occurred when GLEW was compiled with EGL support (using
eglGetProcAddress) but wxWidgets created GLX contexts. This mismatch
caused OpenGL function pointers to fail loading, resulting in blank
3D model preview.

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-02-15 16:27:07 +08:00
SoftFever 240cf9ab5d bump version to 2.3.2-beta2 2026-02-15 14:31:57 +08:00
Branden Cash 05cb8b4d89 feat(MoonrakerPrinterAgent): support Happy Hare as alternative to AFC for filament sync (#12307)
# Description


# Screenshots/Recordings/Graphs


https://github.com/user-attachments/assets/5558b4be-24eb-4f2d-83fd-8482560a0014

<img width="445" height="285" alt="Screenshot 2026-02-14 at 7 31 57 PM" src="https://github.com/user-attachments/assets/e71fee66-05da-4f9c-8123-0f52e93f0ebb" />


## Tests

Removed configured filaments and pressed the sync button. Observed the filaments configured in my system were populated.
2026-02-15 14:31:29 +08:00
SoftFever 897a3e915f bump profile version to "02.03.02.40" (#12309) 2026-02-15 14:26:33 +08:00
Ian Bassi 586e96479a Linux: Repaired VFA tower (#12290)
* Update CalibUtils.cpp

* VFA.drc case to vfa.drc
2026-02-15 14:26:33 +08:00
Ian Bassi 0879b2079b Spanish minor update (#12268) 2026-02-15 14:26:33 +08:00
Ian Bassi c4801250ea Spanish Update (#12254)
* Spanish Update
2026-02-15 14:26:33 +08:00
SoftFever f0386d981f Revert "Switch to self hosted mac runner (#12024)"
This reverts commit f1212be6bb.
2026-02-14 18:11:06 +08:00
Argo cd5c8f2ad0 Ported Bambu Studio wipe tower interface features (with improved preheat and cooldown behaviour) - NEW (#12266)
Wipe tower interface features and preheat fixes

Fresh PR branch rebuilt on upstream/main (squash of origin/BBL-studio-wipe-tower-merge) to avoid merge-history issues.
2026-02-13 22:57:55 +08:00
SoftFever 2d0a0568e7 Change SoftFever version from '2.3.2-dev' to '2.3.2-beta' 2026-02-13 19:43:45 +08:00
91 changed files with 4590 additions and 2591 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
matrix: matrix:
include: include:
- os: windows-latest - os: windows-latest
- os: orca-macos-arm64 - os: macos-14
arch: arm64 arch: arm64
# Don't run scheduled builds on forks: # Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: set outputs - name: set outputs
id: set_outputs id: set_outputs
env: env:
dep-folder-name: ${{ inputs.os != 'orca-macos-arm64' && '/OrcaSlicer_dep' || '' }} dep-folder-name: ${{ inputs.os != 'macos-14' && '/OrcaSlicer_dep' || '' }}
output-cmd: ${{ inputs.os == 'windows-latest' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}} output-cmd: ${{ inputs.os == 'windows-latest' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
run: | run: |
echo cache-key=${{ inputs.os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }} echo cache-key=${{ inputs.os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
+7 -7
View File
@@ -74,18 +74,18 @@ jobs:
cd ${{ github.workspace }}/deps/build cd ${{ github.workspace }}/deps/build
- name: Build on Mac ${{ inputs.arch }} - name: Build on Mac ${{ inputs.arch }}
if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
# brew install automake texinfo libtool brew install automake texinfo libtool
# brew list brew list
# brew uninstall --ignore-dependencies zstd brew uninstall --ignore-dependencies zstd
./build_release_macos.sh -dx -a universal -t 10.15 ./build_release_macos.sh -dx -a universal -t 10.15 -1
for arch in arm64 x86_64; do for arch in arm64 x86_64; do
(cd "${{ github.workspace }}/deps/build/${arch}" && \ (cd "${{ github.workspace }}/deps/build/${arch}" && \
find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +) find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +)
done done
# brew install zstd brew install zstd
- name: Apt-Install Dependencies - name: Apt-Install Dependencies
@@ -104,7 +104,7 @@ jobs:
# Upload Artifacts # Upload Artifacts
# - name: Upload Mac ${{ inputs.arch }} artifacts # - name: Upload Mac ${{ inputs.arch }} artifacts
# if: inputs.os == 'orca-macos-arm64' # if: inputs.os == 'macos-14'
# uses: actions/upload-artifact@v6 # uses: actions/upload-artifact@v6
# with: # with:
# name: OrcaSlicer_dep_mac_${{ env.date }} # name: OrcaSlicer_dep_mac_${{ env.date }}
+18 -18
View File
@@ -86,29 +86,29 @@ jobs:
# Mac # Mac
- name: Install tools mac - name: Install tools mac
if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
run: | run: |
# brew install libtool brew install libtool
# brew list brew list
mkdir -p ${{ github.workspace }}/deps/build mkdir -p ${{ github.workspace }}/deps/build
# - name: Free disk space - name: Free disk space
# if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
# run: | run: |
# df -hI /dev/disk3s1s1 df -hI /dev/disk3s1s1
# sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} + sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} +
# sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/* sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/*
# df -hI /dev/disk3s1s1 df -hI /dev/disk3s1s1
- name: Build slicer mac - name: Build slicer mac
if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
./build_release_macos.sh -s -n -x -a universal -t 10.15 ./build_release_macos.sh -s -n -x -a universal -t 10.15 -1
# Thanks to RaySajuuk, it's working now # Thanks to RaySajuuk, it's working now
- name: Sign app and notary - name: Sign app and notary
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && inputs.os == 'orca-macos-arm64' if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && inputs.os == 'macos-14'
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
env: env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
@@ -162,7 +162,7 @@ jobs:
fi fi
- name: Create DMG without notary - name: Create DMG without notary
if: github.ref != 'refs/heads/main' && inputs.os == 'orca-macos-arm64' if: github.ref != 'refs/heads/main' && inputs.os == 'macos-14'
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
@@ -181,14 +181,14 @@ jobs:
fi fi
- name: Upload artifacts mac - name: Upload artifacts mac
if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: OrcaSlicer_Mac_universal_${{ env.ver }} name: OrcaSlicer_Mac_universal_${{ env.ver }}
path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
- name: Upload OrcaSlicer_profile_validator DMG mac - name: Upload OrcaSlicer_profile_validator DMG mac
if: inputs.os == 'orca-macos-arm64' if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: OrcaSlicer_profile_validator_Mac_universal_DMG_${{ env.ver }} name: OrcaSlicer_profile_validator_Mac_universal_DMG_${{ env.ver }}
@@ -196,7 +196,7 @@ jobs:
if-no-files-found: ignore if-no-files-found: ignore
- name: Deploy Mac release - name: Deploy Mac release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && inputs.os == 'orca-macos-arm64' if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && inputs.os == 'macos-14'
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label} upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
@@ -207,7 +207,7 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Mac OrcaSlicer_profile_validator DMG release - name: Deploy Mac OrcaSlicer_profile_validator DMG release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && inputs.os == 'orca-macos-arm64' if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && inputs.os == 'macos-14'
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label} upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
+2
View File
@@ -5,6 +5,8 @@ find_package(OpenGL QUIET REQUIRED)
orcaslicer_add_cmake_project( orcaslicer_add_cmake_project(
GLEW GLEW
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
CMAKE_ARGS
-DGLEW_USE_EGL=OFF
) )
if (MSVC) if (MSVC)
+10 -2
View File
@@ -3,9 +3,17 @@ project(GLEW)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
if(OpenGL_EGL_FOUND) # Allow parent project to control EGL usage.
message(STATUS "building GLEW for EGL (hope that wxWidgets agrees, otherwise you won't have any output!)") # Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
# Using EGL function loading with GLX contexts causes rendering failures.
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
if(GLEW_USE_EGL)
message(STATUS "Building GLEW with EGL support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
else()
message(STATUS "Building GLEW with GLX support")
endif() endif()
add_library(GLEW src/glew.c) add_library(GLEW src/glew.c)
+1
View File
@@ -51,6 +51,7 @@ orcaslicer_add_cmake_project(
-DwxUSE_UNICODE=ON -DwxUSE_UNICODE=ON
-DwxUSE_PRIVATE_FONTS=ON -DwxUSE_PRIVATE_FONTS=ON
-DwxUSE_OPENGL=ON -DwxUSE_OPENGL=ON
-DwxUSE_GLCANVAS_EGL=OFF
-DwxUSE_WEBREQUEST=ON -DwxUSE_WEBREQUEST=ON
-DwxUSE_WEBVIEW=ON -DwxUSE_WEBVIEW=ON
${_wx_edge} ${_wx_edge}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Afinia", "name": "Afinia",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Afinia configurations", "description": "Afinia configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Anker", "name": "Anker",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Anker configurations", "description": "Anker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Anycubic", "name": "Anycubic",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Anycubic configurations", "description": "Anycubic configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Artillery", "name": "Artillery",
"version": "02.03.02.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Artillery configurations", "description": "Artillery configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Bambulab", "name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.00.00.56", "version": "02.01.00.00",
"force_update": "0", "force_update": "0",
"description": "the initial version of BBL configurations", "description": "the initial version of BBL configurations",
"machine_model_list": [ "machine_model_list": [
@@ -102,6 +102,21 @@
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
], ],
"filament_tower_interface_pre_extrusion_dist": [
"10"
],
"filament_tower_interface_pre_extrusion_length": [
"0"
],
"filament_tower_ironing_area": [
"4"
],
"filament_tower_interface_purge_volume": [
"20"
],
"filament_tower_interface_print_temp": [
"-1"
],
"filament_printable": [ "filament_printable": [
"3" "3"
], ],
@@ -93,6 +93,8 @@
"prime_tower_lift_height": "-1", "prime_tower_lift_height": "-1",
"prime_tower_max_speed": "90", "prime_tower_max_speed": "90",
"prime_tower_flat_ironing": "0", "prime_tower_flat_ironing": "0",
"enable_tower_interface_features": "0",
"enable_tower_interface_cooldown_during_tower": "0",
"raft_layers": "0", "raft_layers": "0",
"reduce_crossing_wall": "0", "reduce_crossing_wall": "0",
"reduce_infill_retraction": "1", "reduce_infill_retraction": "1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "BIQU", "name": "BIQU",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "BIQU configurations", "description": "BIQU configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Blocks", "name": "Blocks",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Blocks configurations", "description": "Blocks configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "CONSTRUCT3D", "name": "CONSTRUCT3D",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Construct3D configurations", "description": "Construct3D configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Chuanying", "name": "Chuanying",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Chuanying configurations", "description": "Chuanying configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Co Print", "name": "Co Print",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "CoPrint configurations", "description": "CoPrint configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "CoLiDo", "name": "CoLiDo",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "CoLiDo configurations", "description": "CoLiDo configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Comgrow", "name": "Comgrow",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Comgrow configurations", "description": "Comgrow configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Creality", "name": "Creality",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Creality configurations", "description": "Creality configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Cubicon", "name": "Cubicon",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Cubicon configurations", "description": "Cubicon configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Custom Printer", "name": "Custom Printer",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "My configurations", "description": "My configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "DeltaMaker", "name": "DeltaMaker",
"url": "", "url": "",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "DeltaMaker configurations", "description": "DeltaMaker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Dremel", "name": "Dremel",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Dremel configurations", "description": "Dremel configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Elegoo", "name": "Elegoo",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Elegoo configurations", "description": "Elegoo configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Eryone", "name": "Eryone",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Eryone configurations", "description": "Eryone configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "FLSun", "name": "FLSun",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "FLSun configurations", "description": "FLSun configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Flashforge", "name": "Flashforge",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Flashforge configurations", "description": "Flashforge configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "FlyingBear", "name": "FlyingBear",
"version": "02.03.01.00", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "FlyingBear configurations", "description": "FlyingBear configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Folgertech", "name": "Folgertech",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Folgertech configurations", "description": "Folgertech configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Geeetech", "name": "Geeetech",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Geeetech configurations", "description": "Geeetech configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Ginger Additive", "name": "Ginger Additive",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "Ginger configuration", "description": "Ginger configuration",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "InfiMech", "name": "InfiMech",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "InfiMech configurations", "description": "InfiMech configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Kingroon", "name": "Kingroon",
"url": "https://kingroon.com/", "url": "https://kingroon.com/",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "Kingroon configuration files", "description": "Kingroon configuration files",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "LONGER", "name": "LONGER",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "LONGER configurations", "description": "LONGER configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Lulzbot", "name": "Lulzbot",
"url": "https://ohai.lulzbot.com/group/taz-6/", "url": "https://ohai.lulzbot.com/group/taz-6/",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Lulzbot configurations", "description": "Lulzbot configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "M3D", "name": "M3D",
"version": "1.0.0", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Configuration for M3D printers", "description": "Configuration for M3D printers",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "MagicMaker", "name": "MagicMaker",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "MagicMaker configurations", "description": "MagicMaker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Mellow", "name": "Mellow",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Mellow Printer Profiles", "description": "Mellow Printer Profiles",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "OpenEYE", "name": "OpenEYE",
"url": "http://www.openeye.tech", "url": "http://www.openeye.tech",
"version": "01.00.00.03", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "OpenEYE Printers Configurations", "description": "OpenEYE Printers Configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Orca Arena Printer", "name": "Orca Arena Printer",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Orca Arena configuration files", "description": "Orca Arena configuration files",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "OrcaFilamentLibrary", "name": "OrcaFilamentLibrary",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Orca Filament Library", "description": "Orca Filament Library",
"filament_list": [ "filament_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Peopoly", "name": "Peopoly",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Peopoly configurations", "description": "Peopoly configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Phrozen", "name": "Phrozen",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Phrozen configurations", "description": "Phrozen configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Positron 3D", "name": "Positron 3D",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Positron 3D Printer Profile", "description": "Positron 3D Printer Profile",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Prusa", "name": "Prusa",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Prusa configurations", "description": "Prusa configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Qidi", "name": "Qidi",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Qidi configurations", "description": "Qidi configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "RH3D", "name": "RH3D",
"version": "00.06.10.25", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "RH3D - printer profiles", "description": "RH3D - printer profiles",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Raise3D", "name": "Raise3D",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Raise3D configurations", "description": "Raise3D configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "RatRig", "name": "RatRig",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "RatRig configurations", "description": "RatRig configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "RolohaunDesign", "name": "RolohaunDesign",
"version": "02.03.02.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "RolohaunDesign Printer Profiles", "description": "RolohaunDesign Printer Profiles",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "SecKit", "name": "SecKit",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "SecKit configurations", "description": "SecKit configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Snapmaker", "name": "Snapmaker",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Snapmaker configurations", "description": "Snapmaker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Sovol", "name": "Sovol",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Sovol configurations", "description": "Sovol configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Tiertime", "name": "Tiertime",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Tiertime configurations", "description": "Tiertime configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Tronxy", "name": "Tronxy",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Tronxy configurations", "description": "Tronxy configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "TwoTrees", "name": "TwoTrees",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "TwoTrees configurations", "description": "TwoTrees configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "UltiMaker", "name": "UltiMaker",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "UltiMaker configurations", "description": "UltiMaker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Vivedino", "name": "Vivedino",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Vivedino configurations", "description": "Vivedino configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Volumic", "name": "Volumic",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "VOLUMIC configurations", "description": "VOLUMIC configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Voron", "name": "Voron",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Voron configurations", "description": "Voron configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Voxelab", "name": "Voxelab",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Voxelab configurations", "description": "Voxelab configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Vzbot", "name": "Vzbot",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Vzbot configurations", "description": "Vzbot configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "WEMAKE3D", "name": "WEMAKE3D",
"version": "02.03.01.20", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "WEMAKE3D configurations", "description": "WEMAKE3D configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Wanhao France", "name": "Wanhao France",
"version": "02.03.01.11", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Wanhao France D12 configurations", "description": "Wanhao France D12 configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Wanhao", "name": "Wanhao",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Wanhao configurations", "description": "Wanhao configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "WonderMaker", "name": "WonderMaker",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "WonderMaker configurations", "description": "WonderMaker configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "Z-Bolt", "name": "Z-Bolt",
"url": "", "url": "",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "0", "force_update": "0",
"description": "Z-Bolt configurations", "description": "Z-Bolt configurations",
"machine_model_list": [ "machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "innovatiQ", "name": "innovatiQ",
"version": "02.03.01.10", "version": "02.03.02.40",
"force_update": "1", "force_update": "1",
"description": "innovatiQ configuration", "description": "innovatiQ configuration",
"machine_model_list": [ "machine_model_list": [
+203 -2
View File
@@ -880,7 +880,23 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange));
config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange));
config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp));
int interface_temp = full_config.filament_tower_interface_print_temp.get_at(new_filament_id);
if (interface_temp == -1)
interface_temp = full_config.nozzle_temperature_range_high.get_at(new_filament_id);
if (full_config.enable_tower_interface_features && tcr.is_contact)
new_filament_temp = interface_temp;
config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp));
if (full_config.enable_tower_interface_features && tcr.is_contact) {
auto temps = full_config.nozzle_temperature.values;
if (new_filament_id >= 0 && new_filament_id < (int)temps.size())
temps[new_filament_id] = interface_temp;
config.set_key_value("temperature", new ConfigOptionInts(temps));
auto first_layer_temps = full_config.nozzle_temperature_initial_layer.values;
if (new_filament_id >= 0 && new_filament_id < (int)first_layer_temps.size())
first_layer_temps[new_filament_id] = interface_temp;
config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps));
}
config.set_key_value("x_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(0))); config.set_key_value("x_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(0)));
config.set_key_value("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1))); config.set_key_value("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1)));
config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2)));
@@ -910,6 +926,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); config.set_key_value("flush_length", new ConfigOptionFloat(purge_length));
config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter)); config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter));
config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x));
config.set_key_value("is_prime_tower_interface", new ConfigOptionBool(tcr.is_contact));
config.set_key_value("filament_tower_interface_purge_volume", new ConfigOptionFloat(full_config.filament_tower_interface_purge_volume.get_at(new_filament_id)));
config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp));
int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time)); int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time));
float flush_unit = purge_length / flush_count; float flush_unit = purge_length / flush_count;
@@ -1144,10 +1163,18 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
std::string toolchange_gcode_str; std::string toolchange_gcode_str;
std::string deretraction_str; std::string deretraction_str;
int toolchange_temp_override = -1;
int interface_temp = -1;
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
if (is_ramming) if (is_ramming)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z if (gcodegen.config().enable_tower_interface_features && tcr.is_contact) {
interface_temp = gcodegen.config().filament_tower_interface_print_temp.get_at(new_extruder_id);
if (interface_temp == -1)
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id);
toolchange_temp_override = interface_temp;
}
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
if (gcodegen.config().enable_prime_tower) { if (gcodegen.config().enable_prime_tower) {
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true); deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
Vec3d position{gcodegen.writer().get_position()}; Vec3d position{gcodegen.writer().get_position()};
@@ -1157,6 +1184,144 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
} }
} }
if (toolchange_temp_override > 0) {
int base_temp = gcodegen.on_first_layer() ? gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id)
: gcodegen.config().nozzle_temperature.get_at(new_extruder_id);
if (std::abs(tcr.print_z) < EPSILON)
base_temp = gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id);
const std::string t_token = " T" + std::to_string(new_extruder_id);
std::string out;
out.reserve(toolchange_gcode_str.size());
size_t pos = 0;
while (pos < toolchange_gcode_str.size()) {
size_t line_end = toolchange_gcode_str.find('\n', pos);
if (line_end == std::string::npos)
line_end = toolchange_gcode_str.size();
std::string line = toolchange_gcode_str.substr(pos, line_end - pos);
std::string trimmed = line;
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
bool skip_line = false;
if (boost::starts_with(trimmed, "M109")) {
bool matches_extruder = trimmed.find(t_token) != std::string::npos;
if (!matches_extruder) {
size_t t_pos = trimmed.find('T');
if (t_pos != std::string::npos) {
size_t t_end = trimmed.find_first_not_of("0123456789", t_pos + 1);
const std::string t_val = trimmed.substr(t_pos + 1, t_end == std::string::npos ? std::string::npos : t_end - (t_pos + 1));
if (!t_val.empty()) {
try {
matches_extruder = std::stoi(t_val) == new_extruder_id;
} catch (...) {
matches_extruder = false;
}
}
}
}
if (matches_extruder) {
size_t s_pos = trimmed.find('S');
if (s_pos != std::string::npos) {
size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1);
const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1));
if (!s_val.empty()) {
try {
skip_line = std::stoi(s_val) == base_temp;
} catch (...) {
skip_line = false;
}
}
}
}
}
if (!skip_line) {
out.append(line);
if (line_end < toolchange_gcode_str.size())
out.push_back('\n');
}
pos = line_end + 1;
}
toolchange_gcode_str.swap(out);
}
if (toolchange_temp_override > 0) {
const std::string preheat_token = "preheat T" + std::to_string(new_extruder_id);
const int preheat_temp = interface_temp > 0 ? interface_temp : toolchange_temp_override;
std::string out;
out.reserve(tcr_rotated_gcode.size());
size_t pos = 0;
while (pos < tcr_rotated_gcode.size()) {
size_t line_end = tcr_rotated_gcode.find('\n', pos);
if (line_end == std::string::npos)
line_end = tcr_rotated_gcode.size();
std::string line = tcr_rotated_gcode.substr(pos, line_end - pos);
std::string trimmed = line;
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
const bool is_preheat_line = (trimmed.find(preheat_token) != std::string::npos);
if (is_preheat_line) {
// Preserve early-preheat timing while forcing interface temp for contact toolchanges.
size_t s_pos = trimmed.find('S');
if (s_pos != std::string::npos) {
size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1);
trimmed.replace(s_pos + 1,
(s_end == std::string::npos ? trimmed.size() : s_end) - (s_pos + 1),
std::to_string(preheat_temp));
// Reapply left indentation from the original line.
size_t line_prefix = line.find_first_not_of(" \t");
if (line_prefix != std::string::npos)
line = line.substr(0, line_prefix) + trimmed;
else
line = trimmed;
}
}
out.append(line);
if (line_end < tcr_rotated_gcode.size())
out.push_back('\n');
pos = line_end + 1;
}
tcr_rotated_gcode.swap(out);
}
if (toolchange_temp_override > 0 && interface_temp > 0) {
const std::string t_token = " T" + std::to_string(new_extruder_id);
std::string out;
out.reserve(tcr_rotated_gcode.size());
size_t pos = 0;
while (pos < tcr_rotated_gcode.size()) {
size_t line_end = tcr_rotated_gcode.find('\n', pos);
if (line_end == std::string::npos)
line_end = tcr_rotated_gcode.size();
std::string line = tcr_rotated_gcode.substr(pos, line_end - pos);
std::string trimmed = line;
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
bool skip_line = false;
if (boost::starts_with(trimmed, "M109")) {
bool matches_extruder = true;
if (trimmed.find('T') != std::string::npos)
matches_extruder = trimmed.find(t_token) != std::string::npos;
if (matches_extruder) {
size_t s_pos = trimmed.find('S');
if (s_pos != std::string::npos) {
size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1);
const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1));
if (!s_val.empty()) {
try {
skip_line = std::stoi(s_val) == interface_temp;
} catch (...) {
skip_line = false;
}
}
}
}
}
if (!skip_line) {
out.append(line);
if (line_end < tcr_rotated_gcode.size())
out.push_back('\n');
}
pos = line_end + 1;
}
tcr_rotated_gcode.swap(out);
}
// Insert the toolchange and deretraction gcode into the generated gcode. // Insert the toolchange and deretraction gcode into the generated gcode.
DynamicConfig config; DynamicConfig config;
@@ -7192,7 +7357,7 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li
return gcode; return gcode;
} }
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object) std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override)
{ {
int new_extruder_id = get_extruder_id(new_filament_id); int new_extruder_id = get_extruder_id(new_filament_id);
if (!m_writer.need_toolchange(new_filament_id)) if (!m_writer.need_toolchange(new_filament_id))
@@ -7280,6 +7445,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
// BBS: if print_z == 0 use first layer temperature // BBS: if print_z == 0 use first layer temperature
if (abs(print_z) < EPSILON) if (abs(print_z) < EPSILON)
new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id);
if (toolchange_temp_override > 0)
new_filament_temp = toolchange_temp_override;
Vec3d nozzle_pos = m_writer.get_position(); Vec3d nozzle_pos = m_writer.get_position();
float old_retract_length, old_retract_length_toolchange, wipe_volume; float old_retract_length, old_retract_length_toolchange, wipe_volume;
@@ -7372,6 +7539,27 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y())));
dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false));
dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x));
dyn_config.set_key_value("is_prime_tower_interface", new ConfigOptionBool(false));
dyn_config.set_key_value("filament_tower_interface_purge_volume", new ConfigOptionFloat(m_config.filament_tower_interface_purge_volume.get_at(new_filament_id)));
{
int interface_temp = m_config.filament_tower_interface_print_temp.get_at(new_filament_id);
if (interface_temp == -1)
interface_temp = m_config.nozzle_temperature_range_high.get_at(new_filament_id);
dyn_config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp));
}
if (toolchange_temp_override > 0) {
auto temps = m_config.nozzle_temperature.values;
if (new_filament_id < temps.size())
temps[new_filament_id] = toolchange_temp_override;
dyn_config.set_key_value("temperature", new ConfigOptionInts(temps));
dyn_config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps));
auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values;
if (new_filament_id < first_layer_temps.size())
first_layer_temps[new_filament_id] = toolchange_temp_override;
dyn_config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps));
dyn_config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps));
}
auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values;
auto flush_temps =m_print->config().filament_flush_temp.values; auto flush_temps =m_print->config().filament_flush_temp.values;
@@ -7472,6 +7660,19 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value)); config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(new_filament_id))); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(new_filament_id)));
if (toolchange_temp_override > 0) {
auto temps = m_config.nozzle_temperature.values;
if (new_filament_id < temps.size())
temps[new_filament_id] = toolchange_temp_override;
config.set_key_value("temperature", new ConfigOptionInts(temps));
config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps));
auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values;
if (new_filament_id < first_layer_temps.size())
first_layer_temps[new_filament_id] = toolchange_temp_override;
config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps));
config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps));
}
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
if (add_change_filament_624) { if (add_change_filament_624) {
gcode += "M625\n"; gcode += "M625\n";
+1 -1
View File
@@ -249,7 +249,7 @@ public:
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type); bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone); std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false); std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
bool is_BBL_Printer(); bool is_BBL_Printer();
bool is_QIDI_Printer(); bool is_QIDI_Printer();
+241 -6
View File
@@ -760,11 +760,23 @@ public:
} }
} }
const std::string& line_at(size_t idx) const
{
return m_lines[idx].line;
}
size_t lines_size() const
{
return m_lines.size();
}
// Insert the gcode lines required by the command cmd by backtracing into the cache // Insert the gcode lines required by the command cmd by backtracing into the cache
void insert_lines(const Backtrace& backtrace, bool insert_lines(const Backtrace& backtrace,
const std::string& cmd, const std::string& cmd,
std::function<std::string(unsigned int, const std::vector<float>&)> line_inserter, std::function<std::string(unsigned int, const std::vector<float>&)> line_inserter,
std::function<std::string(const std::string&)> line_replacer) std::function<std::string(const std::string&)> line_replacer,
std::function<bool(size_t)> allow_insert = nullptr,
bool force_insert_last = false)
{ {
// Orca: find start pos by seaching G28/G29/PRINT_START/START_PRINT commands // Orca: find start pos by seaching G28/G29/PRINT_START/START_PRINT commands
auto is_start_pos = [](const std::string& curr_cmd) { auto is_start_pos = [](const std::string& curr_cmd) {
@@ -775,6 +787,21 @@ public:
const float time_step = backtrace.time_step(); const float time_step = backtrace.time_step();
size_t rev_it_dist = 0; // distance from the end of the cache of the starting point of the backtrace size_t rev_it_dist = 0; // distance from the end of the cache of the starting point of the backtrace
float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time
auto inside_toolchange_block = [this](size_t idx) -> bool {
if (m_lines.empty() || idx >= m_lines.size())
return false;
// Find last START/END marker before or at idx; inside if last marker is START.
for (size_t i = idx + 1; i-- > 0;) {
const std::string &line = m_lines[i].line;
if (line.find("CP TOOLCHANGE START") != std::string::npos)
return true;
if (line.find("CP TOOLCHANGE END") != std::string::npos)
return false;
}
return false;
};
bool inserted = false;
for (int i = 0; i < backtrace.steps; ++i) { for (int i = 0; i < backtrace.steps; ++i) {
const float backtrace_time_i = (i + 1) * time_step; const float backtrace_time_i = (i + 1) * time_step;
const float time_threshold_i = m_times[Normal] - backtrace_time_i; const float time_threshold_i = m_times[Normal] - backtrace_time_i;
@@ -796,6 +823,23 @@ public:
// insert the line for the current step // insert the line for the current step
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) { if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) {
// Avoid inserting inside wipe-tower toolchange blocks.
// If the selected point is inside a block, move to the nearest
// earlier line outside the block instead of dropping preheat.
size_t idx = m_lines.size() - 1 - size_t(std::distance(m_lines.rbegin(), rev_it));
if (inside_toolchange_block(idx)) {
size_t adjusted_idx = idx;
while (adjusted_idx > 0 && inside_toolchange_block(adjusted_idx))
--adjusted_idx;
if (inside_toolchange_block(adjusted_idx))
continue;
const size_t adjusted_rev_dist = m_lines.size() - 1 - adjusted_idx;
rev_it = m_lines.rbegin() + adjusted_rev_dist;
idx = adjusted_idx;
}
if (allow_insert && !allow_insert(idx)) {
continue;
}
last_time_insertion = rev_it->times[Normal]; last_time_insertion = rev_it->times[Normal];
std::vector<float> time_diffs; std::vector<float> time_diffs;
time_diffs.push_back(m_times[Normal] - last_time_insertion); time_diffs.push_back(m_times[Normal] - last_time_insertion);
@@ -814,8 +858,39 @@ public:
} }
++m_added_lines_counter; ++m_added_lines_counter;
inserted = true;
} }
} }
if (!inserted && force_insert_last) {
for (size_t idx = 0; idx < m_lines.size(); ++idx) {
if (inside_toolchange_block(idx))
continue;
if (allow_insert && !allow_insert(idx))
continue;
const LineData &data = m_lines[idx];
std::vector<float> time_diffs;
time_diffs.push_back(m_times[Normal] - data.times[Normal]);
if (!m_machines[Stealth].g1_times_cache.empty())
time_diffs.push_back(m_times[Stealth] - data.times[Stealth]);
const std::string out_line = line_inserter(1, time_diffs);
const size_t rev_it_dist = m_lines.size() - idx;
m_lines.insert(m_lines.begin() + idx, {out_line, data.times});
#ifndef NDEBUG
m_statistics.add_line(out_line.length());
#endif // NDEBUG
m_size += out_line.length();
for (auto map_it = m_gcode_lines_map.rbegin(); map_it != m_gcode_lines_map.rbegin() + rev_it_dist - 1; ++map_it) {
++map_it->second;
}
++m_added_lines_counter;
inserted = true;
break;
}
}
return inserted;
} }
// write to file: // write to file:
@@ -1254,12 +1329,169 @@ void GCodeProcessor::run_post_process()
if (m_print != nullptr) if (m_print != nullptr)
m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning); m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning);
} }
int override_temp = -1;
{
const size_t line_count = export_line.lines_size();
if (line_count > 0) {
size_t start_idx = 0;
bool in_block = false;
for (size_t i = line_count; i-- > 0;) {
const std::string &line = export_line.line_at(i);
if (line.find("CP TOOLCHANGE END") != std::string::npos)
break;
if (line.find("CP TOOLCHANGE START") != std::string::npos) {
start_idx = i;
in_block = true;
break;
}
}
if (in_block) {
int base_temp = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] :
m_filament_nozzle_temp_first_layer[tool_number]);
for (size_t i = start_idx; i < line_count; ++i) {
const std::string &line = export_line.line_at(i);
if (GCodeReader::GCodeLine::cmd_is(line, "M109")) {
GCodeReader::GCodeLine gline;
GCodeReader reader;
reader.parse_line(line, [&gline](GCodeReader& reader, const GCodeReader::GCodeLine& l) { gline = l; });
float s_val = 0.f;
if (gline.has_value('S', s_val)) {
float t_val = -1.f;
if (gline.has_value('T', t_val)) {
if (int(t_val) != tool_number)
continue;
}
int temp = int(std::round(s_val));
if (temp != base_temp)
override_temp = temp;
}
}
}
}
}
}
// Disallow inserting preheat before the last switch away from this tool.
size_t min_insert_idx = 0;
{
const size_t line_count = export_line.lines_size();
if (line_count > 0) {
for (size_t i = line_count; i-- > 0;) {
const std::string &line = export_line.line_at(i);
std::string cmd = GCodeReader::GCodeLine::extract_cmd(line);
if (cmd.empty())
continue;
if (cmd[0] == 'T') {
unsigned int id = 0;
auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id);
if (ret.ec == std::errc()) {
if (static_cast<int>(id) != tool_number) {
min_insert_idx = i;
break;
}
}
} else if (cmd == "M1020") {
size_t pos = line.find("S");
if (pos != std::string::npos) {
size_t start = pos + 1;
size_t end = line.find_first_not_of("0123456789", start);
const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start);
if (!val.empty()) {
try {
if (std::stoi(val) != tool_number) {
min_insert_idx = i;
break;
}
} catch (...) {
}
}
}
}
}
}
}
auto allow_insert = [&export_line, tool_number, min_insert_idx](size_t idx) -> bool {
if (idx <= min_insert_idx)
return false;
// Do not insert preheat for a tool that is already active at the insertion point.
const size_t line_count = export_line.lines_size();
if (idx >= line_count)
return true;
for (size_t i = idx + 1; i-- > 0;) {
const std::string &line = export_line.line_at(i);
std::string cmd = GCodeReader::GCodeLine::extract_cmd(line);
if (cmd.empty())
continue;
if (cmd[0] == 'T') {
unsigned int id = 0;
auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id);
if (ret.ec == std::errc()) {
return static_cast<int>(id) != tool_number;
}
} else if (cmd == "M1020") {
size_t pos = line.find("S");
if (pos != std::string::npos) {
size_t start = pos + 1;
size_t end = line.find_first_not_of("0123456789", start);
const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start);
if (!val.empty()) {
try {
return std::stoi(val) != tool_number;
} catch (...) {
return true;
}
}
}
}
}
return true;
};
// Skip preheat insertion if this T command doesn't change the active tool.
{
const size_t line_count = export_line.lines_size();
if (line_count > 0) {
int last_tool = -1;
for (size_t i = line_count; i-- > 0;) {
const std::string &line = export_line.line_at(i);
std::string cmd = GCodeReader::GCodeLine::extract_cmd(line);
if (cmd.empty())
continue;
if (cmd[0] == 'T') {
unsigned int id = 0;
auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id);
if (ret.ec == std::errc()) {
last_tool = static_cast<int>(id);
break;
}
} else if (cmd == "M1020") {
size_t pos = line.find("S");
if (pos != std::string::npos) {
size_t start = pos + 1;
size_t end = line.find_first_not_of("0123456789", start);
const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start);
if (!val.empty()) {
try {
last_tool = std::stoi(val);
break;
} catch (...) {
}
}
}
}
}
if (last_tool != -1 && last_tool == tool_number)
return;
}
}
export_line.insert_lines( export_line.insert_lines(
backtrace, cmd, backtrace, cmd,
// line inserter // line inserter
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) { [tool_number, override_temp, this](unsigned int id, const std::vector<float>& time_diffs) {
const int temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] : const int base_temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] :
m_filament_nozzle_temp_first_layer[tool_number]); m_filament_nozzle_temp_first_layer[tool_number]);
const int temperature = override_temp > 0 ? override_temp : base_temperature;
// Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from // Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from
// Prusa-Firmware-Buddy here // Prusa-Firmware-Buddy here
/** /**
@@ -1302,7 +1534,11 @@ void GCodeProcessor::run_post_process()
} }
} }
return line; return line;
} },
allow_insert,
// If backtracing can't find a valid insertion point (e.g. dense recurring toolchanges),
// still force one at the earliest allowed line after the last switch-away point.
true
); );
} }
} }
@@ -5897,4 +6133,3 @@ int GCodeProcessor::get_extruder_id(bool force_initialize)const
} }
} /* namespace Slic3r */ } /* namespace Slic3r */
+69 -15
View File
@@ -16,7 +16,6 @@
namespace Slic3r namespace Slic3r
{ {
static constexpr float flat_iron_area = 4.f;
constexpr float flat_iron_speed = 10.f * 60.f; constexpr float flat_iron_speed = 10.f * 60.f;
static const double wipe_tower_wall_infill_overlap = 0.0; static const double wipe_tower_wall_infill_overlap = 0.0;
static constexpr double WIPE_TOWER_RESOLUTION = 0.1; static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
@@ -1243,7 +1242,8 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
size_t old_tool, size_t old_tool,
bool is_finish, bool is_finish,
bool is_tool_change, bool is_tool_change,
float purge_volume) const float purge_volume,
bool is_contact) const
{ {
ToolChangeResult result; ToolChangeResult result;
result.priming = priming; result.priming = priming;
@@ -1260,6 +1260,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
result.is_finish_first = is_finish; result.is_finish_first = is_finish;
result.nozzle_change_result = m_nozzle_change_result; result.nozzle_change_result = m_nozzle_change_result;
result.is_tool_change = is_tool_change; result.is_tool_change = is_tool_change;
result.is_contact = is_contact;
result.tool_change_start_pos = is_tool_change ? result.start_pos : Vec2f(0, 0); result.tool_change_start_pos = is_tool_change ? result.start_pos : Vec2f(0, 0);
// BBS // BBS
@@ -1283,6 +1284,7 @@ WipeTower::ToolChangeResult WipeTower::construct_block_tcr(WipeTowerWriter &writ
result.wipe_path = std::move(writer.wipe_path()); result.wipe_path = std::move(writer.wipe_path());
result.is_finish_first = is_finish; result.is_finish_first = is_finish;
result.is_tool_change = false; result.is_tool_change = false;
result.is_contact = false;
result.tool_change_start_pos = Vec2f(0, 0); result.tool_change_start_pos = Vec2f(0, 0);
// BBS // BBS
result.purge_volume = purge_volume; result.purge_volume = purge_volume;
@@ -1486,7 +1488,9 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
m_used_fillet(config.wipe_tower_fillet_wall.value), m_used_fillet(config.wipe_tower_fillet_wall.value),
m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f), m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f),
m_tower_framework(config.prime_tower_enable_framework.value), m_tower_framework(config.prime_tower_enable_framework.value),
m_flat_ironing(config.prime_tower_flat_ironing.value) m_flat_ironing(config.prime_tower_flat_ironing.value),
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
{ {
m_flat_ironing = (m_flat_ironing && m_use_gap_wall); m_flat_ironing = (m_flat_ironing && m_use_gap_wall);
// Read absolute value of first layer speed, if given as percentage, // Read absolute value of first layer speed, if given as percentage,
@@ -1546,6 +1550,16 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx); m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx);
m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx); m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx);
m_filpar[idx].category = config.filament_adhesiveness_category.get_at(idx); m_filpar[idx].category = config.filament_adhesiveness_category.get_at(idx);
{
int interface_temp = config.filament_tower_interface_print_temp.get_at(idx);
if (interface_temp == -1)
interface_temp = config.nozzle_temperature_range_high.get_at(idx);
m_filpar[idx].interface_print_temperature = interface_temp;
}
m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx);
m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx);
m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx);
// If this is a single extruder MM printer, we will use all the SE-specific config values. // If this is a single extruder MM printer, we will use all the SE-specific config values.
// Otherwise, the defaults will be used to turn off the SE stuff. // Otherwise, the defaults will be used to turn off the SE stuff.
@@ -1608,7 +1622,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
return std::vector<ToolChangeResult>(); return std::vector<ToolChangeResult>();
} }
Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length) Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool)
{ {
const float &xl = cleaning_box.ld.x(); const float &xl = cleaning_box.ld.x();
const float &xr = cleaning_box.rd.x(); const float &xr = cleaning_box.rd.x();
@@ -1740,7 +1754,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per
} }
} }
Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); Vec2f initial_position = get_next_pos(cleaning_box, wipe_length, false, tool);
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
if (extrude_perimeter) { if (extrude_perimeter) {
@@ -1796,7 +1810,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per
if (m_current_tool < m_used_filament_length.size()) if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
return construct_tcr(writer, false, old_tool, false, true, purge_volume); return construct_tcr(writer, false, old_tool, false, true, purge_volume, false);
} }
WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int new_filament_id) WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int new_filament_id)
@@ -2284,6 +2298,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
bool first_layer = is_first_layer(); bool first_layer = is_first_layer();
// BBS: speed up perimeter speed to 90mm/s for non-first layer // BBS: speed up perimeter speed to 90mm/s for non-first layer
float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f);
if (m_enable_tower_interface_features && m_prev_layer_had_interface)
feedrate = std::min(feedrate, 20.f * 60.f);
float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width;
box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y),
m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y);
@@ -2426,7 +2442,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
if (m_current_tool < m_used_filament_length.size()) if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
return construct_tcr(writer, false, old_tool, true, false, 0.f); return construct_tcr(writer, false, old_tool, true, false, 0.f, false);
} }
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
@@ -2646,6 +2662,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
{ {
assert(first.new_tool == second.initial_tool); assert(first.new_tool == second.initial_tool);
WipeTower::ToolChangeResult out = first; WipeTower::ToolChangeResult out = first;
out.is_contact = first.is_contact || second.is_contact;
if ((first.end_pos - second.start_pos).norm() > (float)EPSILON) { if ((first.end_pos - second.start_pos).norm() > (float)EPSILON) {
std::string travel_gcode = "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + std::string travel_gcode = "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" +
Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + " F5400" + "\n"; Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + " F5400" + "\n";
@@ -2769,6 +2786,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
} }
} }
bool interface_layer = solid_toolchange && m_enable_tower_interface_features;
if (interface_layer && new_tool < m_filpar.size()) {
float extra_purge_length = m_filpar[new_tool].tower_interface_purge_length;
if (extra_purge_length > 0.f) {
purge_volume += extra_purge_length * m_filpar[new_tool].filament_area;
wipe_length += extra_purge_length;
}
}
WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false); WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false);
if (!block) { if (!block) {
assert(block != nullptr); assert(block != nullptr);
@@ -2795,7 +2821,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
if (new_tool != (unsigned int) -1) { // This is not the last change. if (new_tool != (unsigned int) -1) { // This is not the last change.
Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); Vec2f initial_position = get_next_pos(cleaning_box, wipe_length, interface_layer, new_tool);
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n");
@@ -2804,6 +2830,23 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
toolchange_Change(writer, new_tool, m_filpar[new_tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Change(writer, new_tool, m_filpar[new_tool].material); // Change the tool, set a speed override for soluble and flex materials.
toolchange_Load(writer, cleaning_box); toolchange_Load(writer, cleaning_box);
int base_temp = is_first_layer() ? m_filpar[new_tool].nozzle_temperature_initial_layer : m_filpar[new_tool].nozzle_temperature;
if (interface_layer) {
int interface_temp = m_filpar[new_tool].interface_print_temperature;
if (interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(interface_temp, true);
if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(base_temp, false);
float pre_dist = m_filpar[new_tool].tower_interface_pre_extrusion_dist;
float pre_len = m_filpar[new_tool].tower_interface_pre_extrusion_length;
if (pre_dist > 0.f && pre_len > 0.f) {
bool start_left = (m_cur_layer_id % 4 == 0 || m_cur_layer_id % 4 == 3);
float target_x = writer.x() + (start_left ? pre_dist : -pre_dist);
target_x = std::max(cleaning_box.ld.x(), std::min(cleaning_box.rd.x(), target_x));
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
}
}
if (m_is_multi_extruder && is_tpu_filament(new_tool)) { if (m_is_multi_extruder && is_tpu_filament(new_tool)) {
float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width; float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width;
if (m_layer_info->extra_spacing < m_tpu_fixed_spacing) { if (m_layer_info->extra_spacing < m_tpu_fixed_spacing) {
@@ -2838,6 +2881,13 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
toolchange_wipe_new(writer, cleaning_box, wipe_length, solid_toolchange); toolchange_wipe_new(writer, cleaning_box, wipe_length, solid_toolchange);
if (interface_layer) {
int base_temp = is_first_layer() ? m_filpar[new_tool].nozzle_temperature_initial_layer : m_filpar[new_tool].nozzle_temperature;
int interface_temp = m_filpar[new_tool].interface_print_temperature;
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(base_temp, false);
}
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n");
++m_num_tool_changes; ++m_num_tool_changes;
} else } else
@@ -2859,7 +2909,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
if (m_current_tool < m_used_filament_length.size()) if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
return construct_tcr(writer, false, old_tool, false, true, purge_volume); return construct_tcr(writer, false, old_tool, false, true, purge_volume, interface_layer);
} }
WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill) WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill)
@@ -3157,7 +3207,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter,
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
m_nozzle_change_result.gcode.clear(); m_nozzle_change_result.gcode.clear();
return construct_tcr(writer, false, m_current_tool, true, false, 0.f); return construct_tcr(writer, false, m_current_tool, true, false, 0.f, false);
} }
WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill) WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill)
@@ -3350,6 +3400,8 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
} }
float retract_length = m_filpar[m_current_tool].retract_length; float retract_length = m_filpar[m_current_tool].retract_length;
float retract_speed = m_filpar[m_current_tool].retract_speed * 60; float retract_speed = m_filpar[m_current_tool].retract_speed * 60;
const float ironing_area = m_filpar[m_current_tool].tower_ironing_area;
const bool do_ironing = m_flat_ironing && (!solid_tool_toolchange || !m_enable_tower_interface_features);
const float &xl = cleaning_box.ld.x(); const float &xl = cleaning_box.ld.x();
const float &xr = cleaning_box.rd.x(); const float &xr = cleaning_box.rd.x();
@@ -3393,10 +3445,10 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed); writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
writer.retract(retract_length, retract_speed); writer.retract(retract_length, retract_speed);
writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.); writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.);
if (m_flat_ironing) { if (do_ironing && ironing_area > 0.f) {
writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.); writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.);
Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()}; Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()};
writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, flat_iron_speed);
writer.travel(pos, wipe_speed); writer.travel(pos, wipe_speed);
} else } else
writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.); writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.);
@@ -3408,10 +3460,10 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed); writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed);
writer.retract(retract_length, retract_speed); writer.retract(retract_length, retract_speed);
writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.); writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.);
if (m_flat_ironing) { if (do_ironing && ironing_area > 0.f) {
writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.); writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.);
Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()}; Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()};
writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, flat_iron_speed);
writer.travel(pos, wipe_speed); writer.travel(pos, wipe_speed);
}else }else
writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.); writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.);
@@ -3837,6 +3889,8 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
for (auto layer : m_plan) { for (auto layer : m_plan) {
reset_block_status(); reset_block_status();
m_cur_layer_id = index++; m_cur_layer_id = index++;
m_prev_layer_had_interface = m_current_layer_has_interface;
m_current_layer_has_interface = !solid_blocks_id.empty();
set_layer(layer.z, layer.height, 0, false, layer.z == m_plan.back().z); set_layer(layer.z, layer.height, 0, false, layer.z == m_plan.back().z);
if (m_layer_info->depth < m_perimeter_width) continue; if (m_layer_info->depth < m_perimeter_width) continue;
@@ -4188,7 +4242,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode)
if (!m_no_sparse_layers || toolchanges_on_layer) if (!m_no_sparse_layers || toolchanges_on_layer)
if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
return construct_tcr(writer, false, old_tool, true, false, 0.f); return construct_tcr(writer, false, old_tool, true, false, 0.f, false);
} }
Polygon WipeTower::generate_rib_polygon(const box_coordinates &wt_box) Polygon WipeTower::generate_rib_polygon(const box_coordinates &wt_box)
+13 -2
View File
@@ -83,6 +83,7 @@ public:
bool priming; bool priming;
bool is_tool_change{false}; bool is_tool_change{false};
bool is_contact{false};
Vec2f tool_change_start_pos; Vec2f tool_change_start_pos;
// Pass a polyline so that normal G-code generator can do a wipe for us. // Pass a polyline so that normal G-code generator can do a wipe for us.
@@ -161,7 +162,8 @@ public:
size_t old_tool, size_t old_tool,
bool is_finish, bool is_finish,
bool is_tool_change, bool is_tool_change,
float purge_volume) const; float purge_volume,
bool is_contact = false) const;
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer, ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
bool priming, bool priming,
@@ -319,6 +321,7 @@ public:
bool is_support = false; bool is_support = false;
int nozzle_temperature = 0; int nozzle_temperature = 0;
int nozzle_temperature_initial_layer = 0; int nozzle_temperature_initial_layer = 0;
int interface_print_temperature = 0;
float loading_speed = 0.f; float loading_speed = 0.f;
float loading_speed_start = 0.f; float loading_speed_start = 0.f;
float unloading_speed = 0.f; float unloading_speed = 0.f;
@@ -336,6 +339,10 @@ public:
float retract_length; float retract_length;
float retract_speed; float retract_speed;
float wipe_dist; float wipe_dist;
float tower_interface_pre_extrusion_dist = 0.f;
float tower_interface_pre_extrusion_length = 0.f;
float tower_ironing_area = 4.f;
float tower_interface_purge_length = 0.f;
}; };
@@ -492,6 +499,10 @@ private:
std::map<float,Polylines> m_outer_wall; std::map<float,Polylines> m_outer_wall;
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
bool m_flat_ironing=false; bool m_flat_ironing=false;
bool m_enable_tower_interface_features=false;
bool m_enable_tower_interface_cooldown_during_tower=false;
bool m_prev_layer_had_interface=false;
bool m_current_layer_has_interface=false;
// Calculates length of extrusion line to extrude given volume // Calculates length of extrusion line to extrude given volume
float volume_to_length(float volume, float line_width, float layer_height) const { float volume_to_length(float volume, float line_width, float layer_height) const {
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
@@ -503,7 +514,7 @@ private:
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
void make_wipe_tower_square(); void make_wipe_tower_square();
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length); Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
void save_on_last_wipe(); void save_on_last_wipe();
+82 -9
View File
@@ -972,6 +972,25 @@ public:
return add_wipe_point(Vec2f(x, y)); return add_wipe_point(Vec2f(x, y));
} }
void spiral_flat_ironing(const Vec2f &center, float area, float step_length, float feedrate)
{
float edge_length = std::sqrt(area);
Vec2f box_max = center + Vec2f{step_length, step_length};
Vec2f box_min = center - Vec2f{step_length, step_length};
int n = std::ceil(edge_length / step_length / 2.f);
if (n <= 0)
return;
while (n--) {
travel(box_max.x(), m_current_pos.y(), feedrate);
travel(m_current_pos.x(), box_max.y(), feedrate);
travel(box_min.x(), m_current_pos.y(), feedrate);
travel(m_current_pos.x(), box_min.y(), feedrate);
box_max += Vec2f{step_length, step_length};
box_min -= Vec2f{step_length, step_length};
}
}
// Extrude with an explicitely provided amount of extrusion. // Extrude with an explicitely provided amount of extrusion.
WipeTowerWriter2& extrude_arc_explicit(ArcSegment& arc, WipeTowerWriter2& extrude_arc_explicit(ArcSegment& arc,
float f = 0.f, float f = 0.f,
@@ -1200,7 +1219,8 @@ private:
WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
bool priming, bool priming,
size_t old_tool, size_t old_tool,
bool is_finish) const bool is_finish,
bool is_contact) const
{ {
WipeTower::ToolChangeResult result; WipeTower::ToolChangeResult result;
result.priming = priming; result.priming = priming;
@@ -1215,6 +1235,7 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
result.extrusions = std::move(writer.extrusions()); result.extrusions = std::move(writer.extrusions());
result.wipe_path = std::move(writer.wipe_path()); result.wipe_path = std::move(writer.wipe_path());
result.is_finish_first = is_finish; result.is_finish_first = is_finish;
result.is_contact = is_contact;
// ORCA: Always initialize the tool_change_start_pos with a valid position // ORCA: Always initialize the tool_change_start_pos with a valid position
// to avoid undefined variable travel on X in Gcode.cpp function std::string WipeTowerIntegration::post_process_wipe_tower_moves // to avoid undefined variable travel on X in Gcode.cpp function std::string WipeTowerIntegration::post_process_wipe_tower_moves
result.tool_change_start_pos = result.start_pos; // always valid fallback result.tool_change_start_pos = result.start_pos; // always valid fallback
@@ -1250,7 +1271,10 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_used_fillet(config.wipe_tower_fillet_wall), m_used_fillet(config.wipe_tower_fillet_wall),
m_rib_width(config.wipe_tower_rib_width), m_rib_width(config.wipe_tower_rib_width),
m_extra_rib_length(config.wipe_tower_extra_rib_length), m_extra_rib_length(config.wipe_tower_extra_rib_length),
m_wall_type((int)config.wipe_tower_wall_type) m_wall_type((int)config.wipe_tower_wall_type),
m_flat_ironing(config.prime_tower_flat_ironing.value),
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
{ {
// Read absolute value of first layer speed, if given as percentage, // Read absolute value of first layer speed, if given as percentage,
// it is taken over following default. Speeds from config are not // it is taken over following default. Speeds from config are not
@@ -1317,6 +1341,16 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx); m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx); m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx); m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
{
int interface_temp = config.filament_tower_interface_print_temp.get_at(idx);
if (interface_temp == -1)
interface_temp = config.nozzle_temperature_range_high.get_at(idx);
m_filpar[idx].interface_print_temperature = interface_temp;
}
m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx);
m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx);
m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx);
// If this is a single extruder MM printer, we will use all the SE-specific config values. // If this is a single extruder MM printer, we will use all the SE-specific config values.
// Otherwise, the defaults will be used to turn off the SE stuff. // Otherwise, the defaults will be used to turn off the SE stuff.
@@ -1440,11 +1474,11 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
toolchange_Load(writer, cleaning_box); // Prime the tool. toolchange_Load(writer, cleaning_box); // Prime the tool.
if (idx_tool + 1 == tools.size()) { if (idx_tool + 1 == tools.size()) {
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color. // Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]); toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false);
} else { } else {
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
toolchange_Wipe(writer, cleaning_box , 20.f); toolchange_Wipe(writer, cleaning_box , 20.f, false);
WipeTower::box_coordinates box = cleaning_box; WipeTower::box_coordinates box = cleaning_box;
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature); toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
@@ -1472,7 +1506,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
"\n\n"); "\n\n");
} }
results.emplace_back(construct_tcr(writer, true, old_tool, true)); results.emplace_back(construct_tcr(writer, true, old_tool, true, false));
} }
m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
@@ -1487,6 +1521,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
float wipe_area = 0.f; float wipe_area = 0.f;
float wipe_volume = 0.f; float wipe_volume = 0.f;
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
// Finds this toolchange info // Finds this toolchange info
if (tool != (unsigned int)(-1)) if (tool != (unsigned int)(-1))
@@ -1501,6 +1536,12 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
else { else {
// Otherwise we are going to Unload only. And m_layer_info would be invalid. // Otherwise we are going to Unload only. And m_layer_info would be invalid.
} }
if (interface_layer && tool != (unsigned int)(-1) && tool < m_filpar.size()) {
float extra_purge_length = m_filpar[tool].tower_interface_purge_length;
if (extra_purge_length > 0.f) {
wipe_volume += extra_purge_length * m_filpar[tool].filament_area;
}
}
WipeTower::box_coordinates cleaning_box( WipeTower::box_coordinates cleaning_box(
Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
@@ -1542,7 +1583,27 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
toolchange_Load(writer, cleaning_box); toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. int base_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
if (interface_layer) {
int interface_temp = m_filpar[tool].interface_print_temperature;
if (interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(interface_temp, true);
if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(base_temp, false);
float pre_dist = m_filpar[tool].tower_interface_pre_extrusion_dist;
float pre_len = m_filpar[tool].tower_interface_pre_extrusion_length;
if (pre_dist > 0.f && pre_len > 0.f) {
float target_x = writer.x() + pre_dist;
target_x = std::max(cleaning_box.ld.x(), std::min(cleaning_box.rd.x(), target_x));
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
}
}
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer); // Wipe the newly loaded filament until the end of the assigned wipe area.
if (interface_layer) {
int interface_temp = m_filpar[tool].interface_print_temperature;
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(base_temp, false);
}
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n");
++ m_num_tool_changes; ++ m_num_tool_changes;
} else } else
@@ -1564,7 +1625,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
if (m_current_tool < m_used_filament_length.size()) if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
return construct_tcr(writer, false, old_tool, false); return construct_tcr(writer, false, old_tool, false, interface_layer);
} }
@@ -1854,7 +1915,8 @@ void WipeTower2::toolchange_Load(
void WipeTower2::toolchange_Wipe( void WipeTower2::toolchange_Wipe(
WipeTowerWriter2 &writer, WipeTowerWriter2 &writer,
const WipeTower::box_coordinates &cleaning_box, const WipeTower::box_coordinates &cleaning_box,
float wipe_volume) float wipe_volume,
bool interface_layer)
{ {
// Increase flow on first layer, slow down print. // Increase flow on first layer, slow down print.
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f)) writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
@@ -1884,6 +1946,9 @@ void WipeTower2::toolchange_Wipe(
m_left_to_right = !m_left_to_right; m_left_to_right = !m_left_to_right;
} }
const bool do_ironing = m_flat_ironing && (!interface_layer || !m_enable_tower_interface_features);
const float ironing_area = m_filpar[m_current_tool].tower_ironing_area;
// now the wiping itself: // now the wiping itself:
for (int i = 0; true; ++i) { for (int i = 0; true; ++i) {
if (i!=0) { if (i!=0) {
@@ -1899,6 +1964,11 @@ void WipeTower2::toolchange_Wipe(
else else
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
if (i == 0 && do_ironing && ironing_area > 0.f) {
writer.travel(writer.x(), writer.y(), 600.f);
writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, 10.f * 60.f);
}
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width) if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
break; // in case next line would not fit break; // in case next line would not fit
@@ -1947,6 +2017,8 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
// If spare layers are excluded -> if 1 or less toolchange has been done, it must be still the first layer, too. So slow down. // If spare layers are excluded -> if 1 or less toolchange has been done, it must be still the first layer, too. So slow down.
bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers); bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers);
float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f);
if (m_enable_tower_interface_features && m_prev_layer_had_interface)
feedrate = std::min(feedrate, 20.f * 60.f);
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)),
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
@@ -2077,7 +2149,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
m_current_height += m_layer_info->height; m_current_height += m_layer_info->height;
} }
return construct_tcr(writer, false, old_tool, true); return construct_tcr(writer, false, old_tool, true, false);
} }
// Static method to get the radius and x-scaling of the stabilizing cone base. // Static method to get the radius and x-scaling of the stabilizing cone base.
@@ -2245,6 +2317,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
{ {
assert(first.new_tool == second.initial_tool); assert(first.new_tool == second.initial_tool);
WipeTower::ToolChangeResult out = first; WipeTower::ToolChangeResult out = first;
out.is_contact = first.is_contact || second.is_contact;
if (first.end_pos != second.start_pos) if (first.end_pos != second.start_pos)
out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3)
+ " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3)
+16 -2
View File
@@ -31,7 +31,8 @@ public:
WipeTower::ToolChangeResult construct_tcr(WipeTowerWriter2& writer, WipeTower::ToolChangeResult construct_tcr(WipeTowerWriter2& writer,
bool priming, bool priming,
size_t old_tool, size_t old_tool,
bool is_finish) const; bool is_finish,
bool is_contact = false) const;
// x -- x coordinates of wipe tower in mm ( left bottom corner ) // x -- x coordinates of wipe tower in mm ( left bottom corner )
// y -- y coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner )
@@ -88,11 +89,13 @@ public:
m_layer_height = layer_height; m_layer_height = layer_height;
m_depth_traversed = 0.f; m_depth_traversed = 0.f;
m_current_layer_finished = false; m_current_layer_finished = false;
m_prev_layer_had_interface = m_current_layer_has_interface;
// Advance m_layer_info iterator, making sure we got it right // Advance m_layer_info iterator, making sure we got it right
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
++m_layer_info; ++m_layer_info;
m_current_layer_has_interface = (m_layer_info != m_plan.end()) && (m_layer_info->toolchanges_depth() > WT_EPSILON);
//m_current_shape = (! this->is_first_layer() && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; //m_current_shape = (! this->is_first_layer() && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL;
m_current_shape = SHAPE_NORMAL; m_current_shape = SHAPE_NORMAL;
@@ -145,6 +148,7 @@ public:
bool is_soluble = false; bool is_soluble = false;
int temperature = 0; int temperature = 0;
int first_layer_temperature = 0; int first_layer_temperature = 0;
int interface_print_temperature = 0;
float loading_speed = 0.f; float loading_speed = 0.f;
float loading_speed_start = 0.f; float loading_speed_start = 0.f;
float unloading_speed = 0.f; float unloading_speed = 0.f;
@@ -168,6 +172,10 @@ public:
float filament_minimal_purge_on_wipe_tower = 0.f; float filament_minimal_purge_on_wipe_tower = 0.f;
float retract_length; float retract_length;
float retract_speed; float retract_speed;
float tower_interface_pre_extrusion_dist = 0.f;
float tower_interface_pre_extrusion_length = 0.f;
float tower_ironing_area = 4.f;
float tower_interface_purge_length = 0.f;
}; };
private: private:
@@ -208,6 +216,11 @@ private:
float m_perimeter_speed = 0.f; float m_perimeter_speed = 0.f;
float m_first_layer_speed = 0.f; float m_first_layer_speed = 0.f;
size_t m_first_layer_idx = size_t(-1); size_t m_first_layer_idx = size_t(-1);
bool m_flat_ironing = false;
bool m_enable_tower_interface_features = false;
bool m_enable_tower_interface_cooldown_during_tower = false;
bool m_prev_layer_had_interface = false;
bool m_current_layer_has_interface = false;
int m_wall_type; int m_wall_type;
bool m_used_fillet = true; bool m_used_fillet = true;
@@ -335,7 +348,8 @@ private:
void toolchange_Wipe( void toolchange_Wipe(
WipeTowerWriter2 &writer, WipeTowerWriter2 &writer,
const WipeTower::box_coordinates &cleaning_box, const WipeTower::box_coordinates &cleaning_box,
float wipe_volume); float wipe_volume,
bool interface_layer);
Polygon generate_support_rib_wall(WipeTowerWriter2& writer, Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
+4
View File
@@ -923,6 +923,8 @@ static std::vector<std::string> s_Preset_print_options {
"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume", "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume",
"prime_tower_infill_gap", "prime_tower_infill_gap",
"prime_tower_flat_ironing", "prime_tower_flat_ironing",
"enable_tower_interface_features",
"enable_tower_interface_cooldown_during_tower",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
"flush_into_infill", "flush_into_objects", "flush_into_support", "flush_into_infill", "flush_into_objects", "flush_into_support",
"tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter", "tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter",
@@ -959,6 +961,8 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
"filament_soluble", "filament_is_support", "filament_printable", "filament_soluble", "filament_is_support", "filament_printable",
"filament_max_volumetric_speed", "filament_adaptive_volumetric_speed", "filament_max_volumetric_speed", "filament_adaptive_volumetric_speed",
"filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
"filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume",
"filament_tower_interface_print_temp",
"nozzle_temperature", "nozzle_temperature_initial_layer", "nozzle_temperature", "nozzle_temperature_initial_layer",
// BBS // BBS
"cool_plate_temp", "textured_cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", "textured_plate_temp_initial_layer", "supertack_plate_temp_initial_layer", "supertack_plate_temp", "cool_plate_temp", "textured_cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", "textured_plate_temp_initial_layer", "supertack_plate_temp_initial_layer", "supertack_plate_temp",
+6
View File
@@ -317,6 +317,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "prime_tower_brim_width" || opt_key == "prime_tower_brim_width"
|| opt_key == "prime_tower_skip_points" || opt_key == "prime_tower_skip_points"
|| opt_key == "prime_tower_flat_ironing" || opt_key == "prime_tower_flat_ironing"
|| opt_key == "enable_tower_interface_features"
|| opt_key == "first_layer_print_sequence" || opt_key == "first_layer_print_sequence"
|| opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence"
|| opt_key == "other_layers_print_sequence_nums" || opt_key == "other_layers_print_sequence_nums"
@@ -324,6 +325,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "filament_map_mode" || opt_key == "filament_map_mode"
|| opt_key == "filament_map" || opt_key == "filament_map"
|| opt_key == "filament_adhesiveness_category" || opt_key == "filament_adhesiveness_category"
|| opt_key == "filament_tower_interface_pre_extrusion_dist"
|| opt_key == "filament_tower_interface_pre_extrusion_length"
|| opt_key == "filament_tower_ironing_area"
|| opt_key == "filament_tower_interface_purge_volume"
|| opt_key == "filament_tower_interface_print_temp"
|| opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_bridging"
|| opt_key == "wipe_tower_extra_flow" || opt_key == "wipe_tower_extra_flow"
|| opt_key == "wipe_tower_no_sparse_layers" || opt_key == "wipe_tower_no_sparse_layers"
+154 -3
View File
@@ -2587,6 +2587,46 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 15. }); def->set_default_value(new ConfigOptionFloats { 15. });
def = this->add("filament_tower_interface_pre_extrusion_dist", coFloats);
def->label = L("Interface layer pre-extrusion distance");
def->tooltip = L("Pre-extrusion distance for prime tower interface layer (where different materials meet).");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 10. });
def = this->add("filament_tower_interface_pre_extrusion_length", coFloats);
def->label = L("Interface layer pre-extrusion length");
def->tooltip = L("Pre-extrusion length for prime tower interface layer (where different materials meet).");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 0. });
def = this->add("filament_tower_ironing_area", coFloats);
def->label = L("Tower ironing area");
def->tooltip = L("Ironing area for prime tower interface layer (where different materials meet).");
def->sidetext = L("mm²");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 4. });
def = this->add("filament_tower_interface_purge_volume", coFloats);
def->label = L("Interface layer purge length");
def->tooltip = L("Purge length for prime tower interface layer (where different materials meet).");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 20. });
def = this->add("filament_tower_interface_print_temp", coInts);
def->label = L("Interface layer print temperature");
def->tooltip = L("Print temperature for prime tower interface layer (where different materials meet). If set to -1, use max recommended nozzle temperature.");
def->sidetext = L("°C");
def->min = -1;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInts { -1 });
def = this->add("filament_cooling_final_speed", coFloats); def = this->add("filament_cooling_final_speed", coFloats);
def->label = L("Speed of the last cooling move"); def->label = L("Speed of the last cooling move");
def->tooltip = L("Cooling moves are gradually accelerating towards this speed."); def->tooltip = L("Cooling moves are gradually accelerating towards this speed.");
@@ -6408,6 +6448,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("enable_tower_interface_features", coBool);
def->label = L("Enable tower interface features");
def->tooltip = L("Enable optimized prime tower interface behavior when different materials meet.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("enable_tower_interface_cooldown_during_tower", coBool);
def->label = L("Cool down from interface boost during prime tower");
def->tooltip = L("When interface-layer temperature boost is active, set the nozzle back to print temperature at the start of the prime tower so it cools down during the tower.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("prime_tower_infill_gap", coPercent); def = this->add("prime_tower_infill_gap", coPercent);
def->label = L("Infill gap"); def->label = L("Infill gap");
def->tooltip = L("Infill gap."); def->tooltip = L("Infill gap.");
@@ -9236,6 +9288,61 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
} }
} }
namespace {
// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant.
// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes.
static void normalize_stride2_floats(ConfigOptionFloats &opt, size_t expected_size)
{
auto &v = opt.values;
if (expected_size == 0) {
v.clear();
return;
}
if (v.empty()) {
// Fallback: keep behavior predictable instead of crashing. This should be rare.
v.resize(expected_size, 0.0);
return;
}
const double first = v[0];
const double second = (v.size() >= 2) ? v[1] : first;
// Ensure we have at least one (normal,silent) pair to replicate.
if (v.size() < 2) {
v.resize(2, first);
v[1] = second;
}
// Keep pair alignment if some legacy preset produced odd length.
if (v.size() % 2 != 0)
v.push_back(second);
if (v.size() > expected_size) {
v.resize(expected_size);
return;
}
const size_t have_variants = v.size() / 2;
const size_t want_variants = expected_size / 2;
v.resize(expected_size);
for (size_t vi = have_variants; vi < want_variants; ++vi) {
v[vi * 2] = first;
if (vi * 2 + 1 < v.size())
v[vi * 2 + 1] = second;
}
}
static void log_normalize_legacy_vector_size(const char *fn, const std::string &key, int stride, size_t src_size, size_t dest_size, size_t expected_size,
size_t restore_n, int cur_variant_count, int target_variant_count, size_t cur_ids, size_t target_ids,
const ConfigOption *opt_src, const ConfigOption *opt_target)
{
BOOST_LOG_TRIVIAL(debug) << fn << ": normalizing legacy vector size for key '" << key << "'"
<< " stride=" << stride << " src_size=" << src_size << " dest_size=" << dest_size << " expected=" << expected_size
<< " restore_index.size=" << restore_n << " cur_variants=" << cur_variant_count << " target_variants=" << target_variant_count
<< " cur_ids=" << cur_ids << " target_ids=" << target_ids << " cur_value=" << opt_src->serialize()
<< " target_value=" << opt_target->serialize();
}
} // namespace
void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys,
std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2) std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2)
{ {
@@ -9258,6 +9365,9 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
variant_index.resize(target_variant_count, -1); variant_index.resize(target_variant_count, -1);
if (cur_variant_count == 0) { if (cur_variant_count == 0) {
// Defensive: target_variant_count may be 0 if the preset doesn't carry extruder_variant_name.
// In that case keep variant_index empty and let the downstream size checks produce a useful error.
if (!variant_index.empty())
variant_index[0] = 0; variant_index[0] = 0;
} }
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
@@ -9300,12 +9410,53 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
//nothing to do, keep the original one //nothing to do, keep the original one
} }
else { else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(opt_target);
int stride = 1; int stride = 1;
if (key_set2.find(opt) != key_set2.end()) if (key_set2.find(opt) != key_set2.end())
stride = 2; stride = 2;
opt_vec_src->set_with_restore(opt_vec_dest, variant_index, stride);
const size_t restore_n = variant_index.size();
const size_t expected_size = restore_n * size_t(stride);
if (stride == 2) {
// Options in key_set2 are machine limits stored as (normal,silent) pairs per printer variant.
if (opt_src->type() != coFloats || opt_target->type() != coFloats)
throw ConfigurationError((boost::format("%1%: key '%2%' is expected to be ConfigOptionFloats for stride=2.") % __FUNCTION__ % opt).str());
auto *src_f = static_cast<ConfigOptionFloats*>(opt_src);
ConfigOptionFloats rhs_tmp(*static_cast<const ConfigOptionFloats*>(opt_target));
const size_t src_size = src_f->values.size();
const size_t dest_size = rhs_tmp.values.size();
if (src_size != expected_size || dest_size != expected_size)
log_normalize_legacy_vector_size(__FUNCTION__, opt, stride, src_size, dest_size, expected_size, restore_n, cur_variant_count,
target_variant_count, cur_extruder_ids.size(), target_extruder_ids.size(), opt_src, opt_target);
// Normalize src in-place so backup_values indexing is safe, normalize rhs via a temporary copy.
normalize_stride2_floats(*src_f, expected_size);
normalize_stride2_floats(rhs_tmp, expected_size);
src_f->set_with_restore(&rhs_tmp, variant_index, stride);
} else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const size_t src_size = opt_vec_src->size();
const size_t dest_size = static_cast<const ConfigOptionVectorBase*>(opt_target)->size();
if (src_size != expected_size || dest_size != expected_size)
log_normalize_legacy_vector_size(__FUNCTION__, opt, stride, src_size, dest_size, expected_size, restore_n, cur_variant_count,
target_variant_count, cur_extruder_ids.size(), target_extruder_ids.size(), opt_src, opt_target);
if (opt_vec_src->size() != expected_size)
opt_vec_src->resize(expected_size, opt_target);
// Normalize rhs via a cloned temporary (rhs itself is const).
ConfigOptionUniquePtr rhs_owner(opt_target->clone());
ConfigOptionVectorBase *rhs_vec = dynamic_cast<ConfigOptionVectorBase*>(rhs_owner.get());
if (rhs_vec == nullptr)
throw ConfigurationError((boost::format("%1%: key '%2%' is expected to be a vector option.") % __FUNCTION__ % opt).str());
if (rhs_vec->size() != expected_size)
rhs_vec->resize(expected_size, opt_target);
opt_vec_src->set_with_restore(rhs_vec, variant_index, stride);
}
} }
} }
} }
+7
View File
@@ -1376,6 +1376,11 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInts, filament_cooling_moves)) ((ConfigOptionInts, filament_cooling_moves))
((ConfigOptionFloats, filament_cooling_initial_speed)) ((ConfigOptionFloats, filament_cooling_initial_speed))
((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower))
((ConfigOptionFloats, filament_tower_interface_pre_extrusion_dist))
((ConfigOptionFloats, filament_tower_interface_pre_extrusion_length))
((ConfigOptionFloats, filament_tower_ironing_area))
((ConfigOptionFloats, filament_tower_interface_purge_volume))
((ConfigOptionInts, filament_tower_interface_print_temp))
((ConfigOptionFloats, filament_cooling_final_speed)) ((ConfigOptionFloats, filament_cooling_final_speed))
((ConfigOptionStrings, filament_ramming_parameters)) ((ConfigOptionStrings, filament_ramming_parameters))
((ConfigOptionBools, filament_multitool_ramming)) ((ConfigOptionBools, filament_multitool_ramming))
@@ -1505,6 +1510,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionPercent, prime_tower_infill_gap)) ((ConfigOptionPercent, prime_tower_infill_gap))
((ConfigOptionBool, prime_tower_skip_points)) ((ConfigOptionBool, prime_tower_skip_points))
((ConfigOptionBool, prime_tower_flat_ironing)) ((ConfigOptionBool, prime_tower_flat_ironing))
((ConfigOptionBool, enable_tower_interface_features))
((ConfigOptionBool, enable_tower_interface_cooldown_during_tower))
((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloat, wipe_tower_bridging))
((ConfigOptionPercent, wipe_tower_extra_flow)) ((ConfigOptionPercent, wipe_tower_extra_flow))
((ConfigOptionFloats, flush_volumes_matrix)) ((ConfigOptionFloats, flush_volumes_matrix))
+4 -1
View File
@@ -816,9 +816,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("preheat_steps", have_ooze_prevention && (preheat_steps > 0)); toggle_line("preheat_steps", have_ooze_prevention && (preheat_steps > 0));
bool have_prime_tower = config->opt_bool("enable_prime_tower"); bool have_prime_tower = config->opt_bool("enable_prime_tower");
for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework"}) for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework", "enable_tower_interface_features"})
toggle_line(el, have_prime_tower); toggle_line(el, have_prime_tower);
toggle_line("enable_tower_interface_cooldown_during_tower",
have_prime_tower && config->opt_bool("enable_tower_interface_features"));
for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}) for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"})
toggle_line(el, !bSEMM); toggle_line(el, !bSEMM);
+12
View File
@@ -25,6 +25,18 @@
#include "../Utils/MacDarkMode.hpp" #include "../Utils/MacDarkMode.hpp"
#endif // __APPLE__ #endif // __APPLE__
// Verify GLEW and wxWidgets use the same OpenGL backend (EGL vs GLX).
// A mismatch causes rendering failures: GLEW's function loading must match
// the context type created by wxWidgets.
#if defined(__linux__)
#if defined(GLEW_EGL) && (!defined(wxUSE_GLCANVAS_EGL) || !wxUSE_GLCANVAS_EGL)
#error "OpenGL backend mismatch: GLEW has EGL support enabled but wxWidgets does not. Ensure GLEW_USE_EGL and wxUSE_GLCANVAS_EGL are both ON or both OFF."
#endif
#if !defined(GLEW_EGL) && defined(wxUSE_GLCANVAS_EGL) && wxUSE_GLCANVAS_EGL
#error "OpenGL backend mismatch: wxWidgets has EGL support enabled but GLEW does not. Ensure GLEW_USE_EGL and wxUSE_GLCANVAS_EGL are both ON or both OFF."
#endif
#endif
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
+7
View File
@@ -2576,6 +2576,8 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); optgroup = page->new_optgroup(L("Prime tower"), L"param_tower");
optgroup->append_single_option_line("enable_prime_tower", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("enable_prime_tower", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_skip_points", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_tower_skip_points", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("enable_tower_interface_features", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("enable_tower_interface_cooldown_during_tower", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width"); optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width");
optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower");
@@ -4040,6 +4042,11 @@ void TabFilament::build()
page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders
optgroup = page->new_optgroup(L("Wipe tower parameters"), "param_tower"); optgroup = page->new_optgroup(L("Wipe tower parameters"), "param_tower");
optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower", "material_multimaterial#multimaterial-wipe-tower-parameters"); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup->append_single_option_line("filament_tower_interface_pre_extrusion_dist", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup->append_single_option_line("filament_tower_interface_pre_extrusion_length", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup->append_single_option_line("filament_tower_ironing_area", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup->append_single_option_line("filament_tower_interface_purge_volume", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup->append_single_option_line("filament_tower_interface_print_temp", "material_multimaterial#multimaterial-wipe-tower-parameters");
optgroup = page->new_optgroup(L("Multi Filament")); optgroup = page->new_optgroup(L("Multi Filament"));
// optgroup->append_single_option_line("filament_flush_temp", "", 0); // optgroup->append_single_option_line("filament_flush_temp", "", 0);
+311 -144
View File
@@ -461,42 +461,6 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
return; return;
} }
// Color normalization helper (handles #RRGGBB, 0xRRGGBB -> RRGGBBAA)
auto normalize_color = [](const std::string& color) -> std::string {
std::string value = color;
boost::trim(value);
// Remove 0x or 0X prefix if present
if (value.size() >= 2 && (value.rfind("0x", 0) == 0 || value.rfind("0X", 0) == 0)) {
value = value.substr(2);
}
// Remove # prefix if present
if (!value.empty() && value[0] == '#') {
value = value.substr(1);
}
// Extract only hex digits
std::string normalized;
for (char c : value) {
if (std::isxdigit(static_cast<unsigned char>(c))) {
normalized.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(c))));
}
}
// If 6 hex digits, add FF alpha
if (normalized.size() == 6) {
normalized += "FF";
}
// Validate length - return default if invalid
if (normalized.size() != 8) {
return "00000000";
}
return normalized;
};
// Build BBL-format JSON for DevFilaSystemParser::ParseV1_0 // Build BBL-format JSON for DevFilaSystemParser::ParseV1_0
nlohmann::json ams_json = nlohmann::json::object(); nlohmann::json ams_json = nlohmann::json::object();
nlohmann::json ams_array = nlohmann::json::array(); nlohmann::json ams_array = nlohmann::json::array();
@@ -535,7 +499,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_info_idx"] = tray->tray_info_idx;
tray_json["tray_type"] = tray->tray_type; tray_json["tray_type"] = tray->tray_type;
tray_json["tray_color"] = normalize_color(tray->tray_color); tray_json["tray_color"] = normalize_color_value(tray->tray_color);
// Add temperature data if provided // Add temperature data if provided
if (tray->bed_temp > 0) { if (tray->bed_temp > 0) {
@@ -604,121 +568,32 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
// Fetch AFC lane data from Moonraker database (inline)
std::string url = join_url(device_info.base_url, "/server/database/item?namespace=lane_data");
std::string response_body;
bool success = false;
std::string http_error;
auto http = Http::get(url);
if (!device_info.api_key.empty()) {
http.header("X-Api-Key", device_info.api_key);
}
http.timeout_connect(5)
.timeout_max(10)
.on_complete([&](std::string body, unsigned status) {
if (status == 200) {
response_body = body;
success = true;
} else {
http_error = "HTTP error: " + std::to_string(status);
}
})
.on_error([&](std::string body, std::string err, unsigned status) {
http_error = err;
if (status > 0) {
http_error += " (HTTP " + std::to_string(status) + ")";
}
})
.perform_sync();
if (!success) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_filament_info: Failed to fetch lane data: " << http_error;
return false;
}
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
if (json.is_discarded()) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_filament_info: Invalid JSON response";
return false;
}
// Expected structure: { "result": { "namespace": "lane_data", "value": { "lane1": {...}, ... } } }
if (!json.contains("result") || !json["result"].contains("value") || !json["result"]["value"].is_object()) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_filament_info: Unexpected JSON structure or no lane_data found";
return false;
}
// Parse response into AmsTrayData
const auto& value = json["result"]["value"];
std::vector<AmsTrayData> trays; std::vector<AmsTrayData> trays;
int max_lane_index = 0; int max_lane_index = 0;
// Null-safe JSON accessors: nlohmann::json::value() throws type_error // Try Happy Hare first (more widely adopted, supports more filament changers)
// when the key exists but the value is null (type mismatch). if (fetch_hh_filament_info(trays, max_lane_index)) {
auto safe_string = [](const nlohmann::json& obj, const char* key) -> std::string { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
auto it = obj.find(key); << (max_lane_index + 1) << " gates";
if (it != obj.end() && it->is_string())
return it->get<std::string>();
return "";
};
auto safe_int = [](const nlohmann::json& obj, const char* key) -> int {
auto it = obj.find(key);
if (it != obj.end() && it->is_number())
return it->get<int>();
return 0;
};
for (const auto& [lane_key, lane_obj] : value.items()) {
if (!lane_obj.is_object()) {
continue;
}
// Extract lane index from the "lane" field (tool number, 0-based)
std::string lane_str = safe_string(lane_obj, "lane");
int lane_index = -1;
if (!lane_str.empty()) {
try {
lane_index = std::stoi(lane_str);
} catch (...) {
lane_index = -1;
}
}
if (lane_index < 0) {
continue;
}
AmsTrayData tray;
tray.slot_index = lane_index;
tray.tray_color = safe_string(lane_obj, "color");
tray.tray_type = safe_string(lane_obj, "material");
tray.bed_temp = safe_int(lane_obj, "bed_temp");
tray.nozzle_temp = safe_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);
max_lane_index = std::max(max_lane_index, lane_index);
trays.push_back(tray);
}
if (trays.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: No AFC lanes found";
return false;
}
// Calculate AMS count from max lane index (4 trays per AMS unit)
int ams_count = (max_lane_index + 4) / 4; int ams_count = (max_lane_index + 4) / 4;
// Build and parse the AMS payload
build_ams_payload(ams_count, max_lane_index, trays); build_ams_payload(ams_count, max_lane_index, trays);
return true; return true;
} }
// Fallback to AFC
if (fetch_afc_filament_info(trays, max_lane_index)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected AFC with "
<< (max_lane_index + 1) << " lanes";
int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays);
return true;
}
// No MMU detected - this is normal for printers without MMU, not an error
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: No MMU system detected (neither HH nor AFC)";
return false;
}
std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input) std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input)
{ {
std::string result = input; std::string result = input;
@@ -780,6 +655,298 @@ std::string MoonrakerPrinterAgent::map_filament_type_to_generic_id(const std::st
return UNKNOWN_FILAMENT_ID; return UNKNOWN_FILAMENT_ID;
} }
// JSON helper methods - null-safe accessors
std::string MoonrakerPrinterAgent::safe_json_string(const nlohmann::json& obj, const char* key)
{
auto it = obj.find(key);
if (it != obj.end() && it->is_string())
return it->get<std::string>();
return "";
}
int MoonrakerPrinterAgent::safe_json_int(const nlohmann::json& obj, const char* key)
{
auto it = obj.find(key);
if (it != obj.end() && it->is_number())
return it->get<int>();
return 0;
}
std::string MoonrakerPrinterAgent::safe_array_string(const nlohmann::json& arr, int idx)
{
if (arr.is_array() && idx >= 0 && idx < static_cast<int>(arr.size()) && arr[idx].is_string())
return arr[idx].get<std::string>();
return "";
}
int MoonrakerPrinterAgent::safe_array_int(const nlohmann::json& arr, int idx)
{
if (arr.is_array() && idx >= 0 && idx < static_cast<int>(arr.size()) && arr[idx].is_number())
return arr[idx].get<int>();
return 0;
}
std::string MoonrakerPrinterAgent::normalize_color_value(const std::string& color)
{
std::string value = color;
boost::trim(value);
// Remove 0x or 0X prefix if present
if (value.size() >= 2 && (value.rfind("0x", 0) == 0 || value.rfind("0X", 0) == 0)) {
value = value.substr(2);
}
// Remove # prefix if present
if (!value.empty() && value[0] == '#') {
value = value.substr(1);
}
// Extract only hex digits
std::string normalized;
for (char c : value) {
if (std::isxdigit(static_cast<unsigned char>(c))) {
normalized.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(c))));
}
}
// If 6 hex digits, add FF alpha
if (normalized.size() == 6) {
normalized += "FF";
}
// Validate length - return default if invalid
if (normalized.size() != 8) {
return "00000000";
}
return normalized;
}
// Fetch filament info from Armored Turtle AFC
bool MoonrakerPrinterAgent::fetch_afc_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index)
{
// Fetch AFC lane data from Moonraker database
std::string url = join_url(device_info.base_url, "/server/database/item?namespace=lane_data");
std::string response_body;
bool success = false;
std::string http_error;
auto http = Http::get(url);
if (!device_info.api_key.empty()) {
http.header("X-Api-Key", device_info.api_key);
}
http.timeout_connect(5)
.timeout_max(10)
.on_complete([&](std::string body, unsigned status) {
if (status == 200) {
response_body = body;
success = true;
} else {
http_error = "HTTP error: " + std::to_string(status);
}
})
.on_error([&](std::string body, std::string err, unsigned status) {
http_error = err;
if (status > 0) {
http_error += " (HTTP " + std::to_string(status) + ")";
}
})
.perform_sync();
if (!success) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_afc_filament_info: Failed to fetch lane data: " << http_error;
return false;
}
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
if (json.is_discarded()) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_afc_filament_info: Invalid JSON response";
return false;
}
// Expected structure: { "result": { "namespace": "lane_data", "value": { "lane1": {...}, ... } } }
if (!json.contains("result") || !json["result"].contains("value") || !json["result"]["value"].is_object()) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_afc_filament_info: Unexpected JSON structure or no lane_data found";
return false;
}
// Parse response into AmsTrayData
const auto& value = json["result"]["value"];
trays.clear();
max_lane_index = 0;
for (const auto& [lane_key, lane_obj] : value.items()) {
if (!lane_obj.is_object()) {
continue;
}
// Extract lane index from the "lane" field (tool number, 0-based)
std::string lane_str = safe_json_string(lane_obj, "lane");
int lane_index = -1;
if (!lane_str.empty()) {
try {
lane_index = std::stoi(lane_str);
} catch (...) {
lane_index = -1;
}
}
if (lane_index < 0) {
continue;
}
AmsTrayData tray;
tray.slot_index = lane_index;
tray.tray_color = safe_json_string(lane_obj, "color");
tray.tray_type = safe_json_string(lane_obj, "material");
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);
max_lane_index = std::max(max_lane_index, lane_index);
trays.push_back(tray);
}
if (trays.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_afc_filament_info: No AFC lanes found";
return false;
}
return true;
}
// Fetch filament info from Happy Hare MMU
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index)
{
// Query Happy Hare MMU status
std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
std::string response_body;
bool success = false;
std::string http_error;
auto http = Http::get(url);
if (!device_info.api_key.empty()) {
http.header("X-Api-Key", device_info.api_key);
}
http.timeout_connect(5)
.timeout_max(10)
.on_complete([&](std::string body, unsigned status) {
if (status == 200) {
response_body = body;
success = true;
} else {
http_error = "HTTP error: " + std::to_string(status);
}
})
.on_error([&](std::string body, std::string err, unsigned status) {
http_error = err;
if (status > 0) {
http_error += " (HTTP " + std::to_string(status) + ")";
}
})
.perform_sync();
if (!success) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Failed to fetch HH data: " << http_error;
return false;
}
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
if (json.is_discarded()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Invalid JSON response";
return false;
}
// Expected structure: { "result": { "status": { "mmu": { ... } } } }
if (!json.contains("result") || !json["result"].contains("status") ||
!json["result"]["status"].contains("mmu") || !json["result"]["status"]["mmu"].is_object()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No mmu object in response";
return false;
}
const auto& mmu = json["result"]["status"]["mmu"];
// Check if HH is installed (empty mmu object means HH not installed)
if (mmu.empty()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Empty mmu object (HH not installed)";
return false;
}
// Get num_gates
if (!mmu.contains("num_gates") || !mmu["num_gates"].is_number()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No num_gates field";
return false;
}
int num_gates = mmu["num_gates"].get<int>();
if (num_gates <= 0) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Invalid num_gates: " << num_gates;
return false;
}
// 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_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();
if (!gate_status.is_array() || !gate_material.is_array() ||
!gate_color.is_array() || !gate_temperature.is_array()) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_hh_filament_info: HH arrays not found or invalid type";
return false;
}
// Parse gate data
trays.clear();
max_lane_index = 0;
for (int gate_idx = 0; gate_idx < num_gates; ++gate_idx) {
// Check gate_status: -1 = unknown, 0 = empty, 1 or 2 = available
int status = safe_array_int(gate_status, gate_idx);
if (status <= 0) {
continue; // Skip unknown or empty gates
}
// Extract gate data
std::string material = safe_array_string(gate_material, gate_idx);
std::string color = safe_array_string(gate_color, gate_idx);
int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
// Skip if no material type (empty gate)
if (material.empty()) {
continue;
}
AmsTrayData tray;
tray.slot_index = gate_idx;
tray.tray_type = material;
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);
max_lane_index = std::max(max_lane_index, gate_idx);
trays.push_back(tray);
}
if (trays.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No valid HH gates found";
return false;
}
return true;
}
int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str) int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str)
{ {
auto json = nlohmann::json::parse(json_str, nullptr, false); auto json = nlohmann::json::parse(json_str, nullptr, false);
@@ -160,6 +160,17 @@ private:
const std::string& api_key, const std::string& api_key,
uint64_t generation); uint64_t generation);
// System-specific filament fetch methods
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
bool fetch_afc_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
// JSON helper methods
static std::string safe_json_string(const nlohmann::json& obj, const char* key);
static int safe_json_int(const nlohmann::json& obj, const char* key);
static std::string safe_array_string(const nlohmann::json& arr, int idx);
static int safe_array_int(const nlohmann::json& arr, int idx);
static std::string normalize_color_value(const std::string& color);
std::string ssdp_announced_host; std::string ssdp_announced_host;
std::string ssdp_announced_id; std::string ssdp_announced_id;
std::shared_ptr<ICloudServiceAgent> m_cloud_agent; std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
+1 -1
View File
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING) if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0") set(BBL_INTERNAL_TESTING "0")
endif() endif()
set(SoftFever_VERSION "2.3.2-dev") set(SoftFever_VERSION "2.3.2-beta2")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION}) SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})