Compare commits

...

11 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 Blaicher
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

View File

@@ -66,7 +66,7 @@ jobs:
matrix:
include:
- os: windows-latest
- os: orca-macos-arm64
- os: macos-14
arch: arm64
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}

View File

@@ -33,7 +33,7 @@ jobs:
- name: set outputs
id: set_outputs
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"'}}
run: |
echo cache-key=${{ inputs.os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}

View File

@@ -74,18 +74,18 @@ jobs:
cd ${{ github.workspace }}/deps/build
- name: Build on Mac ${{ inputs.arch }}
if: inputs.os == 'orca-macos-arm64'
if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
run: |
# brew install automake texinfo libtool
# brew list
# brew uninstall --ignore-dependencies zstd
./build_release_macos.sh -dx -a universal -t 10.15
brew install automake texinfo libtool
brew list
brew uninstall --ignore-dependencies zstd
./build_release_macos.sh -dx -a universal -t 10.15 -1
for arch in arm64 x86_64; do
(cd "${{ github.workspace }}/deps/build/${arch}" && \
find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +)
done
# brew install zstd
brew install zstd
- name: Apt-Install Dependencies
@@ -104,7 +104,7 @@ jobs:
# Upload Artifacts
# - name: Upload Mac ${{ inputs.arch }} artifacts
# if: inputs.os == 'orca-macos-arm64'
# if: inputs.os == 'macos-14'
# uses: actions/upload-artifact@v6
# with:
# name: OrcaSlicer_dep_mac_${{ env.date }}

View File

@@ -86,29 +86,29 @@ jobs:
# Mac
- name: Install tools mac
if: inputs.os == 'orca-macos-arm64'
if: inputs.os == 'macos-14'
run: |
# brew install libtool
# brew list
brew install libtool
brew list
mkdir -p ${{ github.workspace }}/deps/build
# - name: Free disk space
# if: inputs.os == 'orca-macos-arm64'
# run: |
# df -hI /dev/disk3s1s1
# 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/*
# df -hI /dev/disk3s1s1
- name: Free disk space
if: inputs.os == 'macos-14'
run: |
df -hI /dev/disk3s1s1
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/*
df -hI /dev/disk3s1s1
- name: Build slicer mac
if: inputs.os == 'orca-macos-arm64'
if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
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
- 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 }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
@@ -162,7 +162,7 @@ jobs:
fi
- 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 }}
run: |
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
@@ -181,14 +181,14 @@ jobs:
fi
- name: Upload artifacts mac
if: inputs.os == 'orca-macos-arm64'
if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v6
with:
name: OrcaSlicer_Mac_universal_${{ env.ver }}
path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
- name: Upload OrcaSlicer_profile_validator DMG mac
if: inputs.os == 'orca-macos-arm64'
if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v6
with:
name: OrcaSlicer_profile_validator_Mac_universal_DMG_${{ env.ver }}
@@ -196,7 +196,7 @@ jobs:
if-no-files-found: ignore
- 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
with:
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
- 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
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}

View File

@@ -5,6 +5,8 @@ find_package(OpenGL QUIET REQUIRED)
orcaslicer_add_cmake_project(
GLEW
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
CMAKE_ARGS
-DGLEW_USE_EGL=OFF
)
if (MSVC)

View File

@@ -3,9 +3,17 @@ project(GLEW)
find_package(OpenGL REQUIRED)
if(OpenGL_EGL_FOUND)
message(STATUS "building GLEW for EGL (hope that wxWidgets agrees, otherwise you won't have any output!)")
# Allow parent project to control EGL usage.
# 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")
else()
message(STATUS "Building GLEW with GLX support")
endif()
add_library(GLEW src/glew.c)

View File

@@ -51,6 +51,7 @@ orcaslicer_add_cmake_project(
-DwxUSE_UNICODE=ON
-DwxUSE_PRIVATE_FONTS=ON
-DwxUSE_OPENGL=ON
-DwxUSE_GLCANVAS_EGL=OFF
-DwxUSE_WEBREQUEST=ON
-DwxUSE_WEBVIEW=ON
${_wx_edge}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "Afinia",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Afinia configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Anker",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "Anker configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Anycubic",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Anycubic configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Artillery",
"version": "02.03.02.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Artillery configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.00.00.56",
"version": "02.01.00.00",
"force_update": "0",
"description": "the initial version of BBL configurations",
"machine_model_list": [

View File

@@ -102,6 +102,21 @@
"filament_minimal_purge_on_wipe_tower": [
"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": [
"3"
],
@@ -277,4 +292,4 @@
"volumetric_speed_coefficients":[
"0 0 0 0 0 0"
]
}
}

View File

@@ -93,6 +93,8 @@
"prime_tower_lift_height": "-1",
"prime_tower_max_speed": "90",
"prime_tower_flat_ironing": "0",
"enable_tower_interface_features": "0",
"enable_tower_interface_cooldown_during_tower": "0",
"raft_layers": "0",
"reduce_crossing_wall": "0",
"reduce_infill_retraction": "1",
@@ -170,4 +172,4 @@
"xy_contour_compensation": "0",
"xy_hole_compensation": "0",
"z_direction_outwall_speed_continuous": "0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "BIQU",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "BIQU configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Blocks",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Blocks configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "CONSTRUCT3D",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Construct3D configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Chuanying",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Chuanying configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Co Print",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "CoPrint configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "CoLiDo",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "CoLiDo configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Comgrow",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Comgrow configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Creality",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "Creality configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Cubicon",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Cubicon configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Custom Printer",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "DeltaMaker",
"url": "",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "DeltaMaker configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Dremel",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Dremel configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Elegoo",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "Elegoo configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Eryone",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Eryone configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "FLSun",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "FLSun configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Flashforge",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Flashforge configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "FlyingBear",
"version": "02.03.01.00",
"version": "02.03.02.40",
"force_update": "1",
"description": "FlyingBear configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Folgertech",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Folgertech configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Geeetech",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "Geeetech configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Ginger Additive",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "1",
"description": "Ginger configuration",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "InfiMech",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "1",
"description": "InfiMech configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Kingroon",
"url": "https://kingroon.com/",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "1",
"description": "Kingroon configuration files",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "LONGER",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "LONGER configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Lulzbot",
"url": "https://ohai.lulzbot.com/group/taz-6/",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Lulzbot configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "M3D",
"version": "1.0.0",
"version": "02.03.02.40",
"force_update": "0",
"description": "Configuration for M3D printers",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "MagicMaker",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "MagicMaker configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Mellow",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Mellow Printer Profiles",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "OpenEYE",
"url": "http://www.openeye.tech",
"version": "01.00.00.03",
"version": "02.03.02.40",
"force_update": "0",
"description": "OpenEYE Printers Configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Orca Arena Printer",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Orca Arena configuration files",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Peopoly",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Peopoly configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Phrozen",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "Phrozen configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Positron 3D",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Positron 3D Printer Profile",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Prusa",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "Prusa configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Qidi",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "Qidi configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "RH3D",
"version": "00.06.10.25",
"version": "02.03.02.40",
"force_update": "0",
"description": "RH3D - printer profiles",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Raise3D",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Raise3D configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "RatRig",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "RatRig configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "RolohaunDesign",
"version": "02.03.02.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "RolohaunDesign Printer Profiles",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "SecKit",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "SecKit configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Snapmaker",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Sovol",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Sovol configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Tiertime",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Tiertime configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Tronxy",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Tronxy configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "TwoTrees",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "1",
"description": "TwoTrees configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "UltiMaker",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "UltiMaker configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Vivedino",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Vivedino configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Volumic",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "1",
"description": "VOLUMIC configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Voron",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "Voron configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Voxelab",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Voxelab configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Vzbot",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Vzbot configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "WEMAKE3D",
"version": "02.03.01.20",
"version": "02.03.02.40",
"force_update": "0",
"description": "WEMAKE3D configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Wanhao France",
"version": "02.03.01.11",
"version": "02.03.02.40",
"force_update": "0",
"description": "Wanhao France D12 configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Wanhao",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Wanhao configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "WonderMaker",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "WonderMaker configurations",
"machine_model_list": [

View File

@@ -1,7 +1,7 @@
{
"name": "Z-Bolt",
"url": "",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "0",
"description": "Z-Bolt configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "innovatiQ",
"version": "02.03.01.10",
"version": "02.03.02.40",
"force_update": "1",
"description": "innovatiQ configuration",
"machine_model_list": [

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("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange));
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));
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("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1)));
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("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("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));
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 deretraction_str;
int toolchange_temp_override = -1;
int interface_temp = -1;
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
if (is_ramming)
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) {
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
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.
DynamicConfig config;
@@ -7192,7 +7357,7 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li
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);
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
if (abs(print_z) < EPSILON)
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();
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("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("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_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("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
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);
if (add_change_filament_624) {
gcode += "M625\n";

View File

@@ -249,7 +249,7 @@ public:
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 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_QIDI_Printer();

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
void insert_lines(const Backtrace& backtrace,
bool insert_lines(const Backtrace& backtrace,
const std::string& cmd,
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
auto is_start_pos = [](const std::string& curr_cmd) {
@@ -775,6 +787,21 @@ public:
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
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) {
const float backtrace_time_i = (i + 1) * time_step;
const float time_threshold_i = m_times[Normal] - backtrace_time_i;
@@ -796,6 +823,23 @@ public:
// 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) {
// 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];
std::vector<float> time_diffs;
time_diffs.push_back(m_times[Normal] - last_time_insertion);
@@ -814,8 +858,39 @@ public:
}
++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:
@@ -1254,12 +1329,169 @@ void GCodeProcessor::run_post_process()
if (m_print != nullptr)
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(
backtrace, cmd,
// line inserter
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
const int temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] :
m_filament_nozzle_temp_first_layer[tool_number]);
[tool_number, override_temp, this](unsigned int id, const std::vector<float>& time_diffs) {
const int base_temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[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
// Prusa-Firmware-Buddy here
/**
@@ -1302,7 +1534,11 @@ void GCodeProcessor::run_post_process()
}
}
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 */

View File

@@ -16,7 +16,6 @@
namespace Slic3r
{
static constexpr float flat_iron_area = 4.f;
constexpr float flat_iron_speed = 10.f * 60.f;
static const double wipe_tower_wall_infill_overlap = 0.0;
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
@@ -1243,7 +1242,8 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
size_t old_tool,
bool is_finish,
bool is_tool_change,
float purge_volume) const
float purge_volume,
bool is_contact) const
{
ToolChangeResult result;
result.priming = priming;
@@ -1260,6 +1260,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
result.is_finish_first = is_finish;
result.nozzle_change_result = m_nozzle_change_result;
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);
// BBS
@@ -1283,6 +1284,7 @@ WipeTower::ToolChangeResult WipeTower::construct_block_tcr(WipeTowerWriter &writ
result.wipe_path = std::move(writer.wipe_path());
result.is_finish_first = is_finish;
result.is_tool_change = false;
result.is_contact = false;
result.tool_change_start_pos = Vec2f(0, 0);
// BBS
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_extra_spacing((float)config.prime_tower_infill_gap.value/100.f),
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);
// 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_initial_layer = config.nozzle_temperature_initial_layer.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.
// 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>();
}
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 &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);
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())
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)
@@ -2284,6 +2298,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
bool first_layer = is_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);
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;
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);
@@ -2426,7 +2442,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
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);
}
// 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);
WipeTower::ToolChangeResult out = first;
out.is_contact = first.is_contact || second.is_contact;
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" +
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);
if (!block) {
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.
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.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_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)) {
float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width;
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);
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");
++m_num_tool_changes;
} 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())
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)
@@ -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_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)
@@ -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_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 &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.retract(retract_length, retract_speed);
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.);
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);
} else
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.retract(retract_length, retract_speed);
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.);
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);
}else
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) {
reset_block_status();
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);
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_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)

