diff --git a/.gitignore b/.gitignore
index 52cef73065..c7126440f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
Build
Build.bat
/build/
+resources/nonplanar/
/build2022/
deps/build
MYMETA.json
diff --git a/BUILD.md b/BUILD.md
new file mode 100644
index 0000000000..0885fa9203
--- /dev/null
+++ b/BUILD.md
@@ -0,0 +1,206 @@
+# Building BambuStudio with ZAA on macOS
+
+A step-by-step guide to building BambuStudio from source on macOS. No prior experience with building software required.
+
+## What You'll Need
+
+- A Mac running macOS 10.15 (Catalina) or later
+- About 15 GB of free disk space
+- An internet connection
+- About 1-2 hours for the first build (mostly waiting)
+
+## Step 1: Open Terminal
+
+Press **Cmd + Space**, type **Terminal**, and press Enter. A window with a command prompt will appear. All commands below are typed into this window.
+
+## Step 2: Install Xcode Command Line Tools
+
+This installs the compiler and basic development tools.
+
+```bash
+xcode-select --install
+```
+
+A dialog will pop up. Click **Install** and wait for it to finish (a few minutes).
+
+To verify it worked:
+
+```bash
+xcode-select -p
+git --version
+```
+
+You should see a path like `/Library/Developer/CommandLineTools` and a git version. Both git and the C++ compiler are included in the Command Line Tools.
+
+## Step 3: Install Homebrew
+
+[Homebrew](https://brew.sh) is a package manager that makes it easy to install developer tools on macOS.
+
+```bash
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+```
+
+Follow the prompts. When it finishes, it will tell you to run one or two commands to add Homebrew to your PATH. **Run those commands** — they look something like:
+
+```bash
+echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
+eval "$(/opt/homebrew/bin/brew shellenv)"
+```
+
+To verify:
+
+```bash
+brew --version
+```
+
+## Step 4: Install CMake
+
+CMake is the build system BambuStudio uses.
+
+```bash
+brew install cmake
+```
+
+To verify:
+
+```bash
+cmake --version
+```
+
+You need version 3.13 or later (anything recent from Homebrew will work).
+
+## Step 5: Clone the Repository
+
+Choose where you want the source code. For example, in your home directory:
+
+```bash
+cd ~
+git clone https://github.com/mnott/BambuStudio.git
+cd BambuStudio
+```
+
+If you want the ZAA feature branch:
+
+```bash
+git checkout feature/zaa-contouring
+```
+
+## Step 6: Build
+
+The `build.sh` script handles everything. For a first-time build:
+
+```bash
+chmod +x build.sh
+./build.sh --full
+```
+
+This does three things:
+1. **Builds dependencies** (Boost, wxWidgets, OpenSSL, etc.) — takes 30-60 minutes the first time
+2. **Configures CMake** — sets up the build system (uses Xcode generator)
+3. **Builds BambuStudio** — compiles the application
+
+Go get a coffee. The dependency step only needs to run once.
+
+### After the first build
+
+For subsequent builds (after making code changes), just run:
+
+```bash
+./build.sh
+```
+
+This does an incremental build — only recompiles what changed. Usually takes 1-5 minutes.
+
+### Other build options
+
+```bash
+./build.sh --help # Show all options
+./build.sh --clean # Clean rebuild (if something is broken)
+./build.sh --deps # Rebuild dependencies only
+./build.sh --configure # Reconfigure CMake only
+```
+
+## Step 7: Run BambuStudio
+
+After a successful build, the script prints the app location. You can run it with:
+
+```bash
+open build/arm64/src/Release/BambuStudio.app
+```
+
+Or find `BambuStudio.app` in Finder and double-click it.
+
+## Troubleshooting
+
+### "CMake not found"
+
+Make sure Homebrew is in your PATH (see Step 3), then `brew install cmake`.
+
+### Build fails during dependencies
+
+Some deps need a lot of memory. Close other applications and try again. If a specific dependency fails, check if you have the latest Xcode Command Line Tools:
+
+```bash
+softwareupdate --list
+```
+
+### wxWidgets "hardcoded path" errors
+
+wxWidgets bakes the installation path into its config files. If you move the source directory after building deps, you need to rebuild them:
+
+```bash
+./build.sh --deps
+./build.sh --clean
+```
+
+### "-lGL not found" linker error
+
+The wxWidgets GL config may reference bare library names. Fix the wx-config file:
+
+```bash
+# In deps/build/arm64/BambuStudio_deps/usr/local/lib/wx/config/osx_cocoa-unicode-static-3.1
+# Change ldlibs_gl from "libGL.dylib libGLU.dylib" to full paths:
+# /usr/X11R6/lib/libGL.dylib /usr/X11R6/lib/libGLU.dylib
+```
+
+Or install XQuartz which provides the GL libraries at the expected paths.
+
+### "Permission denied" on build.sh
+
+```bash
+chmod +x build.sh
+```
+
+### Apple Silicon (M1/M2/M3/M4) vs Intel
+
+The build script auto-detects your architecture. If you need to specify it explicitly:
+
+```bash
+./build.sh --full --arch arm64 # Apple Silicon
+./build.sh --full --arch x86_64 # Intel
+```
+
+## Project Structure
+
+```
+BambuStudio/
+├── src/
+│ ├── libslic3r/ # Core slicing engine
+│ │ ├── ContourZ.cpp # ZAA raycasting algorithm
+│ │ └── ...
+│ └── slic3r/GUI/ # User interface
+├── deps/ # External dependencies
+├── build/ # Build output (created by build.sh)
+│ └── arm64/
+│ └── src/Release/
+│ └── BambuStudio.app
+├── docs/
+│ └── ZAA.md # ZAA feature documentation
+├── build.sh # Build script (this guide uses it)
+├── BUILD.md # This file
+└── BuildMac.sh # Official BambuStudio build script
+```
+
+## ZAA (Z Anti-Aliasing)
+
+See [docs/ZAA.md](docs/ZAA.md) for details on the Z Anti-Aliasing feature. In short: enable **Z contouring** in Print Settings > Quality to get smoother curved surfaces.
diff --git a/BuildMac.sh b/BuildMac.sh
index 9784e19771..ae60961185 100755
--- a/BuildMac.sh
+++ b/BuildMac.sh
@@ -122,7 +122,8 @@ function build_deps() {
-DOPENSSL_ARCH="darwin64-${_ARCH}-cc" \
-DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
- -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
+ -DCMAKE_POLICY_VERSION_MINIMUM=3.5
fi
cmake --build . --parallel ${CMAKE_BUILD_PARALLEL_LEVEL} --config "$BUILD_CONFIG" --target deps
)
diff --git a/RELEASE_GUIDE.md b/RELEASE_GUIDE.md
new file mode 100644
index 0000000000..dcd1829201
--- /dev/null
+++ b/RELEASE_GUIDE.md
@@ -0,0 +1,197 @@
+# macOS Release Guide: Build, Sign, Notarize, Publish
+
+Step-by-step guide for creating a signed and notarized macOS DMG release of BambuStudio-ZAA.
+
+---
+
+## Prerequisites
+
+### One-time setup
+
+1. **Apple Developer Account** ($99/year) at https://developer.apple.com
+
+2. **Developer ID Application certificate**:
+ - Go to https://developer.apple.com/account/resources/certificates/list
+ - Click "+" → select **"Developer ID Application"**
+ - Choose **G2 Sub-CA** (not "Previous Sub-CA")
+ - Create a CSR in **Keychain Access** → Certificate Assistant → Request a Certificate From a Certificate Authority
+ - Upload the `.certSigningRequest` file, download the `.cer`, double-click to install
+ - Verify: `security find-identity -v -p codesigning` should show `Developer ID Application: Your Name (TEAMID)`
+
+3. **App-specific password** for notarization:
+ - Go to https://account.apple.com → Sign-In and Security → App-Specific Passwords
+ - Generate one, name it "notarytool"
+
+4. **dylibbundler** (for bundling external dylibs):
+ ```bash
+ brew install dylibbundler
+ ```
+
+### Our specific values
+
+| Item | Value |
+|------|-------|
+| Certificate | `Developer ID Application: Matthias Nott (7KU642K5ZL)` |
+| Apple ID | `mn@mnsoft.org` |
+| Team ID | `7KU642K5ZL` |
+
+---
+
+## Step 1: Build
+
+```bash
+cd /path/to/BambuStudio
+./build.sh
+```
+
+The app bundle will be at: `build/arm64/src/Release/BambuStudio.app`
+
+---
+
+## Step 2: Stage the app bundle
+
+The build output has two issues for distribution:
+- `Contents/Resources` is a **symlink** to the source tree
+- The binary links against **external dylibs** (Homebrew zstd, Imath, X11 libGL)
+
+```bash
+# Create staging area
+mkdir -p /tmp/bambu-dmg/staging
+
+# Copy the app
+cp -R build/arm64/src/Release/BambuStudio.app /tmp/bambu-dmg/staging/BambuStudio-ZAA.app
+
+# Replace Resources symlink with actual files
+rm /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/Resources
+cp -R resources /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/Resources
+
+# Create Applications symlink for drag-and-drop DMG
+ln -s /Applications /tmp/bambu-dmg/staging/Applications
+```
+
+---
+
+## Step 3: Bundle external dylibs
+
+```bash
+dylibbundler -od -b \
+ -x /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/MacOS/BambuStudio \
+ -d /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/Frameworks/ \
+ -p @executable_path/../Frameworks/
+```
+
+Fix duplicate rpaths:
+
+```bash
+while install_name_tool -delete_rpath "@executable_path/../Frameworks/" \
+ /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/MacOS/BambuStudio 2>/dev/null; do :; done
+install_name_tool -add_rpath "@executable_path/../Frameworks/" \
+ /tmp/bambu-dmg/staging/BambuStudio-ZAA.app/Contents/MacOS/BambuStudio
+```
+
+---
+
+## Step 4: Create entitlements
+
+Create `/tmp/bambu-dmg/entitlements.plist`:
+
+```xml
+
+
+
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.device.camera
+
+ com.apple.security.network.client
+
+
+
+```
+
+---
+
+## Step 5: Code sign
+
+```bash
+IDENTITY="Developer ID Application: Matthias Nott (7KU642K5ZL)"
+APP="/tmp/bambu-dmg/staging/BambuStudio-ZAA.app"
+ENTITLEMENTS="/tmp/bambu-dmg/entitlements.plist"
+
+# Sign each bundled framework
+for lib in "$APP/Contents/Frameworks/"*.dylib; do
+ codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$lib"
+done
+
+# Sign the app bundle
+codesign --deep --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP"
+
+# Verify
+codesign --verify --deep --strict "$APP"
+```
+
+---
+
+## Step 6: Create DMG
+
+```bash
+rm -f /tmp/bambu-dmg/BambuStudio-ZAA-macOS-arm64.dmg
+
+hdiutil create \
+ -volname "BambuStudio-ZAA" \
+ -srcfolder /tmp/bambu-dmg/staging \
+ -ov -format UDZO \
+ /tmp/bambu-dmg/BambuStudio-ZAA-macOS-arm64.dmg
+```
+
+---
+
+## Step 7: Notarize
+
+```bash
+xcrun notarytool submit /tmp/bambu-dmg/BambuStudio-ZAA-macOS-arm64.dmg \
+ --apple-id "mn@mnsoft.org" \
+ --team-id "7KU642K5ZL" \
+ --password "xxxx-xxxx-xxxx-xxxx" \
+ --wait
+```
+
+---
+
+## Step 8: Staple
+
+```bash
+xcrun stapler staple /tmp/bambu-dmg/BambuStudio-ZAA-macOS-arm64.dmg
+```
+
+---
+
+## Step 9: Upload to GitHub Release
+
+```bash
+gh release create v1.0.0-zaa \
+ --repo mnott/BambuStudio \
+ --target feature/zaa-contouring \
+ --title "BambuStudio with ZAA Contouring (macOS arm64)" \
+ --notes-file release-notes.md \
+ /tmp/bambu-dmg/BambuStudio-ZAA-macOS-arm64.dmg
+```
+
+---
+
+## Known Issues
+
+### "Network Plugin Update Available" dialog
+The app shares config with stock BambuStudio at `~/Library/Application Support/BambuStudio/`. Click **"Don't Ask Again"** — printer connectivity works fine.
+
+### External dylib dependencies
+The build links against Homebrew zstd/Imath and X11 libGL. The dylibbundler step handles this for distribution.
+
+---
+
+*Created: 2026-02-10*
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000000..028d057795
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,215 @@
+#!/usr/bin/env bash
+# BambuStudio Build Script (macOS)
+# Handles the full build lifecycle: deps, configure, build
+#
+# Usage:
+# ./build.sh # Incremental build (after initial setup)
+# ./build.sh --full # Full build from scratch (deps + configure + build)
+# ./build.sh --deps # Build dependencies only
+# ./build.sh --configure # Run CMake configure only
+# ./build.sh --clean # Remove build dir and rebuild
+# ./build.sh --help # Show all options
+
+set -e
+set -o pipefail
+
+# ── Configuration ──────────────────────────────────────────────
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ARCH="${ARCH:-$(uname -m)}"
+BUILD_CONFIG="${BUILD_CONFIG:-Release}"
+OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET:-10.15}"
+NCPU=$(sysctl -n hw.ncpu 2>/dev/null || echo 4)
+
+BUILD_DIR="${SCRIPT_DIR}/build/${ARCH}"
+DEPS_DIR="${SCRIPT_DIR}/deps"
+DEPS_BUILD_DIR="${DEPS_DIR}/build/${ARCH}"
+DEPS_INSTALL_DIR="${DEPS_BUILD_DIR}/BambuStudio_deps"
+
+# CMake 4.x compatibility
+CMAKE_VERSION=$(cmake --version 2>/dev/null | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
+CMAKE_COMPAT=""
+if [ "${CMAKE_VERSION:-3}" -ge 4 ] 2>/dev/null; then
+ CMAKE_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
+fi
+
+# ── Colors ─────────────────────────────────────────────────────
+
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+BLUE='\033[0;34m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+info() { echo -e "${BLUE}▸${NC} $*"; }
+ok() { echo -e "${GREEN}✓${NC} $*"; }
+warn() { echo -e "${YELLOW}⚠${NC} $*"; }
+err() { echo -e "${RED}✗${NC} $*"; }
+
+# ── Parse Arguments ────────────────────────────────────────────
+
+DO_DEPS=false
+DO_CONFIGURE=false
+DO_BUILD=true
+DO_CLEAN=false
+DO_FULL=false
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --full) DO_FULL=true; shift ;;
+ --deps) DO_DEPS=true; DO_BUILD=false; shift ;;
+ --configure) DO_CONFIGURE=true; DO_BUILD=false; shift ;;
+ --clean) DO_CLEAN=true; shift ;;
+ --arch) ARCH="$2"; shift 2 ;;
+ --config) BUILD_CONFIG="$2"; shift 2 ;;
+ --help|-h)
+ echo "BambuStudio Build Script (macOS)"
+ echo ""
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Build modes:"
+ echo " (no flags) Incremental build (fastest, use after initial setup)"
+ echo " --full Full build from scratch: deps → configure → build"
+ echo " --deps Build dependencies only (takes 30-60 min first time)"
+ echo " --configure Run CMake configure only"
+ echo " --clean Remove build directory, then rebuild"
+ echo ""
+ echo "Options:"
+ echo " --arch ARCH Architecture: arm64, x86_64 (default: $(uname -m))"
+ echo " --config CFG Build config: Release, RelWithDebInfo, Debug"
+ echo " (default: Release)"
+ echo ""
+ echo "Examples:"
+ echo " $0 --full # First-time build (do this first!)"
+ echo " $0 # Quick rebuild after code changes"
+ echo " $0 --clean # Clean rebuild if something is broken"
+ echo ""
+ echo "Prerequisites: Xcode Command Line Tools, CMake 3.13+"
+ echo " xcode-select --install"
+ echo " brew install cmake"
+ exit 0
+ ;;
+ *)
+ err "Unknown option: $1 (use --help)"
+ exit 1
+ ;;
+ esac
+done
+
+# --full implies all steps
+if [ "$DO_FULL" = true ]; then
+ DO_DEPS=true
+ DO_CONFIGURE=true
+ DO_BUILD=true
+fi
+
+# ── Preflight Checks ──────────────────────────────────────────
+
+echo ""
+echo -e "${BOLD}BambuStudio Build${NC}"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo " Architecture: ${ARCH}"
+echo " Config: ${BUILD_CONFIG}"
+echo " CPU cores: ${NCPU}"
+echo " CMake: $(cmake --version 2>/dev/null | head -1 || echo 'NOT FOUND')"
+[ -n "$CMAKE_COMPAT" ] && echo " CMake compat: 4.x → policy 3.5"
+echo ""
+
+# Check prerequisites
+if ! command -v cmake &>/dev/null; then
+ err "CMake not found. Install it with: brew install cmake"
+ exit 1
+fi
+
+if ! command -v git &>/dev/null; then
+ err "Git not found. Install Xcode Command Line Tools: xcode-select --install"
+ exit 1
+fi
+
+if ! xcode-select -p &>/dev/null; then
+ err "Xcode Command Line Tools not found. Install: xcode-select --install"
+ exit 1
+fi
+
+# ── Clean ──────────────────────────────────────────────────────
+
+if [ "$DO_CLEAN" = true ]; then
+ warn "Removing build directory: ${BUILD_DIR}"
+ rm -rf "${BUILD_DIR}"
+ ok "Clean complete"
+ echo ""
+fi
+
+# ── Build Dependencies ─────────────────────────────────────────
+
+if [ "$DO_DEPS" = true ]; then
+ info "Building dependencies (this takes a while the first time)..."
+ mkdir -p "${DEPS_BUILD_DIR}"
+ cd "${DEPS_BUILD_DIR}"
+
+ cmake "${DEPS_DIR}" \
+ -DDESTDIR="${DEPS_INSTALL_DIR}" \
+ -DOPENSSL_ARCH="darwin64-${ARCH}-cc" \
+ -DCMAKE_BUILD_TYPE="${BUILD_CONFIG}" \
+ -DCMAKE_OSX_ARCHITECTURES:STRING="${ARCH}" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
+ ${CMAKE_COMPAT}
+
+ cmake --build . --config "${BUILD_CONFIG}" --target deps -j"${NCPU}"
+
+ ok "Dependencies built"
+ echo ""
+fi
+
+# ── Configure ──────────────────────────────────────────────────
+
+if [ "$DO_CONFIGURE" = true ] || { [ "$DO_BUILD" = true ] && [ ! -d "${BUILD_DIR}" ]; }; then
+ info "Configuring CMake..."
+ mkdir -p "${BUILD_DIR}"
+ cd "${BUILD_DIR}"
+
+ cmake "${SCRIPT_DIR}" \
+ -G Xcode \
+ -DBBL_RELEASE_TO_PUBLIC=1 \
+ -DBBL_INTERNAL_TESTING=0 \
+ -DCMAKE_PREFIX_PATH="${DEPS_INSTALL_DIR}/usr/local" \
+ -DCMAKE_INSTALL_PREFIX="${BUILD_DIR}/BambuStudio" \
+ -DCMAKE_BUILD_TYPE="${BUILD_CONFIG}" \
+ -DCMAKE_MACOSX_RPATH=ON \
+ -DCMAKE_INSTALL_RPATH="${DEPS_INSTALL_DIR}/usr/local" \
+ -DCMAKE_MACOSX_BUNDLE=ON \
+ -DCMAKE_OSX_ARCHITECTURES="${ARCH}" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
+ ${CMAKE_COMPAT}
+
+ ok "CMake configured"
+ echo ""
+fi
+
+# ── Build ──────────────────────────────────────────────────────
+
+if [ "$DO_BUILD" = true ]; then
+ if [ ! -d "${BUILD_DIR}" ]; then
+ err "Build directory not found: ${BUILD_DIR}"
+ err "Run '$0 --full' for a first-time build"
+ exit 1
+ fi
+
+ info "Building BambuStudio..."
+ cd "${SCRIPT_DIR}"
+
+ cmake --build "${BUILD_DIR}" \
+ --config "${BUILD_CONFIG}" \
+ --target BambuStudio \
+ -- -jobs "${NCPU}"
+
+ echo ""
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ ok "Build complete!"
+ echo ""
+ echo " App: ${BUILD_DIR}/src/${BUILD_CONFIG}/BambuStudio.app"
+ echo ""
+ echo " Run: open \"${BUILD_DIR}/src/${BUILD_CONFIG}/BambuStudio.app\""
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+fi
diff --git a/cmake/modules/FindOpenVDB.cmake b/cmake/modules/FindOpenVDB.cmake
index 2a61b21a27..46d425eda6 100644
--- a/cmake/modules/FindOpenVDB.cmake
+++ b/cmake/modules/FindOpenVDB.cmake
@@ -123,7 +123,7 @@ if (OPENVDB_FIND_MODULE_PATH)
endif ()
# ###########################################################################
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
# Monitoring _ROOT variables
if(POLICY CMP0074)
cmake_policy(SET CMP0074 NEW)
diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index 46dea0f9d0..2965a19dd5 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -20,8 +20,8 @@
# therefore, unfortunately, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
#
+cmake_minimum_required(VERSION 3.5)
project(BambuStudio-deps)
-cmake_minimum_required(VERSION 3.2)
include(ExternalProject)
include(ProcessorCount)
diff --git a/docs/ZAA.md b/docs/ZAA.md
new file mode 100644
index 0000000000..a40ee98a27
--- /dev/null
+++ b/docs/ZAA.md
@@ -0,0 +1,52 @@
+# Z Anti-Aliasing (ZAA) — Z Contouring
+
+ZAA eliminates stair-stepping on curved and sloped top surfaces by adjusting the Z height of each extrusion point to follow the actual 3D model surface.
+
+Instead of printing flat horizontal layers, ZAA raycasts each point of the toolpath against the original mesh and micro-adjusts its Z coordinate to match the true surface geometry. The result is visibly smoother surfaces on domes, chamfers, and shallow slopes — without post-processing.
+
+This is a port of the ZAA implementation from [BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA) by adob.
+
+## Configuration
+
+ZAA adds five settings under **Print Settings > Quality**:
+
+| Setting | Type | Default | Description |
+|---------|------|---------|-------------|
+| `zaa_enabled` | bool | off | Master enable/disable switch |
+| `zaa_min_z` | float | 0.06 mm | Minimum Z layer height; also controls the slicing plane offset |
+| `zaa_minimize_perimeter_height` | float | 35° | Reduce perimeter heights on slopes below this angle (0 = disabled) |
+| `zaa_dont_alternate_fill_direction` | bool | off | Keep fill direction consistent instead of alternating per layer |
+| `zaa_region_disable` | bool | off | Disable ZAA for a specific print region/material |
+
+## How It Works
+
+1. The slicer slices normally, then runs a **posContouring** step on each layer.
+2. `ContourZ.cpp` raycasts every extrusion point vertically against the source mesh.
+3. Each point's Z is adjusted to the mesh intersection, converting flat `Polyline` paths into `Polyline3` paths that carry per-point Z coordinates.
+4. The G-code writer emits the adjusted Z values, so the printer follows the true surface.
+
+## Key Implementation Details
+
+- **Core algorithm**: `src/libslic3r/ContourZ.cpp` (~330 lines)
+- **3D geometry**: `Point3`, `Line3`, `Polyline3`, `MultiPoint3` extend the existing 2D types
+- **Pipeline step**: `posContouring` in `PrintObject.cpp`, runs after perimeter/infill generation
+- **G-code output**: `GCode.cpp` writes per-point Z when `path.z_contoured` is set
+- **Arc fitting**: Templated to work with both 2D and 3D geometry
+- **ExtrusionPath change**: `polyline` field changed from `Polyline` to `Polyline3`
+
+## Testing
+
+1. Load a model with curved top surfaces (spheres, domes, chamfered edges)
+2. Enable **Z contouring** in Print Settings > Quality
+3. Slice and inspect the G-code — Z values should vary within each layer on contoured surfaces
+
+### Verifying ZAA output
+
+Extract G-code from the .3mf and count unique Z heights:
+
+```bash
+unzip -p output.3mf Metadata/plate_1.gcode > output.gcode
+grep -o 'Z[0-9]*\.[0-9]*' output.gcode | sed 's/Z//' | sort -un | wc -l
+```
+
+With ZAA enabled, you should see significantly more unique Z heights than the number of layers (e.g., 1,300+ vs 85 for a typical model).
diff --git a/sandboxes/opencsg/CMakeLists.txt b/sandboxes/opencsg/CMakeLists.txt
index ace8f4d539..fd1e5601e2 100644
--- a/sandboxes/opencsg/CMakeLists.txt
+++ b/sandboxes/opencsg/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
project(OpenCSG-example)
diff --git a/src/Shiny/CMakeLists.txt b/src/Shiny/CMakeLists.txt
index abdb96a72e..a4e3bbea59 100644
--- a/src/Shiny/CMakeLists.txt
+++ b/src/Shiny/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(Shiny)
add_library(Shiny STATIC
diff --git a/src/admesh/CMakeLists.txt b/src/admesh/CMakeLists.txt
index 217976318a..645495d9bb 100644
--- a/src/admesh/CMakeLists.txt
+++ b/src/admesh/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(admesh)
add_library(admesh STATIC
diff --git a/src/boost/CMakeLists.txt b/src/boost/CMakeLists.txt
index e8c9e11ce6..c8950e1501 100644
--- a/src/boost/CMakeLists.txt
+++ b/src/boost/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(nowide)
add_library(nowide STATIC
diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt
index f625088209..5f8675e93d 100644
--- a/src/clipper/CMakeLists.txt
+++ b/src/clipper/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(clipper)
add_library(clipper STATIC
diff --git a/src/earcut/CMakeLists.txt b/src/earcut/CMakeLists.txt
index 08c7bb1bed..13ad5c8f42 100644
--- a/src/earcut/CMakeLists.txt
+++ b/src/earcut/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
project(earcut_hpp LANGUAGES CXX C)
option(EARCUT_BUILD_TESTS "Build the earcut test program" ON)
diff --git a/src/glu-libtess/CMakeLists.txt b/src/glu-libtess/CMakeLists.txt
index 8fca992a3e..bfa43d3693 100644
--- a/src/glu-libtess/CMakeLists.txt
+++ b/src/glu-libtess/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(glu-libtess)
add_library(glu-libtess STATIC
diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt
index 592e78c8e3..a358a83fa1 100644
--- a/src/imgui/CMakeLists.txt
+++ b/src/imgui/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(imgui)
add_library(imgui STATIC
diff --git a/src/imguizmo/CMakeLists.txt b/src/imguizmo/CMakeLists.txt
index fec4431805..9d5c8edd65 100644
--- a/src/imguizmo/CMakeLists.txt
+++ b/src/imguizmo/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(imguizmo)
add_library(imguizmo STATIC
diff --git a/src/libigl/CMakeLists.txt b/src/libigl/CMakeLists.txt
index f023826a52..f180ea4168 100644
--- a/src/libigl/CMakeLists.txt
+++ b/src/libigl/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
project(libigl)
add_library(libigl INTERFACE)
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index c652d54139..3686a5f7eb 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -475,9 +475,9 @@ void AppConfig::set_defaults()
if (get("show_wrapping_detect_dialog").empty()) {
set_bool("show_wrapping_detect_dialog", true);
}
- if (get("ignore_module_cert").empty()) {
- set_bool("ignore_module_cert", false);
- }
+ // ZAA: Always skip module cert validation — our Developer ID cert
+ // differs from Bambu Lab's, preventing the network plugin from loading.
+ set_bool("ignore_module_cert", true);
if (get("webview_auto_fill").empty()) {
set_bool("webview_auto_fill", true);
}
diff --git a/src/libslic3r/ArcFitter.cpp b/src/libslic3r/ArcFitter.cpp
index cdfd708b10..b15548ee84 100644
--- a/src/libslic3r/ArcFitter.cpp
+++ b/src/libslic3r/ArcFitter.cpp
@@ -1,4 +1,5 @@
#include "ArcFitter.hpp"
+#include "Point.hpp"
#include "Polyline.hpp"
#include
@@ -6,7 +7,17 @@
namespace Slic3r {
-void ArcFitter::do_arc_fitting(const Points& points, std::vector& result, double tolerance)
+// Helper functions to dispatch to the correct douglas_peucker implementation
+static inline Points douglas_peucker_helper(const Points &points, double tolerance) {
+ return MultiPoint::_douglas_peucker(points, tolerance);
+}
+
+static inline Points3 douglas_peucker_helper(const Points3 &points, double tolerance) {
+ return MultiPoint3::_douglas_peucker(points, tolerance);
+}
+
+template
+static void do_arc_fitting_tmpl(const POINTS& points, std::vector& result, double tolerance)
{
#ifdef DEBUG_ARC_FITTING
static int irun = 0;
@@ -39,7 +50,7 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector 2) {
//BBS: althought current point_stack can't be fit as arc,
//but previous must can be fit if removing the top in stack, so save last arc
- result.emplace_back(std::move(PathFittingData{ front_index,
+ result.emplace_back(PathFittingData{ front_index,
back_index - 1,
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
- last_arc }));
+ last_arc });
} else {
//BBS: save the first segment as line move when 3 point-line can't be fit as arc move
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
@@ -94,7 +105,18 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector& result, double tolerance)
+void ArcFitter::do_arc_fitting(const Points &points, std::vector& result, double tolerance)
+{
+ do_arc_fitting_tmpl(points, result, tolerance);
+}
+
+void ArcFitter::do_arc_fitting(const Points3 &points, std::vector& result, double tolerance)
+{
+ do_arc_fitting_tmpl(points, result, tolerance);
+}
+
+template
+static void do_arc_fitting_and_simplify_tmpl(POINTS &points, std::vector& result, double tolerance)
{
//BBS: 1 do arc fit first
if (abs(tolerance) > SCALED_EPSILON)
@@ -106,12 +128,12 @@ void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector reduce_count(result.size(), 0);
@@ -124,11 +146,11 @@ void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector& result, double tolerance)
+{
+ do_arc_fitting_and_simplify_tmpl(points, result, tolerance);
+}
+
+void ArcFitter::do_arc_fitting_and_simplify(Points3& points, std::vector& result, double tolerance)
+{
+ do_arc_fitting_and_simplify_tmpl(points, result, tolerance);
+}
+
}
\ No newline at end of file
diff --git a/src/libslic3r/ArcFitter.hpp b/src/libslic3r/ArcFitter.hpp
index f2b2ee49d8..6672b0b771 100644
--- a/src/libslic3r/ArcFitter.hpp
+++ b/src/libslic3r/ArcFitter.hpp
@@ -42,9 +42,11 @@ class ArcFitter {
public:
//BBS: this function is used to check the point list and return which part can fit as arc, which part should be line
static void do_arc_fitting(const Points& points, std::vector &result, double tolerance);
+ static void do_arc_fitting(const Points3& points, std::vector &result, double tolerance);
//BBS: this function is used to check the point list and return which part can fit as arc, which part should be line.
//By the way, it also use DP simplify to reduce point of straight part and only keep the start and end point of arc.
static void do_arc_fitting_and_simplify(Points& points, std::vector& result, double tolerance);
+ static void do_arc_fitting_and_simplify(Points3& points, std::vector& result, double tolerance);
};
}
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index fa0045a085..6c1ce2ca4f 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -225,7 +225,20 @@ class BoundingBox3 : public BoundingBox3Base
public:
BoundingBox3() : BoundingBox3Base() {}
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {}
- BoundingBox3(const Points3& points) : BoundingBox3Base(points) {}
+ // ZAA fix: Points3 is now std::vector, need custom handling
+ BoundingBox3(const Points3& points) : BoundingBox3Base() {
+ if (!points.empty()) {
+ this->min = points.front();
+ this->max = points.front();
+ for (const auto &p : points) {
+ this->min = this->min.cwiseMin(static_cast(p));
+ this->max = this->max.cwiseMax(static_cast(p));
+ }
+ this->defined = true;
+ }
+ }
+ // Backward compatibility with Vec3crd vectors
+ BoundingBox3(const std::vector& points) : BoundingBox3Base(points) {}
};
class BoundingBoxf : public BoundingBoxBase
diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp
index 4bd7836b6a..2007675b27 100644
--- a/src/libslic3r/Brim.cpp
+++ b/src/libslic3r/Brim.cpp
@@ -1930,10 +1930,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop);
loop->paths.emplace_back(erBrim, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
- Points &points = loop->paths.front().polyline.points;
+ Points3 &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
- points.emplace_back(coord_t(pt.x()), coord_t(pt.y()));
+ points.emplace_back(coord_t(pt.x()), coord_t(pt.y()), 0);
i = j;
} else {
//FIXME The path chaining here may not be optimal.
@@ -1942,10 +1942,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
for (; i < j; ++ i) {
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erBrim, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
- Points &points = dynamic_cast(this_loop_trimmed.entities.back())->polyline.points;
+ Points3 &points = dynamic_cast(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
for (const ClipperLib_Z::IntPoint &pt : path)
- points.emplace_back(coord_t(pt.x()), coord_t(pt.y()));
+ points.emplace_back(coord_t(pt.x()), coord_t(pt.y()), 0);
}
chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt);
brim.entities.reserve(brim.entities.size() + this_loop_trimmed.entities.size());
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 7a9483a472..5069d33a9a 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -67,6 +67,7 @@ set(lisbslic3r_sources
CommonDefs.hpp
Config.cpp
Config.hpp
+ ContourZ.cpp
CurveAnalyzer.cpp
CurveAnalyzer.hpp
CutUtils.cpp
diff --git a/src/libslic3r/Circle.cpp b/src/libslic3r/Circle.cpp
index bcd7fc47c1..c277a67795 100644
--- a/src/libslic3r/Circle.cpp
+++ b/src/libslic3r/Circle.cpp
@@ -3,6 +3,7 @@
#include
#include
#include "Geometry.hpp"
+#include "Polygon.hpp"
//BBS: Refer to ArcWelderLib for the arc fitting functions
@@ -94,6 +95,12 @@ bool Circle::try_create_circle(const Points& points, const double max_radius, co
return found_circle;
}
+bool Circle::try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle)
+{
+ return Circle::try_create_circle(to_points(points), max_radius, tolerance, new_circle);
+}
+
+
double Circle::get_polar_radians(const Point& p1) const
{
double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x());
@@ -291,6 +298,31 @@ bool ArcSegment::try_create_arc(
return false;
}
+bool ArcSegment::try_create_arc(
+ const Points3& points,
+ ArcSegment& target_arc,
+ double approximate_length,
+ double max_radius,
+ double tolerance,
+ double path_tolerance_percent)
+{
+ Circle test_circle = (Circle)target_arc;
+ if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle))
+ return false;
+
+ int mid_point_index = ((points.size() - 2) / 2) + 1;
+ ArcSegment test_arc;
+ if (!ArcSegment::try_create_arc(test_circle, points[0].to_point(), points[mid_point_index].to_point(), points[points.size() - 1].to_point(), test_arc, approximate_length, path_tolerance_percent))
+ return false;
+
+ if (ArcSegment::are_points_within_slice(test_arc, points))
+ {
+ target_arc = test_arc;
+ return true;
+ }
+ return false;
+}
+
bool ArcSegment::try_create_arc(
const Circle& c,
const Point& start_point,
@@ -455,6 +487,87 @@ bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Point
return true;
}
+bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points3& points)
+{
+ //BBS: Check all the points and see if they fit inside of the angles
+ double previous_polar = test_arc.polar_start_theta;
+ bool will_cross_zero = false;
+ bool crossed_zero = false;
+ const int point_count = points.size();
+
+ Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius,
+ ((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius);
+ Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius,
+ ((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius);
+
+ if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
+ will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta;
+ else
+ will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta;
+
+ //BBS: check if point 1 to point 2 cross zero
+ double polar_test;
+ for (int index = point_count - 2; index < point_count; index++)
+ {
+ if (index < point_count - 1)
+ polar_test = test_arc.get_polar_radians(points[index].to_point());
+ else
+ polar_test = test_arc.polar_end_theta;
+
+ //BBS: First ensure the test point is within the arc
+ if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
+ {
+ //BBS: Only check to see if we are within the arc if this isn't the endpoint
+ if (index < point_count - 1) {
+ if (will_cross_zero) {
+ if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta))
+ return false;
+ } else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta))
+ return false;
+ }
+ //BBS: check the angles are increasing
+ if (previous_polar > polar_test) {
+ if (!will_cross_zero)
+ return false;
+
+ //BBS: Allow the angle to cross zero once
+ if (crossed_zero)
+ return false;
+ crossed_zero = true;
+ }
+ } else {
+ if (index < point_count - 1) {
+ if (will_cross_zero) {
+ if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta))
+ return false;
+ } else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta))
+ return false;
+ }
+ //BBS: Now make sure the angles are decreasing
+ if (previous_polar < polar_test)
+ {
+ if (!will_cross_zero)
+ return false;
+ //BBS: Allow the angle to cross zero once
+ if (crossed_zero)
+ return false;
+ crossed_zero = true;
+ }
+ }
+
+ // BBS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc
+ Line segmemt(points[index - 1].to_point(), points[index].to_point());
+ if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) ||
+ (index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt)))
+ return false;
+ previous_polar = polar_test;
+ }
+ //BBS: Ensure that all arcs that cross zero
+ if (will_cross_zero != crossed_zero)
+ return false;
+ return true;
+}
+
// BBS: this function is used to detect whether a ray cross the segment
bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment)
{
diff --git a/src/libslic3r/Circle.hpp b/src/libslic3r/Circle.hpp
index 8c649181dd..c06b197e19 100644
--- a/src/libslic3r/Circle.hpp
+++ b/src/libslic3r/Circle.hpp
@@ -28,6 +28,7 @@ class Circle {
static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle);
static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle);
+ static bool try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle);
double get_polar_radians(const Point& p1) const;
bool is_over_deviation(const Points& points, const double tolerance);
bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation);
@@ -111,8 +112,16 @@ class ArcSegment: public Circle {
double max_radius = DEFAULT_SCALED_MAX_RADIUS,
double tolerance = DEFAULT_SCALED_RESOLUTION,
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
+ static bool try_create_arc(
+ const Points3 &points,
+ ArcSegment& target_arc,
+ double approximate_length,
+ double max_radius = DEFAULT_SCALED_MAX_RADIUS,
+ double tolerance = DEFAULT_SCALED_RESOLUTION,
+ double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
static bool are_points_within_slice(const ArcSegment& test_arc, const Points &points);
+ static bool are_points_within_slice(const ArcSegment& test_arc, const Points3 &points);
// BBS: this function is used to detect whether a ray cross the segment
static bool ray_intersects_segment(const Point& rayOrigin, const Vec2d& rayDirection, const Line& segment);
// BBS: these three functions are used to calculate related arguments of arc in unscale_field.
diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp
index d66abc109a..8ca4946d8b 100644
--- a/src/libslic3r/ClipperUtils.cpp
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -417,6 +417,19 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); }
+
+Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
+{
+ assert(delta > 0);
+ return to_polygons(
+ clipper_union(
+ raw_offset_polyline(
+ ClipperUtils::SinglePathProvider(polyline.to_polyline().points),
+ delta,
+ joinType,
+ miterLimit,
+ end_type)));
+}
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }
diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp
index e306076618..669b9219fa 100644
--- a/src/libslic3r/ClipperUtils.hpp
+++ b/src/libslic3r/ClipperUtils.hpp
@@ -344,6 +344,7 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
+Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
@@ -532,6 +533,8 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip);
+Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip);
+Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip);
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
{
diff --git a/src/libslic3r/ContourZ.cpp b/src/libslic3r/ContourZ.cpp
new file mode 100644
index 0000000000..13a266573f
--- /dev/null
+++ b/src/libslic3r/ContourZ.cpp
@@ -0,0 +1,333 @@
+#include "Exception.hpp"
+#include "ExtrusionEntity.hpp"
+#include "ExtrusionEntityCollection.hpp"
+#include "Layer.hpp"
+#include "Point.hpp"
+#include "Print.hpp"
+#include "SLA/IndexedMesh.hpp"
+#include "libslic3r.h"
+#include
+#include
+#include
+#include
+
+namespace Slic3r {
+
+static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr);
+
+// static double lowest_z_within_distance(const Vec3d &normal, double dist) {
+// const Vec3d p(0.0, 0.0, 0.0);
+// Eigen::Vector3d n_unit = normal.normalized();
+// Eigen::Vector3d z_hat(0.0, 0.0, 1.0);
+
+// // Project the negative z-direction into the tangent plane
+// Eigen::Vector3d v_dir = -z_hat + (z_hat.dot(n_unit)) * n_unit;
+
+// double norm_v = v_dir.norm();
+// if (norm_v == 0.0) {
+// // Surface is horizontal, cannot go lower in z within tangent plane
+// return p.z();
+// }
+
+// Eigen::Vector3d v = dist * v_dir / norm_v;
+// Eigen::Vector3d q = p + v;
+// return q.z();
+// }
+
+static double follow_slope_down(double angle_rad, double dist) {
+ return -dist * std::sin(angle_rad);
+}
+
+static double slope_from_normal(const Eigen::Vector3d& normal) {
+ // Ensure the normal is normalized
+ Eigen::Vector3d n = normal.normalized();
+
+ // Compute angle between normal and z-axis
+ double angle_rad = std::acos(std::abs(n.z())); // angle between normal and vertical
+ return angle_rad;
+
+ // calculate fall over dist
+ // double dist = 0.2;
+ // double z_dist = lowest_z(angle_rad, dist);
+ // printf("fall %f vs %f\n", z_dist, lowest_z_within_distance(normal, dist));
+
+ // double angle_deg = angle_rad * 180.0 / M_PI;
+ // return angle_deg;
+}
+
+// const int LINE = 180;
+
+static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionPath &path) {
+ if (region->region().config().zaa_region_disable) {
+ return false;
+ }
+
+ if (path.role() != erTopSolidInfill && path.role() != erIroning && path.role() != erExternalPerimeter && path.role() != erPerimeter) {
+ return false;
+ }
+
+ Layer *layer = region->layer();
+ coordf_t mesh_z = layer->print_z + mesh.ground_level();
+ coordf_t min_z = layer->object()->config().zaa_min_z;
+
+ const Points3 &points = path.polyline.points;
+ double resolution_mm = 0.1;
+
+ coordf_t height = layer->height;
+ // std::cout << "LAYER " << (layer->id()+1) << std::endl;
+ // std::cout << "PRINT Z " << layer->print_z << std::endl;
+ // std::cout << "LAYER HEIGHT " << height << std::endl;
+ // std::cout << "EXTRUSION HEIGHT " << path.height << std::endl;
+ // std::cout << "EXTRUSION WIDTH " << path.width << std::endl;
+ // std::cout << "EXTRUSION ROLE: " << ExtrusionEntity::role_to_string(path.role()) << std::endl;
+ // std::cout << "FIRST POINT: " << path.polyline.first_point() << std::endl;
+
+ double minimize_perimeter_height_angle = region->region().config().zaa_minimize_perimeter_height;
+
+ Pointf3s contoured_points;
+ bool was_contoured = false;
+ // bool is_perimeter = path.role() == erExternalPerimeter || path.role() == erPerimeter || path.role() == erOverhangPerimeter;
+
+ for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
+ Vec2d p1d(unscale_(it->x()), unscale_(it->y()));
+ Vec2d p2d(unscale_((it+1)->x()), unscale_((it+1)->y()));
+ Linef line(p1d, p2d);
+
+ double length_mm = line.length();
+ int num_segments = int(std::ceil(length_mm / resolution_mm));
+ Vec2d delta = line.vector();
+
+ for (int i = 0; i < num_segments+1; i++) {
+ Vec2d p = p1d + delta*i/num_segments;
+
+ coordf_t x = p.x();
+ coordf_t y = p.y();
+
+ sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, 1.0});
+ sla::IndexedMesh::hit_result hit_down = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, -1.0});
+
+ double up = hit_up.distance();
+ double down = hit_down.distance();
+ double d = up < down ? up : -down;
+ const Vec3d &normal = (up < down ? hit_up : hit_down).normal();
+
+ double max_up = min_z;
+ double min_down = -(height - min_z);
+ double half_width = path.width / 2.0;
+ if (path.role() == erIroning) {
+ max_up = height;
+ min_down = -(height + 0.1);
+ }
+
+ double slope_rad = slope_from_normal(normal);
+ double slope_degrees = slope_rad * 180.0 / M_PI;
+
+ if (d > min_down && minimize_perimeter_height_angle > 0 && minimize_perimeter_height_angle < slope_degrees && path.role() == erExternalPerimeter) {
+ double adjustment = follow_slope_down(slope_rad, half_width);
+ if (adjustment > 0) {
+ throw RuntimeError("ContourZ: got positive adjustment");
+ }
+ d += adjustment;
+ if (d < min_down) {
+ d = min_down;
+ }
+ }
+
+ if (d > max_up + 0.03 || d < min_down) {
+ d = 0;
+ } else {
+ if (d > max_up) {
+ d = max_up;
+ }
+ }
+
+ if (path.role() == erExternalPerimeter && d > 0) {
+ // do not increase height of external perimeters as this may create an appearance of a seam
+ d = 0;
+ }
+
+ if (std::abs(d) > EPSILON) {
+ was_contoured = true;
+ }
+
+ Vec3d new_point = {p.x(), p.y(), d};
+
+ if (contoured_points.size() > 2) {
+ double dist = Linef3::distance_to_infinite_squared(
+ contoured_points[contoured_points.size() - 2],
+ contoured_points[contoured_points.size() - 1],
+ new_point);
+ if (dist < EPSILON) {
+ contoured_points[contoured_points.size() - 1] = new_point;
+ continue;
+ }
+ }
+
+ contoured_points.push_back(new_point);
+ }
+ }
+
+ if (!was_contoured) {
+ return false;
+ }
+
+ Polyline3 polyline;
+ for (const Vec3d &point : contoured_points) {
+ polyline.append(Point3(scale_(point.x()), scale_(point.y()), scale_(point.z())));
+ }
+
+ path.polyline = std::move(polyline);
+ path.z_contoured = true;
+ return true;
+}
+
+static void contour_extrusion_multipath(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionMultiPath &multipath)
+{
+ for (ExtrusionPath &path : multipath.paths) {
+ contour_extrusion_path(region, mesh, path);
+ }
+}
+
+static void contour_extrusion_loop(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionLoop &loop)
+{
+ for (ExtrusionPath &path : loop.paths) {
+ contour_extrusion_path(region, mesh, path);
+ }
+}
+
+static void contour_extrusion_entitiy_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection) {
+ for (ExtrusionEntity *entity : collection.entities) {
+ contour_extrusion_entity(region, mesh, entity);
+ }
+}
+
+static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr) {
+ const ExtrusionPathSloped *sloped = dynamic_cast(extr);
+ if (sloped != nullptr) {
+ throw RuntimeError("ExtrusionPathSloped not implemented");
+ return;
+ }
+
+ ExtrusionMultiPath *multipath = dynamic_cast(extr);
+ if (multipath != nullptr) {
+ contour_extrusion_multipath(region, mesh, *multipath);
+ return;
+ }
+
+ ExtrusionPath *path = dynamic_cast(extr);
+ if (path != nullptr) {
+ contour_extrusion_path(region, mesh, *path);
+ return;
+ }
+
+ ExtrusionLoop *loop = dynamic_cast(extr);
+ if (loop != nullptr) {
+ contour_extrusion_loop(region, mesh, *loop);
+ return;
+ }
+
+ const ExtrusionLoopSloped *loop_sloped = dynamic_cast(extr);
+ if (loop_sloped != nullptr) {
+ throw RuntimeError("ExtrusionLoopSloped not implemented");
+ return;
+ }
+
+ ExtrusionEntityCollection *collection = dynamic_cast(extr);
+ if (collection != nullptr) {
+ contour_extrusion_entitiy_collection(region, mesh, *collection);
+ return;
+ }
+
+ throw RuntimeError("ContourZ: ExtrusionEntity type not implemented: " + std::string(typeid(*extr).name()));
+ return;
+}
+
+static void handle_extrusion_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection, std::initializer_list roles) {
+ for (ExtrusionEntity *extr : collection.entities) {
+ // printf("handling extrusion collection %p %p\n", &collection, extr);
+ if (!contains(roles, extr->role())) {
+ continue;
+ }
+
+ contour_extrusion_entity(region, mesh, extr);
+ }
+}
+
+// static void find_point(ExtrusionPath &path, const std::string &path_info) {
+// Points3 &points = path.polyline.points;
+
+// size_t i = 0;
+// for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
+// if (it->x() == -883971 && it->y() == 979001) {
+// std::cout << "FOUND POINT " << ExtrusionEntity::role_to_string(path.role()) << " at path " << path_info << "[" + std::to_string(i) + "]" << std::endl;
+// }
+// i++;
+// }
+// }
+
+// static void find_point(ExtrusionLoop &loop, const std::string &path_info) {
+// size_t i = 0;
+// for (ExtrusionPath &path : loop.paths) {
+// find_point(path, path_info + "[" + std::to_string(i) + "]");
+// i++;
+// }
+// }
+
+// static void find_point(ExtrusionEntity &extr, const std::string &path);
+
+// static void find_point(ExtrusionEntityCollection &collection, const std::string &path) {
+// size_t i = 0;
+// for (ExtrusionEntity *extr : collection.entities) {
+// find_point(*extr, path + "[" + std::to_string(i) + "]");
+// i++;
+// }
+// }
+
+// static void find_point(ExtrusionEntity &extr, const std::string &path_info) {
+// const ExtrusionPathSloped *sloped = dynamic_cast(&extr);
+// if (sloped != nullptr) {
+// throw RuntimeError("ExtrusionPathSloped not implemented");
+// return;
+// }
+
+// ExtrusionPath *path = dynamic_cast(&extr);
+// if (path != nullptr) {
+// find_point(*path, path_info + " as ExtrusionPath " + ExtrusionEntity::role_to_string(extr.role()));
+// return;
+// }
+
+// ExtrusionLoop *loop = dynamic_cast(&extr);
+// if (loop != nullptr) {
+// find_point(*loop, path_info + " as ExtrusionLoop " + ExtrusionEntity::role_to_string(extr.role()));
+// return;
+// }
+
+// const ExtrusionLoopSloped *loop_sloped = dynamic_cast(&extr);
+// if (loop_sloped != nullptr) {
+// throw RuntimeError("ExtrusionLoopSloped not implemented");
+// return;
+// }
+
+// ExtrusionEntityCollection *collection = dynamic_cast(&extr);
+// if (collection != nullptr) {
+// find_point(*collection, path_info + " as ExtrusionEntityCollection " + ExtrusionEntity::role_to_string(extr.role()));
+// return;
+// }
+
+// throw RuntimeError("ContourZ: ExtrusionEntity type not implemented");
+// return;
+// }
+
+void Layer::make_contour_z(const sla::IndexedMesh &mesh)
+{
+ // printf("make_contour_z() called\n");
+ for (LayerRegion *region : this->regions()) {
+ // printf("processing layer region %p\n", region);
+ // find_point(region->fills, "fills");
+ // find_point(region->perimeters, "perimeters");
+
+ handle_extrusion_collection(region, mesh, region->fills, {erTopSolidInfill, erIroning, erExternalPerimeter, erMixed});
+ handle_extrusion_collection(region, mesh, region->perimeters, {erExternalPerimeter, erMixed});
+ }
+}
+} // namespace Slic3r
\ No newline at end of file
diff --git a/src/libslic3r/CurveAnalyzer.cpp b/src/libslic3r/CurveAnalyzer.cpp
index 330a2b711f..0280ed55ce 100644
--- a/src/libslic3r/CurveAnalyzer.cpp
+++ b/src/libslic3r/CurveAnalyzer.cpp
@@ -29,7 +29,11 @@ void CurveAnalyzer::calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMod
else {
paths_length[i] = paths_length[i - 1] + paths[i].polyline.length();
}
- polygon.points.insert(polygon.points.end(), paths[i].polyline.points.begin(), paths[i].polyline.points.end() - 1);
+ std::transform(
+ paths[i].polyline.points.begin(), paths[i].polyline.points.end() - 1,
+ std::back_inserter(polygon.points),
+ [](const Point3 &pt) {return pt.to_point(); }
+ );
}
// 1 generate point series which is on the line of polygon, point distance along the polygon is smaller than 1mm
polygon.densify(scale_(curvatures_densify_width));
@@ -167,7 +171,7 @@ void CurveAnalyzer::calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMod
//split paths[i]
ExtrusionPath current_path = paths[i];
while (j < curvature_list.size()) {
- Polyline left, right;
+ Polyline3 left, right;
current_path.polyline.split_at(curvature_list[j].first.first, &left, &right);
ExtrusionPath left_path(left, current_path);
left_path.set_curve_degree(current_curva_norm);
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 6308700783..ec8329950d 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -18,12 +18,12 @@ static const int overhang_threshold = 1;
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
- this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
+ this->_inflate_collection(intersection_pl(Polylines{ polyline.to_polyline() }, collection), retval);
}
void ExtrusionPath::subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
- this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection), retval);
+ this->_inflate_collection(diff_pl(Polylines{ this->polyline.to_polyline() }, collection), retval);
}
void ExtrusionPath::clip_end(double distance)
@@ -33,11 +33,17 @@ void ExtrusionPath::clip_end(double distance)
void ExtrusionPath::simplify(double tolerance)
{
+ if (this->z_contoured) {
+ return;
+ }
this->polyline.simplify(tolerance);
}
void ExtrusionPath::simplify_by_fitting_arc(double tolerance)
{
+ if (this->z_contoured) {
+ return;
+ }
this->polyline.simplify_by_fitting_arc(tolerance);
}
@@ -46,15 +52,23 @@ double ExtrusionPath::length() const
return this->polyline.length();
}
+void ExtrusionPath::collect_points(Points &dst) const
+{
+ dst.reserve(dst.size() + this->polyline.points.size());
+ for (const Point3 &point : this->polyline.points) {
+ dst.emplace_back(point.x(), point.y());
+ }
+}
+
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (const Polyline &polyline : polylines)
- collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
+ collection->entities.emplace_back(new ExtrusionPath(Polyline3(polyline), *this));
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
- polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
+ polygons_append(out, offset(this->polyline.to_polyline(), float(scale_(this->width/2)) + scaled_epsilon));
}
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
@@ -64,7 +78,7 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
bool bridge = is_bridge(this->role());
assert(! bridge || this->width == this->height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
- polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
+ polygons_append(out, offset(this->polyline.to_polyline(), 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
bool ExtrusionPath::can_merge(const ExtrusionPath& other)
@@ -128,9 +142,10 @@ Polyline ExtrusionMultiPath::as_polyline() const
len -= paths.size() - 1;
assert(len > 0);
out.points.reserve(len);
- out.points.push_back(paths.front().polyline.points.front());
+ out.points.push_back(paths.front().polyline.points.front().to_point());
for (size_t i_path = 0; i_path < paths.size(); ++ i_path)
- out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end());
+ for (auto it = paths[i_path].polyline.points.begin() + 1; it != paths[i_path].polyline.points.end(); ++it)
+ out.points.push_back(it->to_point());
}
return out;
}
@@ -161,7 +176,9 @@ Polygon ExtrusionLoop::polygon() const
Polygon polygon;
for (const ExtrusionPath &path : this->paths) {
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
- polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
+ for (auto it = path.polyline.points.begin(); it != path.polyline.points.end() - 1; ++it) {
+ polygon.points.push_back(it->to_point());
+ }
}
return polygon;
}
@@ -180,7 +197,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi
if (int idx = path->polyline.find_point(point, scaled_epsilon); idx != -1) {
if (this->paths.size() == 1) {
// just change the order of points
- Polyline p1, p2;
+ Polyline3 p1, p2;
path->polyline.split_at_index(idx, &p1, &p2);
if (p1.is_valid() && p2.is_valid()) {
p2.append(std::move(p1));
@@ -190,7 +207,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi
} else {
// new paths list starts with the second half of current path
ExtrusionPaths new_paths;
- Polyline p1, p2;
+ Polyline3 p1, p2;
path->polyline.split_at_index(idx, &p1, &p2);
new_paths.reserve(this->paths.size() + 1);
{
@@ -230,7 +247,7 @@ ExtrusionLoop::ClosestPathPoint ExtrusionLoop::get_closest_path_and_point(const
ClosestPathPoint best_non_overhang{0, 0};
double min2_non_overhang = std::numeric_limits::max();
for (const ExtrusionPath &path : this->paths) {
- std::pair foot_pt_ = foot_pt(path.polyline.points, point);
+ std::pair foot_pt_ = foot_pt(path.polyline.to_polyline().points, point);
double d2 = (foot_pt_.second - point).cast().squaredNorm();
if (d2 < min2) {
out.foot_pt = foot_pt_.second;
@@ -261,16 +278,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
// Snap p to start or end of segment_idx if closer than scaled_epsilon.
{
- const Point *p1 = this->paths[path_idx].polyline.points.data() + segment_idx;
- const Point *p2 = p1;
+ const Point3 *p1 = this->paths[path_idx].polyline.points.data() + segment_idx;
+ const Point3 *p2 = p1;
++p2;
- double d2_1 = (point - *p1).cast().squaredNorm();
- double d2_2 = (point - *p2).cast().squaredNorm();
+ Point p1_2d = Point(p1->x(), p1->y());
+ Point p2_2d = Point(p2->x(), p2->y());
+ double d2_1 = (point - p1_2d).cast().squaredNorm();
+ double d2_2 = (point - p2_2d).cast().squaredNorm();
const double thr2 = scaled_epsilon * scaled_epsilon;
if (d2_1 < d2_2) {
- if (d2_1 < thr2) p = *p1;
+ if (d2_1 < thr2) p = p1_2d;
} else {
- if (d2_2 < thr2) p = *p2;
+ if (d2_2 < thr2) p = p2_2d;
}
}
@@ -519,16 +538,16 @@ ExtrusionLoopSloped::ExtrusionLoopSloped( ExtrusionPaths &original_paths,
: ExtrusionLoop(role)
{
// create slopes
- const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline &poly, double ratio_begin, double ratio_end) {
+ const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline3 &poly, double ratio_begin, double ratio_end) {
if (poly.empty()) { return; }
// Ensure `slope_max_segment_length`
- Polyline detailed_poly;
+ Polyline3 detailed_poly;
{
detailed_poly.append(poly.first_point());
// Recursively split the line into half until no longer than `slope_max_segment_length`
- const std::function handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line &line) {
+ const std::function handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line3 &line) {
if (line.length() <= slope_max_segment_length) {
detailed_poly.append(line.b);
} else {
@@ -549,8 +568,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped( ExtrusionPaths &original_paths,
const auto seg_length = detailed_poly.length();
if (seg_length > seam_gap) {
// Split the segment and remove the last `seam_gap` bit
- const Polyline orig = detailed_poly;
- Polyline tmp;
+ const Polyline3 orig = detailed_poly;
+ Polyline3 tmp;
orig.split_at_length(seg_length - seam_gap, &detailed_poly, &tmp);
ratio_end = lerp(ratio_begin, ratio_end, (seg_length - seam_gap) / seg_length);
@@ -572,8 +591,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped( ExtrusionPaths &original_paths,
const double path_len = unscale_(path->length());
if (path_len > remaining_length) {
// Split current path into slope and non-slope part
- Polyline slope_path;
- Polyline flat_path;
+ Polyline3 slope_path;
+ Polyline3 flat_path;
path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path);
add_slop(*path, slope_path, start_ratio, 1);
@@ -688,4 +707,28 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
return erNone;
}
+// ExtrusionPathContoured implementation
+ExtrusionEntity *ExtrusionPathContoured::clone() const {
+ return new ExtrusionPathContoured(*this);
+}
+
+ExtrusionEntity *ExtrusionPathContoured::clone_move() {
+ return new ExtrusionPathContoured(std::move(*this));
+}
+
+void ExtrusionPathContoured::simplify(double tolerance) {
+ // Do not simplify contoured paths
+ return;
+}
+
+void ExtrusionPathContoured::simplify_by_fitting_arc(double tolerance) {
+ // Do not simplify contoured paths
+ return;
+}
+
+void ExtrusionPathContoured::reverse() {
+ this->polyline.reverse();
+ std::reverse(this->z_diffs.begin(), this->z_diffs.end());
+}
+
}
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index f3027bcb20..6e852e9531 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -171,8 +171,8 @@ class ExtrusionEntity
virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {}
virtual void reverse() = 0;
- virtual const Point& first_point() const = 0;
- virtual const Point& last_point() const = 0;
+ virtual Point first_point() const = 0;
+ virtual Point last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
@@ -212,7 +212,7 @@ typedef std::vector ExtrusionEntitiesPtr;
class ExtrusionPath : public ExtrusionEntity
{
public:
- Polyline polyline;
+ Polyline3 polyline;
double overhang_degree = 0;
int curve_degree = 0;
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
@@ -222,6 +222,7 @@ class ExtrusionPath : public ExtrusionEntity
// Height of the extrusion, used for visualization purposes.
float height;
double smooth_speed = 0;
+ bool z_contoured = false;
ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1), m_role(erNone), m_no_extrusion(false) {}
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role), m_no_extrusion(false) {}
@@ -237,6 +238,7 @@ class ExtrusionPath : public ExtrusionEntity
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
+ , z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
@@ -250,11 +252,12 @@ class ExtrusionPath : public ExtrusionEntity
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
+ , z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
- ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs)
+ ExtrusionPath(const Polyline3 &polyline, const ExtrusionPath &rhs)
: ExtrusionEntity(rhs)
, polyline(polyline)
, overhang_degree(rhs.overhang_degree)
@@ -263,11 +266,12 @@ class ExtrusionPath : public ExtrusionEntity
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
+ , z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
- ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs)
+ ExtrusionPath(Polyline3 &&polyline, const ExtrusionPath &rhs)
: ExtrusionEntity(rhs)
, polyline(std::move(polyline))
, overhang_degree(rhs.overhang_degree)
@@ -276,6 +280,7 @@ class ExtrusionPath : public ExtrusionEntity
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
+ , z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
@@ -290,6 +295,7 @@ class ExtrusionPath : public ExtrusionEntity
this->width = rhs.width;
this->height = rhs.height;
this->smooth_speed = rhs.smooth_speed;
+ this->z_contoured = rhs.z_contoured;
this->overhang_degree = rhs.overhang_degree;
this->curve_degree = rhs.curve_degree;
this->polyline = rhs.polyline;
@@ -303,7 +309,8 @@ class ExtrusionPath : public ExtrusionEntity
this->mm3_per_mm = rhs.mm3_per_mm;
this->width = rhs.width;
this->height = rhs.height;
- this->smooth_speed = rhs.smooth_speed;
+ this->smooth_speed = rhs.smooth_speed;
+ this->z_contoured = rhs.z_contoured;
this->overhang_degree = rhs.overhang_degree;
this->curve_degree = rhs.curve_degree;
this->polyline = std::move(rhs.polyline);
@@ -314,8 +321,10 @@ class ExtrusionPath : public ExtrusionEntity
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); }
- const Point& first_point() const override { return this->polyline.points.front(); }
- const Point& last_point() const override { return this->polyline.points.back(); }
+ Point first_point() const override { return this->polyline.points.front().to_point(); }
+ Point3 first_point3() const { return this->polyline.points.front(); }
+ Point last_point() const override { return this->polyline.points.back().to_point(); }
+ Point3 last_point3() const { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
@@ -326,7 +335,7 @@ class ExtrusionPath : public ExtrusionEntity
// Currently not used.
void subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance);
- void simplify(double tolerance);
+ virtual void simplify(double tolerance);
double length() const override;
ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
@@ -342,9 +351,10 @@ class ExtrusionPath : public ExtrusionEntity
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
- Polyline as_polyline() const override { return this->polyline; }
- void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
- void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
+ Polyline as_polyline() const override { return this->polyline.to_polyline(); }
+ void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline.to_polyline()); }
+ void collect_points(Points &dst) const override;
+ void collect_points3(Points3 &dst) const { append(dst, this->polyline.points); }
double total_volume() const override { return mm3_per_mm * unscale(length()); }
void set_overhang_degree(int overhang) {
@@ -382,6 +392,23 @@ class ExtrusionPath : public ExtrusionEntity
bool m_no_extrusion = false;
};
+class ExtrusionPathContoured : public ExtrusionPath {
+public:
+ std::vector z_diffs;
+
+ ExtrusionPathContoured(Polyline3 &&polyline, const ExtrusionPath &rhs, std::vector &&z_diffs)
+ : ExtrusionPath(std::move(polyline), rhs), z_diffs(std::move(z_diffs))
+ {}
+
+ virtual ExtrusionEntity *clone() const override;
+ virtual ExtrusionEntity *clone_move() override;
+
+ void simplify(double tolerance) override;
+ virtual void simplify_by_fitting_arc(double tolerance);
+
+ void reverse() override;
+};
+
class ExtrusionPathSloped : public ExtrusionPath
{
public:
@@ -396,9 +423,9 @@ class ExtrusionPathSloped : public ExtrusionPath
Slope slope_end;
ExtrusionPathSloped(const ExtrusionPath &rhs, const Slope &begin, const Slope &end) : ExtrusionPath(rhs), slope_begin(begin), slope_end(end) {}
ExtrusionPathSloped(ExtrusionPath &&rhs, const Slope &begin, const Slope &end) : ExtrusionPath(std::move(rhs)), slope_begin(begin), slope_end(end) {}
- ExtrusionPathSloped(const Polyline &polyline, const ExtrusionPath &rhs, const Slope &begin, const Slope &end) : ExtrusionPath(polyline, rhs), slope_begin(begin), slope_end(end)
+ ExtrusionPathSloped(const Polyline3 &polyline, const ExtrusionPath &rhs, const Slope &begin, const Slope &end) : ExtrusionPath(polyline, rhs), slope_begin(begin), slope_end(end)
{}
- ExtrusionPathSloped(Polyline &&polyline, const ExtrusionPath &rhs, const Slope &begin, const Slope &end) : ExtrusionPath(std::move(polyline), rhs), slope_begin(begin), slope_end(end)
+ ExtrusionPathSloped(Polyline3 &&polyline, const ExtrusionPath &rhs, const Slope &begin, const Slope &end) : ExtrusionPath(std::move(polyline), rhs), slope_begin(begin), slope_end(end)
{}
Slope interpolate(const double ratio) const {
@@ -456,8 +483,8 @@ class ExtrusionMultiPath : public ExtrusionEntity
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override;
- const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
- const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
+ Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
+ Point last_point() const override { return this->paths.back().polyline.points.back().to_point(); }
size_t size() const { return this->paths.size(); }
bool empty() const { return this->paths.empty(); }
double length() const override;
@@ -481,7 +508,7 @@ class ExtrusionMultiPath : public ExtrusionEntity
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
dst.reserve(dst.size() + n);
for (const ExtrusionPath &p : this->paths)
- append(dst, p.polyline.points);
+ append(dst, to_points(p.polyline.points));
}
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
@@ -514,8 +541,8 @@ class ExtrusionLoop : public ExtrusionEntity
bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); }
void reverse() override;
bool is_set_speed_discontinuity_area() const { return this->role() == erExternalPerimeter || this->role() == erPerimeter || this->role() == erOverhangPerimeter; }
- const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
- const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
+ Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
+ Point last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back().to_point()); return this->first_point(); }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001));
@@ -554,7 +581,7 @@ class ExtrusionLoop : public ExtrusionEntity
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
dst.reserve(dst.size() + n);
for (const ExtrusionPath &p : this->paths)
- append(dst, p.polyline.points);
+ append(dst, to_points(p.polyline.points));
}
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
// check if the loop is smooth, angle_threshold is in radians, default is 10 degrees
@@ -598,7 +625,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, Ex
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
- dst.back().polyline = polyline;
+ dst.back().polyline = Polyline3(polyline);
}
}
@@ -608,7 +635,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, do
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(overhang_degree, curva_degree, role, mm3_per_mm, width, height));
- dst.back().polyline = polyline;
+ dst.back().polyline = Polyline3(polyline);
}
}
@@ -618,7 +645,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
- dst.back().polyline = std::move(polyline);
+ dst.back().polyline = Polyline3(std::move(polyline));
}
polylines.clear();
}
@@ -629,7 +656,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, d
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(overhang_degree, curva_degree, role, mm3_per_mm, width, height));
- dst.back().polyline = std::move(polyline);
+ dst.back().polyline = Polyline3(std::move(polyline));
}
polylines.clear();
}
@@ -639,7 +666,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polyline &&polyline, dou
dst.reserve(dst.size() + 1);
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(overhang_degree, curva_degree, role, mm3_per_mm, width, height));
- dst.back().polyline = std::move(polyline);
+ dst.back().polyline = Polyline3(std::move(polyline));
}
polyline.clear();
}
@@ -651,7 +678,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
- extrusion_path->polyline = polyline;
+ extrusion_path->polyline = Polyline3(polyline);
}
}
@@ -662,7 +689,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
- extrusion_path->polyline = std::move(polyline);
+ extrusion_path->polyline = Polyline3(std::move(polyline));
}
polylines.clear();
}
@@ -698,10 +725,10 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst,
connect_line.points.push_back(overlap_curr_p);
connect_line.points.push_back(polyline.first_point());
- multi_path->paths.back().polyline = std::move(connect_line);
+ multi_path->paths.back().polyline = Polyline3(std::move(connect_line));
}
else{
- multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point()));
+ multi_path->paths.back().polyline = Polyline3(Polyline(last_end_point, polyline.first_point()));
}
} else {
@@ -711,10 +738,10 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst,
}
multi_path->paths.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
- multi_path->paths.back().polyline = std::move(polyline);
+ multi_path->paths.back().polyline = Polyline3(std::move(polyline));
last_end_point_valid = true;
- last_end_point = multi_path->paths.back().polyline.last_point();
- last_direction = (last_end_point - multi_path->paths.back().polyline.first_point()).cast().normalized();
+ last_end_point = multi_path->paths.back().polyline.last_point().to_point();
+ last_direction = (last_end_point - multi_path->paths.back().polyline.first_point().to_point()).cast().normalized();
}
}
if (!multi_path->empty())
@@ -729,7 +756,9 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
for (Polygon &poly : loops) {
if (poly.is_valid()) {
ExtrusionPath path(role, mm3_per_mm, width, height);
- path.polyline.points = std::move(poly.points);
+ path.polyline.points.reserve(poly.points.size() + 1);
+ for (const Point &pt : poly.points)
+ path.polyline.points.emplace_back(Point3(pt, 0));
path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path)));
}
@@ -744,11 +773,11 @@ inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst,
if (polyline.is_valid()) {
if (polyline.is_closed()) {
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
- extrusion_path.polyline = std::move(polyline);
+ extrusion_path.polyline = Polyline3(std::move(polyline));
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
} else {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
- extrusion_path->polyline = std::move(polyline);
+ extrusion_path->polyline = Polyline3(std::move(polyline));
dst.emplace_back(extrusion_path);
}
}
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
index dec981eb89..a76266f060 100644
--- a/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -102,8 +102,8 @@ class ExtrusionEntityCollection : public ExtrusionEntity
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse() override;
- const Point& first_point() const override { return this->entities.front()->first_point(); }
- const Point& last_point() const override { return this->entities.back()->last_point(); }
+ Point first_point() const override { return this->entities.front()->first_point(); }
+ Point last_point() const override { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp
index 6b1f76abea..4bf54edea9 100644
--- a/src/libslic3r/ExtrusionSimulator.cpp
+++ b/src/libslic3r/ExtrusionSimulator.cpp
@@ -962,7 +962,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
w = scale_(path.mm3_per_mm / path.height) * scalex;
// printf("scalex: %f, scaley: %f\n", scalex, scaley);
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
- for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
+ for (Points3::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
// printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y);
ExtrusionPoint ept;
ept.center = V2f(float((*it)(0)+shift.x()-bbox.min.x()) * scalex, float((*it)(1)+shift.y()-bbox.min.y()) * scaley);
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index fa48991845..d6df6a43f9 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -607,6 +607,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
+ f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction;
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
@@ -813,6 +814,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
+ f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction;
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
@@ -983,6 +985,7 @@ void Layer::make_ironing()
std::unique_ptr f = std::unique_ptr(Fill::new_from_type(f_pattern));
f->set_bounding_box(this->object()->bounding_box());
f->layer_id = this->id();
+ f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction;
f->z = this->print_z;
f->overlap = 0;
for (size_t i = 0; i < by_extruder.size();) {
@@ -995,6 +998,7 @@ void Layer::make_ironing()
f = std::unique_ptr(Fill::new_from_type(f_pattern));
f->set_bounding_box(this->object()->bounding_box());
f->layer_id = this->id();
+ f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction;
f->z = this->print_z;
f->overlap = 0;
}
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
index 327e00169a..dbe8867b3d 100644
--- a/src/libslic3r/Fill/FillBase.cpp
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -231,7 +231,9 @@ std::pair Fill::_infill_direction(const Surface *surface) const
out_angle = float(surface->bridge_angle);
} else if (this->layer_id != size_t(-1)) {
// alternate fill direction
- out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
+ if (!this->dont_alternate_fill_direction) {
+ out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
+ }
} else {
// printf("Layer_ID undefined!\n");
}
diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp
index ff23a14d82..ed3d70dd87 100644
--- a/src/libslic3r/Fill/FillBase.hpp
+++ b/src/libslic3r/Fill/FillBase.hpp
@@ -131,6 +131,7 @@ class Fill
// BBS: all no overlap expolygons in same layer
ExPolygons no_overlap_expolygons;
+ bool dont_alternate_fill_direction = false;
public:
virtual ~Fill() {}
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 1f5cb6edd9..fea94dd3ae 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -16,8 +16,10 @@
#include "libslic3r/format.hpp"
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -1820,7 +1822,7 @@ namespace DoExport {
Points skirt_points;
for (const ExtrusionEntity *ee : print.skirt().entities)
for (const ExtrusionPath &path : dynamic_cast(ee)->paths)
- append(skirt_points, path.polyline.points);
+ append_points(skirt_points, path.polyline.points);
if (! skirt_points.empty()) {
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
Polygons skirts;
@@ -4651,10 +4653,10 @@ GCode::LayerResult GCode::process_layer(
bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden;
if (is_overridden == (print_wipe_extrusions != 0)) {
// support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for all extrusion paths.
- gcode += this->extrude_support(instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, support_extrusion_role));
+ gcode += this->extrude_support(instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos.to_point(), support_extrusion_role));
if (support_extrusion_role == erSupportMaterialInterface) {
// erSupportMaterialInterface may be mixed with erSupportIroning, thus, should also make ironing here
- gcode += this->extrude_support(instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, erSupportIroning));
+ gcode += this->extrude_support(instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos.to_point(), erSupportIroning));
}
if (!filament_info.use_for_support)
filament_info.use_for_support = true;
@@ -4878,12 +4880,12 @@ void GCode::set_extruders(const std::vector &extruder_ids)
void GCode::set_origin(const Vec2d &pointf)
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
- const Point translate(
+ const Point3 translate(
scale_(m_origin(0) - pointf(0)),
scale_(m_origin(1) - pointf(1))
);
m_last_pos += translate;
- m_wipe.path.translate(translate);
+ m_wipe.path.translate(translate.to_point());
m_origin = pointf;
}
@@ -5068,9 +5070,10 @@ double GCode::get_path_speed(const ExtrusionPath &path)
return speed;
}
-std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed)
+std::string GCode::extrude_loop(const ExtrusionLoop &loop_ref, std::string description, double speed)
{
// get a copy; don't modify the orientation of the original loop object otherwise
+ ExtrusionLoop loop = loop_ref;
// next copies (if any) would not detect the correct orientation
// extrude all loops ccw
@@ -5231,13 +5234,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// BBS
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = Polyline();
- for (ExtrusionPath &path : paths) {
+ for (const ExtrusionPath &path : paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
- m_wipe.path.last_point() == path.first_point())
- m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
- else
- m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
+ m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
+ for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
+ m_wipe.path.append(Point(it->x(), it->y()));
+ } else
+ m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
}
@@ -5284,7 +5288,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
return gcode;
}
-std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed)
+std::string GCode::extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description, double speed)
{
// extrude along the path
std::string gcode;
@@ -5294,13 +5298,14 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
// BBS
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = Polyline();
- for (ExtrusionPath &path : multipath.paths) {
+ for (const ExtrusionPath &path : multipath.paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
- m_wipe.path.last_point() == path.first_point())
- m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
- else
- m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
+ m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
+ for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
+ m_wipe.path.append(Point(it->x(), it->y()));
+ } else
+ m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
m_wipe.path.reverse();
}
@@ -5327,23 +5332,23 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return "";
}
-std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
+std::string GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed)
{
// description += ExtrusionEntity::role_to_string(path.role());
bool flag = path.get_customize_flag() == CustomizeFlag::cfFloatingVerticalShell;
std::string gcode = this->_extrude(path, description, speed,flag);
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
- m_wipe.path = path.polyline;
+ m_wipe.path = path.polyline.to_polyline();
if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) {
if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast().norm() > scale_(0.2)) {
double min_dist = scale_(0.2);
int i = 0;
for (; i < path.polyline.points.size(); i++) {
- double dist = (path.polyline.points[i] - path.last_point()).cast().norm();
+ double dist = (path.polyline.points[i] - path.last_point3()).cast().norm();
if (dist < min_dist) min_dist = dist;
if (min_dist < scale_(0.2) && dist > min_dist) break;
}
- m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end()));
+ m_wipe.path = Polyline3(Points3(path.polyline.points.begin() + i - 1, path.polyline.points.end())).to_polyline();
}
} else
m_wipe.path.reverse();
@@ -5397,11 +5402,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector(fill);
if (eec) {
- for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
+ for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos.to_point()).entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
@@ -5435,11 +5440,11 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
if (extrusions.empty() && ironing_extrusions.empty()) return gcode;
has_support_ironing = has_support_ironing && m_config.enable_support_ironing.value;
if (has_support_ironing) {
- chain_and_reorder_extrusion_entities(ironing_extrusions, &m_last_pos);
+ chain_and_reorder_extrusion_entities(ironing_extrusions, m_last_pos.to_point());
} else{
ironing_extrusions.clear();
}
- chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
+ chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
const double support_speed = NOZZLE_CONFIG(support_speed);
const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed);
@@ -5668,17 +5673,17 @@ void GCode::split_and_mapping_speed(double other_path_v, double final_v, Extrusi
}
// reverse if this slowdown the speed
- Polyline input_polyline = extrusion.polyline;
+ Polyline3 input_polyline = extrusion.polyline;
if (!split_from_left)
std::reverse(input_polyline.begin(), input_polyline.end());
- Point line_start_pt = input_polyline.points.front();
- Point line_end_pt = input_polyline.points[1];
+ Point3 line_start_pt = input_polyline.points.front();
+ Point3 line_end_pt = input_polyline.points[1];
bool get_next_line = false;
size_t end_pt_idx = 1;
// split long extrusion
- Point last_point = line_start_pt;
+ Point3 last_point = line_start_pt;
while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) {
// move to next line
if (get_next_line) {
@@ -5686,8 +5691,8 @@ void GCode::split_and_mapping_speed(double other_path_v, double final_v, Extrusi
line_end_pt = input_polyline.points[end_pt_idx];
}
// This line is cut off as a speed transition area
- Polyline cuted_polyline;
- Line line(line_start_pt, line_end_pt);
+ Polyline3 cuted_polyline;
+ Line3 line(line_start_pt, line_end_pt);
cuted_polyline.append(line_start_pt);
// split polyline and set speed
@@ -5699,7 +5704,9 @@ void GCode::split_and_mapping_speed(double other_path_v, double final_v, Extrusi
} else {
// path is too long, split it
double rate = min_step_length / line.length();
- Point insert_p = line.a + (line.b - line.a) * rate;
+ Point3 insert_p(line.a.x() + coord_t((line.b.x() - line.a.x()) * rate),
+ line.a.y() + coord_t((line.b.y() - line.a.y()) * rate),
+ line.a.z() + coord_t((line.b.z() - line.a.z()) * rate));
split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v);
line_start_pt = insert_p;
@@ -5722,8 +5729,9 @@ void GCode::split_and_mapping_speed(double other_path_v, double final_v, Extrusi
continue;
}
// split polyline
- Polyline p1, p2;
- extrusion.polyline.split_at(last_point, &p1, &p2);
+ Polyline3 p1, p2;
+ Point split_point = splited_path.back().polyline.points.back().to_point();
+ extrusion.polyline.split_at(split_point, &p1, &p2);
if (split_from_left) {
//update split point to avoid travel path
@@ -5995,12 +6003,24 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// go to first point of extrusion path
//BBS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function.
//Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance
- if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || (sloped != nullptr && !sloped->is_flat())) {
+ Point3 first_point = path.first_point3();
+ bool slope_need_z_travel = sloped != nullptr && !sloped->is_flat();
+ if (!m_last_pos_defined || m_last_pos != first_point || m_need_change_layer_lift_z || slope_need_z_travel) {
+ const bool _last_pos_undefined = !m_last_pos_defined;
+ double z = DBL_MAX;
+ if (sloped != nullptr) {
+ z = get_sloped_z(sloped->slope_begin.z_ratio);
+ } else if ((!m_last_pos_defined && first_point.z() != 0) || m_last_pos.z() != first_point.z()) {
+ z = m_nominal_z + unscale_(first_point.z());
+ if (z < 0.1) {
+ throw RuntimeError("GCode: very low z");
+ }
+ }
gcode += this->travel_to(
path.first_point(),
path.role(),
- "move to first " + description + " point",
- sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio)
+ "move to first " + description + " point; size " + std::to_string(path.polyline.size()),
+ z
);
m_need_change_layer_lift_z = false;
}
@@ -6284,18 +6304,42 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (!m_config.enable_arc_fitting ||
path.polyline.fitting_result.empty() ||
m_config.spiral_mode ||
- sloped != nullptr) {
+ sloped != nullptr ||
+ path.z_contoured) {
double path_length = 0.;
double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR;
- for (const Line &line : path.polyline.lines()) {
+ for (const Line3 &line : path.polyline.lines()) {
const double line_length = line.length() * SCALING_FACTOR;
// BBS: extursion cmd should E0 on cmd line
if (line_length < EPSILON) continue;
path_length += line_length;
- if (sloped == nullptr) {
+ if (path.z_contoured) {
+ // ZAA: Z anti-aliased extrusion with variable Z per point
+ Vec2d dest2d = this->point_to_gcode(line.b.to_point());
+ coordf_t z_diff = unscale_(line.b.z());
+
+ double extrusion_ratio = 1;
+ if (path.role() != erIroning) {
+ extrusion_ratio = (path.height + z_diff) / path.height;
+ }
+
+ double e = e_per_mm * line_length * extrusion_ratio;
+
+ double z = m_nominal_z + z_diff;
+ if (z < 0.1) {
+ throw RuntimeError("GCode: very low z");
+ }
+ std::string tempDescription = comment + "; z_diff " + std::to_string(z_diff) + " " +
+ ExtrusionEntity::role_to_string(path.role()) +
+ "; eratio " + std::to_string(extrusion_ratio);
+ gcode += m_writer.extrude_to_xyz(
+ Vec3d(dest2d.x(), dest2d.y(), z),
+ e,
+ tempDescription);
+ } else if (sloped == nullptr) {
gcode += m_writer.extrude_to_xy(
- this->point_to_gcode(line.b),
+ this->point_to_gcode(line.b.to_point()),
e_per_mm * line_length,
comment);
} else {
@@ -6304,7 +6348,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
auto [z_ratio, e_ratio, slope_speed] = sloped->interpolate(path_length / total_length);
//FIX: cooling need to apply correctly
//gcode += m_writer.set_speed(slope_speed * 60, "", comment);
- Vec2d dest2d = this->point_to_gcode(line.b);
+ Vec2d dest2d = this->point_to_gcode(line.b.to_point());
Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio));
//BBS: todo, should use small e at start to get good seam
double slope_e = dE * e_ratio;
@@ -6320,7 +6364,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
size_t start_index = fitting_result[fitting_index].start_point_index;
size_t end_index = fitting_result[fitting_index].end_point_index;
for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) {
- const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]);
+ const Line line = Line(path.polyline.points[point_index - 1].to_point(), path.polyline.points[point_index].to_point());
const double line_length = line.length() * SCALING_FACTOR;
// BBS: extursion cmd should E0 on cmd line
if (line_length < EPSILON)
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 156c43768f..83fa2e489a 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -199,7 +199,7 @@ class GCode {
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
- const Point& last_pos() const { return m_last_pos; }
+ Point last_pos() const { return m_last_pos.to_point(); }
const bool& last_scarf_seam_flag() const { return m_last_scarf_seam_flag; }
const bool& scarf_seam_start_flag() const { return m_scarf_seam_start; }
Vec2d point_to_gcode(const Point &point) const;
@@ -392,7 +392,8 @@ class GCode {
size_t get_extruder_id(unsigned int filament_id) const;
void set_extrude_acceleration(bool is_first_layer);
- void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
+ void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; }
+ void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; }
void set_last_scarf_seam_flag(bool flag) { m_last_scarf_seam_flag = flag; }
void set_scarf_seam_start_flag(bool flag) { m_scarf_seam_start = flag; }
bool last_pos_defined() const { return m_last_pos_defined; }
@@ -401,9 +402,9 @@ class GCode {
// BBS
std::string change_layer(coordf_t print_z);
std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1.);
- std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1.);
- std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
- std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.);
+ std::string extrude_loop(const ExtrusionLoop &loop, std::string description, double speed = -1.);
+ std::string extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description = "", double speed = -1.);
+ std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1.);
//smooth speed function
void smooth_speed_discontinuity_area(ExtrusionPaths &paths);
@@ -543,7 +544,7 @@ class GCode {
double m_last_mm3_per_mm;
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
- Point m_last_pos;
+ Point3 m_last_pos;
bool m_last_pos_defined;
bool m_last_scarf_seam_flag;
bool m_scarf_seam_start;
diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp
index 30a83676e8..a10b969431 100644
--- a/src/libslic3r/GCode/ConflictChecker.hpp
+++ b/src/libslic3r/GCode/ConflictChecker.hpp
@@ -87,7 +87,7 @@ class LinesBucket
for (int i = b; i < e; ++i) {
for (const ExtrusionPath &path : _piles[i].paths) {
if (path.is_force_no_extrusion() == false) {
- Polyline check_polyline = path.polyline;
+ Polyline check_polyline = path.polyline.to_polyline();
check_polyline.translate(_offset);
Lines tmpLines = check_polyline.lines();
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp
index af54f46a1f..4a65ae5ae4 100644
--- a/src/libslic3r/GCode/PrintExtents.cpp
+++ b/src/libslic3r/GCode/PrintExtents.cpp
@@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
{
- BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
+ BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width)));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
- bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
+ bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
- bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
+ bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp
index 4d7a5895dc..42baa15385 100644
--- a/src/libslic3r/GCodeWriter.cpp
+++ b/src/libslic3r/GCodeWriter.cpp
@@ -739,6 +739,11 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
+ // Check if Z actually changes (at export precision) before emitting it.
+ // ZAA sloped extrusions call this for every segment, but many consecutive
+ // segments share the same quantized Z — emitting it every time is redundant.
+ bool z_changed = (GCodeG1Formatter::quantize_xyzf(point(2)) != GCodeG1Formatter::quantize_xyzf(m_pos(2)));
+
m_pos = point;
m_lifted = 0;
if (!force_no_extrusion)
@@ -748,7 +753,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
GCodeG1Formatter w;
- w.emit_xyz(point_on_plate);
+ if (z_changed)
+ w.emit_xyz(point_on_plate);
+ else
+ w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS
diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp
index 08b8629e88..7e75c264c7 100644
--- a/src/libslic3r/GCodeWriter.hpp
+++ b/src/libslic3r/GCodeWriter.hpp
@@ -205,6 +205,13 @@ class GCodeFormatter {
// static constexpr const int XYZF_EXPORT_DIGITS = 6;
// static constexpr const int E_EXPORT_DIGITS = 9;
#endif
+ static constexpr const std::array pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000. };
+ static constexpr const std::array pow_10_inv { 1. / 1., 1. / 10., 1. / 100., 1. / 1000., 1. / 10000., 1. / 100000., 1. / 1000000., 1. / 10000000., 1. / 100000000., 1. / 1000000000. };
+
+ // Quantize doubles to a resolution of the G-code.
+ static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
+ static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
+ static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
void emit_axis(const char axis, const double v, size_t digits);
diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp
index 865034c9ca..27f5a87793 100644
--- a/src/libslic3r/Layer.hpp
+++ b/src/libslic3r/Layer.hpp
@@ -30,6 +30,10 @@ namespace FillLightning {
class Generator;
};
+namespace sla {
+ class IndexedMesh;
+};
+
class LayerRegion
{
public:
@@ -207,6 +211,7 @@ class Layer
FillLightning::Generator* lightning_generator) const;
void make_ironing();
+ void make_contour_z(const sla::IndexedMesh &mesh);
void export_region_slices_to_svg(const char *path) const;
void export_region_fill_surfaces_to_svg(const char *path) const;
diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp
index f161c3ed81..a74b45362e 100644
--- a/src/libslic3r/Line.hpp
+++ b/src/libslic3r/Line.hpp
@@ -212,17 +212,23 @@ class ThickLine : public Line
class Line3
{
public:
- Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {}
- Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {}
+ Line3() : a(Point3()), b(Point3()) {}
+ Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {}
+ // Backward compatibility with Vec3crd
+ Line3(const Vec3crd& _a, const Vec3crd& _b) : a(Point3(_a)), b(Point3(_b)) {}
double length() const { return (this->a - this->b).cast().norm(); }
- Vec3crd vector() const { return this->b - this->a; }
+ Point3 vector() const { Vec3crd v = this->b - this->a; return Point3(v.x(), v.y(), v.z()); }
+ Point3 midpoint() const { return Point3((this->a.x() + this->b.x()) / 2, (this->a.y() + this->b.y()) / 2, (this->a.z() + this->b.z()) / 2); }
- Vec3crd a;
- Vec3crd b;
+ // Convert to 2D line by dropping Z coordinate
+ Line to_line() const { return Line(this->a.to_point(), this->b.to_point()); }
+
+ Point3 a;
+ Point3 b;
static const constexpr int Dim = 3;
- using Scalar = Vec3crd::Scalar;
+ using Scalar = coord_t;
};
class Linef
@@ -231,6 +237,10 @@ class Linef
Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {}
+ Vec2d vector() const { return this->b - this->a; }
+ Vec2d unit_vector() const { return (length() == 0.0) ? Vec2d::Zero() : vector().normalized(); }
+ double length() const { return vector().norm(); }
+
Vec2d a;
Vec2d b;
@@ -251,12 +261,41 @@ class Linef3
Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); }
double length() const { return vector().norm(); }
void reverse() { std::swap(this->a, this->b); }
+
+ // ZAA addition: distance to infinite line
+ double distance_to_infinite_squared(const Vec3d &point, Vec3d *closest_point) const {
+ const Vec3d v = this->b - this->a;
+ const Vec3d va = point - this->a;
+ const double l2 = v.squaredNorm();
+ if (l2 == 0.) {
+ // a == b case
+ *closest_point = this->a;
+ return va.squaredNorm();
+ }
+ // Consider the line extending the segment, parameterized as a + t (b - a).
+ // Find parameter value t of the projection of point onto the line.
+ const double t = va.dot(v) / l2;
+ *closest_point = this->a + t * v;
+ return (point - *closest_point).squaredNorm();
+ }
+
+ double distance_to_infinite_squared(const Vec3d &point) const {
+ Vec3d nearest_point;
+ return distance_to_infinite_squared(point, &nearest_point);
+ }
+
+ static inline double distance_to_infinite_squared(const Vec3d &point, const Vec3d &a, const Vec3d &b) {
+ Linef3 line{a, b};
+ return line.distance_to_infinite_squared(point);
+ }
+
Vec3d a;
Vec3d b;
static const constexpr int Dim = 3;
using Scalar = Vec3d::Scalar;
+ // BambuStudio-specific methods (keep these)
static void get_point_projection_to_line(const Vec3d &pt, const Vec3d &line_start, const Vec3d &line_dir, Vec3d &intersection_pt, float &proj_length)
{
auto line_dir_ = line_dir.normalized();
diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp
index b0362f5b10..c5f29ce5be 100644
--- a/src/libslic3r/MultiPoint.cpp
+++ b/src/libslic3r/MultiPoint.cpp
@@ -476,4 +476,123 @@ void MultiPoint::symmetric_y(const coord_t &x_axis)
}
}
+// MultiPoint3 ZAA implementations
+void MultiPoint3::rotate(double cos_angle, double sin_angle)
+{
+ for (Point3 &pt : this->points) {
+ double cur_x = double(pt(0));
+ double cur_y = double(pt(1));
+ pt(0) = coord_t(round(cos_angle * cur_x - sin_angle * cur_y));
+ pt(1) = coord_t(round(cos_angle * cur_y + sin_angle * cur_x));
+ // Keep Z unchanged
+ }
+}
+
+void MultiPoint3::rotate(double angle, const Point3 ¢er)
+{
+ double s = sin(angle);
+ double c = cos(angle);
+ for (Point3 &pt : points) {
+ Vec3crd v(pt - center);
+ pt(0) = (coord_t)round(double(center(0)) + c * v[0] - s * v[1]);
+ pt(1) = (coord_t)round(double(center(1)) + c * v[1] + s * v[0]);
+ // Keep Z unchanged from original point
+ }
+}
+
+int MultiPoint3::find_point(const Point &point) const
+{
+ for (const Point3 &pt : this->points)
+ if (pt.to_point() == point)
+ return int(&pt - &this->points.front());
+ return -1; // not found
+}
+
+int MultiPoint3::find_point(const Point &point, double scaled_epsilon) const
+{
+ if (scaled_epsilon == 0) return this->find_point(point);
+
+ auto dist2_min = std::numeric_limits::max();
+ auto eps2 = scaled_epsilon * scaled_epsilon;
+ int idx_min = -1;
+ for (const Point3 &pt : this->points) {
+ double d2 = (pt.to_point() - point).cast().squaredNorm();
+ if (d2 < dist2_min) {
+ idx_min = int(&pt - &this->points.front());
+ dist2_min = d2;
+ }
+ }
+ return (dist2_min < eps2) ? idx_min : -1;
+}
+
+int MultiPoint3::find_point(const Point3 &point) const
+{
+ for (const Point3 &pt : this->points)
+ if (pt == point)
+ return int(&pt - &this->points.front());
+ return -1; // not found
+}
+
+int MultiPoint3::find_point(const Point3 &point, double scaled_epsilon) const
+{
+ if (scaled_epsilon == 0) return this->find_point(point);
+
+ auto dist2_min = std::numeric_limits::max();
+ auto eps2 = scaled_epsilon * scaled_epsilon;
+ int idx_min = -1;
+ for (const Point3 &pt : this->points) {
+ double d2 = (pt - point).cast().squaredNorm();
+ if (d2 < dist2_min) {
+ idx_min = int(&pt - &this->points.front());
+ dist2_min = d2;
+ }
+ }
+ return (dist2_min < eps2) ? idx_min : -1;
+}
+
+// Douglas-Peucker simplification for 3D points
+Points3 MultiPoint3::_douglas_peucker(const Points3 &points, double tolerance)
+{
+ if (points.size() <= 2) return points;
+
+ // Find the point with maximum distance from line segment
+ double max_dist = 0;
+ size_t max_idx = 0;
+ const Point3 &first = points.front();
+ const Point3 &last = points.back();
+ Line3 line(first, last);
+
+ for (size_t i = 1; i < points.size() - 1; ++i) {
+ // Calculate perpendicular distance to line segment
+ Point3 proj = points[i].projection_onto(line);
+ double dist = points[i].distance_to(proj);
+ if (dist > max_dist) {
+ max_dist = dist;
+ max_idx = i;
+ }
+ }
+
+ // If max distance is greater than tolerance, recursively simplify
+ if (max_dist > tolerance) {
+ // Recursive call for first part
+ Points3 left_points(points.begin(), points.begin() + max_idx + 1);
+ Points3 left_result = _douglas_peucker(left_points, tolerance);
+
+ // Recursive call for second part
+ Points3 right_points(points.begin() + max_idx, points.end());
+ Points3 right_result = _douglas_peucker(right_points, tolerance);
+
+ // Concatenate results (avoiding duplicate middle point)
+ Points3 result = left_result;
+ result.insert(result.end(), right_result.begin() + 1, right_result.end());
+ return result;
+ } else {
+ // All points between first and last can be removed
+ Points3 result;
+ result.push_back(first);
+ result.push_back(last);
+ return result;
+ }
+}
+
}
diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp
index 26d2dcbb5f..554491e66a 100644
--- a/src/libslic3r/MultiPoint.hpp
+++ b/src/libslic3r/MultiPoint.hpp
@@ -113,18 +113,46 @@ class MultiPoint3
public:
Points3 points;
- void append(const Vec3crd& point) { this->points.push_back(point); }
+ void append(const Point3& point) { this->points.push_back(point); }
+ void append(const Vec3crd& point) { this->points.push_back(Point3(point)); }
void translate(double x, double y);
void translate(const Point& vector);
+ void reverse() { std::reverse(this->points.begin(), this->points.end()); }
+ void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
+ void rotate(double cos_angle, double sin_angle);
+ void rotate(double angle, const Point3 ¢er);
+
+ Point3& first_point() { return this->points.front(); }
+ Point3& last_point() { return this->points.back(); }
+ const Point3& first_point() const { return this->points.front(); }
+ const Point3& last_point() const { return this->points.back(); }
+ size_t size() const { return this->points.size(); }
+ bool empty() const { return this->points.empty(); }
+ void clear() { this->points.clear(); }
+
+ auto begin() { return this->points.begin(); }
+ auto end() { return this->points.end(); }
+ auto begin() const { return this->points.begin(); }
+ auto end() const { return this->points.end(); }
+
virtual Lines3 lines() const = 0;
double length() const;
bool is_valid() const { return this->points.size() >= 2; }
BoundingBox3 bounding_box() const;
+ // Find a point in the points array
+ int find_point(const Point &point) const;
+ int find_point(const Point &point, const double scaled_epsilon) const;
+ int find_point(const Point3 &point) const;
+ int find_point(const Point3 &point, const double scaled_epsilon) const;
+
// Remove exact duplicates, return true if any duplicate has been removed.
bool remove_duplicate_points();
+
+ // Douglas-Peucker simplification
+ static Points3 _douglas_peucker(const Points3 &points, double tolerance);
};
extern BoundingBox get_extents(const MultiPoint &mp);
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index 1f2acba665..fb4ec769a9 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -431,11 +431,12 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
- chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
+ Point start_pt = Point(paths.front().first_point().x(), paths.front().first_point().y());
+ chain_and_reorder_extrusion_paths(paths, &start_pt);
} else {
ExtrusionPath path(role);
//BBS.
- path.polyline = polygon.split_at_first_point();
+ path.polyline = Polyline3(polygon.split_at_first_point());
path.overhang_degree = 0;
path.curve_degree = 0;
path.mm3_per_mm = extrusion_mm3_per_mm;
@@ -736,11 +737,15 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
};
std::unordered_map point_occurrence;
for (const ExtrusionPath& path : paths) {
- ++point_occurrence[path.polyline.first_point()].occurrence;
- ++point_occurrence[path.polyline.last_point()].occurrence;
+ const Point3 &first_p3 = path.polyline.first_point();
+ const Point3 &last_p3 = path.polyline.last_point();
+ Point first_p = Point(first_p3.x(), first_p3.y());
+ Point last_p = Point(last_p3.x(), last_p3.y());
+ ++point_occurrence[first_p].occurrence;
+ ++point_occurrence[last_p].occurrence;
if (path.role() == erOverhangPerimeter) {
- point_occurrence[path.polyline.first_point()].is_overhang = true;
- point_occurrence[path.polyline.last_point()].is_overhang = true;
+ point_occurrence[first_p].is_overhang = true;
+ point_occurrence[last_p].is_overhang = true;
}
}
diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp
index 419b5ab693..6dc1b8ad10 100644
--- a/src/libslic3r/Point.cpp
+++ b/src/libslic3r/Point.cpp
@@ -1,6 +1,7 @@
#include "Point.hpp"
#include "Line.hpp"
#include "MultiPoint.hpp"
+#include "Polyline.hpp"
#include "Int128.hpp"
#include "BoundingBox.hpp"
#include
@@ -300,4 +301,80 @@ int cross(const Vec2crd &v1, const Vec2crd &v2)
}
+// Point3 utility functions for ZAA (Z Anti-Aliasing)
+Polyline to_polyline(const Points &points) { return Polyline(points); }
+Polyline3 to_polyline(const Points3 &points) { return Polyline3(points); }
+
+Points to_points(const Points3 &points3) {
+ Points points2;
+ points2.reserve(points3.size());
+ for (const Point3 &pt : points3) {
+ points2.push_back(pt.to_point());
+ }
+ return points2;
+}
+
+// Point3 method implementations
+void Point3::rotate(double angle, const Point3 ¢er) {
+ Vec3crd diff = *this - center;
+ Point3 temp(diff.x(), diff.y(), diff.z());
+ temp.rotate(angle);
+ Vec3crd sum = temp + center;
+ *this = Point3(sum.x(), sum.y(), sum.z());
+}
+
+int Point3::nearest_point_index(const Points &points) const {
+ return this->to_point().nearest_point_index(points);
+}
+
+bool Point3::nearest_point(const Points &points, Point3* point) const {
+ Point pt2d;
+ bool result = this->to_point().nearest_point(points, &pt2d);
+ if (result && point) {
+ *point = Point3(pt2d, this->z());
+ }
+ return result;
+}
+
+double Point3::ccw(const Point3 &p1, const Point3 &p2) const {
+ return this->to_point().ccw(p1.to_point(), p2.to_point());
+}
+
+double Point3::ccw(const Line3 &line) const {
+ // Convert to 2D and use existing Point ccw implementation
+ Point a2d(line.a.x(), line.a.y());
+ Point b2d(line.b.x(), line.b.y());
+ return this->to_point().ccw(Line(a2d, b2d));
+}
+
+double Point3::ccw_angle(const Point3 &p1, const Point3 &p2) const {
+ return this->to_point().ccw_angle(p1.to_point(), p2.to_point());
+}
+
+Point3 Point3::projection_onto(const MultiPoint3 &poly) const {
+ // TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready
+ // For now, stub implementation
+ return *this;
+}
+
+Point3 Point3::projection_onto(const Line3 &line) const {
+ // Project in 2D plane and interpolate Z
+ Point pt2d = this->to_point();
+ Point line_a(line.a.x(), line.a.y());
+ Point line_b(line.b.x(), line.b.y());
+ Line line2d(line_a, line_b);
+ Point proj2d = pt2d.projection_onto(line2d);
+
+ // Interpolate Z coordinate
+ double line_len = line.length();
+ if (line_len < EPSILON) {
+ return Point3(proj2d, line.a.z());
+ }
+ double dist_from_a = (proj2d - line_a).cast().norm();
+ double t = dist_from_a / line_len;
+ t = std::clamp(t, 0.0, 1.0);
+ coord_t z = coord_t(line.a.z() + t * (line.b.z() - line.a.z()));
+ return Point3(proj2d, z);
+}
+
}
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index 20347764b5..ca06371da8 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -17,8 +17,13 @@ namespace Slic3r {
class BoundingBox;
class BoundingBoxf;
class Line;
+class Line3;
class MultiPoint;
+class MultiPoint3;
class Point;
+class Point3;
+class Polyline;
+class Polyline3;
using Vector = Point;
// Base template for eigen derived vectors
@@ -51,7 +56,7 @@ using Vec4d = Eigen::Matrix;
using Points = std::vector;
using PointPtrs = std::vector;
using PointConstPtrs = std::vector;
-using Points3 = std::vector;
+using Points3 = std::vector;
using Pointfs = std::vector;
using Vec2ds = std::vector;
using Pointf3s = std::vector;
@@ -72,6 +77,11 @@ using Transform2d = Eigen::Transform;
using Transform3d = Eigen::Transform;
+// Utility functions for Point/Polyline conversion
+Polyline to_polyline(const Points &points);
+Polyline3 to_polyline(const Points3 &points);
+Points to_points(const Points3 &points);
+
// I don't know why Eigen::Transform::Identity() return a const object...
template Transform identity() { return Transform::Identity(); }
inline const auto &identity3f = identity<3, float>;
@@ -257,6 +267,116 @@ inline Point operator* (const Point& l, const double& r)
return { coord_t(l.x() * r), coord_t(l.y() * r) };
}
+// Point3 class - 3D point with Z coordinate for non-planar printing (ZAA)
+class Point3 : public Vec3crd {
+public:
+ using coord_type = coord_t;
+
+ Point3() : Vec3crd(0, 0, 0) {}
+ Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
+ Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
+ // Use lrint for consistency with BambuStudio's Point class
+ Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(lrint(x)), coord_t(lrint(y)), coord_t(lrint(z))) {}
+ Point3(const Point3 &rhs) { *this = rhs; }
+ explicit Point3(const Point &rhs, coord_t z = 0) : Vec3crd(rhs.x(), rhs.y(), z) {}
+ explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {}
+
+ static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) {
+ return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z)));
+ }
+ static Point3 new_scale(const Vec3d &v) {
+ return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z())));
+ }
+ static Point3 new_scale(const Vec3f &v) {
+ return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z())));
+ }
+
+ // Assignment operator for Eigen expressions
+ template
+ Point3& operator=(const Eigen::MatrixBase &other)
+ {
+ this->Vec3crd::operator=(other);
+ return *this;
+ }
+
+ Point3& operator+=(const Point3& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); this->z() += rhs.z(); return *this; }
+ Point3& operator-=(const Point3& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); this->z() -= rhs.z(); return *this; }
+ Point3& operator*=(const double &rhs) {
+ this->x() = coord_t(this->x() * rhs);
+ this->y() = coord_t(this->y() * rhs);
+ this->z() = coord_t(this->z() * rhs);
+ return *this;
+ }
+ Point3 operator*(const double &rhs) const { return Point3(this->x() * rhs, this->y() * rhs, this->z() * rhs); }
+
+ bool both_comp(const Point3 &rhs, const std::string& op) {
+ if (op == ">")
+ return this->x() > rhs.x() && this->y() > rhs.y();
+ else if (op == "<")
+ return this->x() < rhs.x() && this->y() < rhs.y();
+ return false;
+ }
+ bool any_comp(const Point3 &rhs, const std::string &op)
+ {
+ if (op == ">")
+ return this->x() > rhs.x() || this->y() > rhs.y();
+ else if (op == "<")
+ return this->x() < rhs.x() || this->y() < rhs.y();
+ return false;
+ }
+ bool any_comp(const coord_t val, const std::string &op)
+ {
+ if (op == ">")
+ return this->x() > val || this->y() > val;
+ else if (op == "<")
+ return this->x() < val || this->y() < val;
+ return false;
+ }
+
+ void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
+ void rotate(double cos_a, double sin_a) {
+ double cur_x = (double)this->x();
+ double cur_y = (double)this->y();
+ this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
+ this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
+ }
+ void rotate(double angle, const Point3 ¢er);
+
+ Point3 rotated(double angle) const { Point3 res(*this); res.rotate(angle); return res; }
+ Point3 rotated(double cos_a, double sin_a) const { Point3 res(*this); res.rotate(cos_a, sin_a); return res; }
+ Point3 rotated(double angle, const Point3 ¢er) const { Point3 res(*this); res.rotate(angle, center); return res; }
+ Point3 rotate_90_degree_ccw() const { return Point3(-this->y(), this->x(), this->z()); }
+
+ int nearest_point_index(const Points &points) const;
+ bool nearest_point(const Points &points, Point3* point) const;
+ double ccw(const Point3 &p1, const Point3 &p2) const;
+ double ccw(const Line3 &line) const;
+ double ccw_angle(const Point3 &p1, const Point3 &p2) const;
+ Point3 projection_onto(const MultiPoint3 &poly) const;
+ Point3 projection_onto(const Line3 &line) const;
+
+ // Convert to 2D Point by dropping Z coordinate
+ Point to_point() const {
+ return Point(this->x(), this->y());
+ }
+
+ double distance_to(const Point3 &point) const { return (point - *this).cast().norm(); }
+};
+
+// Utility function to convert Points3 to Points
+inline void append_points(Points &dst, const Points3 &src) {
+ std::transform(src.begin(), src.end(),
+ std::back_inserter(dst),
+ [](const Point3 &pt) {
+ return pt.to_point();
+ });
+}
+
+inline Point3 operator* (const Point3& l, const double& r)
+{
+ return { coord_t(l.x() * r), coord_t(l.y() * r), coord_t(l.z() * r) };
+}
+
inline std::ostream &operator<<(std::ostream &os, const Point &pt)
{
os << unscale_(pt.x()) << "," << unscale_(pt.y());
diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp
index ae8e67cd57..6b1c063f8b 100644
--- a/src/libslic3r/Polyline.cpp
+++ b/src/libslic3r/Polyline.cpp
@@ -684,4 +684,219 @@ Lines3 Polyline3::lines() const
return lines;
}
+// Polyline3 ZAA methods implementation
+Polyline Polyline3::to_polyline() const {
+ Polyline out;
+ out.points.reserve(this->points.size());
+ for (const Point3 &point : this->points) {
+ out.points.emplace_back(point.x(), point.y());
+ }
+ return out;
+}
+
+void Polyline3::clip_end(double distance) {
+ size_t remove_after_index = this->size();
+ while (distance > 0) {
+ Vec3d last_point = this->last_point().cast();
+ this->points.pop_back();
+ remove_after_index--;
+ if (this->points.empty()) {
+ this->fitting_result.clear();
+ return;
+ }
+ Vec3d v = this->last_point().cast() - last_point;
+ double lsqr = v.squaredNorm();
+ if (lsqr > distance * distance) {
+ Vec3d result = last_point + v * (distance / sqrt(lsqr));
+ this->points.emplace_back(Point3(coord_t(result.x()), coord_t(result.y()), coord_t(result.z())));
+ break;
+ }
+ distance -= sqrt(lsqr);
+ }
+
+ // Clear fitting result if it's affected
+ if (!fitting_result.empty()) {
+ while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index)
+ fitting_result.pop_back();
+ if (!fitting_result.empty()) {
+ fitting_result.back().end_point_index = this->points.size() - 1;
+ }
+ }
+}
+
+void Polyline3::simplify(double tolerance) {
+ this->points = MultiPoint3::_douglas_peucker(this->points, tolerance);
+ this->fitting_result.clear();
+}
+
+void Polyline3::simplify_by_fitting_arc(double tolerance) {
+ // For now, just use regular simplify
+ // Full ZAA implementation would use ArcFitter::do_arc_fitting_and_simplify
+ this->simplify(tolerance);
+}
+
+bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const
+{
+ if (index > this->size() - 1)
+ return false;
+
+ if (index == 0) {
+ p1->clear();
+ p1->append(this->first_point());
+ *p2 = *this;
+ } else if (index == this->size() - 1) {
+ p2->clear();
+ p2->append(this->last_point());
+ *p1 = *this;
+ } else {
+ // Split first part
+ p1->clear();
+ p1->points.reserve(index + 1);
+ p1->points.insert(p1->begin(), this->begin(), this->begin() + index + 1);
+ Point3 new_endpoint;
+ if (this->split_fitting_result_before_index(index, new_endpoint, p1->fitting_result))
+ p1->points.back() = new_endpoint;
+
+ // Split second part
+ p2->clear();
+ p2->points.reserve(this->size() - index);
+ p2->points.insert(p2->begin(), this->begin() + index, this->end());
+ Point3 new_startpoint;
+ if (this->split_fitting_result_after_index(index, new_startpoint, p2->fitting_result))
+ p2->points.front() = new_startpoint;
+ }
+ return true;
+}
+
+void Polyline3::append(const Point3& point) {
+ // Don't append if same as last point
+ if (!this->empty() && this->last_point() == point)
+ return;
+
+ this->points.push_back(point);
+ // Clear fitting result as structure changed
+ this->fitting_result.clear();
+}
+
+void Polyline3::append(const Polyline3 &src) {
+ if (!src.is_valid()) return;
+
+ if (this->points.empty()) {
+ this->points = src.points;
+ this->fitting_result = src.fitting_result;
+ } else {
+ // Append points
+ if (!src.points.empty() && !this->points.empty() && this->last_point() == src.points.front()) {
+ // Skip first point if it's the same as our last point
+ this->points.insert(this->points.end(), src.points.begin() + 1, src.points.end());
+ } else {
+ this->points.insert(this->points.end(), src.points.begin(), src.points.end());
+ }
+ // Note: Full arc fitting integration would merge fitting_result here
+ this->fitting_result.clear();
+ }
+}
+
+void Polyline3::append_before(const Point3& point) {
+ // Don't append if same as first point
+ if (!this->empty() && this->first_point() == point)
+ return;
+
+ this->points.insert(this->points.begin(), point);
+ // Clear fitting result as structure changed
+ this->fitting_result.clear();
+}
+
+void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const {
+ if (this->points.empty()) return;
+
+ // Check if the point is on the polyline
+ int index = this->find_point(point);
+ if (index != -1) {
+ // The split point is on the polyline
+ split_at_index(index, p1, p2);
+ point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point();
+ return;
+ }
+
+ // Find the line to split at
+ size_t line_idx = 0;
+ Point p = this->first_point().to_point();
+ double min = (p - point).cast().norm();
+ Lines3 lines = this->lines();
+ for (Lines3::const_iterator line = lines.begin(); line != lines.end(); ++line) {
+ Point p_tmp = point.projection_onto(line->to_line());
+ if ((p_tmp - point).cast().norm() < min) {
+ p = p_tmp;
+ min = (p - point).cast().norm();
+ line_idx = line - lines.begin();
+ }
+ }
+
+ // Judge whether the closest point is one vertex of polyline
+ index = this->find_point(p);
+ if (index != -1) {
+ this->split_at_index(index, p1, p2);
+ p1->append(Point3(point, p1->last_point().z()));
+ p2->append_before(Point3(point, p2->first_point().z()));
+ } else {
+ Polyline3 temp;
+ this->split_at_index(line_idx, p1, &temp);
+ p1->append(Point3(point, p1->last_point().z()));
+ this->split_at_index(line_idx + 1, &temp, p2);
+ p2->append_before(Point3(point, p2->first_point().z()));
+ }
+ point = p;
+}
+
+void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const {
+ Point p = point.to_point();
+ this->split_at(p, p1, p2);
+ point = Point3(p, point.z());
+}
+
+bool Polyline3::split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const {
+ if (this->points.empty()) return false;
+ if (length < 0 || length > this->length()) { return false; }
+
+ if (length < SCALED_EPSILON) {
+ p1->clear();
+ p1->append_before(this->first_point());
+ *p2 = *this;
+ } else if (is_approx(length, this->length(), SCALED_EPSILON)) {
+ p2->clear();
+ p2->append_before(this->last_point());
+ *p1 = *this;
+ } else {
+ // Find the line to split at
+ size_t line_idx = 0;
+ double acc_length = 0;
+ Point p = this->first_point().to_point();
+ for (const auto &l : this->lines()) {
+ p = l.b.to_point();
+
+ const double current_length = l.length();
+ if (acc_length + current_length >= length) {
+ p = lerp(l.a.to_point(), l.b.to_point(), (length - acc_length) / current_length);
+ break;
+ }
+ acc_length += current_length;
+ line_idx++;
+ }
+
+ // Judge whether the closest point is one vertex of polyline
+ int index = this->find_point(p);
+ if (index != -1) {
+ this->split_at_index(index, p1, p2);
+ } else {
+ Polyline3 temp;
+ this->split_at_index(line_idx, p1, &temp);
+ p1->append_before(Point3(p, p1->last_point().z()));
+ this->split_at_index(line_idx + 1, &temp, p2);
+ p2->append_before(Point3(p, p2->first_point().z()));
+ }
+ }
+ return true;
+}
+
}
diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp
index 20f94b0c39..79f2c2de8b 100644
--- a/src/libslic3r/Polyline.hpp
+++ b/src/libslic3r/Polyline.hpp
@@ -288,7 +288,81 @@ inline ThickPolylines to_thick_polylines(Polylines&& polylines, const coordf_t w
class Polyline3 : public MultiPoint3
{
public:
+ Polyline3() {}
+ explicit Polyline3(const Points3 &points) { this->points = points; }
+ explicit Polyline3(const Polyline &poly, coord_t z = 0) {
+ this->points.reserve(poly.points.size());
+ for (const Point &pt : poly.points) {
+ this->points.emplace_back(pt.x(), pt.y(), z);
+ }
+ }
+
virtual Lines3 lines() const;
+
+ // Convert to 2D Polyline by dropping Z coordinates
+ Polyline to_polyline() const;
+
+ // Clip the end of the polyline by a distance
+ void clip_end(double distance);
+
+ // Simplify polyline using Douglas-Peucker algorithm
+ void simplify(double tolerance);
+
+ // Simplify by arc fitting (for ZAA arc fitting support)
+ void simplify_by_fitting_arc(double tolerance);
+
+ // Reverse the polyline
+ using MultiPoint3::reverse;
+
+ // Split polyline at given index
+ bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const;
+
+ // Split polyline at a given point (2D)
+ void split_at(Point &point, Polyline3* p1, Polyline3* p2) const;
+
+ // Split polyline at a given point (3D)
+ void split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const;
+
+ // Split polyline at a given length
+ bool split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const;
+
+ // Append a single point
+ void append(const Point3& point);
+
+ // Append a 2D point (converted to 3D with z=0)
+ void append(const Point &point) {
+ this->append(Point3(point, 0));
+ }
+
+ // Append 2D points (converted to 3D with z=0)
+ void append(const Points &src) {
+ this->points.reserve(this->points.size() + src.size());
+ for (const Point &point : src) {
+ this->points.emplace_back(point.x(), point.y(), 0);
+ }
+ }
+
+ // Append another Polyline3
+ void append(const Polyline3& src);
+
+ // Append before (prepend)
+ void append_before(const Point3& point);
+
+ // Arc fitting support - fitting_result stores arc path data
+ // This is populated by simplify_by_fitting_arc()
+ // Uses the global PathFittingData from ArcFitter.hpp
+ std::vector fitting_result;
+
+private:
+ // Helper methods for split_at_index
+ bool split_fitting_result_before_index(size_t index, Point3 &new_endpoint, std::vector &result) const {
+ // Simplified stub - full implementation would handle arc fitting data
+ return false;
+ }
+ bool split_fitting_result_after_index(size_t index, Point3 &new_startpoint, std::vector &result) const {
+ // Simplified stub - full implementation would handle arc fitting data
+ return false;
+ }
};
typedef std::vector Polylines3;
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index d803a49dff..826cacf773 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -1000,7 +1000,9 @@ static std::vector s_Preset_print_options {
"exclude_object", "override_filament_scarf_seam_setting", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold",
"seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length",
"seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed", "seam_slope_gap", "precise_outer_wall",
- "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", "embedding_wall_into_infill" };
+ "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", "embedding_wall_into_infill",
+ // Z Anti-Aliasing (ZAA)
+ "zaa_enabled", "zaa_minimize_perimeter_height", "zaa_dont_alternate_fill_direction", "zaa_min_z", "zaa_region_disable", "ironing_expansion" };
static std::vector s_Preset_filament_options{/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "volumetric_speed_coefficients", "filament_type",
"filament_soluble", "filament_is_support", "filament_printable", "filament_scarf_seam_type", "filament_scarf_height",
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 69ad19e7ec..4b95d6fd41 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1963,6 +1963,17 @@ void Print::process(std::unordered_map* slice_time, bool
}
}
+ // Z-Contouring
+ for (PrintObject *obj : m_objects) {
+ bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->config().zaa_enabled;
+ if (need_contouring) {
+ obj->contour_z();
+ } else {
+ if (obj->set_started(posContouring))
+ obj->set_done(posContouring);
+ }
+ }
+
if (slice_time) {
start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc();
}
@@ -2010,6 +2021,8 @@ void Print::process(std::unordered_map* slice_time, bool
obj->set_done(posInfill);
if (obj->set_started(posIroning))
obj->set_done(posIroning);
+ if (obj->set_started(posContouring))
+ obj->set_done(posContouring);
if (obj->set_started(posSupportMaterial))
obj->set_done(posSupportMaterial);
if (obj->set_started(posDetectOverhangsForLift))
@@ -2020,6 +2033,9 @@ void Print::process(std::unordered_map* slice_time, bool
obj->make_perimeters();
obj->infill();
obj->ironing();
+ if (obj->config().zaa_enabled) {
+ obj->contour_z();
+ }
obj->generate_support_material();
obj->detect_overhangs_for_lift();
}
@@ -2426,7 +2442,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
- eloop.paths.back().polyline = loop.split_at_first_point();
+ eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
m_skirt.append(eloop);
if (Print::min_skirt_length > 0) {
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
@@ -2479,7 +2495,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
- eloop.paths.back().polyline = loop.split_at_first_point();
+ eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
object->m_skirt.append(std::move(eloop));
}
}
@@ -3741,7 +3757,8 @@ static void from_json(const json& j, Polyline& poly_line) {
}
static void from_json(const json& j, ExtrusionPath& extrusion_path) {
- extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE];
+ Polyline temp_polyline = j[JSON_EXTRUSION_POLYLINE];
+ extrusion_path.polyline = Polyline3(temp_polyline);
extrusion_path.overhang_degree = j[JSON_EXTRUSION_OVERHANG_DEGREE];
extrusion_path.curve_degree = j[JSON_EXTRUSION_CURVE_DEGREE];
extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM];
@@ -4578,8 +4595,9 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
paths.reserve(it->second.size());
for (auto &polyline : it->second) {
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]);
- path.polyline = polyline;
- for (auto &p : path.polyline.points) p += trans;
+ path.polyline = Polyline3(polyline);
+ Point3 trans3(trans, 0);
+ for (auto &p : path.polyline.points) p += trans3;
paths.push_back(path);
}
el.paths = std::move(paths);
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index fe048cd507..b8bd57e417 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -99,7 +99,7 @@ enum PrintStep {
enum PrintObjectStep {
posSlice, posPerimeters, posPrepareInfill,
- posInfill, posIroning, posSupportMaterial,
+ posInfill, posIroning, posContouring, posSupportMaterial,
// BBS
posDetectOverhangsForLift,
posSimplifyWall, posSimplifyInfill, posSimplifySupportPath,
@@ -547,6 +547,7 @@ class PrintObject : public PrintObjectBaseWithState paths;
for (float h = 0.f; h < height; h += layer_height) {
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_height);
- path.polyline = {minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner};
+ path.polyline = Polyline3(Points3{Point3(minCorner), Point3(maxCorner.x(), minCorner.y()), Point3(maxCorner), Point3(minCorner.x(), maxCorner.y()), Point3(minCorner)});
paths.push_back({path});
if (h == 0.f) { // add brim
ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, layer_height);
Point wtbminCorner = {minCorner - Point{bd, bd}};
Point wtbmaxCorner = {maxCorner + Point{bd, bd}};
- fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner};
+ fakeBrim.polyline = Polyline3(Points3{Point3(wtbminCorner), Point3(wtbmaxCorner.x(), wtbminCorner.y()), Point3(wtbmaxCorner), Point3(wtbminCorner.x(), wtbmaxCorner.y()), Point3(wtbminCorner)});
paths.back().push_back(fakeBrim);
}
}
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 8340f2c1e8..6b14f75e9a 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -3494,6 +3494,57 @@ void PrintConfigDef::init_fff_params()
def->mode = comDevelop;
def->set_default_value(new ConfigOptionFloat(45));
+ def = this->add("ironing_expansion", coFloat);
+ def->label = L("Ironing expansion");
+ def->category = L("Quality");
+ def->tooltip = L("Expand or contract the ironing area.");
+ def->sidetext = L("mm");
+ def->min = -100;
+ def->max = 100;
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionFloat(0));
+
+ def = this->add("zaa_region_disable", coBool);
+ def->label = L("Disable Z contouring for region");
+ def->category = L("Quality");
+ def->tooltip = L("Disable Z contouring for this specific region");
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionBool(false));
+
+ def = this->add("zaa_enabled", coBool);
+ def->label = L("Z contouring enabled");
+ def->category = L("Quality");
+ def->tooltip = L("Enable Z-layer contouring (aka Z-layer anti-aliasing)");
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionBool(false));
+
+ def = this->add("zaa_minimize_perimeter_height", coFloat);
+ def->label = L("Minimize wall height angle");
+ def->category = L("Quality");
+ def->tooltip = L("Reduce top surface perimeter heights to match height of edge for perimeters less than this angle. Set 0 to disable.");
+ def->sidetext = L("°");
+ def->min = 0;
+ def->max = 90;
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionFloat(35));
+
+ def = this->add("zaa_dont_alternate_fill_direction", coBool);
+ def->label = L("Don't alternate fill direction");
+ def->category = L("Quality");
+ def->tooltip = L("Disable alternating fill direction when using Z contouring");
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionBool(false));
+
+ def = this->add("zaa_min_z", coFloat);
+ def->label = L("Minimum z height");
+ def->category = L("Quality");
+ def->tooltip = L("Minimum z layer height. Also controls slicing plane");
+ def->sidetext = L("mm");
+ def->min = 0;
+ def->max = 100;
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionFloat(0.05));
+
def = this->add("layer_change_gcode", coString);
def->label = L("Layer change G-code");
def->tooltip = L("This gcode part is inserted at every layer change after lift z");
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 601a774aa3..9adbb2fc4a 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -932,6 +932,11 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, interlocking_boundary_avoidance))
((ConfigOptionInt, scarf_angle_threshold))
+ // Z Anti-Aliasing (aka Z Contouring)
+ ((ConfigOptionBool, zaa_enabled))
+ ((ConfigOptionBool, zaa_dont_alternate_fill_direction))
+ ((ConfigOptionFloat, zaa_min_z))
+
)
// This object is mapped to Perl as Slic3r::Config::PrintRegion.
@@ -989,6 +994,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, ironing_inset))
((ConfigOptionFloat, ironing_direction))
((ConfigOptionFloat, ironing_speed))
+ ((ConfigOptionFloat, ironing_expansion))
// Detect bridging perimeters
((ConfigOptionBool, detect_overhang_wall))
((ConfigOptionBool, smooth_speed_discontinuity_area))
@@ -1048,6 +1054,10 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, seam_slope_steps))
((ConfigOptionBool, seam_slope_inner_walls))
((ConfigOptionBool, embedding_wall_into_infill))
+
+ // Z Anti-Aliasing (aka Z Contouring)
+ ((ConfigOptionBool, zaa_region_disable))
+ ((ConfigOptionFloat, zaa_minimize_perimeter_height))
)
PRINT_CONFIG_CLASS_DEFINE(
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index c2cceeca5e..1fe1840f79 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -19,8 +19,13 @@
#include "Format/STL.hpp"
#include "InternalBridgeDetector.hpp"
#include "AABBTreeLines.hpp"
+#include "SLA/IndexedMesh.hpp"
#include
+#include
+#include
+#include
+#include
#include
#include
@@ -798,6 +803,46 @@ void PrintObject::ironing()
}
}
+void PrintObject::contour_z()
+{
+ if (!this->set_started(posContouring)) {
+ return;
+ }
+
+ m_print->set_status(40, L("Z contouring"));
+ BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
+
+ TriangleMesh mesh = this->m_model_object->raw_mesh();
+ if (m_model_object->instances.size() != 1) {
+ throw RuntimeError("ContourZ: unexpected number of instances");
+ }
+
+ m_model_object->instances.front()->transform_mesh(&mesh, true);
+ sla::IndexedMesh imesh(mesh);
+
+ std::mutex mtx;
+ size_t completed = 0;
+ tbb::parallel_for(
+ // Contouring starting with layer second layer to avoid build plate collision
+ tbb::blocked_range(1, m_layers.size()),
+ [&, this](const tbb::blocked_range& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); layer_idx++) {
+ m_print->throw_if_canceled();
+ m_layers[layer_idx]->make_contour_z(imesh);
+
+ std::scoped_lock lock(mtx);
+ completed++;
+ std::string msg = (boost::format("Z contoured layer %d/%d (%d%%)") % (completed) % m_layers.size() % int(double(completed) / m_layers.size() * 100)).str();
+ m_print->set_status(40, msg);
+ }
+ }
+ );
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - end";
+
+ this->set_done(posContouring);
+}
+
// BBS
void PrintObject::clear_overhangs_for_lift()
{
@@ -1383,15 +1428,15 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps
if (step == posPerimeters) {
- invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSimplifyWall, posSimplifyInfill });
+ invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posContouring, posSimplifyWall, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posPrepareInfill) {
- invalidated |= this->invalidate_steps({ posInfill, posIroning, posSimplifyWall, posSimplifyInfill });
+ invalidated |= this->invalidate_steps({ posInfill, posIroning, posContouring, posSimplifyWall, posSimplifyInfill });
} else if (step == posInfill) {
- invalidated |= this->invalidate_steps({ posIroning, posSimplifyInfill });
+ invalidated |= this->invalidate_steps({ posIroning, posContouring, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posSlice) {
- invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial, posSimplifyWall, posSimplifyInfill });
+ invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyWall, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
m_slicing_params.valid = false;
} else if (step == posSupportMaterial) {
diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp
index ba1b08db92..ff5cdef0ac 100644
--- a/src/libslic3r/PrintObjectSlice.cpp
+++ b/src/libslic3r/PrintObjectSlice.cpp
@@ -1,4 +1,5 @@
#include "ElephantFootCompensation.hpp"
+#include "Exception.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
@@ -34,6 +35,16 @@ LayerPtrs new_layers(
coordf_t lo = object_layers[i_layer];
coordf_t hi = object_layers[i_layer + 1];
coordf_t slice_z = 0.5 * (lo + hi);
+
+ // ZAA: Adjust slice plane if Z-contouring is enabled
+ if (print_object->config().zaa_enabled) {
+ coordf_t z_offset = print_object->config().zaa_min_z;
+ slice_z = lo + z_offset;
+ if (slice_z < lo || slice_z > hi) {
+ throw RuntimeError("Bad min Z value");
+ }
+ }
+
Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z);
out.emplace_back(layer);
if (prev != nullptr) {
diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp
index ce45376757..66fee73133 100644
--- a/src/libslic3r/ShortestPath.cpp
+++ b/src/libslic3r/ShortestPath.cpp
@@ -1000,7 +1000,7 @@ std::vector> chain_segments_greedy2(SegmentEndPointFunc
std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near)
{
- auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
+ auto segment_end_point = [&entities](size_t idx, bool first_point) -> Point { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector> out = chain_segments_greedy_constrained_reversals(segment_end_point, could_reverse, entities.size(), start_near);
for (std::pair &segment : out) {
@@ -1028,6 +1028,11 @@ void reorder_extrusion_entities(std::vector &entities, const s
entities.swap(out);
}
+void chain_and_reorder_extrusion_entities(std::vector &entities, const Point &start_near)
+{
+ chain_and_reorder_extrusion_entities(entities, &start_near);
+}
+
void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near)
{
// this function crashes if there are empty elements in entities
@@ -1038,7 +1043,7 @@ void chain_and_reorder_extrusion_entities(std::vector &entitie
std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near)
{
- auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
+ auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> Point { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
return chain_segments_greedy(segment_end_point, extrusion_paths.size(), start_near);
}
diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp
index 5a34ef23c1..eb42421d23 100644
--- a/src/libslic3r/ShortestPath.hpp
+++ b/src/libslic3r/ShortestPath.hpp
@@ -17,6 +17,7 @@ std::vector chain_expolygons(const ExPolygons &input_exploy);
std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr);
void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain);
+void chain_and_reorder_extrusion_entities(std::vector &entities, const Point &start_near);
void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr);
std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr);
diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp
index ff3d5dcba0..28792e1d0c 100644
--- a/src/libslic3r/Support/SupportCommon.cpp
+++ b/src/libslic3r/Support/SupportCommon.cpp
@@ -1230,7 +1230,7 @@ static void modulate_extrusion_by_overlapping_layers(
for (ExtrusionEntity *ee : extrusions_in_out) {
ExtrusionPath *path = dynamic_cast(ee);
assert(path != nullptr);
- polylines.emplace_back(Polyline(std::move(path->polyline)));
+ polylines.emplace_back(path->polyline.to_polyline());
path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back()));
delete path;
}
@@ -1355,9 +1355,10 @@ static void modulate_extrusion_by_overlapping_layers(
if (! path->polyline.points.empty())
path->polyline.points.pop_back();
// Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time.
- path->polyline.append(std::move(frag_polyline));
+ path->polyline.append(Polyline3(std::move(frag_polyline)));
frag_polyline.points.clear();
- pt_current = path->polyline.points.back();
+ const Point3 &pt_back3 = path->polyline.points.back();
+ pt_current = Point(pt_back3.x(), pt_back3.y());
if (pt_current == pt_end) {
// End of the path.
break;
diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp
index 510a3cd0e2..841a6fcac4 100644
--- a/src/libslic3r/Support/SupportMaterial.cpp
+++ b/src/libslic3r/Support/SupportMaterial.cpp
@@ -1181,7 +1181,9 @@ namespace SupportMaterialInternal {
// This is a complete loop.
// Add the outer contour first.
Polygon poly;
- poly.points = ep.polyline.points;
+ // Convert Points3 to Points
+ for (const Point3 &p3 : ep.polyline.points)
+ poly.points.emplace_back(p3.x(), p3.y());
poly.points.pop_back();
if (poly.area() < 0)
poly.reverse();
diff --git a/src/libslic3r/VariableWidth.cpp b/src/libslic3r/VariableWidth.cpp
index d1cf8759df..a0ee71abfb 100644
--- a/src/libslic3r/VariableWidth.cpp
+++ b/src/libslic3r/VariableWidth.cpp
@@ -16,11 +16,11 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
if (line_len < SCALED_EPSILON) {
// The line is so tiny that we don't care about its width when we connect it to another line.
if (!path.empty())
- path.polyline.points.back() = line.b; // If the variable path is non-empty, connect this tiny line to it.
+ path.polyline.points.back() = Point3(line.b); // If the variable path is non-empty, connect this tiny line to it.
else if (i + 1 < (int)lines.size()) // If there is at least one following line, connect this tiny line to it.
lines[i + 1].a = line.a;
else if (!multi_path.paths.empty())
- multi_path.paths.back().polyline.points.back() = line.b; // Connect this tiny line to the last finished path.
+ multi_path.paths.back().polyline.points.back() = Point3(line.b); // Connect this tiny line to the last finished path.
// If any of the above isn't satisfied, then remove this tiny line.
continue;
@@ -65,8 +65,8 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
const double w = fmax(line.a_width, line.b_width);
const Flow new_flow = (role == erOverhangPerimeter && flow.bridge()) ? flow : flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI));
if (path.polyline.points.empty()) {
- path.polyline.append(line.a);
- path.polyline.append(line.b);
+ path.polyline.append(Point3(line.a));
+ path.polyline.append(Point3(line.b));
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
#ifdef SLIC3R_DEBUG
@@ -81,7 +81,7 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
if (thickness_delta <= merge_tolerance) {
// the width difference between this line and the current flow
// (of the previous line) width is within the accepted tolerance
- path.polyline.append(line.b);
+ path.polyline.append(Point3(line.b));
} else {
// we need to initialize a new line
multi_path.paths.emplace_back(std::move(path));
@@ -128,9 +128,9 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t
for (int idx = start_index; idx < i; idx++) {
length += lines[idx].length();
sum += lines[idx].length() * 0.5 * (lines[idx].a_width + lines[idx].b_width);
- path.polyline.append(lines[idx].a);
+ path.polyline.append(Point3(lines[idx].a));
}
- path.polyline.append(lines[i].a);
+ path.polyline.append(Point3(lines[i].a));
if (length > SCALED_EPSILON) {
double w = sum / length;
Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI));
@@ -195,9 +195,9 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t
for (int idx = start_index; idx < final_size; idx++) {
length += lines[idx].length();
sum += lines[idx].length() * (lines[idx].a_width + lines[idx].b_width) * 0.5;
- path.polyline.append(lines[idx].a);
+ path.polyline.append(Point3(lines[idx].a));
}
- path.polyline.append(lines[final_size - 1].b);
+ path.polyline.append(Point3(lines[final_size - 1].b));
if (length > SCALED_EPSILON) {
double w = sum / length;
Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI));
diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in
index 109169214b..b63da1a8b2 100644
--- a/src/libslic3r/libslic3r_version.h.in
+++ b/src/libslic3r/libslic3r_version.h.in
@@ -4,6 +4,7 @@
#define SLIC3R_APP_NAME "@SLIC3R_APP_NAME@"
#define SLIC3R_APP_KEY "@SLIC3R_APP_KEY@"
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
+#define ZAA_VERSION "@ZAA_VERSION@"
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
#define SLIC3R_BUILD_TIME "@SLIC3R_BUILD_TIME@"
#define SLIC3R_COMPILE_VERSION "@SLIC3R_COMPILE_VERSION@"
diff --git a/src/minilzo/CMakeLists.txt b/src/minilzo/CMakeLists.txt
index c5122ccf0f..5ab2128c32 100644
--- a/src/minilzo/CMakeLists.txt
+++ b/src/minilzo/CMakeLists.txt
@@ -1,5 +1,5 @@
project(minilzo)
-cmake_minimum_required(VERSION 2.6)
+cmake_minimum_required(VERSION 3.5)
add_library(minilzo INTERFACE)
diff --git a/src/miniz/CMakeLists.txt b/src/miniz/CMakeLists.txt
index 04d562b764..3beefa3921 100644
--- a/src/miniz/CMakeLists.txt
+++ b/src/miniz/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(miniz)
add_library(miniz INTERFACE)
diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt
index ab9aba9afa..310976eb22 100644
--- a/src/qhull/CMakeLists.txt
+++ b/src/qhull/CMakeLists.txt
@@ -28,7 +28,7 @@ endif()
else(Qhull_FOUND)
project(qhull)
-cmake_minimum_required(VERSION 2.6)
+cmake_minimum_required(VERSION 3.5)
# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri
set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c
diff --git a/src/semver/CMakeLists.txt b/src/semver/CMakeLists.txt
index 4b61a7456e..59f12a4ca5 100644
--- a/src/semver/CMakeLists.txt
+++ b/src/semver/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(semver)
add_library(semver STATIC
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index e4c627004e..ec5cdd8658 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -2911,13 +2911,13 @@ void _3DScene::extrusionentity_to_verts(const Polyline &polyline, float width, f
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
{
- extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume);
+ extrusionentity_to_verts(extrusion_path.polyline.to_polyline(), extrusion_path.width, extrusion_path.height, print_z, volume);
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume)
{
- Polyline polyline = extrusion_path.polyline;
+ Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
Lines lines = polyline.lines();
@@ -2933,7 +2933,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, flo
std::vector widths;
std::vector heights;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) {
- Polyline polyline = extrusion_path.polyline;
+ Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
Lines lines_this = polyline.lines();
@@ -2951,7 +2951,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_mult
std::vector widths;
std::vector heights;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) {
- Polyline polyline = extrusion_path.polyline;
+ Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
Lines lines_this = polyline.lines();
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index cefff181c5..5567240d1c 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -2666,6 +2666,20 @@ void GUI_App::init_single_instance_checker(const std::string &name, const std::s
m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path));
}
+bool GUI_App::CallOnInit()
+{
+ // Override wxApp::CallOnInit to catch exceptions from ~wxMacAutoreleasePool
+ try {
+ return wxApp::CallOnInit();
+ } catch (const std::exception& e) {
+ BOOST_LOG_TRIVIAL(fatal) << "Exception in CallOnInit: " << e.what();
+ return false;
+ } catch (...) {
+ // The app was initialized, just the autorelease pool cleanup threw.
+ // Return true to let the app continue.
+ return m_initialized;
+ }
+}
#ifdef __APPLE__
void GUI_App::MacPowerCallBack(void* refcon, io_service_t service, natural_t messageType, void * messageArgument)
@@ -2735,7 +2749,11 @@ bool GUI_App::OnInit()
return on_init_inner();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(fatal) << "OnInit Got Fatal error: " << e.what();
- generic_exception_handle();
+ flush_logs();
+ return false;
+ } catch (...) {
+ BOOST_LOG_TRIVIAL(fatal) << "OnInit caught non-std exception";
+ flush_logs();
return false;
}
}
@@ -3456,7 +3474,15 @@ bool GUI_App::on_init_network(bool try_backup)
//std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
std::string data_directory = data_dir();
- m_agent = new Slic3r::NetworkAgent(data_directory);
+ try {
+ m_agent = new Slic3r::NetworkAgent(data_directory);
+ } catch (const std::exception& e) {
+ BOOST_LOG_TRIVIAL(error) << "Failed to create network agent: " << e.what();
+ m_agent = nullptr;
+ } catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "Failed to create network agent: unknown exception (code signing?)";
+ m_agent = nullptr;
+ }
if (!m_device_manager)
m_device_manager = new Slic3r::DeviceManager(m_agent);
@@ -5613,6 +5639,7 @@ std::string GUI_App::format_display_version()
else
++j;
}
+ version_display += " / ZAA v" + std::string(ZAA_VERSION);
return version_display;
}
@@ -6494,7 +6521,8 @@ void GUI_App::update_mode()
}
void GUI_App::update_internal_development() {
- mainframe->m_webview->update_mode();
+ if (mainframe->m_webview)
+ mainframe->m_webview->update_mode();
}
void GUI_App::show_ip_address_enter_dialog(wxString title)
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index cae87af8f2..dfd852c0f5 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -352,6 +352,7 @@ class GUI_App : public wxApp
void on_start_subscribe_again(std::string dev_id);
std::string get_local_models_path();
bool OnInit() override;
+ bool CallOnInit() override;
int OnExit() override;
bool initialized() const { return m_initialized; }
inline bool is_enable_multi_machine() { return this->app_config&& this->app_config->get("enable_multi_machine") == "true"; }
diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp
index 2584c2fc77..da2e357e94 100644
--- a/src/slic3r/GUI/GUI_Factories.cpp
+++ b/src/slic3r/GUI/GUI_Factories.cpp
@@ -99,7 +99,9 @@ std::map> SettingsFactory::OBJECT_C
// todo multi_extruders: Does the following need to be modified?
std::map> SettingsFactory::PART_CATEGORY_SETTINGS=
- {{L("Quality"), {{"ironing_type", "", 8}, {"ironing_flow", "", 9}, {"ironing_spacing", "", 10}, {"ironing_inset", "", 11}, {"ironing_speed", "", 12}, {"ironing_direction", "",13}
+ {{L("Quality"), {{"ironing_type", "", 8}, {"ironing_flow", "", 9}, {"ironing_spacing", "", 10}, {"ironing_inset", "", 11}, {"ironing_speed", "", 12}, {"ironing_direction", "",13},
+ {"ironing_expansion", "", 14},
+ {"zaa_enabled", "", 1}, {"zaa_region_disable", "", 2}, {"zaa_minimize_perimeter_height", "", 3}, {"zaa_dont_alternate_fill_direction", "", 4}, {"zaa_min_z", "", 5}
}},
{ L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", "",1},{"top_shell_thickness", "",1},
{"bottom_shell_layers", "",1}, {"bottom_shell_thickness", "",1}, {"sparse_infill_density", "",1},
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 50d4ee26e3..0c49aa99ee 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -1,4 +1,7 @@
#include "MainFrame.hpp"
+
+#include
+#include
#include "GLToolbar.hpp"
#include
#include
@@ -21,6 +24,7 @@
#include "libslic3r/Polygon.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Utils.hpp"
#include "Tab.hpp"
#include "ProgressStatusBar.hpp"
@@ -2639,6 +2643,20 @@ void MainFrame::init_menubar_as_editor()
open_recent_project(file_id, filename);
}, wxID_FILE1, wxID_FILE1 + 49); // [5050, 5100)
+ std::vector non_planar_projects;
+ auto nonplanar_dir = resources_dir() + "/nonplanar";
+ if (fs::exists(nonplanar_dir) && fs::is_directory(nonplanar_dir)) {
+ for (auto &&entry : fs::directory_iterator(nonplanar_dir)) {
+ if (fs::is_regular_file(entry) && entry.path().extension() == ".3mf") {
+ non_planar_projects.push_back(entry.path().string());
+ }
+ }
+ }
+ std::sort(non_planar_projects.begin(), non_planar_projects.end());
+ for (auto &&path : non_planar_projects) {
+ m_recent_projects.AddFileToHistory(from_u8(path));
+ }
+
std::vector recent_projects = wxGetApp().app_config->get_recent_projects();
std::reverse(recent_projects.begin(), recent_projects.end());
for (const std::string& project : recent_projects)
diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp
index 6030f4e527..2a8f5e1034 100644
--- a/src/slic3r/GUI/OptionsGroup.cpp
+++ b/src/slic3r/GUI/OptionsGroup.cpp
@@ -634,7 +634,7 @@ void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::
Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/)
{
if (!m_config->has(opt_key)) {
- std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n";
+ // Option not in config — may be newly added (e.g. ZAA options)
}
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index d4cc415ce7..4ac5bd902a 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -3891,7 +3891,7 @@ static std::vector get_search_inputs(ConfigOptionMode mode)
auto& tabs_list = wxGetApp().tabs_list;
auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
for (auto tab : tabs_list)
- if (tab->supports_printer_technology(print_tech))
+ if (tab && tab->supports_printer_technology(print_tech) && tab->get_config())
ret.emplace_back(Search::InputInfo {tab->get_config(), tab->type(), mode});
return ret;
@@ -3915,7 +3915,9 @@ void Sidebar::update_mode()
//obj_list()->get_sizer()->Show(m_mode > comSimple);
obj_list()->unselect_objects();
- obj_list()->update_selections();
+ // Guard: during startup the 3D canvas selection may not be fully initialized
+ if (wxGetApp().initialized())
+ obj_list()->update_selections();
// obj_list()->update_object_menu();
Layout();
@@ -12588,12 +12590,13 @@ void Plater::priv::set_project_name(const wxString& project_name)
{
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << __LINE__ << " project is:" << project_name;
m_project_name = project_name;
+ wxString name = project_name + " - BambuStudio-ZAA";
//update topbar title
#ifdef __WINDOWS__
- wxGetApp().mainframe->SetTitle(m_project_name + " - BambuStudio");
- wxGetApp().mainframe->topbar()->SetTitle(m_project_name);
+ wxGetApp().mainframe->SetTitle(name);
+ wxGetApp().mainframe->topbar()->SetTitle(name);
#else
- wxGetApp().mainframe->SetTitle(m_project_name);
+ wxGetApp().mainframe->SetTitle(name);
if (!m_project_name.IsEmpty())
wxGetApp().mainframe->update_title_colour_after_set_title();
#endif
diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp
index cc5ef21934..ee46aec1d6 100644
--- a/src/slic3r/GUI/Search.cpp
+++ b/src/slic3r/GUI/Search.cpp
@@ -70,6 +70,7 @@ static std::string get_key(const std::string &opt_key, Preset::Type type) { retu
void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
+ if (!config) return;
auto emplace = [this, type](const std::string key, const wxString &label) {
const GroupAndCategory &gc = groups_and_categories[key];
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return;
@@ -89,13 +90,19 @@ void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type ty
};
for (std::string opt_key : config->keys()) {
- const ConfigOptionDef &opt = config->def()->options.at(opt_key);
+ auto def_it = config->def()->options.find(opt_key);
+ if (def_it == config->def()->options.end()) {
+ continue;
+ }
+ const ConfigOptionDef &opt = def_it->second;
if (opt.mode > mode) continue;
int cnt = 0;
- if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area")
- switch (config->option(opt_key)->type()) {
+ if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area") {
+ const ConfigOption *opt_ptr = config->option(opt_key);
+ if (!opt_ptr) continue;
+ switch (opt_ptr->type()) {
case coInts: change_opt_key(opt_key, config, cnt); break;
case coBools: change_opt_key(opt_key, config, cnt); break;
case coFloats: change_opt_key(opt_key, config, cnt); break;
@@ -106,6 +113,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type ty
case coEnums: change_opt_key(opt_key, config, cnt); break;
default: break;
}
+ }
if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.find(opt_key) != filament_options_with_variant.end())
opt_key += "#0";
diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp
index 2c2cf8f6ff..5738a71032 100644
--- a/src/slic3r/GUI/SelectMachine.cpp
+++ b/src/slic3r/GUI/SelectMachine.cpp
@@ -3354,7 +3354,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
show_status(PrintDialogStatus::PrintStatusNoSdcard);
return;
}
- if (wxGetApp().preset_bundle->filament_presets.size() > 16 && m_print_type != PrintFromType::FROM_SDCARD_VIEW) {
+ if (wxGetApp().preset_bundle->filament_presets.size() > (16 + obj_->vt_slot.size()) && m_print_type != PrintFromType::FROM_SDCARD_VIEW) {
if (!obj_->is_enable_ams_np && !obj_->is_enable_np)
{
show_status(PrintDialogStatus::PrintStatusColorQuantityExceed);
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 6dbc23f062..f85c7a6c3c 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -2676,6 +2676,14 @@ void TabPrint::build()
optgroup->append_single_option_line("ironing_inset");
optgroup->append_single_option_line("ironing_direction");
+ optgroup = page->new_optgroup("Z Contouring", L"param_advanced");
+ optgroup->append_single_option_line("zaa_enabled");
+ optgroup->append_single_option_line("zaa_region_disable");
+ optgroup->append_single_option_line("zaa_minimize_perimeter_height");
+ optgroup->append_single_option_line("zaa_dont_alternate_fill_direction");
+ optgroup->append_single_option_line("zaa_min_z");
+ optgroup->append_single_option_line("ironing_expansion");
+
optgroup = page->new_optgroup(L("Wall generator"), L"param_wall");
optgroup->append_single_option_line("wall_generator", "wall-generator");
optgroup->append_single_option_line("wall_transition_angle");
diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp
index 6e5b159601..45e349cdab 100644
--- a/src/slic3r/Utils/bambu_networking.hpp
+++ b/src/slic3r/Utils/bambu_networking.hpp
@@ -97,7 +97,7 @@ namespace BBL {
#define BAMBU_NETWORK_LIBRARY "bambu_networking"
#define BAMBU_NETWORK_AGENT_NAME "bambu_network_agent"
-#define BAMBU_NETWORK_AGENT_VERSION "02.05.01.52"
+#define BAMBU_NETWORK_AGENT_VERSION "02.05.00.52"
//iot preset type strings
#define IOT_PRINTER_TYPE_STRING "printer"
diff --git a/tests/cpp17/CMakeLists.txt b/tests/cpp17/CMakeLists.txt
index 4e151ecbf0..d535f2083f 100644
--- a/tests/cpp17/CMakeLists.txt
+++ b/tests/cpp17/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
project(Cpp17Test)
diff --git a/version.inc b/version.inc
index fd7bbc6943..29e25723fc 100644
--- a/version.inc
+++ b/version.inc
@@ -13,7 +13,8 @@ endif()
# The build_version should start from 50 in master branch
-set(SLIC3R_VERSION "02.05.01.52")
+set(SLIC3R_VERSION "02.05.00.52")
+set(ZAA_VERSION "1.0.3")
string(REPLACE "." "," SLIC3R_COMMA_SEPARATED_VERSION ${SLIC3R_VERSION})
set(SLIC3R_COMMA_SEPARATED_VERSION "${SLIC3R_COMMA_SEPARATED_VERSION}")