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}")