View File

@@ -83,6 +83,7 @@ public:
bool priming;
bool is_tool_change{false};
bool is_contact{false};
Vec2f tool_change_start_pos;
// Pass a polyline so that normal G-code generator can do a wipe for us.
@@ -160,8 +161,9 @@ public:
bool priming,
size_t old_tool,
bool is_finish,
bool is_tool_change,
float purge_volume) const;
bool is_tool_change,
float purge_volume,
bool is_contact = false) const;
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
bool priming,
@@ -319,6 +321,7 @@ public:
bool is_support = false;
int nozzle_temperature = 0;
int nozzle_temperature_initial_layer = 0;
int interface_print_temperature = 0;
float loading_speed = 0.f;
float loading_speed_start = 0.f;
float unloading_speed = 0.f;
@@ -336,6 +339,10 @@ public:
float retract_length;
float retract_speed;
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;
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_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
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))));
@@ -503,7 +514,7 @@ private:
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
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
void save_on_last_wipe();

View File

@@ -972,6 +972,25 @@ public:
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.
WipeTowerWriter2& extrude_arc_explicit(ArcSegment& arc,
float f = 0.f,
@@ -1200,7 +1219,8 @@ private:
WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
bool priming,
size_t old_tool,
bool is_finish) const
bool is_finish,
bool is_contact) const
{
WipeTower::ToolChangeResult result;
result.priming = priming;
@@ -1215,6 +1235,7 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
result.extrusions = std::move(writer.extrusions());
result.wipe_path = std::move(writer.wipe_path());
result.is_finish_first = is_finish;
result.is_contact = is_contact;
// 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
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_rib_width(config.wipe_tower_rib_width),
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,
// 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].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);
{
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.
// 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.
if (idx_tool + 1 == tools.size()) {
// 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 {
// 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);
toolchange_Wipe(writer, cleaning_box , 20.f);
toolchange_Wipe(writer, cleaning_box , 20.f, false);
WipeTower::box_coordinates box = cleaning_box;
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);
@@ -1472,7 +1506,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
"\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
@@ -1487,6 +1521,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
float wipe_area = 0.f;
float wipe_volume = 0.f;
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
// Finds this toolchange info
if (tool != (unsigned int)(-1))
@@ -1501,6 +1536,12 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
else {
// 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(
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_Load(writer, cleaning_box);
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");
++ m_num_tool_changes;
} else
@@ -1564,7 +1625,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
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, false);
return construct_tcr(writer, false, old_tool, false, interface_layer);
}
@@ -1854,7 +1915,8 @@ void WipeTower2::toolchange_Load(
void WipeTower2::toolchange_Wipe(
WipeTowerWriter2 &writer,
const WipeTower::box_coordinates &cleaning_box,
float wipe_volume)
float wipe_volume,
bool interface_layer)
{
// Increase flow on first layer, slow down print.
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;
}
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:
for (int i = 0; true; ++i) {
if (i!=0) {
@@ -1899,6 +1964,11 @@ void WipeTower2::toolchange_Wipe(
else
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)
break; // in case next line would not fit
@@ -1943,10 +2013,12 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
.set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f));
// Slow down on the 1st layer.
// Slow down on the 1st 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.
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);
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();
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);
@@ -2077,7 +2149,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
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.
@@ -2245,6 +2317,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
{
assert(first.new_tool == second.initial_tool);
WipeTower::ToolChangeResult out = first;
out.is_contact = first.is_contact || second.is_contact;
if (first.end_pos != second.start_pos)
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)

View File

@@ -31,7 +31,8 @@ public:
WipeTower::ToolChangeResult construct_tcr(WipeTowerWriter2& writer,
bool priming,
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 )
// y -- y coordinates of wipe tower in mm ( left bottom corner )
@@ -88,11 +89,13 @@ public:
m_layer_height = layer_height;
m_depth_traversed = 0.f;
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
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
++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 = SHAPE_NORMAL;
@@ -145,6 +148,7 @@ public:
bool is_soluble = false;
int temperature = 0;
int first_layer_temperature = 0;
int interface_print_temperature = 0;
float loading_speed = 0.f;
float loading_speed_start = 0.f;
float unloading_speed = 0.f;
@@ -168,6 +172,10 @@ public:
float filament_minimal_purge_on_wipe_tower = 0.f;
float retract_length;
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:
@@ -208,6 +216,11 @@ private:
float m_perimeter_speed = 0.f;
float m_first_layer_speed = 0.f;
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;
bool m_used_fillet = true;
@@ -335,7 +348,8 @@ private:
void toolchange_Wipe(
WipeTowerWriter2 &writer,
const WipeTower::box_coordinates &cleaning_box,
float wipe_volume);
float wipe_volume,
bool interface_layer);
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,

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_infill_gap",
"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",
"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",
@@ -959,6 +961,8 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
"filament_soluble", "filament_is_support", "filament_printable",
"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_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",
// 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",

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_skip_points"
|| opt_key == "prime_tower_flat_ironing"
|| opt_key == "enable_tower_interface_features"
|| opt_key == "first_layer_print_sequence"
|| opt_key == "other_layers_print_sequence"
|| 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"
|| 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_extra_flow"
|| opt_key == "wipe_tower_no_sparse_layers"

View File

@@ -2587,6 +2587,46 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
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->label = L("Speed of the last cooling move");
def->tooltip = L("Cooling moves are gradually accelerating towards this speed.");
@@ -6408,6 +6448,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
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->label = 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,
std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2)
{
@@ -9258,7 +9365,10 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
variant_index.resize(target_variant_count, -1);
if (cur_variant_count == 0) {
variant_index[0] = 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;
}
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
//should not happen
@@ -9300,12 +9410,53 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
//nothing to do, keep the original one
}
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(opt_target);
int stride = 1;
if (key_set2.find(opt) != key_set2.end())
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);
}
}
}
}

View File

@@ -1376,6 +1376,11 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInts, filament_cooling_moves))
((ConfigOptionFloats, filament_cooling_initial_speed))
((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))
((ConfigOptionStrings, filament_ramming_parameters))
((ConfigOptionBools, filament_multitool_ramming))
@@ -1505,6 +1510,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionPercent, prime_tower_infill_gap))
((ConfigOptionBool, prime_tower_skip_points))
((ConfigOptionBool, prime_tower_flat_ironing))
((ConfigOptionBool, enable_tower_interface_features))
((ConfigOptionBool, enable_tower_interface_cooldown_during_tower))
((ConfigOptionFloat, wipe_tower_bridging))
((ConfigOptionPercent, wipe_tower_extra_flow))
((ConfigOptionFloats, flush_volumes_matrix))

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));
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("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"})
toggle_line(el, !bSEMM);

View File

@@ -25,6 +25,18 @@
#include "../Utils/MacDarkMode.hpp"
#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 GUI {

View File

@@ -2576,6 +2576,8 @@ void TabPrint::build()
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("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_width", "multimaterial_settings_prime_tower#width");
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
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_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->append_single_option_line("filament_flush_temp", "", 0);

View File

@@ -1,7 +1,7 @@
#include "CalibUtils.hpp"
#include "../GUI/I18N.hpp"
#include "../GUI/GUI_App.hpp"
#include "../GUI/DeviceCore/DevStorage.h"
#include "../GUI/DeviceCore/DevStorage.h"
#include "../GUI/DeviceManager.hpp"
#include "../GUI/Jobs/ProgressIndicator.hpp"
#include "../GUI/PartPlate.hpp"

View File

@@ -461,42 +461,6 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
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
nlohmann::json ams_json = nlohmann::json::object();
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_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
if (tray->bed_temp > 0) {
@@ -604,119 +568,30 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
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;
int max_lane_index = 0;
// Null-safe JSON accessors: nlohmann::json::value() throws type_error
// when the key exists but the value is null (type mismatch).
auto safe_string = [](const nlohmann::json& obj, const char* key) -> std::string {
auto it = obj.find(key);
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);
// Try Happy Hare first (more widely adopted, supports more filament changers)
if (fetch_hh_filament_info(trays, max_lane_index)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
<< (max_lane_index + 1) << " gates";
int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays);
return true;
}
if (trays.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: No AFC lanes found";
return false;
// 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;
}
// Calculate AMS count from max lane index (4 trays per AMS unit)
int ams_count = (max_lane_index + 4) / 4;
// Build and parse the AMS payload
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)
@@ -780,6 +655,298 @@ std::string MoonrakerPrinterAgent::map_filament_type_to_generic_id(const std::st
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)
{
auto json = nlohmann::json::parse(json_str, nullptr, false);

View File

@@ -160,6 +160,17 @@ private:
const std::string& api_key,
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_id;
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;

View File

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