Compare commits

..

6 Commits

Author SHA1 Message Date
Erik GS
f2d10a0b57 [Profiles] Fix bed_exclude_area excluding the whole bed on Anycubic Kobra 3 (#10914) 2025-10-03 01:33:44 +08:00
Heiko Liebscher
d1b6ebcd06 update de for 2.3.1 after update locale (#10912)
update de
2025-10-03 01:33:40 +08:00
SoftFever
37c5fcd908 update profile version 2025-09-29 22:58:14 +08:00
SoftFever
cde79fc655 update locale 2025-09-29 22:16:14 +08:00
SoftFever
eba242953b [Profile]add missing cover image 2025-09-29 21:49:20 +08:00
SoftFever
7edeb8b277 Update version to 2.3.1 2025-09-28 22:12:44 +08:00
6957 changed files with 206072 additions and 1821503 deletions

View File

@@ -1,19 +0,0 @@
---
allowed-tools: Bash(git checkout --branch:*), Bash(git add:*), Bash(git status:*), Bash(git push:*), Bash(git commit:*), Bash(gh pr create:*)
description: Commit, push, and open a PR
---
## Context
- Current git status: !`git status`
- Current git diff (staged and unstaged changes): !`git diff HEAD`
- Current branch: !`git branch --show-current`
## Your task
Based on the above changes:
1. Create a new branch if on main
2. Create a single commit with an appropriate message
3. Push the branch to origin
4. Create a pull request using `gh pr create`
5. You have the capability to call multiple tools in a single response. You MUST do all of the above in a single message. Do not use any other tools or do anything else. Do not send any other text or messages besides these tool calls.

View File

@@ -1,38 +0,0 @@
---
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
description: Find duplicate GitHub issues
---
Find up to 3 likely duplicate issues for a given GitHub issue.
To do this, follow these steps precisely:
1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
Notes (be sure to tell this to your agents, too):
- Use `gh` to interact with Github, rather than web fetch
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
- Make a todo list first
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
---
Found 3 possible duplicate issues:
1. <link to issue>
2. <link to issue>
3. <link to issue>
This issue will be automatically closed as a duplicate in 3 days.
- If your issue is a duplicate, please close it and 👍 the existing issue instead
- To prevent auto-closure, add a comment or 👎 this comment
🤖 OrcaSlicer bot
---

View File

@@ -1,40 +0,0 @@
---
allowed-tools: Bash(gh issue list:*), Bash(gh issue view:*), Bash(gh issue edit:*), TodoWrite
description: Triage GitHub issues and label critical ones for oncall
---
You're an oncall triage assistant for GitHub issues. Your task is to identify critical issues that require immediate oncall attention and apply the "oncall" label.
Repository: OrcaSlicer/OrcaSlicer
Task overview:
1. First, get all open bugs updated in the last 3 days with at least 50 engagements:
```bash
gh issue list --repo OrcaSlicer/OrcaSlicer --state open --label bug --limit 1000 --json number,title,updatedAt,comments,reactions | jq -r '.[] | select((.updatedAt >= (now - 259200 | strftime("%Y-%m-%dT%H:%M:%SZ"))) and ((.comments | length) + ([.reactions[].content] | length) >= 50)) | "\(.number)"'
```
2. Save the list of issue numbers and create a TODO list with ALL of them. This ensures you process every single one.
3. For each issue in your TODO list:
- Use `gh issue view <number> --repo OrcaSlicer/OrcaSlicer --json title,body,labels,comments` to get full details
- Read and understand the full issue content and comments to determine actual user impact
- Evaluate: Is this truly blocking users from using Claude Code?
- Consider: "crash", "stuck", "frozen", "hang", "unresponsive", "cannot use", "blocked", "broken"
- Does it prevent core functionality? Can users work around it?
- Be conservative - only flag issues that truly prevent users from getting work done
4. For issues that are truly blocking and don't already have the "oncall" label:
- Use `gh issue edit <number> --repo OrcaSlicer/OrcaSlicer --add-label "oncall"`
- Mark the issue as complete in your TODO list
5. After processing all issues, provide a summary:
- List each issue number that received the "oncall" label
- Include the issue title and brief reason why it qualified
- If no issues qualified, state that clearly
Important:
- Process ALL issues in your TODO list systematically
- Don't post any comments to issues
- Only add the "oncall" label, never remove it
- Use individual `gh issue view` commands instead of bash for loops to avoid approval prompts

View File

@@ -17,6 +17,13 @@
"customizations": {
"vscode": {
"settings": {
"cmake.configureArgs": [
"-DSLIC3R_GTK=3",
"-DBBL_RELEASE_TO_PUBLIC=1",
"-DBBL_INTERNAL_TESTING=0",
"-DSLIC3R_STATIC=1",
"-DCMAKE_PREFIX_PATH=${workspaceFolder}/deps/build/destdir/usr/local"
]
},
// Add the IDs of extensions you want installed when the container is created.

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = OrcaSlicer
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = latest
PROJECT_NUMBER = 1.6.3
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -68,7 +68,7 @@ PROJECT_LOGO = ./resources/images/OrcaSlicer_32px.png
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = .
OUTPUT_DIRECTORY = ../
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
@@ -1059,11 +1059,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = */deps/*
EXCLUDE_PATTERNS = */build/*
EXCLUDE_PATTERNS = */deps_src/*
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1290,7 +1286,7 @@ GENERATE_HTML = YES
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = internal_docs
HTML_OUTPUT = OrcaSlicer_Dev_Document
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
@@ -1569,7 +1565,7 @@ TOC_EXPAND = NO
# protocol see https://www.sitemaps.org
# This tag requires that the tag GENERATE_HTML is set to YES.
SITEMAP_URL = internals.orcaslicer.com
SITEMAP_URL =
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that

View File

@@ -8,7 +8,7 @@ body:
**Thank you for using Orca Slicer and wanting to report a bug.**
Please note that this is not the place to make feature requests or ask for help.
For this, please use the [Feature request](https://github.com/OrcaSlicer/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=feature_request.yml) issue type or you can discuss your idea on our [Discord server](https://discord.gg/P4VE9UY9gJ) with others.
For this, please use the [Feature request](https://github.com/SoftFever/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=feature_request.yml) issue type or you can discuss your idea on our [Discord server](https://discord.gg/P4VE9UY9gJ) with others.
Before filing, please check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment.
- type: checkboxes

View File

@@ -4,5 +4,5 @@ contact_links:
url: https://discord.gg/P4VE9UY9gJ
about: Please ask and answer support "how do I?"questions here.
- name: Discussion Forum
url: https://github.com/OrcaSlicer/OrcaSlicer/discussions
url: https://github.com/SoftFever/OrcaSlicer/discussions
about: Please raise ideas and feature suggestions here.

View File

@@ -8,7 +8,7 @@ body:
Thanks for taking the time to fill out this feature request!
If your idea is still at the formulation stage, or you're not sure it would
be useful to many users, you can raise it as a discussion topic under [Ideas](https://github.com/OrcaSlicer/OrcaSlicer/discussions/categories/ideas)
be useful to many users, you can raise it as a discussion topic under [Ideas](https://github.com/SoftFever/OrcaSlicer/discussions/categories/ideas)
or you can raise it on the [Discord server](https://discord.gg/P4VE9UY9gJ).
- type: checkboxes
attributes:

View File

@@ -1,11 +0,0 @@
name: 'Apt Install Deps'
description: 'Install the packages needed for building and deploying'
runs:
using: 'composite'
steps:
- name: Install dependencies from build_linux.sh
shell: bash
run: |
mkdir -p ${{ github.workspace }}/deps/build/destdir
sudo env "ORCA_UPDATER_SIG_KEY=$ORCA_UPDATER_SIG_KEY" ./build_linux.sh -ur
sudo chown $USER -R ./

View File

@@ -14,9 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Assign the user or unassign stale assignments
# Note: v3.0.0 is broken (dist/index.mjs vs action.yml expects index.js)
# See: https://github.com/takanome-dev/assign-issue-action/issues/426
uses: takanome-dev/assign-issue-action@v2.2
uses: takanome-dev/assign-issue-action@v2.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
maintainers: 'noisyfox,softfever'

View File

@@ -1,31 +0,0 @@
name: Auto-close duplicate issues
description: Auto-closes issues that are duplicates of existing issues
on:
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
jobs:
auto-close-duplicates:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Auto-close duplicate issues
run: bun run scripts/auto-close-duplicates.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}

View File

@@ -1,44 +0,0 @@
name: Backfill Duplicate Comments
description: Triggers duplicate detection for old issues that don't have duplicate comments
on:
workflow_dispatch:
inputs:
days_back:
description: 'How many days back to look for old issues'
required: false
default: '90'
type: string
dry_run:
description: 'Dry run mode (true to only log what would be done)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
jobs:
backfill-duplicate-comments:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
issues: read
actions: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Backfill duplicate comments
run: bun run scripts/backfill-duplicate-comments.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DAYS_BACK: ${{ inputs.days_back }}
DRY_RUN: ${{ inputs.dry_run }}

View File

@@ -21,7 +21,6 @@ on:
- release/*
paths:
- 'deps/**'
- 'deps_src/**'
- 'src/**'
- '**/CMakeLists.txt'
- 'version.inc'
@@ -31,9 +30,8 @@ on:
- 'build_release_macos.sh'
- 'scripts/flatpak/**'
schedule:
- cron: '0 17 * * *' # run once a day at 1 AM Singapore time (UTC+8)
- cron: '35 7 * * *' # run once a day near midnight US Pacific time
workflow_dispatch: # allows for manual dispatch
inputs:
@@ -43,123 +41,47 @@ on:
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build_linux:
strategy:
fail-fast: false
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
build-deps-only: ${{ inputs.build-deps-only || false }}
secrets: inherit
build_windows:
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }}
build-deps-only: ${{ inputs.build-deps-only || false }}
force-build: ${{ github.event_name == 'schedule' }}
secrets: inherit
build_macos_arch:
build_all:
name: Build All
strategy:
fail-fast: false
matrix:
arch:
- arm64
- x86_64
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
include:
# Deprecate 20.04appimage
# - os: ubuntu-20.04
- os: ubuntu-24.04
- os: windows-latest
- os: macos-14
arch: arm64
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
os: ${{ matrix.os }}
arch: ${{ matrix.arch }}
build-deps-only: ${{ inputs.build-deps-only || false }}
force-build: ${{ github.event_name == 'schedule' }}
secrets: inherit
build_macos_universal:
name: Build macOS Universal
needs: build_macos_arch
if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_orca.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
arch: universal
macos-combine-only: true
secrets: inherit
unit_tests:
name: Unit Tests
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
needs: build_linux
if: ${{ !cancelled() && success() }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: |
.github
scripts
tests
- name: Apt-Install Dependencies
if: ${{ !vars.SELF_HOSTED }}
uses: ./.github/actions/apt-install-deps
- name: Restore Test Artifact
uses: actions/download-artifact@v7
with:
name: ${{ github.sha }}-tests
- uses: lukka/get-cmake@latest
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
- name: Unpackage and Run Unit Tests
timeout-minutes: 20
run: |
tar -xvf build_tests.tar
scripts/run_unit_tests.sh
- name: Upload Test Logs
uses: actions/upload-artifact@v6
if: ${{ failure() }}
with:
name: unit-test-logs
path: build/tests/**/*.log
- name: Publish Test Results
if: always()
uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: "ctest_results.xml"
- name: Delete Test Artifact
if: success()
uses: geekyeggo/delete-artifact@v5
with:
name: ${{ github.sha }}-tests
flatpak:
name: "Flatpak"
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-49
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-47
options: --privileged
volumes:
- /usr/local/lib/android:/usr/local/lib/android
- /usr/share/dotnet:/usr/share/dotnet
- /opt/ghc:/opt/ghc1
- /usr/local/share/boost:/usr/local/share/boost1
- /opt/hostedtoolcache:/opt/hostedtoolcache1
strategy:
fail-fast: false
matrix:
variant:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
# Don't run scheduled builds on forks; skip entirely on self-hosted runners
if: ${{ !cancelled() && !vars.SELF_HOSTED && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
runs-on: ${{ matrix.variant.runner }}
env:
date:
@@ -168,68 +90,39 @@ jobs:
steps:
- name: "Remove unneeded stuff to free disk space"
run:
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
- uses: actions/checkout@v6
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*"
- uses: actions/checkout@v5
- name: Get the version and date
run: |
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
ver="PR-${{ github.event.number }}"
git_commit_hash="${{ github.event.pull_request.head.sha }}"
else
ver=V$ver_pure
git_commit_hash="${{ github.sha }}"
fi
echo "ver=$ver" >> $GITHUB_ENV
echo "ver_pure=$ver_pure" >> $GITHUB_ENV
echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
shell: bash
# Manage flatpak-builder cache externally so PRs restore but never upload
- name: Restore flatpak-builder cache
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v4
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.event.pull_request.base.sha }}
restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
- name: Save/restore flatpak-builder cache
if: github.event_name != 'pull_request'
uses: actions/cache@v4
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.sha }}
restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
- name: Disable debug info for faster CI builds
run: |
sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
shell: bash
- name: Inject git commit hash into Flatpak manifest
run: |
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
shell: bash
- uses: flatpak/flatpak-github-actions/flatpak-builder@master
- uses: flathub-infra/flatpak-github-actions/flatpak-builder@master
with:
bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
manifest-path: scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
cache: false
manifest-path: scripts/flatpak/io.github.softfever.OrcaSlicer.yml
cache: true
arch: ${{ matrix.variant.arch }}
upload-artifact: false
- name: Upload artifacts Flatpak
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
- name: Deploy Flatpak to nightly release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
if: ${{github.ref == 'refs/heads/main'}}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted

View File

@@ -26,24 +26,23 @@ jobs:
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
lfs: 'true'
- name: set outputs
id: set_outputs
env:
# Keep macOS cache keys and paths architecture-specific.
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && 'windows' || 'linux-clang') }}
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || '/OrcaSlicer_dep' }}
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
# todo: this is mad! refactor other build scripts to use same name
dep-folder-name: ${{ inputs.os == 'windows-latest' && '/OrcaSlicer_dep' || inputs.os == 'macos-14' && '' || inputs.os != 'macos-14' && '/destdir' || '' }}
output-cmd: ${{ inputs.os == 'windows-latest' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
run: |
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
echo cache-key=${{ inputs.os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
- name: load cache
id: cache_deps
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ steps.set_outputs.outputs.cache-path }}
key: ${{ steps.set_outputs.outputs.cache-key }}

View File

@@ -26,7 +26,7 @@ on:
jobs:
build_deps:
name: Build Deps
if: ${{ !cancelled() && (inputs.build-deps-only || inputs.force-build || inputs.valid-cache != true) }}
if: inputs.build-deps-only || inputs.force-build || inputs.valid-cache != true
runs-on: ${{ inputs.os }}
env:
date:
@@ -34,103 +34,115 @@ jobs:
# Setup the environment
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
lfs: 'true'
- name: load cached deps
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
- uses: lukka/get-cmake@latest
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
cmakeVersion: "~3.28.0" # use most recent 3.28.x version
- name: setup dev on Windows
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
uses: microsoft/setup-msbuild@v2
- name: Get the date on Ubuntu and macOS
if: runner.os != 'Windows'
if: inputs.os != 'windows-latest'
run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
shell: bash
- name: Get the date on Windows
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
run: echo "date=$(Get-Date -Format 'yyyyMMdd')" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
shell: pwsh
# Build Dependencies
- name: Build on Windows
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
working-directory: ${{ github.workspace }}
run: |
if (-not "${{ vars.SELF_HOSTED }}") {
choco install strawberryperl
}
.\build_release_vs.bat deps
.\build_release_vs.bat pack
shell: pwsh
choco install strawberryperl
.\build_release_vs2022.bat deps
.\build_release_vs2022.bat pack
cd ${{ github.workspace }}/deps/build
- name: Build on Mac ${{ inputs.arch }}
if: runner.os == 'macOS'
if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
run: |
if [ -z "${{ vars.SELF_HOSTED }}" ]; then
brew install automake texinfo libtool
fi
./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
(cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \
find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +)
brew install automake texinfo libtool
brew list
brew uninstall --ignore-dependencies zstd
./build_release_macos.sh -dx -a universal -t 10.15 -1
for arch in arm64 x86_64; do
(cd "${{ github.workspace }}/deps/build/${arch}" && \
find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +)
done
brew install zstd
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps
- name: Install Ubuntu Build Dependencies
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
working-directory: ${{ github.workspace }}
env:
apt-cmd: ${{ (inputs.os == 'ubuntu-20.04' && 'apt-fast') || (inputs.os == 'ubuntu-24.04' && 'sudo apt-get') || '' }}
webkit-ver: ${{ (inputs.os == 'ubuntu-20.04' && '4.0') || (inputs.os == 'ubuntu-24.04' && '4.1') || '' }}
run: |
${{ env.apt-cmd }} update
${{ env.apt-cmd }} install -y cmake git g++ build-essential libgl1-mesa-dev m4 \
libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules pkgconf \
libglu1-mesa-dev libcairo2-dev libgtk-3-dev libsoup2.4-dev libwebkit2gtk-${{ env.webkit-ver }}-dev \
libgstreamer1.0-dev libgstreamer-plugins-good1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-bad wget sudo autoconf curl libunwind-dev texinfo
- name: Build on Ubuntu
if: runner.os == 'Linux'
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
working-directory: ${{ github.workspace }}
run: |
mkdir -p ${{ github.workspace }}/deps/build
mkdir -p ${{ github.workspace }}/deps/build/destdir
./build_linux.sh -drlL
sudo ./build_linux.sh -ur
sudo chown $USER -R ./
./build_linux.sh -dr
cd deps/build
tar -czvf OrcaSlicer_dep_ubuntu_$(date +"%Y%m%d").tar.gz destdir
# Upload Artifacts
# - name: Upload Mac ${{ inputs.arch }} artifacts
# if: runner.os == 'macOS'
# uses: actions/upload-artifact@v6
# if: inputs.os == 'macos-14'
# uses: actions/upload-artifact@v4
# with:
# name: OrcaSlicer_dep_mac_${{ env.date }}
# path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep*.tar.gz
# - name: Upload Windows artifacts
# if: runner.os == 'Windows'
# uses: actions/upload-artifact@v6
# with:
# name: OrcaSlicer_dep_win64_${{ env.date }}
# path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep*.zip
- name: Upload Windows artifacts
if: inputs.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_dep_win64_${{ env.date }}
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep*.zip
# - name: Upload Ubuntu artifacts
# if: runner.os == 'Linux' && !env.ACT
# env:
# ubuntu-ver: '2404'
# uses: actions/upload-artifact@v6
# with:
# name: OrcaSlicer_dep_ubuntu_${{ env.ubuntu-ver }}_${{ env.date }}
# path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep_ubuntu_*.tar.gz
- name: Upload Ubuntu artifacts
if: ${{ ! env.ACT && inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04' }}
env:
ubuntu-ver: ${{ (inputs.os == 'ubuntu-20.04' && '2004') || (inputs.os == 'ubuntu-24.04' && '2404') || '' }}
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_dep_ubuntu_${{ env.ubuntu-ver }}_${{ env.date }}
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep_ubuntu_*.tar.gz
build_orca:
name: Build OrcaSlicer
needs: [build_deps]
if: ${{ !cancelled() && !inputs.build-deps-only && (inputs.force-build || (inputs.valid-cache == true && needs.build_deps.result == 'skipped') || (inputs.valid-cache != true && success())) }}
if: ${{ !cancelled() && !inputs.build-deps-only && inputs.force-build || (inputs.valid-cache == true && needs.build_deps.result == 'skipped') || (inputs.valid-cache != true && success()) }}
uses: ./.github/workflows/build_orca.yml
with:
cache-key: ${{ inputs.cache-key }}

View File

@@ -2,10 +2,10 @@ on:
workflow_call:
inputs:
cache-key:
required: false
required: true
type: string
cache-path:
required: false
required: true
type: string
os:
required: true
@@ -13,10 +13,6 @@ on:
arch:
required: false
type: string
macos-combine-only:
required: false
type: boolean
default: false
jobs:
build_orca:
@@ -26,19 +22,16 @@ jobs:
date:
ver:
ver_pure:
ubuntu-ver: '2204'
ubuntu-ver-str: '_Ubuntu2204'
ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
lfs: 'true'
- name: load cached deps
if: ${{ !(runner.os == 'macOS' && inputs.macos-combine-only) }}
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
@@ -46,29 +39,24 @@ jobs:
- uses: lukka/get-cmake@latest
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
cmakeVersion: "~3.28.0" # use most recent 3.28.x version
- name: Get the version and date on Ubuntu and macOS
if: runner.os != 'Windows'
if: inputs.os != 'windows-latest'
run: |
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
ver="PR-${{ github.event.number }}"
git_commit_hash="${{ github.event.pull_request.head.sha }}"
else
ver=V$ver_pure
git_commit_hash=""
fi
echo "ver=$ver" >> $GITHUB_ENV
echo "ver_pure=$ver_pure" >> $GITHUB_ENV
echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
shell: bash
- name: Get the version and date on Windows
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
run: |
$date = Get-Date -Format 'yyyyMMdd'
$ref = "${{ github.ref }}"
@@ -77,34 +65,29 @@ jobs:
if ($eventName -eq 'pull_request') {
$ver = "PR" + $prNumber
$git_commit_hash = "${{ github.event.pull_request.head.sha }}"
} else {
$versionContent = Get-Content version.inc -Raw
if ($versionContent -match 'set\(SoftFever_VERSION "(.*?)"\)') {
$ver = $matches[1]
}
$ver = "V$ver"
$git_commit_hash = ""
}
echo "ver=$ver" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
echo "date=$date" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
echo "git_commit_hash=$git_commit_hash" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
echo "date: ${{ env.date }} version: ${{ env.ver }}"
shell: pwsh
# Mac
- name: Install tools mac
if: runner.os == 'macOS' && !inputs.macos-combine-only
if: inputs.os == 'macos-14'
run: |
if [ -z "${{ vars.SELF_HOSTED }}" ]; then
brew install libtool
brew list
fi
mkdir -p ${{ github.workspace }}/deps/build/${{ inputs.arch }}
brew install libtool
brew list
mkdir -p ${{ github.workspace }}/deps/build
- name: Free disk space
if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED
if: inputs.os == 'macos-14'
run: |
df -hI /dev/disk3s1s1
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} +
@@ -112,58 +95,14 @@ jobs:
df -hI /dev/disk3s1s1
- name: Build slicer mac
if: runner.os == 'macOS' && !inputs.macos-combine-only
if: inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
run: |
./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
- name: Pack macOS app bundle ${{ inputs.arch }}
if: runner.os == 'macOS' && !inputs.macos-combine-only
working-directory: ${{ github.workspace }}
run: |
tar -czvf OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }}.tar.gz -C build/${{ inputs.arch }} OrcaSlicer
- name: Upload macOS app bundle ${{ inputs.arch }}
if: runner.os == 'macOS' && !inputs.macos-combine-only
uses: actions/upload-artifact@v6
with:
name: OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }}
path: ${{ github.workspace }}/OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }}.tar.gz
- name: Download macOS app bundles
if: runner.os == 'macOS' && inputs.macos-combine-only
uses: actions/download-artifact@v7
with:
pattern: OrcaSlicer_Mac_bundle_*_${{ github.sha }}
path: ${{ github.workspace }}/mac_bundles
- name: Extract macOS app bundles
if: runner.os == 'macOS' && inputs.macos-combine-only
working-directory: ${{ github.workspace }}
run: |
mkdir -p build/arm64 build/x86_64
arm_bundle=$(find "${{ github.workspace }}/mac_bundles/OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}" -name '*.tar.gz' -print -quit)
x86_bundle=$(find "${{ github.workspace }}/mac_bundles/OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}" -name '*.tar.gz' -print -quit)
tar -xzvf "$arm_bundle" -C "${{ github.workspace }}/build/arm64"
tar -xzvf "$x86_bundle" -C "${{ github.workspace }}/build/x86_64"
- name: Build universal mac app bundle
if: runner.os == 'macOS' && inputs.macos-combine-only
working-directory: ${{ github.workspace }}
run: |
./build_release_macos.sh -u -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a universal -t 10.15
- name: Delete intermediate per-arch artifacts
if: runner.os == 'macOS' && inputs.macos-combine-only
uses: geekyeggo/delete-artifact@v5
with:
name: |
OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}
OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}
./build_release_macos.sh -s -n -x -a universal -t 10.15 -1
# Thanks to RaySajuuk, it's working now
- name: Sign app and notary
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
@@ -177,8 +116,6 @@ jobs:
security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
# Set the temporary keychain as the default to prevent codesign from accessing the locked login keychain
security default-keychain -s "$KEYCHAIN_PATH"
security import $CERTIFICATE_PATH -P $P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $P12_PASSWORD $KEYCHAIN_PATH
@@ -207,17 +144,18 @@ jobs:
fi
# Notarize main DMG
xcrun notarytool submit "OrcaSlicer_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
xcrun notarytool store-credentials "notarytool-profile" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}"
xcrun notarytool submit "OrcaSlicer_Mac_universal_${{ env.ver }}.dmg" --keychain-profile "notarytool-profile" --wait
xcrun stapler staple OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
# Notarize profile validator DMG if it exists
if [ -f "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" ]; then
xcrun notarytool submit "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
xcrun notarytool submit "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" --keychain-profile "notarytool-profile" --wait
xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
fi
- name: Create DMG without notary
if: github.ref != 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only
if: github.ref != 'refs/heads/main' && inputs.os == 'macos-14'
working-directory: ${{ github.workspace }}
run: |
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
@@ -236,25 +174,25 @@ jobs:
fi
- name: Upload artifacts mac
if: runner.os == 'macOS' && inputs.macos-combine-only
uses: actions/upload-artifact@v6
if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_Mac_universal_${{ env.ver }}
path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
- name: Upload OrcaSlicer_profile_validator DMG mac
if: runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
uses: actions/upload-artifact@v6
if: inputs.os == 'macos-14'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_profile_validator_Mac_universal_DMG_${{ env.ver }}
path: ${{ github.workspace }}/OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
if-no-files-found: ignore
- name: Deploy Mac release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'macos-14'
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
asset_name: OrcaSlicer_Mac_universal_nightly.dmg
@@ -262,10 +200,10 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Mac OrcaSlicer_profile_validator DMG release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'macos-14'
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
asset_name: OrcaSlicer_profile_validator_Mac_universal_nightly.dmg
@@ -274,74 +212,74 @@ jobs:
# Windows
- name: setup MSVC
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
uses: microsoft/setup-msbuild@v2
- name: Install nsis
if: runner.os == 'Windows' && !vars.SELF_HOSTED
if: inputs.os == 'windows-latest'
run: |
dir "C:/Program Files (x86)/Windows Kits/10/Include"
choco install nsis
- name: Build slicer Win
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
working-directory: ${{ github.workspace }}
env:
WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\'
WindowsSDKVersion: '10.0.26100.0\'
run: .\build_release_vs.bat slicer
run: .\build_release_vs2022.bat slicer
- name: Create installer Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
if: inputs.os == 'windows-latest'
working-directory: ${{ github.workspace }}/build
run: |
cpack -G NSIS
- name: Pack app
if: runner.os == 'Windows'
if: inputs.os == 'windows-latest'
working-directory: ${{ github.workspace }}/build
shell: cmd
run: '"C:/Program Files/7-Zip/7z.exe" a -tzip OrcaSlicer_Windows_${{ env.ver }}_portable.zip ${{ github.workspace }}/build/OrcaSlicer'
- name: Pack PDB
if: runner.os == 'Windows' && !vars.SELF_HOSTED
if: inputs.os == 'windows-latest'
working-directory: ${{ github.workspace }}/build/src/Release
shell: cmd
run: '"C:/Program Files/7-Zip/7z.exe" a -m0=lzma2 -mx9 Debug_PDB_${{ env.ver }}_for_developers_only.7z *.pdb'
- name: Upload artifacts Win zip
if: runner.os == 'Windows'
uses: actions/upload-artifact@v6
if: inputs.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_Windows_${{ env.ver }}_portable
path: ${{ github.workspace }}/build/OrcaSlicer
- name: Upload artifacts Win installer
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v6
if: inputs.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_Windows_${{ env.ver }}
path: ${{ github.workspace }}/build/OrcaSlicer*.exe
- name: Upload artifacts Win PDB
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v6
if: inputs.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: PDB
path: ${{ github.workspace }}/build/src/Release/Debug_PDB_${{ env.ver }}_for_developers_only.7z
- name: Upload OrcaSlicer_profile_validator Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v6
if: inputs.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_profile_validator_Windows_${{ env.ver }}
path: ${{ github.workspace }}/build/src/Release/OrcaSlicer_profile_validator.exe
- name: Deploy Windows release portable
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'windows-latest'
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/build/OrcaSlicer_Windows_${{ env.ver }}_portable.zip
asset_name: OrcaSlicer_Windows_nightly_portable.zip
@@ -349,10 +287,10 @@ jobs:
max_releases: 1
- name: Deploy Windows release installer
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'windows-latest'
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/build/OrcaSlicer_Windows_Installer_${{ env.ver }}.exe
asset_name: OrcaSlicer_Windows_Installer_nightly.exe
@@ -360,10 +298,10 @@ jobs:
max_releases: 1
- name: Deploy Windows OrcaSlicer_profile_validator release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'windows-latest'
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/build/src/Release/OrcaSlicer_profile_validator.exe
asset_name: OrcaSlicer_profile_validator_Windows_nightly.exe
@@ -371,36 +309,42 @@ jobs:
max_releases: 1
# Ubuntu
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps
# Tests must built at the same time as the slicer;
# if you untangle them feel free to separate them here too
- name: Build slicer and tests
if: runner.os == 'Linux'
shell: bash
- name: Install dependencies
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
env:
apt-cmd: ${{ (inputs.os == 'ubuntu-20.04' && 'apt-fast') || (inputs.os == 'ubuntu-24.04' && 'sudo apt-get') || '' }}
webkit-ver: ${{ (inputs.os == 'ubuntu-20.04' && '4.0') || (inputs.os == 'ubuntu-24.04' && '4.1') || '' }}
libfuse2-pkg: ${{ (inputs.os == 'ubuntu-20.04' && 'libfuse2') || (inputs.os == 'ubuntu-24.04' && 'libfuse2t64') || '' }}
run: |
./build_linux.sh -istrlL
./scripts/check_appimage_libs.sh ./build/package ./build/package/bin/orca-slicer
${{ env.apt-cmd }} update
${{ env.apt-cmd }} install -y autoconf build-essential cmake curl eglexternalplatform-dev \
extra-cmake-modules file git libcairo2-dev libcurl4-openssl-dev libdbus-1-dev libglew-dev libglu1-mesa-dev \
libglu1-mesa-dev libgstreamer1.0-dev libgstreamerd-3-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1.0-dev \
libgtk-3-dev libgtk-3-dev libmspack-dev libsecret-1-dev libsoup2.4-dev libssl-dev libudev-dev libwayland-dev \
libwebkit2gtk-${{ env.webkit-ver }}-dev libxkbcommon-dev locales locales-all m4 pkgconf sudo wayland-protocols wget ${{ env.libfuse2-pkg }}
- name: Install dependencies from build_linux.sh
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
shell: bash
run: sudo env "ORCA_UPDATER_SIG_KEY=$ORCA_UPDATER_SIG_KEY" ./build_linux.sh -ur
- name: Fix permissions
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
shell: bash
run: sudo chown $USER -R ./
- name: Build slicer
if: inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04'
shell: bash
env:
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
run: |
./build_linux.sh -isr
mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
chmod +x ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
tar -cvpf build_tests.tar build/tests
# Use tar because upload-artifacts won't always preserve directory structure
# and doesn't preserve file permissions
- name: Upload Test Artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v6
with:
name: ${{ github.sha }}-tests
overwrite: true
path: build_tests.tar
retention-days: 5
if-no-files-found: error
- name: Build orca_custom_preset_tests
if: github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && inputs.os == 'ubuntu-24.04'
working-directory: ${{ github.workspace }}/build/src/Release
shell: bash
run: |
@@ -409,31 +353,38 @@ jobs:
zip -r orca_custom_preset_tests.zip user/
- name: Upload artifacts Ubuntu
if: ${{ ! env.ACT && runner.os == 'Linux' }}
uses: actions/upload-artifact@v6
if: ${{ ! env.ACT && inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04' }}
env:
ubuntu-ver: ${{ (inputs.os == 'ubuntu-20.04' && '2004') || (inputs.os == 'ubuntu-24.04' && '2404') || '' }}
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
path: './build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage'
- name: Upload OrcaSlicer_profile_validator Ubuntu
if: ${{ ! env.ACT && runner.os == 'Linux' && !vars.SELF_HOSTED }}
uses: actions/upload-artifact@v6
if: ${{ ! env.ACT && inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04' }}
env:
ubuntu-ver: ${{ (inputs.os == 'ubuntu-20.04' && '2004') || (inputs.os == 'ubuntu-24.04' && '2404') || '' }}
uses: actions/upload-artifact@v4
with:
name: OrcaSlicer_profile_validator_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
path: './build/src/Release/OrcaSlicer_profile_validator'
- name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ ! env.ACT && github.ref == 'refs/heads/main' && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }}
env:
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
asset_name: OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_nightly.AppImage
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ ! env.ACT && github.ref == 'refs/heads/main' && inputs.os == 'ubuntu-24.04' }}
uses: rickstaa/action-create-tag@v1
with:
tag: "nightly-builds"
@@ -442,10 +393,12 @@ jobs:
message: "nightly-builds"
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ ! env.ACT && github.ref == 'refs/heads/main' && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }}
env:
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ./build/src/Release/OrcaSlicer_profile_validator
asset_name: OrcaSlicer_profile_validator_Linux${{ env.ubuntu-ver-str }}_nightly
@@ -453,10 +406,10 @@ jobs:
max_releases: 1
- name: Deploy orca_custom_preset_tests
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ ! env.ACT && github.ref == 'refs/heads/main' && inputs.os == 'ubuntu-24.04' }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
upload_url: https://uploads.github.com/repos/SoftFever/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/resources/profiles/orca_custom_preset_tests.zip
asset_name: orca_custom_preset_tests.zip

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install gettext
run: |

View File

@@ -1,8 +1,8 @@
name: Check profiles
on:
on:
pull_request:
branches:
- main
branches:
- main
paths:
- 'resources/profiles/**'
- ".github/workflows/check_profiles.yml"
@@ -10,32 +10,25 @@ on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
description: 'Log level'
required: true
default: 'warning'
permissions:
contents: read
jobs:
check_profiles:
check_translation:
name: Check profiles
runs-on: ubuntu-24.04
steps:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Run extra JSON check
id: extra_json_check
continue-on-error: true
run: |
set +e
python3 ./scripts/orca_extra_profile_check.py 2>&1 | tee ${{ runner.temp }}/extra_json_check.log
exit ${PIPESTATUS[0]}
python3 ./scripts/orca_extra_profile_check.py
# download
- name: Download
- name: Download
working-directory: ${{ github.workspace }}
run: |
curl -LJO https://github.com/SoftFever/Orca_tools/releases/download/1/OrcaSlicer_profile_validator
@@ -43,76 +36,16 @@ jobs:
# validate profiles
- name: validate system profiles
id: validate_system
continue-on-error: true
run: |
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2
- name: validate custom presets
id: validate_custom
continue-on-error: true
working-directory: ${{ github.workspace }}
run: |
set +e
curl -LJO https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/orca_custom_preset_tests.zip
unzip -q ./orca_custom_preset_tests.zip -d ${{ github.workspace }}/resources/profiles
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_custom.log
exit ${PIPESTATUS[0]}
curl -LJO https://github.com/SoftFever/OrcaSlicer/releases/download/nightly-builds/orca_custom_preset_tests.zip
unzip ./orca_custom_preset_tests.zip -d ${{ github.workspace }}/resources/profiles
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
mkdir -p ${{ runner.temp }}/profile-check-results
{
echo "## :x: Profile Validation Errors"
echo ""
if [ "${{ steps.extra_json_check.outcome }}" = "failure" ]; then
echo "### Extra JSON Check Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/extra_json_check.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then
echo "### System Profile Validation Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_system.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_custom.outcome }}" = "failure" ]; then
echo "### Custom Preset Validation Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_custom.log || echo "No output captured"
echo '```'
echo ""
fi
echo "---"
echo "*Please fix the above errors and push a new commit.*"
} > ${{ runner.temp }}/profile-check-results/pr_comment.md
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Upload comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
uses: actions/upload-artifact@v4
with:
name: profile-check-results
path: ${{ runner.temp }}/profile-check-results/
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -1,46 +0,0 @@
name: Post profile check comment
# NOTE: The workflow name in the 'workflows' filter below must match the 'name'
# field in check_profiles.yml exactly. If that name changes, update it here too.
on:
workflow_run:
workflows: ["Check profiles"]
types:
- completed
permissions:
pull-requests: write
jobs:
post_comment:
name: Post PR comment
runs-on: ubuntu-24.04
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: Download artifact
id: download
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: profile-check-results
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Post comment on PR
if: ${{ steps.download.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
if [ ! -f pr_number.txt ] || [ ! -f pr_comment.md ]; then
echo "No comment artifact found, skipping."
exit 0
fi
PR_NUMBER=$(cat pr_number.txt)
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "Invalid PR number: $PR_NUMBER"
exit 1
fi
gh pr comment "$PR_NUMBER" --body-file pr_comment.md

View File

@@ -0,0 +1,71 @@
name: Claude Code Review
on:
workflow_dispatch:
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
# model: "claude-opus-4-1-20250805"
# Direct prompt for automated review (no @claude mention needed)
direct_prompt: |
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
Be constructive and helpful in your feedback.
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
# use_sticky_comment: true
# Optional: Customize review based on file types
# direct_prompt: |
# Review this PR focusing on:
# - For TypeScript files: Type safety and proper interface usage
# - For API endpoints: Security, input validation, and error handling
# - For React components: Performance, accessibility, and best practices
# - For tests: Coverage, edge cases, and test quality
# Optional: Different prompts for different authors
# direct_prompt: |
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
# Optional: Add specific tools for running tests or linting
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
# Optional: Skip review for certain conditions
# if: |
# !contains(github.event.pull_request.title, '[skip-review]') &&
# !contains(github.event.pull_request.title, '[WIP]')

64
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
# model: "claude-opus-4-1-20250805"
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Optional: Allow Claude to run specific commands
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test

View File

@@ -1,81 +0,0 @@
name: Orca Issue Dedupe
description: Automatically dedupe GitHub issues using AI
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to process for duplicate detection'
required: true
type: string
jobs:
dedupe-issues:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@beta
with:
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model claude-sonnet-4-5-20250929"
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log duplicate comment event to Statsig
if: always()
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
run: |
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
REPO=${{ github.repository }}
if [ -z "$STATSIG_API_KEY" ]; then
echo "STATSIG_API_KEY not found, skipping Statsig logging"
exit 0
fi
# Prepare the event payload
EVENT_PAYLOAD=$(jq -n \
--arg issue_number "$ISSUE_NUMBER" \
--arg repo "$REPO" \
--arg triggered_by "${{ github.event_name }}" \
'{
events: [{
eventName: "github_duplicate_comment_added",
value: 1,
metadata: {
repository: $repo,
issue_number: ($issue_number | tonumber),
triggered_by: $triggered_by,
workflow_run_id: "${{ github.run_id }}"
},
time: (now | floor | tostring)
}]
}')
# Send to Statsig API
echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
-H "Content-Type: application/json" \
-H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
-d "$EVENT_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}"
else
echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
fi

View File

@@ -1,78 +0,0 @@
name: Generate Doxygen Documentation
on:
schedule:
- cron: '0 0 * * 1' # Every Monday at midnight UTC
workflow_dispatch: # Manual trigger
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
build-and-deploy:
name: Build and Deploy Docs
runs-on: ubuntu-latest
timeout-minutes: 60
# Only run on main branch of the main repository
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
permissions:
contents: read
steps:
- uses: thejerrybao/setup-swap-space@v1
with:
swap-space-path: /swapfile
swap-size-gb: 8
remove-existing-swap-files: true
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Doxygen and Graphviz
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y doxygen graphviz
- name: Generate documentation
run: |
set -euo pipefail
# Override DOT_NUM_THREADS to avoid parallel dot race condition bug
sed -i 's/^DOT_NUM_THREADS.*/DOT_NUM_THREADS = 1/' .doxygen
doxygen .doxygen
# Verify documentation was generated
if [ ! -f "internal_docs/index.html" ]; then
echo "Error: Documentation generation failed - index.html not found"
exit 1
fi
- name: Install Rclone
run: |
set -euo pipefail
sudo -v
curl -fsSL https://rclone.org/install.sh | sudo bash
- name: optimize
run: |
set -euo pipefail
rm -f internal_docs/Nodes.xml internal_docs/Tokens.xml
find internal_docs -name "*.map" -type f -delete || true
find internal_docs -name "*.md5" -type f -delete || true
- name: upload
# We configure rclone dynamically using environment variables
run: |
set -euo pipefail
# Remove existing config if it exists to avoid conflicts
rclone config delete cloudflare 2>/dev/null || true
rclone config create cloudflare s3 \
provider Cloudflare \
access_key_id ${{ secrets.R2_ACCESS_KEY_ID }} \
secret_access_key ${{ secrets.R2_SECRET_ACCESS_KEY }} \
endpoint ${{ secrets.R2_ENDPOINT }}
rclone sync internal_docs/ cloudflare:orcaslicer-internals \
--progress \
--transfers 512 \
--checkers 512
echo "Documentation upload completed successfully"

73
.github/workflows/orca_bot.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Orca bot
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: write # only for delete-branch option
steps:
- uses: actions/stale@v9
with:
# PAT for GitHub API authentication
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Max number of operations per run
operations-per-run: 1000
# Order to get issues/PRs
ascending: true
# ISSUES
# Do not auto-close an issue if it is assigned to a milestone
exempt-all-issue-milestones: true
# Exempt all issues with assignees from stale
exempt-all-issue-assignees: true
# Exempt feature requests
exempt-issue-labels: "enhancement"
# Idle number of days before marking issues stale
days-before-issue-stale: 90
# Idle number of days before marking issues close
days-before-issue-close: 7
# Label to apply on staled issues
stale-issue-label: "stale"
# Issue close reason
close-issue-reason: not_planned
# Remove stale label from issues on updates
remove-issue-stale-when-updated: true
# Issue stale message
stale-issue-message: "Orca bot: this issue is stale because it has been open for 90 days with no activity."
# Issue closure message
close-issue-message: "Orca bot: This issue was closed because it has been inactive for 7 days since being marked as stale."
# PRs
# Do not auto-close a PR if it is assigned to a milestone
exempt-all-pr-milestones: true
# Exempt all PRs with assignees from stale
exempt-all-pr-assignees: true
# Skip the stale action for draft PRs
exempt-draft-pr: true
# Idle number of days before marking PRs stale
days-before-pr-stale: -1
# Idle number of days before marking PRs close
days-before-pr-close: -1
# Label to apply on staled PRs
stale-pr-label: "stale"
# Label to apply on closed PRs
close-pr-label: not_planned
# Remove stale label from PRs on updates
remove-pr-stale-when-updated: true
# PR stale message
stale-pr-message: "Orca bot: this PR is stale because it has been open for XX days with no activity."
# PR closure message
close-pr-message: "Orca bot: This PR was closed because it has been inactive for X days since being marked as stale."
# Delete branch after closing a stale PR
delete-branch: true

View File

@@ -0,0 +1,46 @@
name: Publish docs to Wiki
# Trigger this action only if there are changes pushed to the doc/** directory under the main branch
on:
push:
paths:
- doc/** # This includes all sub folders
branches:
- main # This can be changed to any branch of your preference
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
env:
USER_TOKEN: ${{ secrets.GH_WIKI_PAT }} # This is the repository secret personal access token
USER_NAME: ${{ vars.BOT_USER_NAME }} # Enter the username of your (bot) account
OWNER: ${{ github.event.repository.owner.name }} # This is the repository owner
REPOSITORY_NAME: ${{ github.event.repository.name }} # This is the repository name
jobs:
publish_docs_to_wiki:
name: Publish docs to Wiki
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
# 1. Clone the current wiki master branch to a folder named `tmp_wiki`
- name: Pull content from wiki
run: |
git config --global user.name "$USER_NAME"
git config --global user.email "$USER_NAME"@users.noreply.github.com
git clone https://"$USER_TOKEN"@github.com/SoftFever/"$REPOSITORY_NAME".wiki.git tmp_wiki
# 4. Synchronize differences between `doc` & `tmp_wiki`
# 5. Push new Wiki content
- name: Push main repo content to wiki
run: |
rsync -av --delete doc/ tmp_wiki/ --exclude .git
cd tmp_wiki
git add .
git commit -m "Updated Wiki content"
git push origin master

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Cache shellcheck download
id: cache-shellcheck-v0_11
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ~/shellcheck
key: ${{ runner.os }}-shellcheck-v0_11
@@ -36,7 +36,7 @@ jobs:
tar -xvf ~/sc.tar.xz -C ~
mv ~/shellcheck-"${INPUT_VERSION}"/shellcheck ~/shellcheck
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 1

View File

@@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: '3.12'

View File

@@ -0,0 +1,335 @@
name: Validate Documentation
on:
pull_request:
paths:
- 'src/slic3r/GUI/Tab.cpp'
- 'doc/**/*.md'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
issues: write
jobs:
validate:
runs-on: windows-latest
name: Check Documentation
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
src/slic3r/GUI/Tab.cpp
doc/**/*.md
- name: Run validation
if: steps.changed-files.outputs.any_changed == 'true'
shell: pwsh
run: |
# Helper Functions
function Normalize-Fragment($fragment) {
return $fragment.ToLower().Trim() -replace '[^a-z0-9\s-]', '' -replace ' ', '-' -replace '^-+|-+$', ''
}
function Add-BrokenReference($sourceFile, $line, $target, $issue, $type) {
return @{
SourceFile = $sourceFile
Line = $line
Target = $target
Issue = $issue
Type = $type
}
}
function Validate-Fragment($fragment, $availableAnchors, $sourceFile, $line, $target, $type) {
$cleanFragment = $fragment.StartsWith('#') ? $fragment.Substring(1) : $fragment
$normalizedFragment = Normalize-Fragment $cleanFragment
if ($availableAnchors -notcontains $normalizedFragment) {
return Add-BrokenReference $sourceFile $line $target "Fragment does not exist" $type
}
return $null
}
# Initialize
$tabFile = Join-Path $PWD "src/slic3r/GUI/Tab.cpp"
$docDir = Join-Path $PWD 'doc'
$brokenReferences = @()
$docIndex = @{}
Write-Host "Validating documentation..." -ForegroundColor Blue
# Validate paths
$hasTabFile = Test-Path $tabFile
if (-not $hasTabFile) { Write-Host "::warning::Tab.cpp file not found at: $tabFile" }
if (-not (Test-Path $docDir)) { Write-Host "::error::doc folder does not exist"; exit 1 }
# Build documentation index
$mdFiles = Get-ChildItem -Path $docDir -Filter *.md -Recurse -File -ErrorAction SilentlyContinue
foreach ($mdFile in $mdFiles) {
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($mdFile.Name)
$relPath = (Resolve-Path $mdFile.FullName).Path.Substring($docDir.Length).TrimStart('\', '/')
$content = Get-Content -Path $mdFile.FullName -Encoding UTF8 -Raw
$lines = Get-Content -Path $mdFile.FullName -Encoding UTF8
# Extract anchors
$anchors = @()
$anchors += [regex]::Matches($content, '(?i)<a\s+[^>]*(?:name|id)\s*=\s*[`"'']([^`"'']+)[`"'']') |
ForEach-Object { $_.Groups[1].Value.ToLower() }
$anchors += [regex]::Matches($content, '(?m)^#+\s+(.+)$') |
ForEach-Object { Normalize-Fragment $_.Groups[1].Value.Trim() }
# Parse links
$links = @()
$inCodeFence = $false
for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i]
if ($line.TrimStart() -match '^(```|~~~)') {
$inCodeFence = -not $inCodeFence
continue
}
if ($inCodeFence) { continue }
$lineForParsing = [regex]::Replace($line, '`[^`]*`', '')
foreach ($linkMatch in [regex]::Matches($lineForParsing, '(?<!!)[^\]]*\]\(([^)]+)\)')) {
$destRaw = $linkMatch.Groups[1].Value.Trim()
# Handle internal fragments
if ($destRaw.StartsWith('#')) {
$fragment = $destRaw.Substring(1)
if ($fragment.Contains('#')) {
$brokenReferences += Add-BrokenReference $relPath ($i + 1) $destRaw "Internal link must use only one #." "Link"
} else {
$validationResult = Validate-Fragment $fragment $anchors $relPath ($i + 1) $destRaw "Link"
if ($validationResult) { $brokenReferences += $validationResult }
}
continue
}
# Skip external URLs
if ($destRaw -match '^(?:https?:|mailto:|data:|#|\\)') { continue }
# Check for double ##
if ($destRaw.Contains('##')) {
$brokenReferences += Add-BrokenReference $relPath ($i + 1) $destRaw "Use single # for fragments." "Link"
continue
}
# Parse file and fragment
$destParts = $destRaw -split '#', 2
$destNoFragment = $destParts[0]
$fragment = ($destParts.Length -gt 1) ? $destParts[1] : $null
if ($destNoFragment) {
$leaf = ($destNoFragment -split '[\\/]')[-1]
if ($leaf) {
$targetBase = $leaf.ToLower().EndsWith('.md') ? $leaf.Substring(0, $leaf.Length - 3) : $leaf
$targetBase = $targetBase.Trim()
if ($targetBase) {
$linkInfo = @{
TargetBase = $targetBase
Fragment = $fragment
Line = $i + 1
SourceFile = $relPath
}
$links += $linkInfo
}
}
}
}
}
$docIndex[$baseName] = @{ Anchors = $anchors; Links = $links }
}
# Parse Tab.cpp references
if ($hasTabFile) {
$regex = 'optgroup->append_single_option_line\s*\(\s*(?:"([^"]+)"|([^,]+?))\s*,\s*"([^"]+)"\s*\)'
$lines = Get-Content -Path $tabFile -Encoding UTF8
for ($i = 0; $i -lt $lines.Count; $i++) {
foreach ($match in [regex]::Matches($lines[$i], $regex)) {
$arg2Full = $match.Groups[3].Value.Trim()
if ($arg2Full.Contains('##')) {
$brokenReferences += Add-BrokenReference "Tab.cpp" ($i + 1) $arg2Full "Use single # for fragments." "Link"
continue
}
$arg2Parts = $arg2Full -split '#', 2
$docBase = $arg2Parts[0].Trim()
$fragment = ($arg2Parts.Length -gt 1) ? $arg2Parts[1].Trim() : $null
if (-not $docIndex.ContainsKey($docBase)) {
$brokenReferences += Add-BrokenReference "Tab.cpp" ($i + 1) $docBase "File does not exist" "Link"
} elseif ($fragment) {
$validationResult = Validate-Fragment $fragment $docIndex[$docBase].Anchors "Tab.cpp" ($i + 1) "$docBase#$fragment" "Link"
if ($validationResult) { $brokenReferences += $validationResult }
}
}
}
}
# Validate markdown links
foreach ($baseName in $docIndex.Keys) {
foreach ($link in $docIndex[$baseName].Links) {
if (-not $docIndex.ContainsKey($link.TargetBase)) {
$brokenReferences += Add-BrokenReference $link.SourceFile $link.Line "$($link.TargetBase).md" "File does not exist" "Link"
} elseif ($link.Fragment) {
$validationResult = Validate-Fragment $link.Fragment $docIndex[$link.TargetBase].Anchors $link.SourceFile $link.Line "$($link.TargetBase)#$($link.Fragment)" "Link"
if ($validationResult) { $brokenReferences += $validationResult }
}
}
}
# Validate images
Write-Host "Validating images..." -ForegroundColor Blue
$expectedUrlPattern = '^https://github\.com/SoftFever/OrcaSlicer/blob/main/([^?]+)\?raw=true$'
foreach ($file in $mdFiles) {
$lines = Get-Content $file.FullName -Encoding UTF8
$relPath = (Resolve-Path $file.FullName).Path.Substring($docDir.Length).TrimStart('\', '/')
$inCodeFence = $false
for ($lineNumber = 0; $lineNumber -lt $lines.Count; $lineNumber++) {
$line = $lines[$lineNumber]
if ($line.TrimStart() -match '^(```|~~~)') {
$inCodeFence = -not $inCodeFence
continue
}
if ($inCodeFence) { continue }
$lineForParsing = [regex]::Replace($line, '`[^`]*`', '')
# Process markdown and HTML images
$imagePatterns = @(
@{ Pattern = "!\[([^\]]*)\]\(([^)]+)\)"; Type = "Markdown"; AltGroup = 1; UrlGroup = 2 }
@{ Pattern = '<img\s+[^>]*>'; Type = "HTML"; AltGroup = -1; UrlGroup = -1 }
)
foreach ($pattern in $imagePatterns) {
foreach ($match in [regex]::Matches($lineForParsing, $pattern.Pattern)) {
$altText = ""
$url = ""
if ($pattern.Type -eq "Markdown") {
$altText = $match.Groups[$pattern.AltGroup].Value
$url = $match.Groups[$pattern.UrlGroup].Value
} else {
# Extract from HTML
$imgTag = $match.Value
if ($imgTag -match 'alt\s*=\s*[`"'']([^`"'']*)[`"'']') { $altText = $matches[1] }
if ($imgTag -match 'src\s*=\s*[`"'']([^`"'']*)[`"'']') { $url = $matches[1] }
}
if (-not $altText.Trim() -and $url) {
$brokenReferences += Add-BrokenReference $relPath ($lineNumber + 1) $match.Value "[$($pattern.Type)] Missing alt text for image" "Image"
} elseif ($url -and $altText) {
# Validate URL format and file existence
if ($url -match $expectedUrlPattern) {
$relativePathInUrl = $matches[1]
$fileNameFromUrl = [System.IO.Path]::GetFileNameWithoutExtension($relativePathInUrl)
if ($altText -ne $fileNameFromUrl) {
$brokenReferences += Add-BrokenReference $relPath ($lineNumber + 1) $match.Value "[$($pattern.Type)] Alt text `"$altText`" ≠ filename `"$fileNameFromUrl`"" "Image"
}
$expectedImagePath = Join-Path $PWD ($relativePathInUrl -replace "/", "\")
if (-not (Test-Path $expectedImagePath)) {
$brokenReferences += Add-BrokenReference $relPath ($lineNumber + 1) $match.Value "[$($pattern.Type)] Image not found at path: $relativePathInUrl" "Image"
}
} else {
$urlIssues = @()
if (-not $url.StartsWith('https://github.com/SoftFever/OrcaSlicer/blob/main/')) { $urlIssues += "URL must start with expected prefix" }
if (-not $url.EndsWith('?raw=true')) { $urlIssues += "URL must end with '?raw=true'" }
if ($url -match '^https?://(?!github\.com/SoftFever/OrcaSlicer)') { $urlIssues += "External URLs not allowed" }
$issueText = "[$($pattern.Type)] URL format issues: " + ($urlIssues -join '; ')
$brokenReferences += Add-BrokenReference $relPath ($lineNumber + 1) $match.Value $issueText "Image"
}
}
}
}
}
}
# Report results
$linkErrors = $brokenReferences | Where-Object { $_.Type -eq "Link" }
$imageErrors = $brokenReferences | Where-Object { $_.Type -eq "Image" }
if ($brokenReferences.Count -gt 0) {
Write-Host "::error::Documentation validation failed"
# Build error summary for PR comment
$errorSummary = ""
# Report link errors
if ($linkErrors) {
Write-Host "::group::🔗 Link Validation Errors"
$errorSummary += "## 🔗 Link Validation Errors`n`n"
$linkErrors | Group-Object SourceFile | ForEach-Object {
Write-Host "📄 $($_.Name):" -ForegroundColor Yellow
$errorSummary += "**📄 doc/$($_.Name):**`n"
$_.Group | Sort-Object Line | ForEach-Object {
Write-Host " Line $($_.Line): $($_.Target) - $($_.Issue)" -ForegroundColor Red
Write-Host "::error file=doc/$($_.SourceFile),line=$($_.Line)::$($_.Target) - $($_.Issue)"
$errorSummary += "- Line $($_.Line): ``$($_.Target)`` - $($_.Issue)`n"
}
$errorSummary += "`n"
}
Write-Host "::endgroup::"
}
# Report image errors
if ($imageErrors) {
Write-Host "::group::🖼️ Image Validation Errors"
$errorSummary += "## 🖼️ Image Validation Errors`n`n"
$imageErrors | Group-Object SourceFile | ForEach-Object {
Write-Host "📄 $($_.Name):" -ForegroundColor Yellow
$errorSummary += "**📄 doc/$($_.Name):**`n"
$_.Group | Sort-Object Line | ForEach-Object {
Write-Host " Line $($_.Line): $($_.Issue)" -ForegroundColor Red
Write-Host "::error file=doc/$($_.SourceFile),line=$($_.Line)::$($_.Issue)"
$errorSummary += "- Line $($_.Line): $($_.Issue)`n"
}
$errorSummary += "`n"
}
Write-Host "::endgroup::"
}
# Export error summary for PR comment
Add-Content -Path $env:GITHUB_ENV -Value "VALIDATION_ERRORS<<EOF"
Add-Content -Path $env:GITHUB_ENV -Value $errorSummary
Add-Content -Path $env:GITHUB_ENV -Value "EOF"
exit 1
} else {
Write-Host "::notice::All documentation is valid!"
exit 0
}
- name: Comment on PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const validationErrors = process.env.VALIDATION_ERRORS || '';
const body = `❌ **Documentation validation failed**
${validationErrors || 'Please check the workflow logs for details about the validation errors.'}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})

View File

@@ -1,14 +0,0 @@
name: Publish to WinGet
on:
release:
types: [ released ]
jobs:
publish:
runs-on: windows-latest
steps:
- uses: vedantmgoyal9/winget-releaser@main
with:
identifier: SoftFever.OrcaSlicer
version: ${{ github.event.release.tag_name }}
token: ${{ secrets.WINGET_TOKEN }}
installers-regex: '\.exe$'

7
.gitignore vendored
View File

@@ -36,13 +36,6 @@ src/OrcaSlicer-doc/
/deps/DL_CACHE/
/deps/DL_CACHE
**/.flatpak-builder/
*.no-debug.yml
resources/profiles/user/default
*.code-workspace
deps_src/build/
test.js
/.cache/
.clangd
internal_docs/
*.flatpak
/flatpak-repo/

View File

@@ -9,43 +9,67 @@ OrcaSlicer is an open-source 3D slicer application forked from Bambu Studio, bui
## Build Commands
### Building on Windows
**Always use this command to build the project when testing build issues on Windows.**
```bash
cmake --build . --config %build_type% --target ALL_BUILD -- -m
# Build everything
build_release_vs2022.bat
# Build with debug symbols
build_release_vs2022.bat debug
# Build only dependencies
build_release_vs2022.bat deps
# Build only slicer (after deps are built)
build_release_vs2022.bat slicer
```
### Building on macOS
**Always use this command to build the project when testing build issues on macOS.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
# Build everything (dependencies and slicer)
./build_release_macos.sh
# Build only dependencies
./build_release_macos.sh -d
# Build only slicer (after deps are built)
./build_release_macos.sh -s
# Use Ninja generator for faster builds
./build_release_macos.sh -x
# Build for specific architecture
./build_release_macos.sh -a arm64 # or x86_64 or universal
# Build for specific macOS version target
./build_release_macos.sh -t 11.3
```
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
# First time setup - install system dependencies
./build_linux.sh -u
# Build dependencies and slicer
./build_linux.sh -dsi
# Build everything (alternative)
./build_linux.sh -dsi
# Individual options:
./build_linux.sh -d # dependencies only
./build_linux.sh -s # slicer only
./build_linux.sh -i # build AppImage
# Performance and debug options:
./build_linux.sh -j N # limit to N cores
./build_linux.sh -1 # single core build
./build_linux.sh -b # debug build
./build_linux.sh -c # clean build
./build_linux.sh -r # skip RAM/disk checks
./build_linux.sh -l # use Clang instead of GCC
```
### Build test:
**Always use this command to build the project when testing build issues on Windows.**
```bash
cmake --build . --config %build_type% --target ALL_BUILD -- -m
```
### Building on macOS
**Always use this command to build the project when testing build issues on macOS.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build --config RelWithDebInfo --target all --
```
### Build System
- Uses CMake with minimum version 3.13 (maximum 3.31.x on Windows)
@@ -83,10 +107,9 @@ cd build && ctest --output-on-failure
Run individual test suites:
```bash
# From build directory
ctest --test-dir ./tests/libslic3r/libslic3r_tests
ctest --test-dir ./tests/fff_print/fff_print_tests
ctest --test-dir ./tests/sla_print/sla_print_tests
# and so on
./tests/libslic3r/libslic3r_tests
./tests/fff_print/fff_print_tests
./tests/sla_print/sla_print_tests
```
## Architecture
@@ -231,4 +254,4 @@ ctest --test-dir ./tests/sla_print/sla_print_tests
- **Regression testing** important due to algorithm complexity
- **Performance benchmarks** help catch performance regressions
- **Memory leak** detection important for long-running GUI application
- **Cross-platform** testing required before releases
- **Cross-platform** testing required before releases

View File

@@ -1,20 +1,10 @@
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0")
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE)
endif()
cmake_minimum_required(VERSION 3.13)
# Verify that your CMake version is exactly 3.5 series or higher on windows
if ( (MSVC OR WIN32) AND (${CMAKE_VERSION} VERSION_LESS "3.5") )
message(FATAL_ERROR "CMake current version ${CMAKE_VERSION} is too old. Minimum required is 3.5.")
# Verify that your CMake version is exactly 3.31.x series or lower on windows
if ( ((MSVC) OR (WIN32)) AND (${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0") )
message(FATAL_ERROR "Only cmake versions between 3.13.x and 3.31.x is supported on windows. Detected version: ${CMAKE_VERSION}")
endif()
# The following line used to be in tests/CMakeLists.txt
# Having it there causes rebuilds of all targets on any CMakeLists.txt change under tests/
# It has no effect on how code is compiled or linked.
# It just lets you later do `set_property(TARGET foo PROPERTY FOLDER "bar")`
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if (WIN32)
# Detect known CI environments
set(IS_CI FALSE)
@@ -65,11 +55,6 @@ endif ()
project(OrcaSlicer)
# Backward compatibility for old CMake versions
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_VERSION VERSION_LESS "3.25")
set(LINUX ON CACHE BOOL "" FORCE)
endif ()
include("version.inc")
include(GNUInstallDirs)
include(CMakeDependentOption)
@@ -82,29 +67,8 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE)
endif()
if (DEFINED BBL_RELEASE_TO_PUBLIC)
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=${BBL_RELEASE_TO_PUBLIC}")
else ()
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=$<CONFIG:Release>")
endif ()
find_package(Git)
if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "")
message(STATUS "Specified git commit hash: $ENV{git_commit_hash}")
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# Convert the given hash to short hash
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short "$ENV{git_commit_hash}"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
else()
# No .git directory (e.g., Flatpak sandbox) — truncate directly
string(SUBSTRING "$ENV{git_commit_hash}" 0 7 GIT_COMMIT_HASH)
endif()
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# Check current Git commit hash
execute_process(
COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
@@ -116,9 +80,13 @@ elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
endif()
if(DEFINED ENV{SLIC3R_STATIC})
set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC})
set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC})
else()
set(SLIC3R_STATIC_INITIAL 1)
if (MSVC OR MINGW OR APPLE)
set(SLIC3R_STATIC_INITIAL 1)
else()
set(SLIC3R_STATIC_INITIAL 0)
endif()
endif()
option(SLIC3R_STATIC "Compile OrcaSlicer with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL})
@@ -129,13 +97,14 @@ option(SLIC3R_PROFILE "Compile OrcaSlicer with an invasive Shiny profiler" 0)
option(SLIC3R_PCH "Use precompiled headers" 1)
option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0)
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable.
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow performing desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow perfoming desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.")
set(SLIC3R_GTK "3" CACHE STRING "GTK version to use with wxWidgets on Linux")
set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
set(IS_CROSS_COMPILE FALSE)
@@ -149,10 +118,6 @@ if (${COLORED_OUTPUT})
endif ()
if (APPLE)
list(LENGTH CMAKE_OSX_ARCHITECTURES _arch_len)
if (_arch_len GREATER 1)
message(FATAL_ERROR "OrcaSlicer only supports building for one architecture at a time. Please make sure only one architecture is specified in CMAKE_OSX_ARCHITECTURES")
endif ()
set(CMAKE_FIND_FRAMEWORK LAST)
set(CMAKE_FIND_APPBUNDLE LAST)
list(FIND CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} _arch_idx)
@@ -162,7 +127,7 @@ if (APPLE)
if (CMAKE_MACOSX_BUNDLE)
set(CMAKE_INSTALL_RPATH @executable_path/../Frameworks)
endif()
SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer")
SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.softfever3d.orca-slicer")
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
endif ()
@@ -172,13 +137,9 @@ option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
option(BUILD_TESTS "Build unit tests" OFF)
option(ORCA_TOOLS "Build Orca tools" OFF)
if (FLATPAK)
set(SLIC3R_FHS ON CACHE BOOL "" FORCE)
set(SLIC3R_DESKTOP_INTEGRATION OFF CACHE BOOL "" FORCE)
endif ()
if (IS_CROSS_COMPILE)
message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!")
set(SLIC3R_PERL_XS OFF CACHE BOOL "" FORCE)
set(BUILD_TESTS OFF CACHE BOOL "" FORCE)
endif ()
@@ -199,13 +160,6 @@ if(SLIC3R_DESKTOP_INTEGRATION)
add_definitions(-DSLIC3R_DESKTOP_INTEGRATION)
endif ()
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
message(STATUS "Automatically setting CMAKE_INSTALL_PREFIX")
set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "${CMAKE_BINARY_DIR}/OrcaSlicer")
endif()
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(IS_CLANG_CL TRUE)
@@ -251,61 +205,24 @@ if (NOT MSVC)
add_compile_options(-fsigned-char)
endif ()
if ("${DEP_BUILD_DIR}" STREQUAL "")
get_filename_component(BIN_DIR_NAME ${CMAKE_BINARY_DIR} NAME)
if (APPLE AND BIN_DIR_NAME STREQUAL "${CMAKE_OSX_ARCHITECTURES}")
file(RELATIVE_PATH BIN_DIR_NAME ${CMAKE_BINARY_DIR}/../.. ${CMAKE_BINARY_DIR})
endif ()
set(DEP_BUILD_DIR "${CMAKE_SOURCE_DIR}/deps/${BIN_DIR_NAME}" CACHE PATH "Path to dependencies build directory" FORCE)
message(STATUS "DEP_BUILD_DIR: ${DEP_BUILD_DIR} (generated automatically and saved to cache)")
set(AUTOGENERATED_DEP_BUILD_DIR ${DEP_BUILD_DIR} CACHE PATH "Provides the last autogenerated DEP_BUILD_DIR" FORCE)
else ()
message(STATUS "DEP_BUILD_DIR: ${DEP_BUILD_DIR} (from cache or command line)")
endif ()
if ("${CMAKE_PREFIX_PATH}" STREQUAL "" OR "${CMAKE_PREFIX_PATH}" STREQUAL "${AUTOGENERATED_PREFIX_PATH}")
if (DEFINED AUTOGENERATED_DEP_BUILD_DIR AND NOT "${DEP_BUILD_DIR}" STREQUAL "${AUTOGENERATED_DEP_BUILD_DIR}")
message(STATUS "CMAKE_PREFIX_PATH is being re-generated due to DEP_BUILD_DIR being manually updated")
set(REGEN_DESTDIR TRUE)
unset(AUTOGENERATED_DEP_BUILD_DIR CACHE)
endif ()
else ()
unset(AUTOGENERATED_PREFIX_PATH CACHE)
endif ()
# Display and check CMAKE_PREFIX_PATH
if ("${CMAKE_PREFIX_PATH}" STREQUAL "" OR REGEN_DESTDIR)
set(CMAKE_PREFIX_PATH "${DEP_BUILD_DIR}/OrcaSlicer_dep/usr/local" CACHE PATH "Path to dependencies install directory" FORCE)
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH} (generated automatically and saved to cache)")
set(AUTOGENERATED_PREFIX_PATH ${CMAKE_PREFIX_PATH} CACHE STRING "Provides the last autogenerated CMAKE_PREFIX_PATH" FORCE)
unset(REGEN_DESTDIR CACHE)
else ()
message(STATUS "SLIC3R_STATIC: ${SLIC3R_STATIC}")
if (NOT "${CMAKE_PREFIX_PATH}" STREQUAL "")
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH} (from cache or command line)")
endif ()
set(PREFIX_PATH_CHECK ${CMAKE_PREFIX_PATH})
# the CMAKE_PREFIX_PATH environment variable is separate from the CMAKE_PREFIX_PATH cache variable and provides additional paths to search for libraries.
if (NOT "$ENV{CMAKE_PREFIX_PATH}" STREQUAL "")
set(PREFIX_PATH_CHECK ${CMAKE_PREFIX_PATH})
elseif (NOT "$ENV{CMAKE_PREFIX_PATH}" STREQUAL "")
message(STATUS "CMAKE_PREFIX_PATH: $ENV{CMAKE_PREFIX_PATH} (from environment)")
list(APPEND PREFIX_PATH_CHECK $ENV{CMAKE_PREFIX_PATH})
set(PREFIX_PATH_CHECK $ENV{CMAKE_PREFIX_PATH})
else ()
message(STATUS "CMAKE_PREFIX_PATH: (default)")
endif ()
# Check all directories in CMAKE_PREFIX_PATH variables
foreach (DIR ${PREFIX_PATH_CHECK})
if (NOT EXISTS "${DIR}")
message(WARNING "CMAKE_PREFIX_PATH element doesn't exist: ${DIR}")
endif ()
endforeach ()
if (APPLE)
set(CMAKE_MACOSX_RPATH ON CACHE BOOL "")
set(CMAKE_MACOSX_BUNDLE ON CACHE BOOL "")
endif ()
if (APPLE AND CMAKE_MACOSX_RPATH AND "${CMAKE_INSTALL_RPATH}" STREQUAL "")
set(CMAKE_INSTALL_RPATH ${CMAKE_PREFIX_PATH})
endif ()
# Add our own cmake module path.
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
@@ -346,11 +263,7 @@ if(WIN32)
endif()
else()
# Try to use the default Windows 10 SDK path.
if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion})
set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}")
else ()
set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0")
endif ()
set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}")
if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h")
message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found")
message("STL fixing by the Netfabb service will not be compiled")
@@ -435,11 +348,7 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
endif()
if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wno-error=enum-constexpr-conversion HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
add_compile_options(-Wno-error=enum-constexpr-conversion)
endif()
add_compile_options(-Wno-error=enum-constexpr-conversion)
endif()
#GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see
@@ -454,12 +363,9 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
add_compile_options(-Wno-unknown-pragmas)
endif()
# Compress the debug info with zstd to save space in Flatpak CI builds
if(FLATPAK)
if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 13.0) OR
("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0))
add_compile_options(-gz=zstd)
endif()
# Bit of a hack for flatpak building: compress the debug info with zstd to save space in CI
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 13.0)
add_compile_options(-gz=zstd)
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14)
@@ -493,8 +399,7 @@ if (APPLE)
endif ()
if(MSVC)
# Ignore truncating casts in initializers & constructors
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4305
# 添加编译选项,忽略警告 C4305 (格式转换截断)
add_compile_options(/wd4305)
endif()
@@ -582,7 +487,6 @@ endif()
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()
set(Boost_NO_SYSTEM_PATHS TRUE)
find_package(Boost 1.83.0 REQUIRED COMPONENTS system filesystem thread log log_setup locale regex chrono atomic date_time iostreams program_options nowide)
add_library(boost_libs INTERFACE)
@@ -700,10 +604,6 @@ find_package(PNG REQUIRED)
set(OpenGL_GL_PREFERENCE "LEGACY")
find_package(OpenGL REQUIRED)
if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set(OPENGL_LIBRARIES "-framework OpenGL" CACHE STRING "OpenGL framework" FORCE)
endif()
set(GLEW_ROOT "${CMAKE_PREFIX_PATH}")
message("GLEW_ROOT: ${GLEW_ROOT}")
# Find glew or use bundled version
@@ -738,7 +638,7 @@ add_custom_target(gettext_make_pot
)
add_custom_target(gettext_merge_po_with_pot
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Merge localization po with new generated pot file"
COMMENT "Merge localization po with new generted pot file"
)
file(GLOB BBL_L10N_PO_FILES "${BBL_L18N_DIR}/*/OrcaSlicer*.po")
foreach(po_file ${BBL_L10N_PO_FILES})
@@ -903,6 +803,13 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT O
add_dependencies(gettext_make_pot hintsToPot)
# Perl bindings, currently only used for the unit / integration tests of libslic3r.
# Also runs the unit / integration tests.
#FIXME Port the tests into C++ to finally get rid of the Perl!
if (SLIC3R_PERL_XS)
add_subdirectory(xs)
endif ()
if(SLIC3R_BUILD_SANDBOXES)
add_subdirectory(sandboxes)
endif()
@@ -952,7 +859,7 @@ set (CPACK_PACKAGE_VERSION_MINOR "${ORCA_VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${ORCA_VERSION_PATCH}")
set (CPACK_PACKAGE_FILE_NAME "OrcaSlicer_Windows_Installer_V${SoftFever_VERSION}")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Orca Slicer is an open source slicer for FDM printers")
set (CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/OrcaSlicer/OrcaSlicer")
set (CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/SoftFever/OrcaSlicer")
set (CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
set (CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/resources/images\\\\OrcaSlicer.ico")
set (CPACK_NSIS_MUI_ICON "${CPACK_PACKAGE_ICON}")

View File

@@ -4,9 +4,9 @@
<img alt="OrcaSlicer logo" src="resources/images/OrcaSlicer.png" width="15%" height="15%">
</picture>
<a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/952" target="_blank"><img src="https://trendshift.io/api/badge/repositories/952" alt="SoftFever%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
[![GitHub Repo stars](https://img.shields.io/github/stars/SoftFever/OrcaSlicer)](https://github.com/SoftFever/OrcaSlicer/stargazers) [![Build all](https://github.com/SoftFever/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/SoftFever/OrcaSlicer/actions/workflows/build_all.yml)
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
@@ -18,7 +18,7 @@ Optimize your prints with ultra-fast slicing, intelligent support generation, an
<a href="https://www.orcaslicer.com/" style="font-size:2em;">OrcaSlicer.com</a>
#### Github Repository:
<a href="https://github.com/OrcaSlicer/OrcaSlicer"><img src="https://img.shields.io/badge/OrcaSlicer-181717?style=flat&logo=github&logoColor=white" width="200" alt="GitHub Logo"/> </a>
<a href="https://github.com/SoftFever/OrcaSlicer"><img src="https://img.shields.io/badge/OrcaSlicer-181717?style=flat&logo=github&logoColor=white" width="200" alt="GitHub Logo"/> </a>
#### Follow us:
<a href="https://twitter.com/real_OrcaSlicer"><img src="https://img.shields.io/badge/real__OrcaSlicer-000000?style=flat&logo=x&logoColor=white" width="200" alt="X Logo"/> </a>
@@ -29,13 +29,10 @@ Optimize your prints with ultra-fast slicing, intelligent support generation, an
<table border="2" style="border-color: #ffa500; background-color:rgb(232, 220, 180); color: #856404;">
<tr>
<td>
<strong>⚠️ CAUTION:</strong><br>
Several clickbait and malicious websites, such as <b>orca-slicer[.]com</b> and <b>orcaslicer[.]net</b>, are pretending to be the official OrcaSlicer site. These sites may redirect you to dangerous downloads or contain misleading information.<br>
<b>Our only official website is <a href="https://www.orcaslicer.com/">www.orcaslicer.com</a>.</b><br><br>
If you come across any of these in search results, please <b>report them</b> as unsafe or phishing to help keep the community secure with:<br>
- <a href="https://safebrowsing.google.com/safebrowsing/report_phish/">Google Safe Browsing</a><br>
- <a href="https://www.microsoft.com/en-us/wdsi/support/report-unsafe-site">Microsoft Security Intelligence</a><br>
- <a href="https://ipthreat.net/tools/reportphishing">IPThreat</a>
<strong>⚠️ CAUTION:</strong><br><br>
There are several clickbait and malicious websites pretending to be Official OrcaSlicer. These sites may redirect you to dangerous downloads or contain misleading information.
<br><br>
If you come across any of these in search results, please <a href="https://safebrowsing.google.com/safebrowsing/report_phish/?">report them as unsafe or phishing</a> to help keep the community secure.
</td>
</tr>
</table>
@@ -44,62 +41,64 @@ If you come across any of these in search results, please <b>report them</b> as
# Main features
- **[Advanced Calibration Tools](https://www.orcaslicer.com/wiki/Calibration)**
- **[Advanced Calibration Tools](https://github.com/SoftFever/OrcaSlicer/wiki/Calibration)**
Comprehensive suite: temperature towers, flow rate, retraction & more for optimal performance.
- **[Precise Wall](https://www.orcaslicer.com/wiki/quality_settings_precision#precise-wall) and [Seam Control](https://www.orcaslicer.com/wiki/quality_settings_seam)**
- **[Precise Wall](https://github.com/SoftFever/OrcaSlicer/wiki/quality_settings_precision#precise-wall) and [Seam Control](https://github.com/SoftFever/OrcaSlicer/wiki/quality_settings_seam)**
Adjust outer wall spacing and apply scarf seams to enhance print accuracy.
- **[Sandwich Mode](https://www.orcaslicer.com/wiki/quality_settings_wall_and_surfaces#innerouterinner) and [Polyholes](https://www.orcaslicer.com/wiki/quality_settings_precision#polyholes) Support**
Use varied infill [patterns](https://www.orcaslicer.com/wiki/strength_settings_patterns) and accurate hole shapes for improved clarity.
- **[Overhang](https://www.orcaslicer.com/wiki/quality_settings_overhangs) and [Support Optimization](https://www.orcaslicer.com/wiki#support-settings)**
- **[Sandwich Mode](https://github.com/SoftFever/OrcaSlicer/wiki/quality_settings_wall_and_surfaces#innerouterinner) and [Polyholes](https://github.com/SoftFever/OrcaSlicer/wiki/quality_settings_precision#polyholes) Support**
Use varied infill [patterns](https://github.com/SoftFever/OrcaSlicer/wiki/strength_settings_patterns) and accurate hole shapes for improved clarity.
- **[Overhang](https://github.com/SoftFever/OrcaSlicer/wiki/quality_settings_overhangs) and [Support Optimization](https://github.com/SoftFever/OrcaSlicer/wiki#support-settings)**
Modify geometry for printable overhangs with precise support placement.
- **[Granular Controls and Customization](https://www.orcaslicer.com/wiki#process-settings)**
- **[Granular Controls](https://github.com/SoftFever/OrcaSlicer/wiki#process-settings) and Customization**
Fine-tune print speed, layer height, pressure, and temperature with precision.
- **Network Printer Support**
Seamless integration with Klipper, PrusaLink, and OctoPrint for remote control.
- **[Mouse Ear Brims](https://www.orcaslicer.com/wiki/others_settings_brim) & [Adaptive Bed Mesh](https://www.orcaslicer.com/wiki/printer_basic_information_adaptive_bed_mesh)**
- **[Mouse Ear Brims](https://github.com/SoftFever/OrcaSlicer/wiki/others_settings_brim) & Adaptive Bed Mesh**
Automatic brims and adaptive mesh calibration ensure consistent adhesion.
- **User-Friendly Interface**
Intuitive drag-and-drop design with pre-made profiles for popular printers.
- **[Open-Source](https://github.com/OrcaSlicer/OrcaSlicer) & [Community Driven](https://discord.gg/P4VE9UY9gJ)**
- **[Open-Source](https://github.com/SoftFever/OrcaSlicer) & [Community Driven](https://discord.gg/P4VE9UY9gJ)**
Regular updates fueled by continuous community contributions.
- **Wide Printer Compatibility**
Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more.
- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
- Additional features can be found in the [change notes](https://github.com/SoftFever/OrcaSlicer/releases/).
# Wiki
The [wiki](https://www.orcaslicer.com/wiki) aims to provide a detailed explanation of the slicer settings, including how to maximize their use and how to calibrate and set up your printer.
The wiki below aims to provide a detailed explanation of the slicer settings, including how to maximize their use and how to calibrate and set up your printer.
- **[Access the wiki here](https://www.orcaslicer.com/wiki)**
- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/How-to-wiki)**
Please note that the wiki is a work in progress. We appreciate your patience as we continue to develop and improve it!
- **[Access the wiki here](https://github.com/SoftFever/OrcaSlicer/wiki)**
- **[Contribute to the wiki](https://github.com/SoftFever/OrcaSlicer/wiki/How-to-wiki)**
# Download
## Stable Release
📥 **[Download the Latest Stable Release](https://github.com/OrcaSlicer/OrcaSlicer/releases/latest)**
📥 **[Download the Latest Stable Release](https://github.com/SoftFever/OrcaSlicer/releases/latest)**
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users.
## Nightly Builds
🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds)**
🌙 **[Download the Latest Nightly Build](https://github.com/SoftFever/OrcaSlicer/releases/tag/nightly-builds)**
Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated.
# How to install
## Windows
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/SoftFever/OrcaSlicer/releases).
- *For convenience there is also a portable build available.*
<details>
<summary>Troubleshooting</summary>
- *If you have troubles to run the build, you might need to install following runtimes:*
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/SoftFever/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [Details of this runtime](https://aka.ms/webview2)
- [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703)
- [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [vcredist2019_x64](https://github.com/SoftFever/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe)
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
</details>
@@ -144,7 +143,7 @@ winget install --id=SoftFever.OrcaSlicer -e
# How to Compile
All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://www.orcaslicer.com/wiki/How-to-build) page.
All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://github.com/SoftFever/OrcaSlicer/wiki/How-to-build) page.
Please refer to the wiki to ensure you're following the latest and most accurate steps for your platform.
@@ -194,16 +193,20 @@ Thank you! :)
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://paypal.me/softfever3d"><img src="https://img.shields.io/badge/PayPal-003087?style=flat&logo=paypal&logoColor=fff" height="50"></a>
## Some Background
## Some background
Open-source slicing has always been built on a tradition of collaboration and attribution. [Slic3r](https://github.com/Slic3r/Slic3r), created by Alessandro Ranellucci and the RepRap community, laid the foundation. [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research built on Slic3r and acknowledged that heritage. [Bambu Studio](https://github.com/bambulab/BambuStudio) in turn forked from PrusaSlicer, and [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill extended PrusaSlicer with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.
OrcaSlicer was originally forked from Bambu Studio, it was previously known as BambuStudio-SoftFever.
OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and ideas inspired by CuraSlicer and SuperSlicer. But it has since grown far beyond its origins. Through relentless innovation — introducing advanced calibration tools, precise wall and seam control, tree supports, adaptive slicing, and hundreds of other features — OrcaSlicer has become the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.
The OrcaSlicer logo was designed by community member Justin Levine (@freejstnalxndr).
[Bambu Studio](https://github.com/bambulab/BambuStudio) is forked from [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research, which is from [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranellucci and the RepRap community.
OrcaSlicer incorporates a lot of features from [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill
OrcaSlicer's logo is designed by community member Justin Levine (@freejstnalxndr).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3. OrcaSlicer is based on Bambu Studio by BambuLab.
- **Bambu Studio** is licensed under the GNU Affero General Public License, version 3. Bambu Studio is based on PrusaSlicer by PrusaResearch.
- **PrusaSlicer** is licensed under the GNU Affero General Public License, version 3. PrusaSlicer is owned by Prusa Research. PrusaSlicer is originally based on Slic3r by Alessandro Ranellucci.
- **Slic3r** is licensed under the GNU Affero General Public License, version 3. Slic3r was created by Alessandro Ranellucci with the help of many other contributors.
- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license.
- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3.
- The **Bambu networking plugin** is based on non-free libraries from BambuLab. It is optional to the OrcaSlicer and provides extended functionalities for Bambulab printer users.

View File

@@ -5,7 +5,6 @@
# Based on the GitHub Actions workflow in .github/workflows/build_all.yml
set -e
SECONDS=0
# Colors for output
RED='\033[0;31m'
@@ -22,8 +21,6 @@ INSTALL_RUNTIME=false
JOBS=$(nproc)
FORCE_CLEAN=false
ENABLE_CCACHE=false
DISABLE_ROFILES_FUSE=false
NO_DEBUGINFO=true
CACHE_DIR=".flatpak-builder"
# Help function
@@ -39,8 +36,6 @@ show_help() {
echo " -c, --cleanup Clean build directory before building"
echo " -f, --force-clean Force clean build (disables caching)"
echo " --ccache Enable ccache for faster rebuilds (requires ccache in SDK)"
echo " --disable-rofiles-fuse Disable rofiles-fuse (workaround for FUSE issues)"
echo " --with-debuginfo Include debug info (slower builds, needed for Flathub)"
echo " --cache-dir DIR Flatpak builder cache directory [default: $CACHE_DIR]"
echo " -i, --install-runtime Install required Flatpak runtime and SDK"
echo " -h, --help Show this help message"
@@ -80,14 +75,6 @@ while [[ $# -gt 0 ]]; do
ENABLE_CCACHE=true
shift
;;
--disable-rofiles-fuse)
DISABLE_ROFILES_FUSE=true
shift
;;
--with-debuginfo)
NO_DEBUGINFO=false
shift
;;
--cache-dir)
CACHE_DIR="$2"
shift 2
@@ -199,22 +186,22 @@ echo -e "${GREEN}All required dependencies found${NC}"
# Install runtime and SDK if requested
if [[ "$INSTALL_RUNTIME" == true ]]; then
echo -e "${YELLOW}Installing GNOME runtime and SDK...${NC}"
flatpak install --user -y flathub org.gnome.Platform//49
flatpak install --user -y flathub org.gnome.Sdk//49
flatpak install --user -y flathub org.gnome.Platform//47
flatpak install --user -y flathub org.gnome.Sdk//47
fi
# Check if required runtime is available
if ! flatpak info --user org.gnome.Platform//49 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 49 runtime is not installed.${NC}"
if ! flatpak info --user org.gnome.Platform//47 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 47 runtime is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Platform//49"
echo "flatpak install --user flathub org.gnome.Platform//47"
exit 1
fi
if ! flatpak info --user org.gnome.Sdk//49 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 49 is not installed.${NC}"
if ! flatpak info --user org.gnome.Sdk//47 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 47 is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Sdk//49"
echo "flatpak install --user flathub org.gnome.Sdk//47"
exit 1
fi
@@ -255,8 +242,8 @@ mkdir -p "$BUILD_DIR"
rm -rf "$BUILD_DIR/build-dir"
# Check if flatpak manifest exists
if [[ ! -f "./scripts/flatpak/com.orcaslicer.OrcaSlicer.yml" ]]; then
echo -e "${RED}Error: Flatpak manifest not found at scripts/flatpak/com.orcaslicer.OrcaSlicer.yml${NC}"
if [[ ! -f "./scripts/flatpak/io.github.softfever.OrcaSlicer.yml" ]]; then
echo -e "${RED}Error: Flatpak manifest not found at scripts/flatpak/io.github.softfever.OrcaSlicer.yml${NC}"
exit 1
fi
@@ -292,7 +279,6 @@ BUILDER_ARGS=(
--verbose
--state-dir="$CACHE_DIR"
--jobs="$JOBS"
--mirror-screenshots-url=https://dl.flathub.org/media/
)
# Add force-clean only if explicitly requested (disables caching)
@@ -309,40 +295,21 @@ if [[ "$ENABLE_CCACHE" == true ]]; then
echo -e "${GREEN}Using ccache for compiler caching${NC}"
fi
# Disable rofiles-fuse if requested (workaround for FUSE issues)
if [[ "$DISABLE_ROFILES_FUSE" == true ]]; then
BUILDER_ARGS+=(--disable-rofiles-fuse)
echo -e "${YELLOW}rofiles-fuse disabled${NC}"
fi
# Use a temp manifest with no-debuginfo if requested
MANIFEST="scripts/flatpak/com.orcaslicer.OrcaSlicer.yml"
if [[ "$NO_DEBUGINFO" == true ]]; then
MANIFEST="scripts/flatpak/com.orcaslicer.OrcaSlicer.no-debug.yml"
sed '/^build-options:/a\ no-debuginfo: true\n strip: true' \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml > "$MANIFEST"
echo -e "${YELLOW}Debug info disabled (using temp manifest)${NC}"
fi
if ! flatpak-builder \
"${BUILDER_ARGS[@]}" \
"$BUILD_DIR/build-dir" \
"$MANIFEST"; then
scripts/flatpak/io.github.softfever.OrcaSlicer.yml; then
echo -e "${RED}Error: flatpak-builder failed${NC}"
echo -e "${YELLOW}Check the build log above for details${NC}"
rm -f "scripts/flatpak/com.orcaslicer.OrcaSlicer.no-debug.yml"
exit 1
fi
# Clean up temp manifest
rm -f "scripts/flatpak/com.orcaslicer.OrcaSlicer.no-debug.yml"
# Create bundle
echo -e "${YELLOW}Creating Flatpak bundle...${NC}"
if ! flatpak build-bundle \
"$BUILD_DIR/repo" \
"$BUNDLE_NAME" \
com.orcaslicer.OrcaSlicer \
io.github.softfever.OrcaSlicer \
--arch="$ARCH"; then
echo -e "${RED}Error: Failed to create Flatpak bundle${NC}"
exit 1
@@ -361,10 +328,10 @@ echo -e "${BLUE}To install the Flatpak:${NC}"
echo -e "flatpak install --user $BUNDLE_NAME"
echo ""
echo -e "${BLUE}To run OrcaSlicer:${NC}"
echo -e "flatpak run com.orcaslicer.OrcaSlicer"
echo -e "flatpak run io.github.softfever.OrcaSlicer"
echo ""
echo -e "${BLUE}To uninstall:${NC}"
echo -e "flatpak uninstall --user com.orcaslicer.OrcaSlicer"
echo -e "flatpak uninstall --user io.github.softfever.OrcaSlicer"
echo ""
if [[ "$FORCE_CLEAN" != true ]]; then
echo -e "${BLUE}Cache Management:${NC}"
@@ -372,6 +339,3 @@ if [[ "$FORCE_CLEAN" != true ]]; then
echo -e "• To force a clean build: $0 -f"
echo -e "• To clean cache manually: rm -rf $CACHE_DIR"
fi
elapsed=$SECONDS
printf "\nBuild completed in %dh %dm %ds\n" $((elapsed/3600)) $((elapsed%3600/60)) $((elapsed%60))

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env bash
set -e # Exit immediately if a command exits with a non-zero status.
SECONDS=0
SCRIPT_NAME=$(basename "$0")
SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
@@ -8,115 +7,76 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
pushd "${SCRIPT_PATH}" > /dev/null
function usage() {
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
echo " -1: limit builds to one core (where possible)"
echo " -j N: limit builds to N cores (where possible)"
echo " -b: build in Debug mode"
echo " -b: build in debug mode"
echo " -c: force a clean build"
echo " -C: enable ANSI-colored compile output (GNU/Clang only)"
echo " -d: download and build dependencies in ./deps/ (build prerequisite)"
echo " -D: dry run"
echo " -e: build in RelWithDebInfo mode"
echo " -F: rebuild the cached Docker/Podman runner image from scratch when used with -g"
echo " -g: run the requested build steps inside a Docker/Podman Ubuntu 24.04 container similar to the GitHub Actions Linux runner"
echo " -h: prints this help text"
echo " -i: build the Orca Slicer AppImage (optional)"
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
echo " -r: skip RAM and disk checks (low RAM compiling)"
echo " -s: build the Orca Slicer (optional)"
echo " -t: build tests (optional), requires -s flag"
echo " -t: build tests (optional)"
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
echo " -l: use Clang instead of GCC (default: GCC)"
echo " -L: use ld.lld as linker (if available)"
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
echo " and then './${SCRIPT_NAME} -dsi'"
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
echo "Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first."
echo "Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version."
echo "To build with tests: './${SCRIPT_NAME} -st' or './${SCRIPT_NAME} -dst'"
}
SLIC3R_PRECOMPILED_HEADERS="ON"
unset name
BUILD_DIR=build
BUILD_CONFIG=Release
FORWARDED_ARGS=()
while getopts ":1j:bcCdDeFghiprstulL" opt ; do
while getopts ":1j:bcCdhiprstulL" opt ; do
case ${opt} in
1 )
export CMAKE_BUILD_PARALLEL_LEVEL=1
FORWARDED_ARGS+=("-1")
;;
j )
export CMAKE_BUILD_PARALLEL_LEVEL=$OPTARG
FORWARDED_ARGS+=("-j" "$OPTARG")
;;
b )
BUILD_DIR=build-dbg
BUILD_CONFIG=Debug
FORWARDED_ARGS+=("-b")
BUILD_DEBUG="1"
;;
c )
CLEAN_BUILD=1
FORWARDED_ARGS+=("-c")
;;
C )
COLORED_OUTPUT="-DCOLORED_OUTPUT=ON"
FORWARDED_ARGS+=("-C")
;;
d )
BUILD_DEPS="1"
FORWARDED_ARGS+=("-d")
;;
D )
DRY_RUN="1"
FORWARDED_ARGS+=("-D")
;;
e )
BUILD_DIR=build-dbginfo
BUILD_CONFIG=RelWithDebInfo
FORWARDED_ARGS+=("-e")
;;
F )
CLEAN_DOCKER_IMAGE="1"
;;
g )
USE_DOCKER="1"
;;
h ) usage
exit 1
;;
i )
BUILD_IMAGE="1"
FORWARDED_ARGS+=("-i")
;;
p )
SLIC3R_PRECOMPILED_HEADERS="OFF"
FORWARDED_ARGS+=("-p")
;;
r )
SKIP_RAM_CHECK="1"
FORWARDED_ARGS+=("-r")
;;
s )
BUILD_ORCA="1"
FORWARDED_ARGS+=("-s")
;;
t )
BUILD_TESTS="1"
FORWARDED_ARGS+=("-t")
;;
u )
export UPDATE_LIB="1"
FORWARDED_ARGS+=("-u")
;;
l )
USE_CLANG="1"
FORWARDED_ARGS+=("-l")
;;
L )
USE_LLD="1"
FORWARDED_ARGS+=("-L")
;;
* )
echo "Unknown argument '${opt}', aborting."
@@ -130,11 +90,6 @@ if [ ${OPTIND} -eq 1 ] ; then
exit 1
fi
if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] && [[ -z "${USE_DOCKER}" ]] ; then
echo "Error: -F requires -g."
exit 1
fi
function check_available_memory_and_disk() {
FREE_MEM_GB=$(free --gibi --total | grep 'Mem' | rev | cut --delimiter=" " --fields=1 | rev)
MIN_MEM_GB=10
@@ -157,292 +112,6 @@ function check_available_memory_and_disk() {
fi
}
function print_and_run() {
cmd=()
# Remove empty arguments, leading and trailing spaces
for item in "$@" ; do
if [[ -n $item ]]; then
cmd+=( "$(echo "${item}" | xargs)" )
fi
done
echo "${cmd[@]}"
if [[ -z "${DRY_RUN}" ]] ; then
"${cmd[@]}"
fi
}
function resolve_container_cli() {
if [[ -n "${ORCA_CONTAINER_CLI}" ]] ; then
if ! command -v "${ORCA_CONTAINER_CLI}" >/dev/null 2>&1 ; then
echo "Error: container runtime '${ORCA_CONTAINER_CLI}' was not found." >&2
exit 1
fi
echo "${ORCA_CONTAINER_CLI}"
return
fi
if command -v docker >/dev/null 2>&1 ; then
echo "docker"
return
fi
if command -v podman >/dev/null 2>&1 ; then
echo "podman"
return
fi
echo "Error: neither docker nor podman is available. Install one of them or set ORCA_CONTAINER_CLI." >&2
exit 1
}
function get_docker_runner_image() {
local base_image
local docker_cmake_version
local recipe_hash
local sanitized_base_image
local sanitized_cmake_version
if [[ -n "${ORCA_DOCKER_IMAGE}" ]] ; then
echo "${ORCA_DOCKER_IMAGE}"
return
fi
base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}"
docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}"
recipe_hash=$(find "${SCRIPT_PATH}/build_linux.sh" "${SCRIPT_PATH}/scripts/linux.d" -type f -print0 | sort -z | xargs -0 cat | sha256sum | cut -c1-12)
sanitized_base_image=$(echo "${base_image}" | tr '/:@' '---' | tr -cs 'A-Za-z0-9_.-' '-')
sanitized_cmake_version=$(echo "${docker_cmake_version:-system}" | tr -cs 'A-Za-z0-9_.-' '-')
echo "orcaslicer-linux-builder:${sanitized_base_image}-cmake-${sanitized_cmake_version}-${recipe_hash}"
}
function docker_runner_dockerfile() {
cat <<'EOF'
ARG BASE_IMAGE=ubuntu:24.04
FROM ${BASE_IMAGE}
ARG CMAKE_VERSION=4.3.0
ENV DEBIAN_FRONTEND=noninteractive
SHELL ["/bin/bash", "-c"]
RUN apt-get update && apt-get install -y sudo ca-certificates curl tar
COPY build_linux.sh /tmp/orcaslicer/build_linux.sh
COPY scripts/linux.d /tmp/orcaslicer/scripts/linux.d
WORKDIR /tmp/orcaslicer
RUN chmod +x ./build_linux.sh
RUN ./build_linux.sh -ur
RUN if [[ -n "${CMAKE_VERSION}" ]] ; then \
case "$(uname -m)" in \
x86_64|amd64) cmake_arch="x86_64" ;; \
aarch64|arm64) cmake_arch="aarch64" ;; \
*) cmake_arch="" ;; \
esac ; \
if [[ -n "${cmake_arch}" ]] ; then \
cmake_root="/opt/cmake-${CMAKE_VERSION}-linux-${cmake_arch}" ; \
if ! curl -fsSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.tar.gz" | tar -xz -C /opt ; then \
echo "Warning: failed to install CMake ${CMAKE_VERSION}; falling back to the distro cmake package." ; \
elif [[ -d "${cmake_root}" ]] ; then \
ln -sf "${cmake_root}/bin/"* /usr/local/bin/ ; \
fi ; \
else \
echo "Skipping GitHub Actions CMake install for unsupported architecture $(uname -m)." ; \
fi ; \
fi
RUN rm -rf /var/lib/apt/lists/* /tmp/orcaslicer
EOF
}
function ensure_docker_runner_image() {
local container_cli
local base_image
local runner_image
local docker_cmake_version
local image_exists="0"
local force_rebuild="0"
local -a build_cmd
container_cli="$1"
runner_image="$2"
base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}"
docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}"
if "${container_cli}" image inspect "${runner_image}" >/dev/null 2>&1 ; then
image_exists="1"
fi
if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] ; then
force_rebuild="1"
if [[ "${image_exists}" == "1" ]] ; then
echo "Removing cached container image ${runner_image} ..."
if [[ -z "${DRY_RUN}" ]] ; then
"${container_cli}" image rm -f "${runner_image}" >/dev/null
else
printf '%q ' "${container_cli}" image rm -f "${runner_image}"
echo
fi
image_exists="0"
fi
fi
if [[ "${image_exists}" == "1" ]] ; then
echo "Using cached container image ${runner_image}"
return
fi
build_cmd=(
"${container_cli}" build --pull
-t "${runner_image}"
--build-arg "BASE_IMAGE=${base_image}"
--build-arg "CMAKE_VERSION=${docker_cmake_version}"
)
if [[ "${force_rebuild}" == "1" ]] ; then
build_cmd+=(--no-cache)
fi
build_cmd+=(-f - "${SCRIPT_PATH}")
printf '%q ' "${build_cmd[@]}"
echo
if [[ -n "${DRY_RUN}" ]] ; then
return
fi
docker_runner_dockerfile | "${build_cmd[@]}"
}
function run_in_docker() {
local container_cli
local runner_image
local container_workspace
local host_uid
local host_gid
local host_user
local -a build_args
local -a container_env
container_cli=$(resolve_container_cli)
runner_image=$(get_docker_runner_image)
host_uid=$(id -u)
host_gid=$(id -g)
host_user="${USER:-orca}"
container_workspace="/__w/OrcaSlicer/OrcaSlicer"
build_args=()
for item in "${FORWARDED_ARGS[@]}" ; do
if [[ "${item}" == "-u" ]] || [[ "${item}" == "-D" ]] ; then
continue
fi
build_args+=("${item}")
done
container_env=(
-e "CI=true"
-e "GITHUB_ACTIONS=true"
-e "GITHUB_WORKSPACE=${container_workspace}"
-e "GIT_CEILING_DIRECTORIES=${container_workspace}/deps/build"
-e "RUNNER_OS=Linux"
-e "RUNNER_TEMP=/tmp"
-e "HOST_UID=${host_uid}"
-e "HOST_GID=${host_gid}"
-e "HOST_USER=${host_user}"
)
if [[ -n "${CMAKE_BUILD_PARALLEL_LEVEL}" ]] ; then
container_env+=( -e "CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL}" )
fi
if [[ -n "${ORCA_UPDATER_SIG_KEY}" ]] ; then
container_env+=( -e "ORCA_UPDATER_SIG_KEY=${ORCA_UPDATER_SIG_KEY}" )
fi
ensure_docker_runner_image "${container_cli}" "${runner_image}"
printf '%q ' "${container_cli}" run --rm -i \
-v "${SCRIPT_PATH}:${container_workspace}" \
-w "${container_workspace}" \
"${container_env[@]}" \
"${runner_image}" \
bash -s -- "${build_args[@]}"
echo
if [[ -n "${DRY_RUN}" ]] ; then
return
fi
"${container_cli}" run --rm -i \
-v "${SCRIPT_PATH}:${container_workspace}" \
-w "${container_workspace}" \
"${container_env[@]}" \
"${runner_image}" \
bash -s -- "${build_args[@]}" <<'EOF'
set -e
function create_builder_user() {
if [[ "${HOST_UID}" == "0" ]] ; then
HOST_USER=root
return
fi
if getent group "${HOST_GID}" >/dev/null 2>&1 ; then
HOST_GROUP=$(getent group "${HOST_GID}" | cut -d: -f1)
else
HOST_GROUP="orca-builder"
if getent group "${HOST_GROUP}" >/dev/null 2>&1 ; then
HOST_GROUP="orca-builder-${HOST_GID}"
fi
groupadd -g "${HOST_GID}" "${HOST_GROUP}"
fi
if getent passwd "${HOST_UID}" >/dev/null 2>&1 ; then
HOST_USER=$(getent passwd "${HOST_UID}" | cut -d: -f1)
usermod -g "${HOST_GROUP}" "${HOST_USER}"
elif id -u "${HOST_USER}" >/dev/null 2>&1 ; then
usermod -u "${HOST_UID}" -g "${HOST_GROUP}" "${HOST_USER}"
else
useradd -m -u "${HOST_UID}" -g "${HOST_GROUP}" -s /bin/bash "${HOST_USER}"
fi
echo "${HOST_USER} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/orcaslicer-builder
chmod 0440 /etc/sudoers.d/orcaslicer-builder
}
create_builder_user
mkdir -p "${GITHUB_WORKSPACE}/deps/build/destdir"
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/deps/build"
if [[ -d "${GITHUB_WORKSPACE}/build" ]] ; then
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build"
fi
if [[ -d "${GITHUB_WORKSPACE}/build-dbg" ]] ; then
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbg"
fi
if [[ -d "${GITHUB_WORKSPACE}/build-dbginfo" ]] ; then
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbginfo"
fi
sudo -H -u "${HOST_USER}" env \
CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL-}" \
GITHUB_WORKSPACE="${GITHUB_WORKSPACE}" \
GIT_CEILING_DIRECTORIES="${GIT_CEILING_DIRECTORIES-}" \
ORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY-}" \
bash -c '
set -e
cd "${GITHUB_WORKSPACE}"
if [[ "$#" -gt 0 ]] ; then
./build_linux.sh "$@"
else
echo "No build steps were requested after container setup."
fi
' bash "$@"
EOF
}
if [[ -n "${USE_DOCKER}" ]] ; then
run_in_docker
popd > /dev/null # ${SCRIPT_PATH}
exit 0
fi
# cmake 4.x compatibility workaround
export CMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -468,7 +137,7 @@ else
source "./scripts/linux.d/${DISTRIBUTION}"
fi
echo "FOUND_GTK3_DEV=${FOUND_GTK3_DEV}"
echo "FOUND_GTK3=${FOUND_GTK3}"
if [[ -z "${FOUND_GTK3_DEV}" ]] ; then
echo "Error, you must install the dependencies before."
echo "Use option -u with sudo"
@@ -507,27 +176,51 @@ fi
if [[ -n "${BUILD_DEPS}" ]] ; then
echo "Configuring dependencies..."
read -r -a BUILD_ARGS <<< "${DEPS_EXTRA_BUILD_ARGS}"
BUILD_ARGS+=(-DDEP_WX_GTK3=ON)
if [[ -n "${CLEAN_BUILD}" ]]
then
print_and_run rm -fr deps/$BUILD_DIR
rm -fr deps/build
fi
mkdir -p deps/$BUILD_DIR
if [[ $BUILD_CONFIG != Release ]] ; then
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
mkdir -p deps/build
if [[ -n "${BUILD_DEBUG}" ]] ; then
# build deps with debug and release else cmake will not find required sources
mkdir -p deps/build/release
set -x
cmake -S deps -B deps/build/release "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G Ninja \
-DSLIC3R_PCH="${SLIC3R_PRECOMPILED_HEADERS}" \
-DDESTDIR="${SCRIPT_PATH}/deps/build/destdir" \
-DDEP_DOWNLOAD_DIR="${SCRIPT_PATH}/deps/DL_CACHE" \
"${COLORED_OUTPUT}" \
"${BUILD_ARGS[@]}"
set +x
cmake --build deps/build/release
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE=Debug)
fi
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
print_and_run cmake --build deps/$BUILD_DIR
set -x
cmake -S deps -B deps/build "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G Ninja \
-DSLIC3R_PCH="${SLIC3R_PRECOMPILED_HEADERS}" \
-DDESTDIR="${SCRIPT_PATH}/deps/build/destdir" \
-DDEP_DOWNLOAD_DIR="${SCRIPT_PATH}/deps/DL_CACHE" \
"${COLORED_OUTPUT}" \
"${BUILD_ARGS[@]}"
set +x
cmake --build deps/build
fi
if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
if [[ -n "${BUILD_ORCA}" ]] ; then
echo "Configuring OrcaSlicer..."
if [[ -n "${CLEAN_BUILD}" ]] ; then
print_and_run rm -fr $BUILD_DIR
rm -fr build
fi
read -r -a BUILD_ARGS <<< "${ORCA_EXTRA_BUILD_ARGS}"
if [[ $BUILD_CONFIG != Release ]] ; then
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
if [[ -n "${FOUND_GTK3_DEV}" ]] ; then
BUILD_ARGS+=(-DSLIC3R_GTK=3)
fi
if [[ -n "${BUILD_DEBUG}" ]] ; then
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE=Debug -DBBL_INTERNAL_TESTING=1)
else
BUILD_ARGS+=(-DBBL_RELEASE_TO_PUBLIC=1 -DBBL_INTERNAL_TESTING=0)
fi
if [[ -n "${BUILD_TESTS}" ]] ; then
BUILD_ARGS+=(-DBUILD_TESTS=ON)
@@ -536,42 +229,47 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
BUILD_ARGS+=(-DORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY}")
fi
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G "Ninja Multi-Config" \
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
-DORCA_TOOLS=ON \
"${COLORED_OUTPUT}" \
"${BUILD_ARGS[@]}"
echo "Configuring OrcaSlicer..."
set -x
cmake -S . -B build "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G "Ninja Multi-Config" \
-DSLIC3R_PCH="${SLIC3R_PRECOMPILED_HEADERS}" \
-DCMAKE_PREFIX_PATH="${SCRIPT_PATH}/deps/build/destdir/usr/local" \
-DSLIC3R_STATIC=1 \
-DORCA_TOOLS=ON \
"${COLORED_OUTPUT}" \
"${BUILD_ARGS[@]}"
set +x
echo "done"
if [[ -n "${BUILD_ORCA}" ]]; then
echo "Building OrcaSlicer ..."
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
echo "Building OrcaSlicer_profile_validator .."
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer_profile_validator
./scripts/run_gettext.sh
echo "Building OrcaSlicer ..."
if [[ -n "${BUILD_DEBUG}" ]] ; then
cmake --build build --config Debug --target OrcaSlicer
else
cmake --build build --config Release --target OrcaSlicer
fi
if [[ -n "${BUILD_TESTS}" ]] ; then
echo "Building tests ..."
print_and_run cmake --build ${BUILD_DIR} --config "${BUILD_CONFIG}" --target tests/all
echo "Building OrcaSlicer_profile_validator .."
if [[ -n "${BUILD_DEBUG}" ]] ; then
cmake --build build --config Debug --target OrcaSlicer_profile_validator
else
cmake --build build --config Release --target OrcaSlicer_profile_validator
fi
./scripts/run_gettext.sh
echo "done"
fi
if [[ -n "${BUILD_IMAGE}" || -n "${BUILD_ORCA}" ]] ; then
pushd $BUILD_DIR > /dev/null
pushd build > /dev/null
echo "[9/9] Generating Linux app..."
build_linux_image="./src/build_linux_image.sh"
if [[ -e ${build_linux_image} ]] ; then
extra_script_args=""
if [[ -n "${BUILD_IMAGE}" ]] ; then
extra_script_args="-i"
fi
print_and_run ${build_linux_image} ${extra_script_args} -R "${BUILD_CONFIG}"
${build_linux_image} ${extra_script_args}
echo "done"
fi
popd > /dev/null # build
fi
elapsed=$SECONDS
printf "\nBuild completed in %dh %dm %ds\n" $((elapsed/3600)) $((elapsed%3600/60)) $((elapsed%60))
popd > /dev/null # ${SCRIPT_PATH}

View File

@@ -31,8 +31,8 @@ if "%1"=="slicer" (
)
echo "building deps.."
echo cmake ../ -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake ../ -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=%build_type%
echo cmake ../ -G "Visual Studio 16 2019" -A x64 -DDESTDIR="%CD%/OrcaSlicer_dep" -DCMAKE_BUILD_TYPE=%build_type% -DDEP_DEBUG=%debug% -DORCA_INCLUDE_DEBUG_INFO=%debuginfo%
cmake ../ -G "Visual Studio 16 2019" -A x64 -DDESTDIR="%CD%/OrcaSlicer_dep" -DCMAKE_BUILD_TYPE=%build_type% -DDEP_DEBUG=%debug% -DORCA_INCLUDE_DEBUG_INFO=%debuginfo%
cmake --build . --config %build_type% --target deps -- -m
if "%1"=="deps" exit /b 0
@@ -43,8 +43,8 @@ cd %WP%
mkdir %build_dir%
cd %build_dir%
echo cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=%build_type% %SIG_FLAG%
echo cmake .. -G "Visual Studio 16 2019" -A x64 -DBBL_RELEASE_TO_PUBLIC=1 -DCMAKE_PREFIX_PATH="%DEPS%/usr/local" -DCMAKE_INSTALL_PREFIX="./OrcaSlicer" -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G "Visual Studio 16 2019" -A x64 -DBBL_RELEASE_TO_PUBLIC=1 %SIG_FLAG% -DCMAKE_PREFIX_PATH="%DEPS%/usr/local" -DCMAKE_INSTALL_PREFIX="./OrcaSlicer" -DCMAKE_BUILD_TYPE=%build_type% -DWIN10SDK_PATH="C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0"
cmake --build . --config %build_type% --target ALL_BUILD -- -m
cd ..
call scripts/run_gettext.bat

View File

@@ -2,9 +2,8 @@
set -e
set -o pipefail
SECONDS=0
while getopts ":dpa:snt:xbc:i:1Tuh" opt; do
while getopts ":dpa:snt:xbc:1h" opt; do
case "${opt}" in
d )
export BUILD_TARGET="deps"
@@ -35,31 +34,19 @@ while getopts ":dpa:snt:xbc:i:1Tuh" opt; do
c )
export BUILD_CONFIG="$OPTARG"
;;
i )
export CMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH:+$CMAKE_IGNORE_PREFIX_PATH;}$OPTARG"
;;
1 )
export CMAKE_BUILD_PARALLEL_LEVEL=1
;;
T )
export BUILD_TESTS="1"
;;
u )
export BUILD_TARGET="universal"
;;
h ) echo "Usage: ./build_release_macos.sh [-d]"
echo " -d: Build deps only"
echo " -a: Set ARCHITECTURE (arm64 or x86_64 or universal)"
echo " -s: Build slicer only"
echo " -u: Build universal app only (requires existing arm64 and x86_64 app bundles)"
echo " -n: Nightly build"
echo " -t: Specify minimum version of the target platform, default is 11.3"
echo " -x: Use Ninja Multi-Config CMake generator, default is Xcode"
echo " -b: Build without reconfiguring CMake"
echo " -c: Set CMake build configuration, default is Release"
echo " -i: Add a prefix to ignore during CMake dependency discovery (repeatable), defaults to /opt/local:/usr/local:/opt/homebrew"
echo " -1: Use single job for building"
echo " -T: Build and run tests"
exit 0
;;
* )
@@ -98,26 +85,12 @@ if [ -z "$OSX_DEPLOYMENT_TARGET" ]; then
export OSX_DEPLOYMENT_TARGET="11.3"
fi
if [ -z "$CMAKE_IGNORE_PREFIX_PATH" ]; then
export CMAKE_IGNORE_PREFIX_PATH="/opt/local:/usr/local:/opt/homebrew"
fi
CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then
export CMAKE_POLICY_VERSION_MINIMUM=3.5
export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
echo "Detected CMake 4.x, adding compatibility flag (env + cmake arg)"
else
export CMAKE_POLICY_COMPAT=""
fi
echo "Build params:"
echo " - ARCH: $ARCH"
echo " - BUILD_CONFIG: $BUILD_CONFIG"
echo " - BUILD_TARGET: $BUILD_TARGET"
echo " - CMAKE_GENERATOR: $SLICER_CMAKE_GENERATOR for Slicer, $DEPS_CMAKE_GENERATOR for deps"
echo " - OSX_DEPLOYMENT_TARGET: $OSX_DEPLOYMENT_TARGET"
echo " - CMAKE_IGNORE_PREFIX_PATH: $CMAKE_IGNORE_PREFIX_PATH"
echo
# if which -s brew; then
@@ -136,6 +109,8 @@ echo
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_BUILD_DIR="$PROJECT_DIR/build/$ARCH"
DEPS_DIR="$PROJECT_DIR/deps"
DEPS_BUILD_DIR="$DEPS_DIR/build/$ARCH"
DEPS="$DEPS_BUILD_DIR/OrcaSlicer_deps"
# For Multi-config generators like Ninja and Xcode
export BUILD_DIR_CONFIG_SUBDIR="/$BUILD_CONFIG"
@@ -158,11 +133,11 @@ function build_deps() {
if [ "1." != "$BUILD_ONLY". ]; then
cmake "${DEPS_DIR}" \
-G "${DEPS_CMAKE_GENERATOR}" \
-DDESTDIR="$DEPS" \
-DOPENSSL_ARCH="darwin64-${_ARCH}-cc" \
-DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
${CMAKE_POLICY_COMPAT}
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}"
fi
cmake --build . --config "$BUILD_CONFIG" --target deps
)
@@ -197,27 +172,21 @@ function build_slicer() {
if [ "1." != "$BUILD_ONLY". ]; then
cmake "${PROJECT_DIR}" \
-G "${SLICER_CMAKE_GENERATOR}" \
-DBBL_RELEASE_TO_PUBLIC=1 \
-DORCA_TOOLS=ON \
${ORCA_UPDATER_SIG_KEY:+-DORCA_UPDATER_SIG_KEY="$ORCA_UPDATER_SIG_KEY"} \
${BUILD_TESTS:+-DBUILD_TESTS=ON} \
-DCMAKE_PREFIX_PATH="$DEPS/usr/local" \
-DCMAKE_INSTALL_PREFIX="$PWD/OrcaSlicer" \
-DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \
-DCMAKE_MACOSX_RPATH=ON \
-DCMAKE_INSTALL_RPATH="${DEPS}/usr/local" \
-DCMAKE_MACOSX_BUNDLE=ON \
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
${CMAKE_POLICY_COMPAT}
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}"
fi
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
)
if [ "1." == "$BUILD_TESTS". ]; then
echo "Running tests for $_ARCH..."
(
set -x
cd "$PROJECT_BUILD_DIR"
ctest --build-config "$BUILD_CONFIG" --output-on-failure
)
fi
echo "Verify localization with gettext..."
(
cd "$PROJECT_DIR"
@@ -265,54 +234,48 @@ function build_slicer() {
done
}
function lipo_dir() {
local universal_dir="$1"
local x86_64_dir="$2"
# Find all Mach-O files in the universal (arm64-based) copy and lipo them
while IFS= read -r -d '' f; do
local rel="${f#"$universal_dir"/}"
local x86="$x86_64_dir/$rel"
if [ -f "$x86" ]; then
echo " lipo: $rel"
lipo -create "$f" "$x86" -output "$f.tmp"
mv "$f.tmp" "$f"
else
echo " warning: no x86_64 counterpart for $rel, keeping arm64 only"
fi
done < <(find "$universal_dir" -type f -print0 | while IFS= read -r -d '' candidate; do
if file "$candidate" | grep -q "Mach-O"; then
printf '%s\0' "$candidate"
fi
done)
}
function build_universal() {
echo "Building universal binary..."
PROJECT_BUILD_DIR="$PROJECT_DIR/build/$ARCH"
ARM64_APP="$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app"
X86_64_APP="$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer.app"
# Create universal binary
echo "Creating universal binary..."
# PROJECT_BUILD_DIR="$PROJECT_DIR/build_Universal"
mkdir -p "$PROJECT_BUILD_DIR/OrcaSlicer"
UNIVERSAL_APP="$PROJECT_BUILD_DIR/OrcaSlicer/OrcaSlicer.app"
rm -rf "$UNIVERSAL_APP"
cp -R "$ARM64_APP" "$UNIVERSAL_APP"
echo "Creating universal binaries for OrcaSlicer.app..."
lipo_dir "$UNIVERSAL_APP" "$X86_64_APP"
echo "Universal OrcaSlicer.app created at $UNIVERSAL_APP"
cp -R "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app" "$UNIVERSAL_APP"
# Get the binary path inside the .app bundle
BINARY_PATH="Contents/MacOS/OrcaSlicer"
# Create universal binary using lipo
lipo -create \
"$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer.app/$BINARY_PATH" \
"$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app/$BINARY_PATH" \
-output "$UNIVERSAL_APP/$BINARY_PATH"
echo "Universal binary created at $UNIVERSAL_APP"
# Create universal binary for profile validator if it exists
ARM64_VALIDATOR="$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app"
X86_64_VALIDATOR="$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app"
if [ -d "$ARM64_VALIDATOR" ] && [ -d "$X86_64_VALIDATOR" ]; then
echo "Creating universal binaries for OrcaSlicer_profile_validator.app..."
if [ -f "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ] && \
[ -f "$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
echo "Creating universal binary for OrcaSlicer_profile_validator..."
UNIVERSAL_VALIDATOR_APP="$PROJECT_BUILD_DIR/OrcaSlicer/OrcaSlicer_profile_validator.app"
rm -rf "$UNIVERSAL_VALIDATOR_APP"
cp -R "$ARM64_VALIDATOR" "$UNIVERSAL_VALIDATOR_APP"
lipo_dir "$UNIVERSAL_VALIDATOR_APP" "$X86_64_VALIDATOR"
echo "Universal OrcaSlicer_profile_validator.app created at $UNIVERSAL_VALIDATOR_APP"
cp -R "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app" "$UNIVERSAL_VALIDATOR_APP"
# Get the binary path inside the profile validator .app bundle
VALIDATOR_BINARY_PATH="Contents/MacOS/OrcaSlicer_profile_validator"
# Create universal binary using lipo
lipo -create \
"$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app/$VALIDATOR_BINARY_PATH" \
"$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/$VALIDATOR_BINARY_PATH" \
-output "$UNIVERSAL_VALIDATOR_APP/$VALIDATOR_BINARY_PATH"
echo "Universal binary for OrcaSlicer_profile_validator created at $UNIVERSAL_VALIDATOR_APP"
fi
}
@@ -327,22 +290,16 @@ case "${BUILD_TARGET}" in
slicer)
build_slicer
;;
universal)
build_universal
;;
*)
echo "Unknown target: $BUILD_TARGET. Available targets: deps, slicer, universal, all."
echo "Unknown target: $BUILD_TARGET. Available targets: deps, slicer, all."
exit 1
;;
esac
if [ "$ARCH" = "universal" ] && { [ "$BUILD_TARGET" = "all" ] || [ "$BUILD_TARGET" = "slicer" ]; }; then
if [ "$ARCH" = "universal" ] && [ "$BUILD_TARGET" != "deps" ]; then
build_universal
fi
if [ "1." == "$PACK_DEPS". ]; then
pack_deps
fi
elapsed=$SECONDS
printf "\nBuild completed in %dh %dm %ds\n" $((elapsed/3600)) $((elapsed%3600/60)) $((elapsed%60))

View File

@@ -1,158 +0,0 @@
@REM OrcaSlicer build script for Windows with VS auto-detect
@echo off
set WP=%CD%
set _START_TIME=%TIME%
@REM Check for Ninja Multi-Config option (-x)
set USE_NINJA=0
for %%a in (%*) do (
if "%%a"=="-x" set USE_NINJA=1
)
if "%USE_NINJA%"=="1" (
echo Using Ninja Multi-Config generator
set CMAKE_GENERATOR="Ninja Multi-Config"
set VS_VERSION=Ninja
goto :generator_ready
)
@REM Detect Visual Studio version using msbuild
echo Detecting Visual Studio version using msbuild...
@REM Try to get MSBuild version - the output format varies by VS version
set VS_MAJOR=
for /f "tokens=*" %%i in ('msbuild -version 2^>^&1 ^| findstr /r "^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"') do (
for /f "tokens=1 delims=." %%a in ("%%i") do set VS_MAJOR=%%a
set MSBUILD_OUTPUT=%%i
goto :version_found
)
@REM Alternative method for newer MSBuild versions
if "%VS_MAJOR%"=="" (
for /f "tokens=*" %%i in ('msbuild -version 2^>^&1 ^| findstr /r "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"') do (
for /f "tokens=1 delims=." %%a in ("%%i") do set VS_MAJOR=%%a
set MSBUILD_OUTPUT=%%i
goto :version_found
)
)
:version_found
echo MSBuild version detected: %MSBUILD_OUTPUT%
echo Major version: %VS_MAJOR%
if "%VS_MAJOR%"=="" (
echo Error: Could not determine Visual Studio version from msbuild
echo Please ensure Visual Studio and MSBuild are properly installed
exit /b 1
)
if "%VS_MAJOR%"=="16" (
set VS_VERSION=2019
set CMAKE_GENERATOR="Visual Studio 16 2019"
) else if "%VS_MAJOR%"=="17" (
set VS_VERSION=2022
set CMAKE_GENERATOR="Visual Studio 17 2022"
) else if "%VS_MAJOR%"=="18" (
set VS_VERSION=2026
set CMAKE_GENERATOR="Visual Studio 18 2026"
) else (
echo Error: Unsupported Visual Studio version: %VS_MAJOR%
echo Supported versions: VS2019 (16.x^), VS2022 (17.x^), VS2026 (18.x^)
exit /b 1
)
echo Detected Visual Studio %VS_VERSION% (version %VS_MAJOR%)
echo Using CMake generator: %CMAKE_GENERATOR%
:generator_ready
@REM Pack deps
if "%1"=="pack" (
setlocal ENABLEDELAYEDEXPANSION
cd %WP%/deps/build
for /f "tokens=2-4 delims=/ " %%a in ('date /t') do set build_date=%%c%%b%%a
echo packing deps: OrcaSlicer_dep_win64_!build_date!_vs!VS_VERSION!.zip
%WP%/tools/7z.exe a OrcaSlicer_dep_win64_!build_date!_vs!VS_VERSION!.zip OrcaSlicer_dep
goto :done
)
set debug=OFF
set debuginfo=OFF
if "%1"=="debug" set debug=ON
if "%2"=="debug" set debug=ON
if "%1"=="debuginfo" set debuginfo=ON
if "%2"=="debuginfo" set debuginfo=ON
if "%debug%"=="ON" (
set build_type=Debug
set build_dir=build-dbg
) else (
if "%debuginfo%"=="ON" (
set build_type=RelWithDebInfo
set build_dir=build-dbginfo
) else (
set build_type=Release
set build_dir=build
)
)
echo build type set to %build_type%
setlocal DISABLEDELAYEDEXPANSION
cd deps
mkdir %build_dir%
cd %build_dir%
set "SIG_FLAG="
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%"
if "%1"=="slicer" (
GOTO :slicer
)
echo "building deps.."
echo on
REM Set minimum CMake policy to avoid <3.5 errors
set CMAKE_POLICY_VERSION_MINIMUM=3.5
if "%USE_NINJA%"=="1" (
cmake ../ -G %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps
) else (
cmake ../ -G %CMAKE_GENERATOR% -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps -- -m
)
@echo off
if "%1"=="deps" goto :done
:slicer
echo "building Orca Slicer..."
cd %WP%
mkdir %build_dir%
cd %build_dir%
echo on
set CMAKE_POLICY_VERSION_MINIMUM=3.5
if "%USE_NINJA%"=="1" (
cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD
) else (
cmake .. -G %CMAKE_GENERATOR% -A x64 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD -- -m
)
@echo off
cd ..
call scripts/run_gettext.bat
cd %build_dir%
cmake --build . --target install --config %build_type%
:done
@echo off
for /f "tokens=1-3 delims=:.," %%a in ("%_START_TIME: =0%") do set /a "_start_s=%%a*3600+%%b*60+%%c"
for /f "tokens=1-3 delims=:.," %%a in ("%TIME: =0%") do set /a "_end_s=%%a*3600+%%b*60+%%c"
set /a "_elapsed=_end_s - _start_s"
if %_elapsed% lss 0 set /a "_elapsed+=86400"
set /a "_hours=_elapsed / 3600"
set /a "_remainder=_elapsed - _hours * 3600"
set /a "_mins=_remainder / 60"
set /a "_secs=_remainder - _mins * 60"
echo.
echo Build completed in %_hours%h %_mins%m %_secs%s

View File

@@ -37,6 +37,7 @@ setlocal DISABLEDELAYEDEXPANSION
cd deps
mkdir %build_dir%
cd %build_dir%
set DEPS=%CD%/OrcaSlicer_dep
set "SIG_FLAG="
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%"
@@ -46,9 +47,7 @@ if "%1"=="slicer" (
echo "building deps.."
echo on
REM Set minimum CMake policy to avoid <3.5 errors
set CMAKE_POLICY_VERSION_MINIMUM=3.5
cmake ../ -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake ../ -G "Visual Studio 17 2022" -A x64 -DDESTDIR="%DEPS%" -DCMAKE_BUILD_TYPE=%build_type% -DDEP_DEBUG=%debug% -DORCA_INCLUDE_DEBUG_INFO=%debuginfo%
cmake --build . --config %build_type% --target deps -- -m
@echo off
@@ -61,8 +60,7 @@ mkdir %build_dir%
cd %build_dir%
echo on
set CMAKE_POLICY_VERSION_MINIMUM=3.5
cmake .. -G "Visual Studio 17 2022" -A x64 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G "Visual Studio 17 2022" -A x64 -DBBL_RELEASE_TO_PUBLIC=1 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_PREFIX_PATH="%DEPS%/usr/local" -DCMAKE_INSTALL_PREFIX="./OrcaSlicer" -DCMAKE_BUILD_TYPE=%build_type% -DWIN10SDK_PATH="%WindowsSdkDir%Include\%WindowsSDKVersion%\"
cmake --build . --config %build_type% --target ALL_BUILD -- -m
@echo off
cd ..

View File

@@ -0,0 +1,175 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
Catch
-----
This module defines a function to help use the Catch test framework.
The :command:`catch_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Catch test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: catch_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
catch_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
)
``catch_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-names-only`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Catch executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the Catch executable with the ``--list-test-names-only`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``catch_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``catch_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest.
#]=======================================================================]
#------------------------------------------------------------------------------
function(catch_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX='${_TEST_PREFIX}'"
-D "TEST_SUFFIX='${_TEST_SUFFIX}'"
-D "TEST_LIST=${_TEST_LIST}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction()
###############################################################################
set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
)

View File

@@ -0,0 +1,106 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
macro(_add_catch_test_labels LINE)
# convert to list of tags
string(REPLACE "][" "]\\;[" tags ${line})
add_command(
set_tests_properties "${prefix}${test}${suffix}"
PROPERTIES
LABELS "${tags}"
)
endmacro()
macro(_add_catch_test LINE)
set(test ${line})
# use escape commas to handle properly test cases with commans inside the name
string(REPLACE "," "\\," test_name ${test})
# ...and add to script
add_command(
add_test "${prefix}${test}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"${test_name}"
${extra_args}
)
add_command(
set_tests_properties "${prefix}${test}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
)
list(APPEND tests "${prefix}${test}${suffix}")
endmacro()
# Run test executable to get list of available tests
if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
)
endif()
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests
OUTPUT_VARIABLE output
RESULT_VARIABLE result
)
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
if(${result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
)
elseif(${result} LESS 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n"
" Output: ${output}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
set(test)
set(tags_regex "(\\[([^\\[]*)\\])+$")
# Parse output
foreach(line ${output})
# lines without leading whitespaces are catch output not tests
if(${line} MATCHES "^[ \t]+")
# strip leading spaces and tabs
string(REGEX REPLACE "^[ \t]+" "" line ${line})
if(${line} MATCHES "${tags_regex}")
_add_catch_test_labels(${line})
else()
_add_catch_test(${line})
endif()
endif()
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")

View File

@@ -0,0 +1,225 @@
#==================================================================================================#
# supported macros #
# - TEST_CASE, #
# - SCENARIO, #
# - TEST_CASE_METHOD, #
# - CATCH_TEST_CASE, #
# - CATCH_SCENARIO, #
# - CATCH_TEST_CASE_METHOD. #
# #
# Usage #
# 1. make sure this module is in the path or add this otherwise: #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# 2. make sure that you've enabled testing option for the project by the call: #
# enable_testing() #
# 3. add the lines to the script for testing target (sample CMakeLists.txt): #
# project(testing_target) #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# enable_testing() #
# #
# find_path(CATCH_INCLUDE_DIR "catch.hpp") #
# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) #
# #
# file(GLOB SOURCE_FILES "*.cpp") #
# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) #
# #
# include(ParseAndAddCatchTests) #
# ParseAndAddCatchTests(${PROJECT_NAME}) #
# #
# The following variables affect the behavior of the script: #
# #
# PARSE_CATCH_TESTS_VERBOSE (Default OFF) #
# -- enables debug messages #
# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) #
# -- excludes tests marked with [!hide], [.] or [.foo] tags #
# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) #
# -- adds fixture class name to the test name #
# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) #
# -- adds cmake target name to the test name #
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
# #
# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way #
# a test should be run. For instance to use test MPI, one can write #
# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) #
# just before calling this ParseAndAddCatchTests function #
# #
# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test #
# command. For example, to include successful tests in the output, one can write #
# set(AdditionalCatchParameters --success) #
# #
# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source #
# file in the target is set, and contains the list of the tests extracted from that target, or #
# from that file. This is useful, for example to add further labels or properties to the tests. #
# #
#==================================================================================================#
if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
endif()
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
function(ParseAndAddCatchTests_PrintDebugMessage)
if(PARSE_CATCH_TESTS_VERBOSE)
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
endif()
endfunction()
# This removes the contents between
# - block comments (i.e. /* ... */)
# - full line comments (i.e. // ... )
# contents have been read into '${CppCode}'.
# !keep partial line comments
function(ParseAndAddCatchTests_RemoveComments CppCode)
string(ASCII 2 CMakeBeginBlockComment)
string(ASCII 3 CMakeEndBlockComment)
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}")
set(${CppCode} "${${CppCode}}" PARENT_SCOPE)
endfunction()
# Worker function
function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
# If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file.
if(SourceFile MATCHES "\\\$<TARGET_OBJECTS:.+>")
ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.")
return()
endif()
# According to CMake docs EXISTS behavior is well-defined only for full paths.
get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
if(NOT EXISTS ${SourceFile})
message(WARNING "Cannot find source file: ${SourceFile}")
return()
endif()
ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}")
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
# Remove block and fullline comments
ParseAndAddCatchTests_RemoveComments(Contents)
# Find definition of test names
string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
)
endif()
foreach(TestName ${Tests})
# Strip newlines
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
# Get test type and fixture if applicable
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}")
# Get string parts of test definition
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
# Strip wrapping quotation marks
string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
# Validate that a test name and tags have been provided
list(LENGTH TestStrings TestStringsLength)
if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
endif()
# Assign name and tags
list(GET TestStrings 0 Name)
if("${TestType}" STREQUAL "SCENARIO")
set(Name "Scenario: ${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
set(CTestName "${TestFixture}:${Name}")
else()
set(CTestName "${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
set(CTestName "${TestTarget}:${CTestName}")
endif()
# add target to labels to enable running all tests added from this target
set(Labels ${TestTarget})
if(TestStringsLength EQUAL 2)
list(GET TestStrings 1 Tags)
string(TOLOWER "${Tags}" Tags)
# remove target from labels if the test is hidden
if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
list(REMOVE_ITEM Labels ${TestTarget})
endif()
string(REPLACE "]" ";" Tags "${Tags}")
string(REPLACE "[" "" Tags "${Tags}")
else()
# unset tags variable from previous loop
unset(Tags)
endif()
list(APPEND Labels ${Tags})
set(HiddenTagFound OFF)
foreach(label ${Labels})
string(REGEX MATCH "^!hide|^\\." result ${label})
if(result)
set(HiddenTagFound ON)
break()
endif(result)
endforeach(label)
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9")
ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
else()
ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"")
if(Labels)
ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}")
endif()
# Escape commas in the test spec
string(REPLACE "," "\\," Name ${Name})
# Add the test and set its properties
add_test(NAME "\"${CTestName}\"" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters})
# Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8")
ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property")
set_tests_properties("\"${CTestName}\"" PROPERTIES DISABLED ON)
else()
set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
LABELS "${Labels}")
endif()
set_property(
TARGET ${TestTarget}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"")
set_property(
SOURCE ${SourceFile}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"")
endif()
endforeach()
endfunction()
# entry point
function(ParseAndAddCatchTests TestTarget)
ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
get_target_property(SourceFiles ${TestTarget} SOURCES)
ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}")
foreach(SourceFile ${SourceFiles})
ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget})
endforeach()
ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}")
endfunction()

View File

@@ -1,26 +0,0 @@
set(_q "")
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
set(_q QUIET)
set(_quietly TRUE)
endif()
find_package(${CMAKE_FIND_PACKAGE_NAME} ${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION} CONFIG ${_q})
if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FOUND)
include(CheckIncludeFileCXX)
add_library(draco INTERFACE)
target_include_directories(draco INTERFACE include)
if (_quietly)
set(CMAKE_REQUIRED_QUIET ON)
endif()
CHECK_INCLUDE_FILE_CXX("draco/draco_features.h" HAVE_DRACO_H)
if (NOT HAVE_DRACO_H)
if (${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
message(FATAL_ERROR "Draco library not found. Please install the dependency.")
elseif(NOT _quietly)
message(WARNING "Draco library not found.")
endif()
endif ()
endif()

View File

@@ -3,7 +3,7 @@
# PrusaSlicer specifics:
# This file is backported from CMake 3.15 distribution to behave uniformly
# across all versions of CMake. It explicitly adds GLEW_STATIC compile
# across all versions of CMake. It explicitly adds GLEW_STATIC complile
# definition to static targets which is needed to prevent link errors.
#[=======================================================================[.rst:
@@ -152,7 +152,7 @@ find_library(GLEW_STATIC_LIBRARY_RELEASE
PATHS ENV GLEW_ROOT)
find_library(GLEW_STATIC_LIBRARY_DEBUG
NAMES GLEWds GLEWd glewd glewds glew32ds
NAMES GLEWds glewd glewds glew32ds
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
@@ -223,13 +223,8 @@ if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS)
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
if(GLEW_SHARED_LIBRARY_RELEASE)
@@ -263,13 +258,8 @@ elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS)
set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
if(GLEW_STATIC_LIBRARY_RELEASE)
@@ -302,13 +292,8 @@ if(NOT TARGET GLEW::GLEW)
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
if(TARGET GLEW::glew)

View File

@@ -3,6 +3,13 @@
#//
#// Description:
#// cmake module for finding NLopt installation
#// NLopt installation location is defined by environment variable $NLOPT
#//
#// following variables are defined:
#// NLopt_DIR - NLopt installation directory
#// NLopt_INCLUDE_DIR - NLopt header directory
#// NLopt_LIBRARY_DIR - NLopt library directory
#// NLopt_LIBS - NLopt library files
#//
#// Example usage:
#// find_package(NLopt 1.4 REQUIRED)
@@ -10,27 +17,114 @@
#//
#//-------------------------------------------------------------------------
include(FindPackageHandleStandardArgs)
unset(_q)
if (NLopt_FIND_QUIETLY)
set(_q QUIET)
endif ()
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "")
set(NLopt_DEFINITIONS "")
unset(NLopt_LIBS CACHE)
set(NLopt_DIR $ENV{NLOPT})
if(NOT NLopt_DIR)
set(NLopt_FOUND TRUE)
set(_NLopt_LIB_NAMES "nlopt")
find_library(NLopt_LIBS
NAMES ${_NLopt_LIB_NAMES})
if(NOT NLopt_LIBS)
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt library '${_NLopt_LIB_NAMES}'.")
else()
get_filename_component(NLopt_DIR ${NLopt_LIBS} PATH)
endif()
unset(_NLopt_LIB_NAMES)
set(_NLopt_HEADER_FILE_NAME "nlopt.hpp")
find_file(_NLopt_HEADER_FILE
NAMES ${_NLopt_HEADER_FILE_NAME})
if(NOT _NLopt_HEADER_FILE)
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}'.")
endif()
unset(_NLopt_HEADER_FILE_NAME)
if(NOT NLopt_FOUND)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} NLopt not found in system directories (and environment variable NLOPT is not set).")
else()
get_filename_component(NLopt_INCLUDE_DIR ${_NLopt_HEADER_FILE} DIRECTORY )
endif()
unset(_NLopt_HEADER_FILE CACHE)
else()
set(NLopt_FOUND TRUE)
set(NLopt_INCLUDE_DIR "${NLopt_DIR}/include")
if(NOT EXISTS "${NLopt_INCLUDE_DIR}")
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Directory '${NLopt_INCLUDE_DIR}' does not exist.")
endif()
set(NLopt_LIBRARY_DIR "${NLopt_DIR}/lib")
if(NOT EXISTS "${NLopt_LIBRARY_DIR}")
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Directory '${NLopt_LIBRARY_DIR}' does not exist.")
endif()
set(_NLopt_LIB_NAMES "nlopt_cxx")
find_library(NLopt_LIBS
NAMES ${_NLopt_LIB_NAMES}
PATHS ${NLopt_LIBRARY_DIR}
NO_DEFAULT_PATH)
if(NOT NLopt_LIBS)
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt library '${_NLopt_LIB_NAMES}' in '${NLopt_LIBRARY_DIR}'.")
endif()
unset(_NLopt_LIB_NAMES)
set(_NLopt_HEADER_FILE_NAME "nlopt.hpp")
find_file(_NLopt_HEADER_FILE
NAMES ${_NLopt_HEADER_FILE_NAME}
PATHS ${NLopt_INCLUDE_DIR}
NO_DEFAULT_PATH)
if(NOT _NLopt_HEADER_FILE)
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}' in '${NLopt_INCLUDE_DIR}'.")
endif()
unset(_NLopt_HEADER_FILE_NAME)
unset(_NLopt_HEADER_FILE CACHE)
endif()
# make variables changeable
mark_as_advanced(
NLopt_INCLUDE_DIR
NLopt_LIBRARY_DIR
NLopt_LIBS
NLopt_DEFINITIONS
)
find_package(NLopt ${NLopt_VERSION} CONFIG ${_q})
find_package_handle_standard_args(NLopt CONFIG_MODE)
# report result
if(NLopt_FOUND AND NOT _q)
get_filename_component(NLOPT_LIBRARY_DIRS ${NLOPT_LIBRARY_DIRS} ABSOLUTE)
get_filename_component(NLOPT_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS} ABSOLUTE)
message(STATUS "Found NLopt in '${NLOPT_LIBRARY_DIRS}'.")
message(STATUS "Using NLopt include directory '${NLOPT_INCLUDE_DIRS}'.")
get_target_property(_configs NLopt::nlopt IMPORTED_CONFIGURATIONS)
foreach (_config ${_configs})
get_target_property(_lib NLopt::nlopt IMPORTED_LOCATION_${_config})
message(STATUS "Found NLopt ${_config} library: ${_lib}")
endforeach ()
if(NLopt_FOUND)
message(STATUS "Found NLopt in '${NLopt_DIR}'.")
message(STATUS "Using NLopt include directory '${NLopt_INCLUDE_DIR}'.")
message(STATUS "Using NLopt library '${NLopt_LIBS}'.")
add_library(NLopt::nlopt INTERFACE IMPORTED)
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS})
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR})
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}")
# target_link_libraries(Nlopt::Nlopt INTERFACE ${NLopt_LIBS})
# target_include_directories(Nlopt::Nlopt INTERFACE ${NLopt_INCLUDE_DIR})
# target_compile_definitions(Nlopt::Nlopt INTERFACE ${NLopt_DEFINITIONS})
else()
if(NLopt_FIND_REQUIRED)
message(FATAL_ERROR "Unable to find requested NLopt installation:${NLopt_ERROR_REASON}")
else()
if(NOT NLopt_FIND_QUIETLY)
message(STATUS "NLopt was not found:${NLopt_ERROR_REASON}")
endif()
endif()
endif()

View File

@@ -61,7 +61,7 @@
#
# * TBB_FOUND - Set to false, or undefined, if we havent found, or
# dont want to use TBB.
# * TBB_<component>_FOUND - If False, optional <component> part of TBB system is
# * TBB_<component>_FOUND - If False, optional <component> part of TBB sytem is
# not available.
# * TBB_VERSION - The full version string
# * TBB_VERSION_MAJOR - The major version
@@ -77,7 +77,7 @@
# tbbmalloc, tbbmalloc_debug, tbb_preview, or
# tbb_preview_debug.
#
# The following variables should be used to build and link with TBB:
# The following varibles should be used to build and link with TBB:
#
# * TBB_INCLUDE_DIRS - The include directory for TBB.
# * TBB_LIBRARIES - The libraries to link against to use TBB.

View File

@@ -1,41 +1,15 @@
find_path(LIBNOISE_INCLUDE_DIR libnoise/noise.h)
find_library(LIBNOISE_LIBRARY_RELEASE NAMES libnoise libnoise_static liblibnoise_static)
find_library(LIBNOISE_LIBRARY_DEBUG NAMES libnoised libnoise_staticd liblibnoise_staticd)
set(libnoise_LIB_FOUND FALSE)
if (LIBNOISE_LIBRARY_RELEASE OR LIBNOISE_LIBRARY_DEBUG)
set(libnoise_LIB_FOUND TRUE)
endif ()
find_library(LIBNOISE_LIBRARY NAMES libnoise libnoise_static liblibnoise_static)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(libnoise DEFAULT_MSG
libnoise_LIB_FOUND
LIBNOISE_LIBRARY
LIBNOISE_INCLUDE_DIR
)
if(libnoise_FOUND)
add_library(noise::noise STATIC IMPORTED)
set_target_properties(noise::noise PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LIBNOISE_INCLUDE_DIR}"
IMPORTED_LOCATION "${LIBNOISE_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${LIBNOISE_INCLUDE_DIR}"
)
if (NOT libnoise_FIND_QUIETLY)
message(STATUS "Found libnoise include directory: ${LIBNOISE_INCLUDE_DIR}")
if (LIBNOISE_LIBRARY_RELEASE)
message(STATUS "Found libnoise RELEASE library: ${LIBNOISE_LIBRARY_RELEASE}")
endif ()
if (LIBNOISE_LIBRARY_DEBUG)
message(STATUS "Found libnoise DEBUG library: ${LIBNOISE_LIBRARY_DEBUG}")
endif ()
endif ()
if (LIBNOISE_LIBRARY_RELEASE)
set_property(TARGET noise::noise APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(noise::noise PROPERTIES IMPORTED_LOCATION_RELEASE ${LIBNOISE_LIBRARY_RELEASE})
endif ()
if (LIBNOISE_LIBRARY_DEBUG)
set_property(TARGET noise::noise APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(noise::noise PROPERTIES IMPORTED_LOCATION_DEBUG ${LIBNOISE_LIBRARY_DEBUG})
endif ()
endif()

View File

@@ -11,7 +11,7 @@
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>com.orcaslicer.OrcaSlicer</string>
<string>com.softfever3d.orca-slicer</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
@@ -133,7 +133,7 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Disable App Transport Security. Resolves https://github.com/OrcaSlicer/OrcaSlicer/issues/791 -->
<!-- Disable App Transport Security. Resolves https://github.com/SoftFever/OrcaSlicer/issues/791 -->
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>

View File

@@ -18,8 +18,6 @@ orcaslicer_add_cmake_project(Boost
-DBOOST_EXCLUDE_LIBRARIES:STRING=contract|fiber|numpy|stacktrace|wave|test
-DBOOST_LOCALE_ENABLE_ICU:BOOL=OFF # do not link to libicu, breaks compatibility between distros
-DBUILD_TESTING:BOOL=OFF
-DBOOST_IOSTREAMS_ENABLE_BZIP2:BOOL=OFF # avoid libbz2 soname differences in AppImage builds
-DBOOST_IOSTREAMS_ENABLE_ZSTD:BOOL=OFF
"${_context_abi_line}"
"${_context_arch_line}"
)

59
deps/CGAL/0001-clang19.patch vendored Normal file
View File

@@ -0,0 +1,59 @@
--- a/BGL/include/CGAL/boost/graph/iterator.h 2022-10-07 19:04:41 UTC
+++ b/BGL/include/CGAL/boost/graph/iterator.h
@@ -213,18 +213,7 @@ class Halfedge_around_source_iterator { (public)
{}
#ifndef DOXYGEN_RUNNING
- // design patter: "safe bool"
- // will be replaced by explicit operator bool with C++11
- typedef void (Halfedge_around_source_iterator::*bool_type)() const;
- void this_type_does_not_support_comparisons() const {}
-
- operator bool_type() const
- {
- return (! (this->base() == nullptr)) ?
- &Halfedge_around_source_iterator::this_type_does_not_support_comparisons : 0;
- }
-
bool operator==( const Self& i) const {
CGAL_assertion( anchor == anchor);
return ( g == i.g) && ( pos == i.pos) && ( winding == i.winding);
@@ -313,18 +302,7 @@ class Halfedge_around_target_iterator { (public)
{}
#ifndef DOXYGEN_RUNNING
- // design patter: "safe bool"
- // will be replaced by explicit operator bool with C++11
- typedef void (Halfedge_around_target_iterator::*bool_type)() const;
- void this_type_does_not_support_comparisons() const {}
-
- operator bool_type() const
- {
- return (! (this->base() == nullptr)) ?
- &Halfedge_around_target_iterator::this_type_does_not_support_comparisons : 0;
- }
-
bool operator==( const Self& i) const {
CGAL_assertion( anchor == anchor);
return ( g == i.g) && ( pos == i.pos) && ( winding == i.winding);
@@ -411,18 +389,6 @@ class Halfedge_around_face_iterator { (public)
const value_type& operator * ( ) const { return pos; }
pointer operator -> ( ) { return &pos; }
const value_type* operator -> ( ) const { return &pos; }
-
- // design patter: "safe bool"
- // will be replaced by explicit operator bool with C++11
- typedef void (Halfedge_around_face_iterator::*bool_type)() const;
-
- void this_type_does_not_support_comparisons() const {}
-
- operator bool_type() const
- {
- return (! (this->base() == nullptr)) ?
- &Halfedge_around_face_iterator::this_type_does_not_support_comparisons : 0;
- }
bool operator==( const Self& i) const {
CGAL_assertion( anchor == anchor);

View File

@@ -5,10 +5,11 @@ endif ()
orcaslicer_add_cmake_project(
CGAL
# GIT_REPOSITORY https://github.com/CGAL/cgal.git
# GIT_TAG 3654f780ae0c64675cabaef0e5ddaf904c48b4b7 # releases/CGAL-5.6.3
# GIT_TAG bec70a6d52d8aacb0b3d82a7b4edc3caa899184b # releases/CGAL-5.0
# For whatever reason, this keeps downloading forever (repeats downloads if finished)
URL https://github.com/CGAL/cgal/releases/download/v5.6.3/CGAL-5.6.3.zip
URL_HASH SHA256=5d577acb4a9918ccb960491482da7a3838f8d363aff47e14d703f19fd84733d4
URL https://github.com/CGAL/cgal/archive/refs/tags/v5.4.zip
URL_HASH SHA256=d7605e0a5a5ca17da7547592f6f6e4a59430a0bc861948974254d0de43eab4c0
PATCH_COMMAND git apply ${CGAL_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-clang19.patch
DEPENDS dep_Boost dep_GMP dep_MPFR
)

130
deps/CMakeLists.txt vendored
View File

@@ -1,7 +1,3 @@
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0")
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE)
endif()
#
# This CMake project downloads, configures and builds OrcaSlicer dependencies on Unix and Windows.
#
@@ -20,8 +16,8 @@ endif()
# On Windows, architecture (64 vs 32 bits) is judged based on the compiler variant.
# To build dependencies for either 64 or 32 bit OS, use the respective compiler command line.
#
# WARNING: On UNIX platforms wxWidgets hardcode the destdir path into its `wx-config` utility,
# therefore, unfortunately, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
# WARNING: On UNIX platforms wxWidgets hardcode the destdir path into its `wx-conffig` utility,
# therefore, unfortunatelly, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
#
cmake_minimum_required(VERSION 3.2)
@@ -40,11 +36,6 @@ endif()
project(OrcaSlicer-deps)
# Backward compatibility for old CMake versions
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_VERSION VERSION_LESS "3.25")
set(LINUX ON CACHE BOOL "" FORCE)
endif ()
include(ExternalProject)
include(ProcessorCount)
@@ -53,36 +44,9 @@ if (NPROC EQUAL 0)
set(NPROC 1)
endif ()
set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination directory")
set(DEP_DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/DL_CACHE CACHE PATH "Path for downloaded source packages.")
set(FLATPAK FALSE CACHE BOOL "Toggles various build settings for flatpak, like /usr/local in DESTDIR or not building wxwidgets")
if ("${DESTDIR}" STREQUAL "" OR "${DESTDIR}" STREQUAL "${AUTOGENERATED_DESTDIR}")
if (LINUX AND (NOT DEFINED USE_OLD_DESTDIR_PREV OR USE_OLD_DESTDIR_PREV) AND EXISTS "${CMAKE_BINARY_DIR}/destdir/usr/local" AND NOT EXISTS "${CMAKE_BINARY_DIR}/OrcaSlicer_dep/usr/local")
set(USE_OLD_DESTDIR TRUE)
message(WARNING "You are using an old directory name for dependencies that is being deprecated. "
"Please remove the \"destdir\" directory and rebuild to update to the new name. "
"The current \"destdir\" directory will be used until then.")
endif ()
if (NOT USE_OLD_DESTDIR AND USE_OLD_DESTDIR_PREV)
set(REGEN_DESTDIR TRUE)
endif ()
else ()
unset(AUTOGENERATED_DESTDIR CACHE)
endif ()
if ("${DESTDIR}" STREQUAL "" OR REGEN_DESTDIR)
if (USE_OLD_DESTDIR) # backward compatibility for old directory name
set(DESTDIR "${CMAKE_BINARY_DIR}/destdir" CACHE PATH "Path to dependencies install directory" FORCE)
else ()
set(DESTDIR "${CMAKE_BINARY_DIR}/OrcaSlicer_dep" CACHE PATH "Path to dependencies install directory" FORCE)
endif ()
set(DESTDIR_MSG "(generated automatically and saved to cache)")
set(AUTOGENERATED_DESTDIR ${DESTDIR} CACHE STRING "Provides the last autogenerated destdir" FORCE)
unset(REGEN_DESTDIR CACHE)
else ()
set(DESTDIR_MSG "(from cache or command line)")
endif ()
if (NOT FLATPAK)
set(DESTDIR "${DESTDIR}/usr/local/")
endif()
@@ -92,32 +56,15 @@ get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (_is_multi)
option(DEP_DEBUG "Build debug variants (only applicable on Windows)" OFF)
option(ORCA_INCLUDE_DEBUG_INFO "Includes debug information in a release build (like RelWithDebInfo) in a way that works with multi-configuration generators and incompatible dependencies. DEP_DEBUG option takes priority over this." OFF)
option(AUTO_DEBUG_WORKAROUND "Automatically sets DEP_DEBUG and ORCA_INCLUDE_DEBUG_INFO based on CMAKE_BUILD_TYPE" ON)
if (AUTO_DEBUG_WORKAROUND)
set(DEP_DEBUG OFF)
set(ORCA_INCLUDE_DEBUG_INFO OFF)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DEP_DEBUG ON)
message(STATUS "DEP_DEBUG has been automatically turned ON due to CMAKE_BUILD_TYPE being set to Debug")
elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(ORCA_INCLUDE_DEBUG_INFO ON)
message(STATUS "ORCA_INCLUDE_DEBUG_INFO has been automatically turned ON due to CMAKE_BUILD_TYPE being set to RelWithDebInfo")
endif ()
endif ()
endif ()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
option(DEP_WX_GTK3 "Build wxWidgets against GTK3" ON)
option(DEP_WX_GTK3 "Build wxWidgets against GTK3" OFF)
endif()
set(IS_CROSS_COMPILE FALSE)
if (APPLE)
list(LENGTH CMAKE_OSX_ARCHITECTURES _arch_len)
if (_arch_len GREATER 1)
message(FATAL_ERROR "OrcaSlicer only supports building for one architecture at a time. Please make sure only one architecture is specified in CMAKE_OSX_ARCHITECTURES")
endif ()
set(CMAKE_FIND_FRAMEWORK LAST)
set(CMAKE_FIND_APPBUNDLE LAST)
list(FIND CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} _arch_idx)
@@ -140,7 +87,7 @@ endif ()
# Slic3r compiles with a different version which will cause runtime errors.
# option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors and increase binary size." OFF)
message(STATUS "OrcaSlicer deps DESTDIR: ${DESTDIR} ${DESTDIR_MSG}")
message(STATUS "OrcaSlicer deps DESTDIR: ${DESTDIR}")
message(STATUS "OrcaSlicer download dir for source packages: ${DEP_DOWNLOAD_DIR}")
message(STATUS "OrcaSlicer deps debug build: ${DEP_DEBUG}")
@@ -181,18 +128,13 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DCMAKE_DEBUG_POSTFIX:STRING=d
-DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS}
-DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
@@ -202,26 +144,39 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release
)
if (FLATPAK)
# Free up space during flatpak builds to prevent running out of space during CI/CD
# note for future devs: shared libs may actually create a size reduction
# but orcaslicer_deps tends to get really funny regarding linking after that (notably boost)
# so, as much as I would like to use that, it's not happening
ExternalProject_Add_Step(dep_${projectname} free_download_space
DEPENDEES download # do after download
COMMENT "Freeing Space: Removing source archive"
WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR}
COMMAND ${CMAKE_COMMAND} -E rm -r ${projectname}
)
ExternalProject_Add_Step(dep_${projectname} free_build_space
DEPENDEES install # do after install
COMMENT "Freeing Space: Removing source and build files"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/dep_${projectname}-prefix/src
COMMAND ${CMAKE_COMMAND} -E rm -rf dep_${projectname} dep_${projectname}-build
)
endif ()
elseif(FLATPAK)
# the only reason this is here is because of the HACK at the bottom for ci
#
# note for future devs: shared libs may actually create a size reduction
# but orcaslicer_deps tends to get really funny regarding linking after that (notably boost)
# so, as much as I would like to use that, it's not happening
ExternalProject_Add(
dep_${projectname}
EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_gen}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
-DCMAKE_DEBUG_POSTFIX:STRING=d
-DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
${DEP_CMAKE_OPTS}
${P_ARGS_CMAKE_ARGS}
${P_ARGS_UNPARSED_ARGUMENTS}
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release
# HACK: save space after each compile job, because CI
# reasoning: cmake changes directory after this command, so just keep only the folders
# so that it can navigate out
COMMAND find "${CMAKE_BINARY_DIR}/dep_${projectname}-prefix/" -type f -delete
)
else()
ExternalProject_Add(
dep_${projectname}
@@ -230,10 +185,8 @@ else()
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
@@ -341,8 +294,6 @@ include(CGAL/CGAL.cmake)
include(NLopt/NLopt.cmake)
include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake)
# I *think* 1.1 is used for *just* md5 hashing?
# 3.1 has everything in the right place, but the md5 funcs used are deprecated
@@ -369,10 +320,10 @@ if (NOT JPEG_FOUND)
set(JPEG_PKG dep_JPEG)
endif()
# flatpak builds wxwidgets separately, so it is not included in the deps target
# flatpak builds wxwidgets separately
set(WXWIDGETS_PKG "")
include(wxWidgets/wxWidgets.cmake)
if (NOT FLATPAK)
include(wxWidgets/wxWidgets.cmake)
set(WXWIDGETS_PKG "dep_wxWidgets")
endif()
@@ -406,7 +357,6 @@ set(_dep_list
${CURL_PKG}
${WXWIDGETS_PKG}
dep_Cereal
dep_Draco
dep_NLopt
dep_OpenVDB
dep_OpenCSG

View File

@@ -1,4 +0,0 @@
orcaslicer_add_cmake_project(Draco
URL https://github.com/google/draco/archive/refs/tags/1.5.7.zip
URL_HASH SHA256=27b72ba2d5ff3d0a9814ad40d4cb88f8dc89a35491c0866d952473f8f9416b77
)

View File

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

View File

@@ -3,17 +3,9 @@ project(GLEW)
find_package(OpenGL REQUIRED)
# Allow parent project to control EGL usage.
# Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
# Using EGL function loading with GLX contexts causes rendering failures.
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
if(GLEW_USE_EGL)
message(STATUS "Building GLEW with EGL support")
if(OpenGL_EGL_FOUND)
message(STATUS "building GLEW for EGL (hope that wxWidgets agrees, otherwise you won't have any output!)")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
else()
message(STATUS "Building GLEW with GLX support")
endif()
add_library(GLEW src/glew.c)

2
deps/GMP/GMP.cmake vendored
View File

@@ -65,7 +65,7 @@ else ()
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/GMP
PATCH_COMMAND git apply ${GMP_DIRECTORY_FLAG} --verbose ${CMAKE_CURRENT_LIST_DIR}/0001-GMP_GCC15.patch
BUILD_IN_SOURCE ON
CONFIGURE_COMMAND env "CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}" "CFLAGS=${_gmp_ccflags}" "CXXFLAGS=${_gmp_ccflags}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" ./configure ${_cross_compile_arg} --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}" ${_gmp_build_tgt}
CONFIGURE_COMMAND env "CFLAGS=${_gmp_ccflags}" "CXXFLAGS=${_gmp_ccflags}" ./configure ${_cross_compile_arg} --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}" ${_gmp_build_tgt}
BUILD_COMMAND make -j
INSTALL_COMMAND make install
)

View File

@@ -25,13 +25,12 @@ else ()
endif ()
ExternalProject_Add(dep_MPFR
URL https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.2.tar.bz2
https://www.mpfr.org/mpfr-4.2.2/mpfr-4.2.2.tar.bz2
URL https://www.mpfr.org/mpfr-4.2.2/mpfr-4.2.2.tar.bz2
URL_HASH SHA256=9ad62c7dc910303cd384ff8f1f4767a655124980bb6d8650fe62c815a231bb7b
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/MPFR
BUILD_IN_SOURCE ON
CONFIGURE_COMMAND autoreconf -f -i &&
env "CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}" "CFLAGS=${_gmp_ccflags}" "CXXFLAGS=${_gmp_ccflags}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" ./configure ${_cross_compile_arg} --prefix=${DESTDIR} --enable-shared=no --enable-static=yes --with-gmp=${DESTDIR} ${_gmp_build_tgt}
env "CFLAGS=${_gmp_ccflags}" "CXXFLAGS=${_gmp_ccflags}" ./configure ${_cross_compile_arg} --prefix=${DESTDIR} --enable-shared=no --enable-static=yes --with-gmp=${DESTDIR} ${_gmp_build_tgt}
BUILD_COMMAND make -j
INSTALL_COMMAND make install
DEPENDS dep_GMP

View File

@@ -16,7 +16,6 @@ orcaslicer_add_cmake_project(OCCT
#DEPENDS dep_Boost
DEPENDS ${FREETYPE_PKG}
CMAKE_ARGS
-DCMAKE_CXX_STANDARD=17
-DBUILD_LIBRARY_TYPE=${library_build_type}
-DUSE_TK=OFF
-DUSE_TBB=OFF

View File

@@ -1,54 +0,0 @@
diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake
index 7f229cde96..d8e20f8fe9 100644
--- a/cmake/OpenCVDetectCXXCompiler.cmake
+++ b/cmake/OpenCVDetectCXXCompiler.cmake
@@ -171,8 +171,10 @@ elseif(MSVC)
set(OpenCV_RUNTIME vc15)
elseif(MSVC_VERSION MATCHES "^192[0-9]$")
set(OpenCV_RUNTIME vc16)
- elseif(MSVC_VERSION MATCHES "^193[0-9]$")
+ elseif(MSVC_VERSION MATCHES "^19[34][0-9]$")
set(OpenCV_RUNTIME vc17)
+ elseif(MSVC_VERSION MATCHES "^195[0-9]$")
+ set(OpenCV_RUNTIME vc18)
else()
message(WARNING "OpenCV does not recognize MSVC_VERSION \"${MSVC_VERSION}\". Cannot set OpenCV_RUNTIME")
endif()
diff --git a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
index b0f254ebe8..8f0178b0ae 100644
--- a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
+++ b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
@@ -137,7 +137,7 @@ elseif(MSVC)
set(OpenCV_RUNTIME vc14) # selecting previous compatible runtime version
endif()
endif()
- elseif(MSVC_VERSION MATCHES "^193[0-9]$")
+ elseif(MSVC_VERSION MATCHES "^19[34][0-9]$")
set(OpenCV_RUNTIME vc17)
check_one_config(has_VS2022)
if(NOT has_VS2022)
@@ -151,6 +151,24 @@ elseif(MSVC)
endif()
endif()
endif()
+ elseif(MSVC_VERSION MATCHES "^195[0-9]$")
+ set(OpenCV_RUNTIME vc18)
+ check_one_config(has_VS2026)
+ if(NOT has_VS2026)
+ set(OpenCV_RUNTIME vc17)
+ check_one_config(has_VS2022)
+ if(NOT has_VS2022)
+ set(OpenCV_RUNTIME vc16)
+ check_one_config(has_VS2019)
+ if(NOT has_VS2019)
+ set(OpenCV_RUNTIME vc15) # selecting previous compatible runtime version
+ check_one_config(has_VS2017)
+ if(NOT has_VS2017)
+ set(OpenCV_RUNTIME vc14) # selecting previous compatible runtime version
+ endif()
+ endif()
+ endif()
+ endif()
endif()
elseif(MINGW)
set(OpenCV_RUNTIME mingw)

52
deps/OpenCV/0001-vs2022.patch vendored Normal file
View File

@@ -0,0 +1,52 @@
From 6fb3f6333150a777e835fc7c48e49750591bf7fe Mon Sep 17 00:00:00 2001
From: Benjamin Buch <bebuch@users.noreply.github.com>
Date: Thu, 23 May 2024 16:05:19 +0200
Subject: [PATCH] Support VS 2022 17.1x.y
With 17.10.0 the MSVC toolset was set to 19.40.x which breaks the compatibility test in the OpenCV's CMake Config files.
---
cmake/templates/OpenCVConfig.root-WIN32.cmake.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
index b0f254ebe8..62e36272f3 100644
--- a/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
+++ b/cmake/templates/OpenCVConfig.root-WIN32.cmake.in
@@ -137,7 +137,7 @@ elseif(MSVC)
set(OpenCV_RUNTIME vc14) # selecting previous compatible runtime version
endif()
endif()
- elseif(MSVC_VERSION MATCHES "^193[0-9]$")
+ elseif(MSVC_VERSION MATCHES "^19[34][0-9]$")
set(OpenCV_RUNTIME vc17)
check_one_config(has_VS2022)
if(NOT has_VS2022)
--
2.45.2.windows.1
From f85818ba6f9031c450475a7453dee0acce31a881 Mon Sep 17 00:00:00 2001
From: Benjamin Buch <bebuch@users.noreply.github.com>
Date: Fri, 24 May 2024 11:10:09 +0200
Subject: [PATCH] Support VS 2022 17.1x.y in OpenCVDetectCXXCompiler.cmake
With 17.10.0 the MSVC toolset was set to 19.40.x which breaks the compatibility test in the OpenCV's CMake Config files.
---
cmake/OpenCVDetectCXXCompiler.cmake | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake
index 1743aca11f..448afd46ea 100644
--- a/cmake/OpenCVDetectCXXCompiler.cmake
+++ b/cmake/OpenCVDetectCXXCompiler.cmake
@@ -176,7 +176,7 @@ elseif(MSVC)
set(OpenCV_RUNTIME vc15)
elseif(MSVC_VERSION MATCHES "^192[0-9]$")
set(OpenCV_RUNTIME vc16)
- elseif(MSVC_VERSION MATCHES "^193[0-9]$")
+ elseif(MSVC_VERSION MATCHES "^19[34][0-9]$")
set(OpenCV_RUNTIME vc17)
else()
message(WARNING "OpenCV does not recognize MSVC_VERSION \"${MSVC_VERSION}\". Cannot set OpenCV_RUNTIME")
--
2.45.2.windows.1

View File

@@ -11,7 +11,7 @@ endif ()
orcaslicer_add_cmake_project(OpenCV
URL https://github.com/opencv/opencv/archive/refs/tags/4.6.0.tar.gz
URL_HASH SHA256=1ec1cba65f9f20fe5a41fda1586e01c70ea0c9a6d7b67c9e13edf0cfe2239277
PATCH_COMMAND git apply ${OpenCV_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-vs.patch ${CMAKE_CURRENT_LIST_DIR}/0002-clang19-macos.patch
PATCH_COMMAND git apply ${OpenCV_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-vs2022.patch ${CMAKE_CURRENT_LIST_DIR}/0002-clang19-macos.patch
CMAKE_ARGS
-DBUILD_SHARED_LIBS=0
-DBUILD_PERE_TESTS=OFF
@@ -53,7 +53,6 @@ orcaslicer_add_cmake_project(OpenCV
-DWITH_OPENJPEG=OFF
-DWITH_QUIRC=OFF
-DWITH_VTK=OFF
-DWITH_JPEG=OFF
-DWITH_WEBP=OFF
-DENABLE_PRECOMPILED_HEADERS=OFF
-DINSTALL_TESTS=OFF

View File

@@ -8,7 +8,7 @@ else()
if(WIN32)
set(_cross_arch "VC-WIN64A")
elseif(APPLE)
set(_cross_arch "darwin64-${CMAKE_OSX_ARCHITECTURES}-cc")
set(_cross_arch "darwin64-arm64-cc")
endif()
endif()
@@ -21,7 +21,7 @@ else()
if(APPLE)
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
else()
set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config")
set(_conf_cmd "./config")
endif()
set(_cross_comp_prefix_line "")
set(_make_cmd make -j${NPROC})

View File

@@ -1,11 +1,11 @@
include(GNUInstallDirs)
orcaslicer_add_cmake_project(Qhull
URL "https://github.com/qhull/qhull/archive/v8.0.2.zip"
URL_HASH SHA256=a378e9a39e718e289102c20d45632f873bfdc58a7a5f924246ea4b176e185f1e
URL "https://github.com/qhull/qhull/archive/v8.0.1.zip"
URL_HASH SHA256=5287f5edd6a0372588f5d6640799086a4033d89d19711023ef8229dd9301d69b
CMAKE_ARGS
-DINCLUDE_INSTALL_DIR=${CMAKE_INSTALL_INCLUDEDIR}
)
if (MSVC)
add_debug_dep(dep_Qhull)
endif ()
endif ()

2
deps/TBB/TBB.cmake vendored
View File

@@ -1,4 +1,4 @@
if (FLATPAK AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
if (FLATPAK)
set(_patch_command ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/GNU.cmake ./cmake/compilers/GNU.cmake)
else()
set(_patch_command "")

View File

@@ -19,10 +19,6 @@ elseif (MSVC_VERSION LESS 1950)
# 1930-1949 = VS 17.0 (v143 toolset)
set(DEP_VS_VER "17")
set(DEP_BOOST_TOOLSET "msvc-14.3")
elseif (MSVC_VERSION LESS 1960)
# 1950-1959 = VS 18.0 (v145 toolset)
set(DEP_VS_VER "18")
set(DEP_BOOST_TOOLSET "msvc-14.5")
else ()
message(FATAL_ERROR "Unsupported MSVC version")
endif ()

View File

@@ -1,6 +1,6 @@
set(_wx_toolkit "")
set(_wx_debug_postfix "")
set(_wx_shared -DwxBUILD_SHARED=OFF)
set(_wx_private_font "-DwxUSE_PRIVATE_FONTS=1")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(_gtk_ver 2)
@@ -9,10 +9,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
endif ()
set(_wx_toolkit "-DwxBUILD_TOOLKIT=gtk${_gtk_ver}")
if (FLATPAK)
set(_wx_debug_postfix "d")
set(_wx_shared -DwxBUILD_SHARED=ON -DBUILD_SHARED_LIBS:BOOL=ON)
endif ()
endif()
if (MSVC)
@@ -21,13 +17,10 @@ else ()
set(_wx_edge "-DwxUSE_WEBVIEW_EDGE=OFF")
endif ()
set(_wx_opengl_override "")
if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set(_wx_opengl_override
-DOPENGL_gl_LIBRARY="-framework OpenGL"
-DOPENGL_glu_LIBRARY="-framework OpenGL"
)
endif()
# Note: The flatpak build builds wxwidgets separately due to CI size constraints.
# ANY CHANGES MADE IN HERE MUST ALSO BE REFLECTED IN `scripts/flatpak/io.github.SoftFever.OrcaSlicer.yml`.
# ** THIS INCLUDES BUILD ARGS. **
# ...if you can find a way around this size limitation, be my guest.
orcaslicer_add_cmake_project(
wxWidgets
@@ -35,24 +28,23 @@ orcaslicer_add_cmake_project(
GIT_SHALLOW ON
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
CMAKE_ARGS
${_wx_opengl_override}
-DwxBUILD_PRECOMP=ON
${_wx_toolkit}
"-DCMAKE_DEBUG_POSTFIX:STRING=${_wx_debug_postfix}"
"-DCMAKE_DEBUG_POSTFIX:STRING="
-DwxBUILD_DEBUG_LEVEL=0
-DwxBUILD_SAMPLES=OFF
${_wx_shared}
-DwxBUILD_SHARED=OFF
-DwxUSE_MEDIACTRL=ON
-DwxUSE_DETECT_SM=OFF
-DwxUSE_UNICODE=ON
-DwxUSE_PRIVATE_FONTS=ON
${_wx_private_font}
-DwxUSE_OPENGL=ON
-DwxUSE_GLCANVAS_EGL=OFF
-DwxUSE_WEBREQUEST=ON
-DwxUSE_WEBVIEW=ON
${_wx_edge}
-DwxUSE_WEBVIEW_IE=OFF
-DwxUSE_REGEX=builtin
-DwxUSE_LIBXPM=builtin
-DwxUSE_LIBSDL=OFF
-DwxUSE_XTEST=OFF
-DwxUSE_STC=OFF
@@ -61,6 +53,7 @@ orcaslicer_add_cmake_project(
-DwxUSE_ZLIB=sys
-DwxUSE_LIBJPEG=sys
-DwxUSE_LIBTIFF=OFF
-DwxUSE_NANOSVG=OFF
-DwxUSE_EXPAT=sys
)

View File

@@ -6,17 +6,16 @@ project(deps_src)
# Header-only libraries (INTERFACE)
add_subdirectory(agg)
add_subdirectory(ankerl)
add_subdirectory(earcut)
add_subdirectory(fast_float)
add_subdirectory(nanosvg)
add_subdirectory(nlohmann)
add_subdirectory(spline) # Header-only spline library
add_subdirectory(stb_dxt) # Header-only STB DXT compression library
# Static libraries
add_subdirectory(Shiny)
add_subdirectory(admesh)
add_subdirectory(clipper)
add_subdirectory(clipper2)
add_subdirectory(expat)
add_subdirectory(glu-libtess)
add_subdirectory(hidapi)
@@ -26,7 +25,6 @@ add_subdirectory(imguizmo)
add_subdirectory(libigl)
add_subdirectory(libnest2d)
add_subdirectory(mcut)
add_subdirectory(md4c)
add_subdirectory(miniz)
add_subdirectory(minilzo)
add_subdirectory(qhull)

View File

@@ -24,7 +24,18 @@ target_link_libraries(your_target PRIVATE semver::semver)
target_link_libraries(your_target PRIVATE hints)
```
### 3. **stb_dxt** (Interface Library)
### 3. **spline** (Interface Library)
- **Type**: Interface library (header-only)
- **Target**: `spline` or `spline::spline`
- **Headers**: `spline.h`
- **Usage**:
```cmake
target_link_libraries(your_target PRIVATE spline)
# or
target_link_libraries(your_target PRIVATE spline::spline)
```
### 4. **stb_dxt** (Interface Library)
- **Type**: Interface library (header-only)
- **Target**: `stb_dxt` or `stb_dxt::stb_dxt`
- **Headers**: `stb_dxt.h`
@@ -42,9 +53,10 @@ target_link_libraries(your_target PRIVATE stb_dxt::stb_dxt)
1. **In your CMakeLists.txt**, simply link the library:
```cmake
add_executable(my_app main.cpp)
target_link_libraries(my_app
PRIVATE
target_link_libraries(my_app
PRIVATE
semver::semver # For version parsing
spline::spline # For spline interpolation
stb_dxt::stb_dxt # For DXT texture compression
hints # For hints functionality
)
@@ -55,6 +67,9 @@ target_link_libraries(my_app
// For semver
#include <semver.h>
// For spline
#include <spline.h>
// For stb_dxt
#include <stb_dxt.h>
@@ -85,6 +100,7 @@ target_link_libraries(mycomponent
PUBLIC
semver::semver # Version handling is part of public API
PRIVATE
spline::spline # Used internally for interpolation
stb_dxt::stb_dxt # Used internally for texture compression
)

View File

@@ -1,54 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set_property(GLOBAL PROPERTY USE_FOLDERS OFF)
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
include(GNUInstallDirs)
set(CLIPPER2_INC
Clipper2Lib/include/clipper2/clipper.h
Clipper2Lib/include/clipper2/clipper.core.h
Clipper2Lib/include/clipper2/clipper.engine.h
Clipper2Lib/include/clipper2/clipper.export.h
Clipper2Lib/include/clipper2/clipper.minkowski.h
Clipper2Lib/include/clipper2/clipper.offset.h
Clipper2Lib/include/clipper2/clipper.rectclip.h
Clipper2Lib/include/clipper2/clipper2_z.hpp
)
set(CLIPPER2_SRC
Clipper2Lib/src/clipper.engine.cpp
Clipper2Lib/src/clipper.offset.cpp
Clipper2Lib/src/clipper.rectclip.cpp
Clipper2Lib/src/clipper2_z.cpp
)
# 2d version of Clipper2
add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC})
target_include_directories(Clipper2
PUBLIC Clipper2Lib/include
)
if (WIN32)
target_compile_options(Clipper2 PRIVATE /W4 /WX)
else()
target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_link_libraries(Clipper2 PUBLIC -lm)
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14.1)
target_compile_options(Clipper2 PRIVATE -Wno-error=template-id-cdtor)
endif()
endif()
set_target_properties(Clipper2 PROPERTIES FOLDER Libraries
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER "${CLIPPER2_INC}"
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,646 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 17 September 2024 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This is the main polygon clipping module *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_ENGINE_H
#define CLIPPER_ENGINE_H
#include "clipper2/clipper.core.h"
#include <queue>
#include <functional>
#include <memory>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
struct Scanline;
struct IntersectNode;
struct Active;
struct Vertex;
struct LocalMinima;
struct OutRec;
struct HorzSegment;
//Note: all clipping operations except for Difference are commutative.
enum class ClipType { NoClip, Intersection, Union, Difference, Xor };
enum class PathType { Subject, Clip };
enum class JoinWith { NoJoin, Left, Right };
enum class VertexFlags : uint32_t {
Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
};
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
}
constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
}
struct Vertex {
Point64 pt;
Vertex* next = nullptr;
Vertex* prev = nullptr;
VertexFlags flags = VertexFlags::Empty;
};
struct OutPt {
Point64 pt;
OutPt* next = nullptr;
OutPt* prev = nullptr;
OutRec* outrec;
HorzSegment* horz = nullptr;
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
next = this;
prev = this;
}
};
class PolyPath;
class PolyPath64;
class PolyPathD;
using PolyTree64 = PolyPath64;
using PolyTreeD = PolyPathD;
struct OutRec;
typedef std::vector<OutRec*> OutRecList;
//OutRec: contains a path in the clipping solution. Edges in the AEL will
//have OutRec pointers assigned when they form part of the clipping solution.
struct OutRec {
size_t idx = 0;
OutRec* owner = nullptr;
Active* front_edge = nullptr;
Active* back_edge = nullptr;
OutPt* pts = nullptr;
PolyPath* polypath = nullptr;
OutRecList* splits = nullptr;
OutRec* recursive_split = nullptr;
Rect64 bounds = {};
Path64 path;
bool is_open = false;
~OutRec() {
if (splits) delete splits;
// nb: don't delete the split pointers
// as these are owned by ClipperBase's outrec_list_
};
};
///////////////////////////////////////////////////////////////////
//Important: UP and DOWN here are premised on Y-axis positive down
//displays, which is the orientation used in Clipper's development.
///////////////////////////////////////////////////////////////////
struct Active {
Point64 bot;
Point64 top;
int64_t curr_x = 0; //current (updated at every new scanline)
double dx = 0.0;
int wind_dx = 1; //1 or -1 depending on winding direction
int wind_cnt = 0;
int wind_cnt2 = 0; //winding count of the opposite polytype
OutRec* outrec = nullptr;
//AEL: 'active edge list' (Vatti's AET - active edge table)
// a linked list of all edges (from left to right) that are present
// (or 'active') within the current scanbeam (a horizontal 'beam' that
// sweeps from bottom to top over the paths in the clipping operation).
Active* prev_in_ael = nullptr;
Active* next_in_ael = nullptr;
//SEL: 'sorted edge list' (Vatti's ST - sorted table)
// linked list used when sorting edges into their new positions at the
// top of scanbeams, but also (re)used to process horizontals.
Active* prev_in_sel = nullptr;
Active* next_in_sel = nullptr;
Active* jump = nullptr;
Vertex* vertex_top = nullptr;
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
bool is_left_bound = false;
JoinWith join_with = JoinWith::NoJoin;
};
struct LocalMinima {
Vertex* vertex;
PathType polytype;
bool is_open;
LocalMinima(Vertex* v, PathType pt, bool open) :
vertex(v), polytype(pt), is_open(open){}
};
struct IntersectNode {
Point64 pt;
Active* edge1;
Active* edge2;
IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
pt(pt_), edge1(e1), edge2(e2) {}
};
struct HorzSegment {
OutPt* left_op;
OutPt* right_op = nullptr;
bool left_to_right = true;
HorzSegment() : left_op(nullptr) { }
explicit HorzSegment(OutPt* op) : left_op(op) { }
};
struct HorzJoin {
OutPt* op1 = nullptr;
OutPt* op2 = nullptr;
HorzJoin() {};
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
};
#ifdef USINGZ
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
#endif
typedef std::vector<HorzSegment> HorzSegmentList;
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
typedef std::vector<IntersectNode> IntersectNodeList;
// ReuseableDataContainer64 ------------------------------------------------
class ReuseableDataContainer64 {
private:
friend class ClipperBase;
LocalMinimaList minima_list_;
std::vector<Vertex*> vertex_lists_;
void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
public:
virtual ~ReuseableDataContainer64();
void Clear();
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
};
// ClipperBase -------------------------------------------------------------
class ClipperBase {
private:
ClipType cliptype_ = ClipType::NoClip;
FillRule fillrule_ = FillRule::EvenOdd;
FillRule fillpos = FillRule::Positive;
int64_t bot_y_ = 0;
bool minima_list_sorted_ = false;
bool using_polytree_ = false;
Active* actives_ = nullptr;
Active *sel_ = nullptr;
LocalMinimaList minima_list_; //pointers in case of memory reallocs
LocalMinimaList::iterator current_locmin_iter_;
std::vector<Vertex*> vertex_lists_;
std::priority_queue<int64_t> scanline_list_;
IntersectNodeList intersect_nodes_;
HorzSegmentList horz_seg_list_;
std::vector<HorzJoin> horz_join_list_;
void Reset();
inline void InsertScanline(int64_t y);
inline bool PopScanline(int64_t &y);
inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
void DisposeAllOutRecs();
void DisposeVerticesAndLocalMinima();
void DeleteEdges(Active*& e);
inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
bool IsContributingClosed(const Active &e) const;
inline bool IsContributingOpen(const Active &e) const;
void SetWindCountForClosedPathEdge(Active &edge);
void SetWindCountForOpenPathEdge(Active &e);
void InsertLocalMinimaIntoAEL(int64_t bot_y);
void InsertLeftEdge(Active &e);
inline void PushHorz(Active &e);
inline bool PopHorz(Active *&e);
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
inline void UpdateEdgeIntoAEL(Active *e);
void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
inline void DeleteFromAEL(Active &e);
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
void DoIntersections(const int64_t top_y);
void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
bool BuildIntersectList(const int64_t top_y);
void ProcessIntersectList();
void SwapPositionsInAEL(Active& edge1, Active& edge2);
OutRec* NewOutRec();
OutPt* AddOutPt(const Active &e, const Point64& pt);
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
const Point64& pt, bool is_new = false);
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
void DoHorizontal(Active &horz);
bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
int64_t &horz_left, int64_t &horz_right);
void DoTopOfScanbeam(const int64_t top_y);
Active *DoMaxima(Active &e);
void JoinOutrecPaths(Active &e1, Active &e2);
void FixSelfIntersects(OutRec* outrec);
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
inline void AddTrialHorzJoin(OutPt* op);
void ConvertHorzSegsToJoins();
void ProcessHorzJoins();
void Split(Active& e, const Point64& pt);
inline void CheckJoinLeft(Active& e,
const Point64& pt, bool check_curr_x = false);
inline void CheckJoinRight(Active& e,
const Point64& pt, bool check_curr_x = false);
protected:
bool preserve_collinear_ = true;
bool reverse_solution_ = false;
int error_code_ = 0;
bool has_open_paths_ = false;
bool succeeded_ = true;
OutRecList outrec_list_; //pointers in case list memory reallocated
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
void CleanCollinear(OutRec* outrec);
bool CheckBounds(OutRec* outrec);
bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
#ifdef USINGZ
ZCallback64 zCallback_ = nullptr;
void SetZ(const Active& e1, const Active& e2, Point64& pt);
#endif
void CleanUp(); // unlike Clear, CleanUp preserves added paths
void AddPath(const Path64& path, PathType polytype, bool is_open);
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
public:
virtual ~ClipperBase();
int ErrorCode() const { return error_code_; };
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
bool PreserveCollinear() const { return preserve_collinear_;};
void ReverseSolution(bool val) { reverse_solution_ = val; };
bool ReverseSolution() const { return reverse_solution_; };
void Clear();
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
#ifdef USINGZ
int64_t DefaultZ = 0;
#endif
};
// PolyPath / PolyTree --------------------------------------------------------
//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
//by clipping operations. While this structure is more complex than the
//alternative Paths structure, it does preserve path 'ownership' - ie those
//paths that contain (or own) other paths. This will be useful to some users.
class PolyPath {
protected:
PolyPath* parent_;
public:
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
virtual ~PolyPath() {};
//https://en.cppreference.com/w/cpp/language/rule_of_three
PolyPath(const PolyPath&) = delete;
PolyPath& operator=(const PolyPath&) = delete;
unsigned Level() const
{
unsigned result = 0;
const PolyPath* p = parent_;
while (p) { ++result; p = p->parent_; }
return result;
}
virtual PolyPath* AddChild(const Path64& path) = 0;
virtual void Clear() = 0;
virtual size_t Count() const { return 0; }
const PolyPath* Parent() const { return parent_; }
bool IsHole() const
{
unsigned lvl = Level();
//Even levels except level 0
return lvl && !(lvl & 1);
}
template<typename T>
static double Clipper2LibArea(const Path<T> &poly)
{
#ifdef USINGZ
return Clipper2Lib_Z::Area<T>(poly);
#else
return Clipper2Lib::Area<T>(poly);
#endif
}
};
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
class PolyPath64 : public PolyPath {
private:
PolyPath64List childs_;
Path64 polygon_;
public:
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
~PolyPath64() {
childs_.resize(0);
}
PolyPath64* operator [] (size_t index) const
{
return childs_[index].get(); //std::unique_ptr
}
PolyPath64* Child(size_t index) const
{
return childs_[index].get();
}
PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
PolyPath64List::const_iterator end() const { return childs_.cend(); }
PolyPath64* AddChild(const Path64& path) override
{
return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
}
void Clear() override
{
childs_.resize(0);
}
size_t Count() const override
{
return childs_.size();
}
const Path64& Polygon() const { return polygon_; };
double Area() const
{
return std::accumulate(childs_.cbegin(), childs_.cend(), Clipper2LibArea<int64_t>(polygon_),
[](double a, const auto& child) {return a + child->Area(); });
}
};
class PolyPathD : public PolyPath {
private:
PolyPathDList childs_;
double scale_;
PathD polygon_;
public:
explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
}
explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
int error_code = 0;
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
}
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
polygon_ = path;
}
~PolyPathD() {
childs_.resize(0);
}
PolyPathD* operator [] (size_t index) const
{
return childs_[index].get();
}
PolyPathD* Child(size_t index) const
{
return childs_[index].get();
}
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
PolyPathDList::const_iterator end() const { return childs_.cend(); }
void SetScale(double value) { scale_ = value; }
double Scale() const { return scale_; }
PolyPathD* AddChild(const Path64& path) override
{
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
}
PolyPathD* AddChild(const PathD& path)
{
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
}
void Clear() override
{
childs_.resize(0);
}
size_t Count() const override
{
return childs_.size();
}
const PathD& Polygon() const { return polygon_; };
double Area() const
{
return std::accumulate(childs_.begin(), childs_.end(), Clipper2LibArea<double>(polygon_),
[](double a, const auto& child) {return a + child->Area(); });
}
};
class Clipper64 : public ClipperBase
{
private:
void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen);
void BuildTree64(PolyPath64& polytree, Paths64& open_paths);
public:
#ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
#endif
void AddSubject(const Paths64& subjects)
{
AddPaths(subjects, PathType::Subject, false);
}
void AddOpenSubject(const Paths64& open_subjects)
{
AddPaths(open_subjects, PathType::Subject, true);
}
void AddClip(const Paths64& clips)
{
AddPaths(clips, PathType::Clip, false);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& closed_paths)
{
Paths64 dummy;
return Execute(clip_type, fill_rule, closed_paths, dummy);
}
bool Execute(ClipType clip_type, FillRule fill_rule,
Paths64& closed_paths, Paths64& open_paths)
{
closed_paths.clear();
open_paths.clear();
if (ExecuteInternal(clip_type, fill_rule, false))
BuildPaths64(closed_paths, &open_paths);
CleanUp();
return succeeded_;
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
{
Paths64 dummy;
return Execute(clip_type, fill_rule, polytree, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
{
if (ExecuteInternal(clip_type, fill_rule, true))
{
open_paths.clear();
polytree.Clear();
BuildTree64(polytree, open_paths);
}
CleanUp();
return succeeded_;
}
};
class ClipperD : public ClipperBase {
private:
double scale_ = 1.0, invScale_ = 1.0;
#ifdef USINGZ
ZCallbackD zCallbackD_ = nullptr;
#endif
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
public:
explicit ClipperD(int precision = 2) : ClipperBase()
{
CheckPrecisionRange(precision, error_code_);
// to optimize scaling / descaling precision
// set the scale to a power of double's radix (2) (#25)
scale_ = std::pow(std::numeric_limits<double>::radix,
std::ilogb(std::pow(10, precision)) + 1);
invScale_ = 1 / scale_;
}
#ifdef USINGZ
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
void ZCB(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)
{
// de-scale (x & y)
// temporarily convert integers to their initial float values
// this will slow clipping marginally but will make it much easier
// to understand the coordinates passed to the callback function
PointD tmp = PointD(pt) * invScale_;
PointD e1b = PointD(e1bot) * invScale_;
PointD e1t = PointD(e1top) * invScale_;
PointD e2b = PointD(e2bot) * invScale_;
PointD e2t = PointD(e2top) * invScale_;
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
pt.z = tmp.z; // only update 'z'
};
void CheckCallback()
{
if(zCallbackD_)
// if the user defined float point callback has been assigned
// then assign the proxy callback function
ClipperBase::zCallback_ =
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
else
ClipperBase::zCallback_ = nullptr;
}
#endif
void AddSubject(const PathsD& subjects)
{
AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
}
void AddOpenSubject(const PathsD& open_subjects)
{
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
}
void AddClip(const PathsD& clips)
{
AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
}
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
{
PathsD dummy;
return Execute(clip_type, fill_rule, closed_paths, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
if (ExecuteInternal(clip_type, fill_rule, false))
{
BuildPathsD(closed_paths, &open_paths);
}
CleanUp();
return succeeded_;
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
{
PathsD dummy;
return Execute(clip_type, fill_rule, polytree, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
if (ExecuteInternal(clip_type, fill_rule, true))
{
polytree.Clear();
polytree.SetScale(invScale_);
open_paths.clear();
BuildTreeD(polytree, open_paths);
}
CleanUp();
return succeeded_;
}
};
} // namespace
#endif // CLIPPER_ENGINE_H

View File

@@ -1,836 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 24 January 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
/*
Boolean clipping:
cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4
fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
Polygon offsetting (inflate/deflate):
jointype: Square=0, Bevel=1, Round=2, Miter=3
endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
The path structures used extensively in other parts of this library are all
based on std::vector classes. Since C++ classes can't be accessed by other
languages, these paths are exported here as very simple array structures
(either of int64_t or double) that can be parsed by just about any
programming language.
These 2D paths are defined by series of x and y coordinates together with an
optional user-defined 'z' value (see Z-values below). Hence, a vertex refers
to a single x and y coordinate (+/- a user-defined value). Data structures
have names with suffixes that indicate the array type (either int64_t or
double). For example, the data structure CPath64 contains an array of int64_t
values, whereas the data structure CPathD contains an array of double.
Where documentation omits the type suffix (eg CPath), it is referring to an
array whose data type could be either int64_t or double.
For conciseness, the following letters are used in the diagrams below:
N: Number of vertices in a given path
C: Count (ie number) of paths (or PolyPaths) in the structure
A: Number of elements in an array
CPath64 and CPathD:
These are arrays of either int64_t or double values. Apart from
the first two elements, these arrays are a series of vertices
that together define a path. The very first element contains the
number of vertices (N) in the path, while second element should
contain a 0 value.
_______________________________________________________________
| counters | vertex1 | vertex2 | ... | vertexN |
| N, 0 | x1, y1, (z1) | x2, y2, (z2) | ... | xN, yN, (zN) |
---------------------------------------------------------------
CPaths64 and CPathsD:
These are also arrays of either int64_t or double values that
contain any number of consecutive CPath structures. However,
preceding the first path is a pair of values. The first value
contains the length of the entire array structure (A), and the
second contains the number (ie count) of contained paths (C).
Memory allocation for CPaths64 = A * sizeof(int64_t)
Memory allocation for CPathsD = A * sizeof(double)
__________________________________________
| counters | path1 | path2 | ... | pathC |
| A, C | | | ... | |
------------------------------------------
CPolytree64 and CPolytreeD:
The entire polytree structure is an array of int64_t or double. The
first element in the array indicates the array's total length (A).
The second element indicates the number (C) of CPolyPath structures
that are the TOP LEVEL CPolyPath in the polytree, and these top
level CPolyPath immediately follow these first two array elements.
These top level CPolyPath structures may, in turn, contain nested
CPolyPath children, and these collectively make a tree structure.
_________________________________________________________
| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC |
| A, C | | | ... | |
---------------------------------------------------------
CPolyPath64 and CPolyPathD:
These array structures consist of a pair of counter values followed by a
series of polygon vertices and a series of nested CPolyPath children.
The first counter values indicates the number of vertices in the
polygon (N), and the second counter indicates the CPolyPath child count (C).
_____________________________________________________________________________
|cntrs |vertex1 |vertex2 |...|vertexN |child1|child2|...|childC|
|N, C |x1, y1, (z1)| x2, y2, (z2)|...|xN, yN, (zN)| | |...| |
-----------------------------------------------------------------------------
DisposeArray64 & DisposeArrayD:
All array structures are allocated in heap memory which will eventually
need to be released. However, since applications linking to these DLL
functions may use different memory managers, the only safe way to release
this memory is to use the exported DisposeArray functions.
(Optional) Z-Values:
Structures will only contain user-defined z-values when the USINGZ
pre-processor identifier is used. The library does not assign z-values
because this field is intended for users to assign custom values to vertices.
Z-values in input paths (subject and clip) will be copied to solution paths.
New vertices at path intersections will generate a callback event that allows
users to assign z-values at these new vertices. The user's callback function
must conform with the DLLZCallback definition and be registered with the
DLL via SetZCallback. To assist the user in assigning z-values, the library
passes in the callback function the new intersection point together with
the four vertices that define the two segments that are intersecting.
*/
#ifndef CLIPPER2_EXPORT_H
#define CLIPPER2_EXPORT_H
#include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "clipper2/clipper.rectclip.h"
#include <cstdlib>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
typedef int64_t* CPath64;
typedef int64_t* CPaths64;
typedef double* CPathD;
typedef double* CPathsD;
typedef int64_t* CPolyPath64;
typedef int64_t* CPolyTree64;
typedef double* CPolyPathD;
typedef double* CPolyTreeD;
template <typename T>
struct CRect {
T left;
T top;
T right;
T bottom;
};
typedef CRect<int64_t> CRect64;
typedef CRect<double> CRectD;
template <typename T>
inline bool CRectIsEmpty(const CRect<T>& rect)
{
return (rect.right <= rect.left) || (rect.bottom <= rect.top);
}
template <typename T>
inline Rect<T> CRectToRect(const CRect<T>& rect)
{
Rect<T> result;
result.left = rect.left;
result.top = rect.top;
result.right = rect.right;
result.bottom = rect.bottom;
return result;
}
template <typename T1, typename T2>
inline T1 Reinterpret(T2 value) {
return *reinterpret_cast<T1*>(&value);
}
#ifdef _WIN32
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define EXTERN_DLL_EXPORT extern "C"
#endif
//////////////////////////////////////////////////////
// EXPORTED FUNCTION DECLARATIONS
//////////////////////////////////////////////////////
EXTERN_DLL_EXPORT const char* Version();
EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p)
{
delete[] p;
}
EXTERN_DLL_EXPORT void DisposeArrayD(double*& p)
{
delete[] p;
}
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPaths64& solution, CPaths64& solution_open,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPolyTree64& sol_tree, CPaths64& solution_open,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPathsD& solution, CPathsD& solution_open, int precision = 2,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPolyTreeD& solution, CPathsD& solution_open, int precision = 2,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
double delta, uint8_t jointype, uint8_t endtype,
double miter_limit = 2.0, double arc_tolerance = 0.0,
bool reverse_solution = false);
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
double delta, uint8_t jointype, uint8_t endtype,
int precision = 2, double miter_limit = 2.0,
double arc_tolerance = 0.0, bool reverse_solution = false);
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
double delta, uint8_t jointype, uint8_t endtype,
double miter_limit = 2.0, double arc_tolerance = 0.0,
bool reverse_solution = false);
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
double delta, uint8_t jointype, uint8_t endtype,
int precision = 2, double miter_limit = 2.0,
double arc_tolerance = 0.0, bool reverse_solution = false);
// RectClip & RectClipLines:
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
const CPathsD paths, int precision = 2);
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision = 2);
//////////////////////////////////////////////////////
// INTERNAL FUNCTIONS
//////////////////////////////////////////////////////
#ifdef USINGZ
ZCallback64 dllCallback64 = nullptr;
ZCallbackD dllCallbackD = nullptr;
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3;
#else
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2;
#endif
template <typename T>
static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
size_t& cnt, size_t& array_len)
{
array_len = 2;
cnt = 0;
for (const Path<T>& path : paths)
if (path.size())
{
array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2;
++cnt;
}
}
static size_t GetPolyPathArrayLen64(const PolyPath64& pp)
{
size_t result = 2; // poly_length + child_count
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
//plus nested children :)
for (size_t i = 0; i < pp.Count(); ++i)
result += GetPolyPathArrayLen64(*pp[i]);
return result;
}
static size_t GetPolyPathArrayLenD(const PolyPathD& pp)
{
size_t result = 2; // poly_length + child_count
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
//plus nested children :)
for (size_t i = 0; i < pp.Count(); ++i)
result += GetPolyPathArrayLenD(*pp[i]);
return result;
}
static void GetPolytreeCountAndCStorageSize64(const PolyTree64& tree,
size_t& cnt, size_t& array_len)
{
cnt = tree.Count(); // nb: top level count only
array_len = GetPolyPathArrayLen64(tree);
}
static void GetPolytreeCountAndCStorageSizeD(const PolyTreeD& tree,
size_t& cnt, size_t& array_len)
{
cnt = tree.Count(); // nb: top level count only
array_len = GetPolyPathArrayLenD(tree);
}
template <typename T>
static T* CreateCPathsFromPathsT(const Paths<T>& paths)
{
size_t cnt = 0, array_len = 0;
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
T* result = new T[array_len], * v = result;
*v++ = array_len;
*v++ = cnt;
for (const Path<T>& path : paths)
{
if (!path.size()) continue;
*v++ = path.size();
*v++ = 0;
for (const Point<T>& pt : path)
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
*v++ = Reinterpret<T>(pt.z);
#endif
}
}
return result;
}
CPathsD CreateCPathsDFromPathsD(const PathsD& paths)
{
if (!paths.size()) return nullptr;
size_t cnt, array_len;
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
CPathsD result = new double[array_len], v = result;
*v++ = (double)array_len;
*v++ = (double)cnt;
for (const PathD& path : paths)
{
if (!path.size()) continue;
*v = (double)path.size();
++v; *v++ = 0;
for (const PointD& pt : path)
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
* v++ = Reinterpret<double>(pt.z);
#endif
}
}
return result;
}
CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
{
if (!paths.size()) return nullptr;
size_t cnt, array_len;
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
CPathsD result = new double[array_len], v = result;
*v++ = (double)array_len;
*v++ = (double)cnt;
for (const Path64& path : paths)
{
if (!path.size()) continue;
*v = (double)path.size();
++v; *v++ = 0;
for (const Point64& pt : path)
{
*v++ = pt.x * scale;
*v++ = pt.y * scale;
#ifdef USINGZ
*v++ = Reinterpret<double>(pt.z);
#endif
}
}
return result;
}
template <typename T>
static Path<T> ConvertCPathToPathT(T* path)
{
Path<T> result;
if (!path) return result;
T* v = path;
size_t cnt = static_cast<size_t>(*v);
v += 2; // skip 0 value
result.reserve(cnt);
for (size_t j = 0; j < cnt; ++j)
{
T x = *v++, y = *v++;
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
result.emplace_back(x, y, z);
#else
result.emplace_back(x, y);
#endif
}
return result;
}
template <typename T>
static Paths<T> ConvertCPathsToPathsT(T* paths)
{
Paths<T> result;
if (!paths) return result;
T* v = paths; ++v;
size_t cnt = static_cast<size_t>(*v++);
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
size_t cnt2 = static_cast<size_t>(*v);
v += 2;
Path<T> path;
path.reserve(cnt2);
for (size_t j = 0; j < cnt2; ++j)
{
T x = *v++, y = *v++;
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
path.emplace_back(x, y, z);
#else
path.emplace_back(x, y);
#endif
}
result.emplace_back(std::move(path));
}
return result;
}
static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale)
{
Path64 result;
if (!path) return result;
double* v = path;
size_t cnt = static_cast<size_t>(*v);
v += 2; // skip 0 value
result.reserve(cnt);
for (size_t j = 0; j < cnt; ++j)
{
double x = *v++ * scale;
double y = *v++ * scale;
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
result.emplace_back(x, y, z);
#else
result.emplace_back(x, y);
#endif
}
return result;
}
static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
{
Paths64 result;
if (!paths) return result;
double* v = paths;
++v; // skip the first value (0)
size_t cnt = static_cast<size_t>(*v++);
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
size_t cnt2 = static_cast<size_t>(*v);
v += 2;
Path64 path;
path.reserve(cnt2);
for (size_t j = 0; j < cnt2; ++j)
{
double x = *v++ * scale;
double y = *v++ * scale;
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
path.emplace_back(x, y, z);
#else
path.emplace_back(x, y);
#endif
}
result.emplace_back(std::move(path));
}
return result;
}
static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v)
{
*v++ = static_cast<int64_t>(pp->Polygon().size());
*v++ = static_cast<int64_t>(pp->Count());
for (const Point64& pt : pp->Polygon())
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
* v++ = Reinterpret<int64_t>(pt.z); // raw memory copy
#endif
}
for (size_t i = 0; i < pp->Count(); ++i)
CreateCPolyPath64(pp->Child(i), v);
}
static void CreateCPolyPathD(const PolyPathD* pp, double*& v)
{
*v++ = static_cast<double>(pp->Polygon().size());
*v++ = static_cast<double>(pp->Count());
for (const PointD& pt : pp->Polygon())
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
* v++ = Reinterpret<double>(pt.z); // raw memory copy
#endif
}
for (size_t i = 0; i < pp->Count(); ++i)
CreateCPolyPathD(pp->Child(i), v);
}
static int64_t* CreateCPolyTree64(const PolyTree64& tree)
{
size_t cnt, array_len;
GetPolytreeCountAndCStorageSize64(tree, cnt, array_len);
if (!cnt) return nullptr;
// allocate storage
int64_t* result = new int64_t[array_len];
int64_t* v = result;
*v++ = static_cast<int64_t>(array_len);
*v++ = static_cast<int64_t>(tree.Count());
for (size_t i = 0; i < tree.Count(); ++i)
CreateCPolyPath64(tree.Child(i), v);
return result;
}
static double* CreateCPolyTreeD(const PolyTreeD& tree)
{
double scale = std::log10(tree.Scale());
size_t cnt, array_len;
GetPolytreeCountAndCStorageSizeD(tree, cnt, array_len);
if (!cnt) return nullptr;
// allocate storage
double* result = new double[array_len];
double* v = result;
*v++ = static_cast<double>(array_len);
*v++ = static_cast<double>(tree.Count());
for (size_t i = 0; i < tree.Count(); ++i)
CreateCPolyPathD(tree.Child(i), v);
return result;
}
//////////////////////////////////////////////////////
// EXPORTED FUNCTION DEFINITIONS
//////////////////////////////////////////////////////
EXTERN_DLL_EXPORT const char* Version()
{
return CLIPPER2_VERSION;
}
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPaths64& solution, CPaths64& solution_open,
bool preserve_collinear, bool reverse_solution)
{
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
Paths64 sub, sub_open, clp, sol, sol_open;
sub = ConvertCPathsToPathsT(subjects);
sub_open = ConvertCPathsToPathsT(subjects_open);
clp = ConvertCPathsToPathsT(clips);
Clipper64 clipper;
clipper.PreserveCollinear(preserve_collinear);
clipper.ReverseSolution(reverse_solution);
#ifdef USINGZ
if (dllCallback64)
clipper.SetZCallback(dllCallback64);
#endif
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
return -1; // clipping bug - should never happen :)
solution = CreateCPathsFromPathsT(sol);
solution_open = CreateCPathsFromPathsT(sol_open);
return 0; //success !!
}
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPolyTree64& sol_tree, CPaths64& solution_open,
bool preserve_collinear, bool reverse_solution)
{
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
Paths64 sub, sub_open, clp, sol_open;
sub = ConvertCPathsToPathsT(subjects);
sub_open = ConvertCPathsToPathsT(subjects_open);
clp = ConvertCPathsToPathsT(clips);
PolyTree64 tree;
Clipper64 clipper;
clipper.PreserveCollinear(preserve_collinear);
clipper.ReverseSolution(reverse_solution);
#ifdef USINGZ
if (dllCallback64)
clipper.SetZCallback(dllCallback64);
#endif
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
return -1; // clipping bug - should never happen :)
sol_tree = CreateCPolyTree64(tree);
solution_open = CreateCPathsFromPathsT(sol_open);
return 0; //success !!
}
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPathsD& solution, CPathsD& solution_open, int precision,
bool preserve_collinear, bool reverse_solution)
{
if (precision < -8 || precision > 8) return -5;
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
//const double scale = std::pow(10, precision);
PathsD sub, sub_open, clp, sol, sol_open;
sub = ConvertCPathsToPathsT(subjects);
sub_open = ConvertCPathsToPathsT(subjects_open);
clp = ConvertCPathsToPathsT(clips);
ClipperD clipper(precision);
clipper.PreserveCollinear(preserve_collinear);
clipper.ReverseSolution(reverse_solution);
#ifdef USINGZ
if (dllCallbackD)
clipper.SetZCallback(dllCallbackD);
#endif
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype),
FillRule(fillrule), sol, sol_open)) return -1;
solution = CreateCPathsDFromPathsD(sol);
solution_open = CreateCPathsDFromPathsD(sol_open);
return 0;
}
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPolyTreeD& solution, CPathsD& solution_open, int precision,
bool preserve_collinear, bool reverse_solution)
{
if (precision < -8 || precision > 8) return -5;
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
//double scale = std::pow(10, precision);
int err = 0;
PathsD sub, sub_open, clp, sol_open;
sub = ConvertCPathsToPathsT(subjects);
sub_open = ConvertCPathsToPathsT(subjects_open);
clp = ConvertCPathsToPathsT(clips);
PolyTreeD tree;
ClipperD clipper(precision);
clipper.PreserveCollinear(preserve_collinear);
clipper.ReverseSolution(reverse_solution);
#ifdef USINGZ
if (dllCallbackD)
clipper.SetZCallback(dllCallbackD);
#endif
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
return -1; // clipping bug - should never happen :)
solution = CreateCPolyTreeD(tree);
solution_open = CreateCPathsDFromPathsD(sol_open);
return 0; //success !!
}
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
Paths64 pp;
pp = ConvertCPathsToPathsT(paths);
ClipperOffset clip_offset( miter_limit,
arc_tolerance, reverse_solution);
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
Paths64 result;
clip_offset.Execute(delta, result);
return CreateCPathsFromPathsT(result);
}
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
double delta, uint8_t jointype, uint8_t endtype,
int precision, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
if (precision < -8 || precision > 8 || !paths) return nullptr;
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
Paths64 result;
clip_offset.Execute(delta * scale, result);
return CreateCPathsDFromPaths64(result, 1 / scale);
}
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
Path64 pp;
pp = ConvertCPathToPathT(path);
ClipperOffset clip_offset(miter_limit,
arc_tolerance, reverse_solution);
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
Paths64 result;
clip_offset.Execute(delta, result);
return CreateCPathsFromPathsT(result);
}
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
double delta, uint8_t jointype, uint8_t endtype,
int precision, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
if (precision < -8 || precision > 8 || !path) return nullptr;
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
Path64 pp = ConvertCPathDToPath64WithScale(path, scale);
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
Paths64 result;
clip_offset.Execute(delta * scale, result);
return CreateCPathsDFromPaths64(result, 1 / scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r64 = CRectToRect(rect);
class RectClip64 rc(r64);
Paths64 pp = ConvertCPathsToPathsT(paths);
Paths64 result = rc.Execute(pp);
return CreateCPathsFromPathsT(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr;
const double scale = std::pow(10, precision);
RectD r = CRectToRect(rect);
Rect64 rec = ScaleRect<int64_t, double>(r, scale);
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
class RectClip64 rc(rec);
Paths64 result = rc.Execute(pp);
return CreateCPathsDFromPaths64(result, 1 / scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r = CRectToRect(rect);
class RectClipLines64 rcl (r);
Paths64 pp = ConvertCPathsToPathsT(paths);
Paths64 result = rcl.Execute(pp);
return CreateCPathsFromPathsT(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr;
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
class RectClipLines64 rcl(r);
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
Paths64 result = rcl.Execute(pp);
return CreateCPathsDFromPaths64(result, 1 / scale);
}
EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
{
Path64 path = ConvertCPathToPathT(cpath);
Path64 pattern = ConvertCPathToPathT(cpattern);
Paths64 solution = MinkowskiSum(pattern, path, is_closed);
return CreateCPathsFromPathsT(solution);
}
EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
{
Path64 path = ConvertCPathToPathT(cpath);
Path64 pattern = ConvertCPathToPathT(cpattern);
Paths64 solution = MinkowskiDiff(pattern, path, is_closed);
return CreateCPathsFromPathsT(solution);
}
#ifdef USINGZ
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
EXTERN_DLL_EXPORT void SetZCallback64(DLLZCallback64 callback)
{
dllCallback64 = callback;
}
EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback)
{
dllCallbackD = callback;
}
#endif
}
#endif // CLIPPER2_EXPORT_H

View File

@@ -1,770 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 27 April 2024 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This module provides a simple interface to the Clipper Library *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_H
#define CLIPPER_H
#include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "clipper2/clipper.minkowski.h"
#include "clipper2/clipper.rectclip.h"
#include <type_traits>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
{
Paths64 sol_open;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, solution, sol_open);
}
inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips, int precision = 2)
{
int error_code = 0;
CheckPrecisionRange(precision, error_code);
PathsD result;
if (error_code) return result;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips,
PolyTreeD& polytree, int precision = 2)
{
polytree.Clear();
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (error_code) return;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, polytree);
}
inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
}
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips);
}
inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
{
PathsD result;
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (error_code) return result;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
}
inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
}
inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 InflatePaths(const Paths64& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0,
double arc_tolerance = 0.0)
{
if (!delta) return paths;
ClipperOffset clip_offset(miter_limit, arc_tolerance);
clip_offset.AddPaths(paths, jt, et);
Paths64 solution;
clip_offset.Execute(delta, solution);
return solution;
}
inline PathsD InflatePaths(const PathsD& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0,
int precision = 2, double arc_tolerance = 0.0)
{
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (!delta) return paths;
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit, arc_tolerance);
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
if (error_code) return PathsD();
Paths64 solution;
clip_offset.Execute(delta * scale, solution);
return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
}
template <typename T>
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
{
Path<T> result;
result.reserve(path.size());
std::transform(path.begin(), path.end(), back_inserter(result),
[dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
return result;
}
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
{
return TranslatePath<int64_t>(path, dx, dy);
}
inline PathD TranslatePath(const PathD& path, double dx, double dy)
{
return TranslatePath<double>(path, dx, dy);
}
template <typename T>
inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
{
Paths<T> result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result;
}
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
{
return TranslatePaths<int64_t>(paths, dx, dy);
}
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
{
return TranslatePaths<double>(paths, dx, dy);
}
inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
{
if (rect.IsEmpty() || paths.empty()) return Paths64();
RectClip64 rc(rect);
return rc.Execute(paths);
}
inline Paths64 RectClip(const Rect64& rect, const Path64& path)
{
if (rect.IsEmpty() || path.empty()) return Paths64();
RectClip64 rc(rect);
return rc.Execute(Paths64{ path });
}
inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
{
if (rect.IsEmpty() || paths.empty()) return PathsD();
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClip64 rc(r);
Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
if (error_code) return PathsD(); // ie: error_code result is lost
return ScalePaths<double, int64_t>(
rc.Execute(pp), 1 / scale, error_code);
}
inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
{
return RectClip(rect, PathsD{ path }, precision);
}
inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
{
if (rect.IsEmpty() || lines.empty()) return Paths64();
RectClipLines64 rcl(rect);
return rcl.Execute(lines);
}
inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
{
return RectClipLines(rect, Paths64{ line });
}
inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
{
if (rect.IsEmpty() || lines.empty()) return PathsD();
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClipLines64 rcl(r);
Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
if (error_code) return PathsD();
p = rcl.Execute(p);
return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
}
inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2)
{
return RectClipLines(rect, PathsD{ line }, precision);
}
namespace details
{
inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
{
paths.emplace_back(polypath.Polygon());
for (const auto& child : polypath)
PolyPathToPaths64(*child, paths);
}
inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
{
paths.emplace_back(polypath.Polygon());
for (const auto& child : polypath)
PolyPathToPathsD(*child, paths);
}
inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
{
for (const auto& child : pp)
{
// return false if this child isn't fully contained by its parent
// checking for a single vertex outside is a bit too crude since
// it doesn't account for rounding errors. It's better to check
// for consecutive vertices found outside the parent's polygon.
int outsideCnt = 0;
for (const Point64& pt : child->Polygon())
{
PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
if (result == PointInPolygonResult::IsInside) --outsideCnt;
else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
if (outsideCnt > 1) return false;
else if (outsideCnt < -1) break;
}
// now check any nested children too
if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
return false;
}
return true;
}
static void OutlinePolyPath(std::ostream& os,
size_t idx, bool isHole, size_t count, const std::string& preamble)
{
std::string plural = (count == 1) ? "." : "s.";
if (isHole)
os << preamble << "+- Hole (" << idx << ") contains " << count <<
" nested polygon" << plural << std::endl;
else
os << preamble << "+- Polygon (" << idx << ") contains " << count <<
" hole" << plural << std::endl;
}
static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
size_t idx, std::string preamble)
{
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
for (size_t i = 0; i < pp.Count(); ++i)
if (pp.Child(i)->Count())
details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " ");
}
static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
size_t idx, std::string preamble)
{
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
for (size_t i = 0; i < pp.Count(); ++i)
if (pp.Child(i)->Count())
details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " ");
}
template<typename T, typename U>
inline constexpr void MakePathGeneric(const T an_array,
size_t array_size, std::vector<U>& result)
{
result.reserve(array_size / 2);
for (size_t i = 0; i < array_size; i +=2)
#ifdef USINGZ
result.emplace_back( an_array[i], an_array[i + 1], 0 );
#else
result.emplace_back( an_array[i], an_array[i + 1] );
#endif
}
} // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
{
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
for (size_t i = 0; i < pp.Count(); ++i)
if (pp.Child(i)->Count())
details::OutlinePolyPath64(os, *pp.Child(i), i, " ");
os << std::endl << std::endl;
return os;
}
inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
{
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
for (size_t i = 0; i < pp.Count(); ++i)
if (pp.Child(i)->Count())
details::OutlinePolyPathD(os, *pp.Child(i), i, " ");
os << std::endl << std::endl;
if (!pp.Level()) os << std::endl;
return os;
}
inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
{
Paths64 result;
for (const auto& child : polytree)
details::PolyPathToPaths64(*child, result);
return result;
}
inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
{
PathsD result;
for (const auto& child : polytree)
details::PolyPathToPathsD(*child, result);
return result;
}
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
{
for (const auto& child : polytree)
if (child->Count() > 0 &&
!details::PolyPath64ContainsChildren(*child))
return false;
return true;
}
template<typename T,
typename std::enable_if<
std::is_integral<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline Path64 MakePath(const std::vector<T>& list)
{
const auto size = list.size() - list.size() % 2;
if (list.size() != size)
DoError(non_pair_error_i); // non-fatal without exception handling
Path64 result;
details::MakePathGeneric(list, size, result);
return result;
}
template<typename T, std::size_t N,
typename std::enable_if<
std::is_integral<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline Path64 MakePath(const T(&list)[N])
{
// Make the compiler error on unpaired value (i.e. no runtime effects).
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
Path64 result;
details::MakePathGeneric(list, N, result);
return result;
}
template<typename T,
typename std::enable_if<
std::is_arithmetic<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline PathD MakePathD(const std::vector<T>& list)
{
const auto size = list.size() - list.size() % 2;
if (list.size() != size)
DoError(non_pair_error_i); // non-fatal without exception handling
PathD result;
details::MakePathGeneric(list, size, result);
return result;
}
template<typename T, std::size_t N,
typename std::enable_if<
std::is_arithmetic<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline PathD MakePathD(const T(&list)[N])
{
// Make the compiler error on unpaired value (i.e. no runtime effects).
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
PathD result;
details::MakePathGeneric(list, N, result);
return result;
}
#ifdef USINGZ
template<typename T2, std::size_t N>
inline Path64 MakePathZ(const T2(&list)[N])
{
static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
"MakePathZ requires integer values in multiples of 3");
std::size_t size = N / 3;
Path64 result(size);
for (size_t i = 0; i < size; ++i)
result[i] = Point64(list[i * 3],
list[i * 3 + 1], list[i * 3 + 2]);
return result;
}
template<typename T2, std::size_t N>
inline PathD MakePathZD(const T2(&list)[N])
{
static_assert(N % 3 == 0,
"MakePathZD requires values in multiples of 3");
std::size_t size = N / 3;
PathD result(size);
if constexpr (std::numeric_limits<T2>::is_integer)
for (size_t i = 0; i < size; ++i)
result[i] = PointD(list[i * 3],
list[i * 3 + 1], list[i * 3 + 2]);
else
for (size_t i = 0; i < size; ++i)
result[i] = PointD(list[i * 3], list[i * 3 + 1],
static_cast<int64_t>(list[i * 3 + 2]));
return result;
}
#endif
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
{
size_t len = p.size();
if (len < 3)
{
if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
else return p;
}
Path64 dst;
dst.reserve(len);
Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
if (!is_open_path)
{
while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1)))
++srcIt;
while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt))
--stop;
if (srcIt == stop) return Path64();
}
prevIt = srcIt++;
dst.emplace_back(*prevIt);
for (; srcIt != stop; ++srcIt)
{
if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1)))
{
prevIt = srcIt;
dst.emplace_back(*prevIt);
}
}
if (is_open_path)
dst.emplace_back(*srcIt);
else if (!IsCollinear(*prevIt, *stop, dst[0]))
dst.emplace_back(*stop);
else
{
while (dst.size() > 2 &&
IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
dst.pop_back();
if (dst.size() < 3) return Path64();
}
return dst;
}
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
{
int error_code = 0;
CheckPrecisionRange(precision, error_code);
if (error_code) return PathD();
const double scale = std::pow(10, precision);
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathD();
p = TrimCollinear(p, is_open_path);
return ScalePath<double, int64_t>(p, 1/scale, error_code);
}
template <typename T>
inline double Distance(const Point<T> pt1, const Point<T> pt2)
{
return std::sqrt(DistanceSqr(pt1, pt2));
}
template <typename T>
inline double Length(const Path<T>& path, bool is_closed_path = false)
{
double result = 0.0;
if (path.size() < 2) return result;
auto it = path.cbegin(), stop = path.end() - 1;
for (; it != stop; ++it)
result += Distance(*it, *(it + 1));
if (is_closed_path)
result += Distance(*stop, *path.cbegin());
return result;
}
template <typename T>
inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
{
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
}
template <typename T>
inline Path<T> Ellipse(const Rect<T>& rect, size_t steps = 0)
{
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
static_cast<double>(rect.Height()) * 0.5, steps);
}
template <typename T>
inline Path<T> Ellipse(const Point<T>& center,
double radiusX, double radiusY = 0, size_t steps = 0)
{
if (radiusX <= 0) return Path<T>();
if (radiusY <= 0) radiusY = radiusX;
if (steps <= 2)
steps = static_cast<size_t>(PI * sqrt((radiusX + radiusY) / 2));
double si = std::sin(2 * PI / steps);
double co = std::cos(2 * PI / steps);
double dx = co, dy = si;
Path<T> result;
result.reserve(steps);
result.emplace_back(center.x + radiusX, static_cast<double>(center.y));
for (size_t i = 1; i < steps; ++i)
{
result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy);
double x = dx * co - dy * si;
dy = dy * co + dx * si;
dx = x;
}
return result;
}
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
while (current <= high && flags[current]) ++current;
if (current <= high) return current;
current = 0;
while (flags[current]) ++current;
return current;
}
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
else --current;
while (current > 0 && flags[current]) --current;
if (!flags[current]) return current;
current = high;
while (flags[current]) --current;
return current;
}
template <typename T>
inline Path<T> SimplifyPath(const Path<T> &path,
double epsilon, bool isClosedPath = true)
{
const size_t len = path.size(), high = len -1;
const double epsSqr = Sqr(epsilon);
if (len < 4) return Path<T>(path);
std::vector<bool> flags(len);
std::vector<double> distSqr(len);
size_t prior = high, curr = 0, start, next, prior2;
if (isClosedPath)
{
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
}
else
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
}
for (size_t i = 1; i < high; ++i)
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
for (;;)
{
if (distSqr[curr] > epsSqr)
{
start = curr;
do
{
curr = GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
if (next == prior) break;
// flag for removal the smaller of adjacent 'distances'
if (distSqr[next] < distSqr[curr])
{
prior2 = prior;
prior = curr;
curr = next;
next = GetNext(next, high, flags);
}
else
prior2 = GetPrior(prior, high, flags);
flags[curr] = true;
curr = next;
next = GetNext(next, high, flags);
if (isClosedPath || ((curr != high) && (curr != 0)))
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (isClosedPath || ((prior != 0) && (prior != high)))
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
}
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (!flags[i]) result.emplace_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> SimplifyPaths(const Paths<T> &paths,
double epsilon, bool isClosedPath = true)
{
Paths<T> result;
result.reserve(paths.size());
for (const auto& path : paths)
result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath)));
return result;
}
template <typename T>
inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags)
{
typename Path<T>::size_type idx = 0;
double max_d = 0;
while (end > begin && path[begin] == path[end]) flags[end--] = false;
for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
{
// PerpendicDistFromLineSqrd - avoids expensive Sqrt()
double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
if (d <= max_d) continue;
max_d = d;
idx = i;
}
if (max_d <= epsSqrd) return;
flags[idx] = true;
if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
}
template <typename T>
inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
{
const typename Path<T>::size_type len = path.size();
if (len < 5) return Path<T>(path);
std::vector<bool> flags(len);
flags[0] = true;
flags[len - 1] = true;
RDP(path, 0, len - 1, Sqr(epsilon), flags);
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (flags[i])
result.emplace_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
{
Paths<T> result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[epsilon](const auto& path)
{ return RamerDouglasPeucker<T>(path, epsilon); });
return result;
}
} // end Clipper2Lib namespace
#endif // CLIPPER_H

View File

@@ -1,120 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 1 November 2023 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Minkowski Sum and Difference *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_MINKOWSKI_H
#define CLIPPER_MINKOWSKI_H
#include "clipper2/clipper.core.h"
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
namespace detail
{
inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed)
{
size_t delta = isClosed ? 0 : 1;
size_t patLen = pattern.size(), pathLen = path.size();
if (patLen == 0 || pathLen == 0) return Paths64();
Paths64 tmp;
tmp.reserve(pathLen);
if (isSum)
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p + pt2; });
tmp.emplace_back(std::move(path2));
}
}
else
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p - pt2; });
tmp.emplace_back(std::move(path2));
}
}
Paths64 result;
result.reserve((pathLen - delta) * patLen);
size_t g = isClosed ? pathLen - 1 : 0;
for (size_t h = patLen - 1, i = delta; i < pathLen; ++i)
{
for (size_t j = 0; j < patLen; j++)
{
Path64 quad;
quad.reserve(4);
{
quad.emplace_back(tmp[g][h]);
quad.emplace_back(tmp[i][h]);
quad.emplace_back(tmp[i][j]);
quad.emplace_back(tmp[g][j]);
};
if (!IsPositive(quad))
std::reverse(quad.begin(), quad.end());
result.emplace_back(std::move(quad));
h = j;
}
g = i;
}
return result;
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
} // namespace internal
inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
int error_code = 0;
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
}
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
int error_code = 0;
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
}
} // Clipper2Lib namespace
#endif // CLIPPER_MINKOWSKI_H

View File

@@ -1,129 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 January 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_OFFSET_H_
#define CLIPPER_OFFSET_H_
#include "clipper.core.h"
#include "clipper.engine.h"
#include <optional>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
enum class JoinType { Square, Bevel, Round, Miter };
//Square : Joins are 'squared' at exactly the offset distance (more complex code)
//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster)
enum class EndType {Polygon, Joined, Butt, Square, Round};
//Butt : offsets both sides of a path, with square blunt ends
//Square : offsets both sides of a path, with square extended ends
//Round : offsets both sides of a path, with round extended ends
//Joined : offsets both sides of a path, with joined ends
//Polygon: offsets only one side of a closed path
typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;
class ClipperOffset {
private:
class Group {
public:
Paths64 paths_in;
std::optional<size_t> lowest_path_idx{};
bool is_reversed = false;
JoinType join_type;
EndType end_type;
Group(const Paths64& _paths, JoinType _join_type, EndType _end_type);
};
int error_code_ = 0;
double delta_ = 0.0;
double group_delta_ = 0.0;
double temp_lim_ = 0.0;
double steps_per_rad_ = 0.0;
double step_sin_ = 0.0;
double step_cos_ = 0.0;
PathD norms;
Path64 path_out;
Paths64* solution = nullptr;
PolyTree64* solution_tree = nullptr;
std::vector<Group> groups_;
JoinType join_type_ = JoinType::Bevel;
EndType end_type_ = EndType::Polygon;
double miter_limit_ = 0.0;
double arc_tolerance_ = 0.0;
bool preserve_collinear_ = false;
bool reverse_solution_ = false;
#ifdef USINGZ
ZCallback64 zCallback64_ = nullptr;
void ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip);
#endif
DeltaCallback64 deltaCallback64_ = nullptr;
size_t CalcSolutionCapacity();
bool CheckReverseOrientation();
void DoBevel(const Path64& path, size_t j, size_t k);
void DoSquare(const Path64& path, size_t j, size_t k);
void DoMiter(const Path64& path, size_t j, size_t k, double cos_a);
void DoRound(const Path64& path, size_t j, size_t k, double angle);
void BuildNormals(const Path64& path);
void OffsetPolygon(Group& group, const Path64& path);
void OffsetOpenJoined(Group& group, const Path64& path);
void OffsetOpenPath(Group& group, const Path64& path);
void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k);
void DoGroupOffset(Group &group);
void ExecuteInternal(double delta);
public:
explicit ClipperOffset(double miter_limit = 2.0,
double arc_tolerance = 0.0,
bool preserve_collinear = false,
bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear),
reverse_solution_(reverse_solution) { };
~ClipperOffset() { Clear(); };
int ErrorCode() const { return error_code_; };
void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
void Clear() { groups_.clear(); norms.clear(); };
void Execute(double delta, Paths64& sols_64);
void Execute(double delta, PolyTree64& polytree);
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
double MiterLimit() const { return miter_limit_; }
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
double ArcTolerance() const { return arc_tolerance_; }
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
bool PreserveCollinear() const { return preserve_collinear_; }
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
bool ReverseSolution() const { return reverse_solution_; }
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
#ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
#endif
void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
};
}
#endif /* CLIPPER_OFFSET_H_ */

View File

@@ -1,83 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 5 July 2024 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : FAST rectangular clipping *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_RECTCLIP_H
#define CLIPPER_RECTCLIP_H
#include "clipper2/clipper.core.h"
#include <queue>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
// Location: the order is important here, see StartLocsIsClockwise()
enum class Location { Left, Top, Right, Bottom, Inside };
class OutPt2;
typedef std::vector<OutPt2*> OutPt2List;
class OutPt2 {
public:
Point64 pt;
size_t owner_idx = 0;
OutPt2List* edge = nullptr;
OutPt2* next = nullptr;
OutPt2* prev = nullptr;
};
//------------------------------------------------------------------------------
// RectClip64
//------------------------------------------------------------------------------
class RectClip64 {
private:
void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op);
protected:
const Rect64 rect_;
const Path64 rect_as_path_;
const Point64 rect_mp_;
Rect64 path_bounds_;
std::deque<OutPt2> op_container_;
OutPt2List results_; // each path can be broken into multiples
OutPt2List edges_[8]; // clockwise and counter-clockwise
std::vector<Location> start_locs_;
void CheckEdges();
void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw);
void GetNextLocation(const Path64& path,
Location& loc, size_t& i, size_t highI);
OutPt2* Add(Point64 pt, bool start_new = false);
void AddCorner(Location prev, Location curr);
void AddCorner(Location& loc, bool isClockwise);
public:
explicit RectClip64(const Rect64& rect) :
rect_(rect),
rect_as_path_(rect.AsPath()),
rect_mp_(rect.MidPoint()) {}
Paths64 Execute(const Paths64& paths);
};
//------------------------------------------------------------------------------
// RectClipLines64
//------------------------------------------------------------------------------
class RectClipLines64 : public RectClip64 {
private:
void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op);
public:
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
Paths64 Execute(const Paths64& paths);
};
} // Clipper2Lib namespace
#endif // CLIPPER_RECTCLIP_H

View File

@@ -1,6 +0,0 @@
#ifndef CLIPPER_VERSION_H
#define CLIPPER_VERSION_H
constexpr auto CLIPPER2_VERSION = "1.5.2";
#endif // CLIPPER_VERSION_H

View File

@@ -1,17 +0,0 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper2 library with the Z support.
#ifndef clipper2_z_hpp
#ifdef CLIPPER_H
#error "You should include clipper2_z.hpp before clipper.h"
#endif
#define clipper2_z_hpp
// Enable the Z coordinate support.
#define USINGZ
#include "clipper.h"
#undef CLIPPER_H
#undef USINGZ
#endif // clipper2_z_hpp

File diff suppressed because it is too large Load Diff

View File

@@ -1,658 +0,0 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 January 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#include "clipper2/clipper.h"
#include "clipper2/clipper.offset.h"
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
const double floating_point_tolerance = 1e-12;
// Clipper2 approximates arcs by using series of relatively short straight
//line segments. And logically, shorter line segments will produce better arc
// approximations. But very short segments can degrade performance, usually
// with little or no discernable improvement in curve quality. Very short
// segments can even detract from curve quality, due to the effects of integer
// rounding. Since there isn't an optimal number of line segments for any given
// arc radius (that perfectly balances curve approximation with performance),
// arc tolerance is user defined. Nevertheless, when the user doesn't define
// an arc tolerance (ie leaves alone the 0 default value), the calculated
// default arc tolerance (offset_radius / 500) generally produces good (smooth)
// arc approximations without producing excessively small segment lengths.
// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm
const double arc_const = 0.002; // <-- 1/500
//------------------------------------------------------------------------------
// Miscellaneous methods
//------------------------------------------------------------------------------
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
{
std::optional<size_t> result;
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
for (size_t i = 0; i < paths.size(); ++i)
{
for (const Point64& pt : paths[i])
{
if ((pt.y < botPt.y) ||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
result = i;
botPt.x = pt.x;
botPt.y = pt.y;
}
}
return result;
}
inline double Hypot(double x, double y)
{
// given that this is an internal function, and given the x and y parameters
// will always be coordinate values (or the difference between coordinate values),
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
// there should be no risk that the following computation will overflow
// see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
}
static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
{
if (pt1 == pt2) return PointD(0.0, 0.0);
double dx = static_cast<double>(pt2.x - pt1.x);
double dy = static_cast<double>(pt2.y - pt1.y);
double inverse_hypot = 1.0 / Hypot(dx, dy);
dx *= inverse_hypot;
dy *= inverse_hypot;
return PointD(dy, -dx);
}
inline bool AlmostZero(double value, double epsilon = 0.001)
{
return std::fabs(value) < epsilon;
}
inline PointD NormalizeVector(const PointD& vec)
{
double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h;
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
}
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
{
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
}
inline bool IsClosedPath(EndType et)
{
return et == EndType::Polygon || et == EndType::Joined;
}
static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
{
#ifdef USINGZ
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
#else
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
#endif
}
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
{
#ifdef USINGZ
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
#else
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
#endif
}
inline void NegatePath(PathD& path)
{
for (PointD& pt : path)
{
pt.x = -pt.x;
pt.y = -pt.y;
#ifdef USINGZ
pt.z = pt.z;
#endif
}
}
//------------------------------------------------------------------------------
// ClipperOffset::Group methods
//------------------------------------------------------------------------------
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
paths_in(_paths), join_type(_join_type), end_type(_end_type)
{
bool is_joined =
(end_type == EndType::Polygon) ||
(end_type == EndType::Joined);
for (Path64& p: paths_in)
StripDuplicates(p, is_joined);
if (end_type == EndType::Polygon)
{
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
// the lowermost path must be an outer path, so if its orientation is negative,
// then flag the whole group is 'reversed' (will negate delta etc.)
// as this is much more efficient than reversing every path.
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
}
else
{
lowest_path_idx = std::nullopt;
is_reversed = false;
}
}
//------------------------------------------------------------------------------
// ClipperOffset methods
//------------------------------------------------------------------------------
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
{
groups_.emplace_back(Paths64(1, path), jt_, et_);
}
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
{
if (paths.size() == 0) return;
groups_.emplace_back(paths, jt_, et_);
}
void ClipperOffset::BuildNormals(const Path64& path)
{
norms.clear();
norms.reserve(path.size());
if (path.size() == 0) return;
Path64::const_iterator path_iter, path_stop_iter = --path.cend();
for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1)));
norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
}
void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
{
PointD pt1, pt2;
if (j == k)
{
double abs_delta = std::abs(group_delta_);
#ifdef USINGZ
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
#else
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
#endif
}
else
{
#ifdef USINGZ
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
#else
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
#endif
}
path_out.emplace_back(pt1);
path_out.emplace_back(pt2);
}
void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{
PointD vec;
if (j == k)
vec = PointD(norms[j].y, -norms[j].x);
else
vec = GetAvgUnitVector(
PointD(-norms[k].y, norms[k].x),
PointD(norms[j].y, -norms[j].x));
double abs_delta = std::abs(group_delta_);
// now offset the original vertex delta units along unit vector
PointD ptQ = PointD(path[j]);
ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
// get perpendicular vertices
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
// get 2 vertices along one edge offset
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
if (j == k)
{
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
//get the second intersect point through reflecion
path_out.emplace_back(ReflectPoint(pt, ptQ));
path_out.emplace_back(pt);
}
else
{
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
path_out.emplace_back(pt);
//get the second intersect point through reflecion
path_out.emplace_back(ReflectPoint(pt, ptQ));
}
}
void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
{
double q = group_delta_ / (cos_a + 1);
#ifdef USINGZ
path_out.emplace_back(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q,
path[j].z);
#else
path_out.emplace_back(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q);
#endif
}
void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
{
if (deltaCallback64_) {
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
// so we'll need to do the following calculations for *every* vertex.
double abs_delta = std::fabs(group_delta_);
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const);
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
step_sin_ = std::sin(2 * PI / steps_per_360);
step_cos_ = std::cos(2 * PI / steps_per_360);
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
steps_per_rad_ = steps_per_360 / (2 * PI);
}
Point64 pt = path[j];
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
if (j == k) offsetVec.Negate();
#ifdef USINGZ
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
#else
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#endif
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
for (int i = 1; i < steps; ++i) // ie 1 less than steps
{
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
#ifdef USINGZ
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
#else
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#endif
}
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
}
void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
{
// Let A = change in angle where edges join
// A == 0: ie no change in angle (flat join)
// A == PI: edges 'spike'
// sin(A) < 0: right turning
// cos(A) < 0: change in angle is more than 90 degree
if (path[j] == path[k]) return;
double sin_a = CrossProduct(norms[j], norms[k]);
double cos_a = DotProduct(norms[j], norms[k]);
if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0;
if (deltaCallback64_) {
group_delta_ = deltaCallback64_(path, norms, j, k);
if (group.is_reversed) group_delta_ = -group_delta_;
}
if (std::fabs(group_delta_) <= floating_point_tolerance)
{
path_out.emplace_back(path[j]);
return;
}
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// to ensure that path reversals (ie over-shrunk paths) are removed.
#ifdef USINGZ
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
path_out.emplace_back(path[j]); // (#405, #873, #916)
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z);
#else
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_));
path_out.emplace_back(path[j]); // (#405, #873, #916)
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
#endif
}
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
{
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
DoMiter(path, j, k, cos_a);
}
else if (join_type_ == JoinType::Miter)
{
// miter unless the angle is sufficiently acute to exceed ML
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
else DoSquare(path, j, k);
}
else if (join_type_ == JoinType::Round)
DoRound(path, j, k, std::atan2(sin_a, cos_a));
else if ( join_type_ == JoinType::Bevel)
DoBevel(path, j, k);
else
DoSquare(path, j, k);
}
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
{
path_out.clear();
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
OffsetPoint(group, path, j, k);
solution->emplace_back(path_out);
}
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
{
OffsetPolygon(group, path);
Path64 reverse_path(path);
std::reverse(reverse_path.begin(), reverse_path.end());
//rebuild normals
std::reverse(norms.begin(), norms.end());
norms.emplace_back(norms[0]);
norms.erase(norms.begin());
NegatePath(norms);
OffsetPolygon(group, reverse_path);
}
void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
{
// do the line start cap
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
if (std::fabs(group_delta_) <= floating_point_tolerance)
path_out.emplace_back(path[0]);
else
{
switch (end_type_)
{
case EndType::Butt:
DoBevel(path, 0, 0);
break;
case EndType::Round:
DoRound(path, 0, 0, PI);
break;
default:
DoSquare(path, 0, 0);
break;
}
}
size_t highI = path.size() - 1;
// offset the left side going forward
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
OffsetPoint(group, path, j, k);
// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI];
// do the line end cap
if (deltaCallback64_)
group_delta_ = deltaCallback64_(path, norms, highI, highI);
if (std::fabs(group_delta_) <= floating_point_tolerance)
path_out.emplace_back(path[highI]);
else
{
switch (end_type_)
{
case EndType::Butt:
DoBevel(path, highI, highI);
break;
case EndType::Round:
DoRound(path, highI, highI, PI);
break;
default:
DoSquare(path, highI, highI);
break;
}
}
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
OffsetPoint(group, path, j, k);
solution->emplace_back(path_out);
}
void ClipperOffset::DoGroupOffset(Group& group)
{
if (group.end_type == EndType::Polygon)
{
// a straight path (2 points) can now also be 'polygon' offset
// where the ends will be treated as (180 deg.) joins
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
}
else
group_delta_ = std::abs(delta_);// *0.5;
double abs_delta = std::fabs(group_delta_);
join_type_ = group.join_type;
end_type_ = group.end_type;
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
{
// calculate the number of steps required to approximate a circle
// (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm)
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
// will be relative to the size of the offset (delta). Obviously very
//large offsets will almost always require much less precision.
double arcTol = (arc_tolerance_ > floating_point_tolerance) ?
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const;
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
step_sin_ = std::sin(2 * PI / steps_per_360);
step_cos_ = std::cos(2 * PI / steps_per_360);
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
steps_per_rad_ = steps_per_360 / (2 * PI);
}
//double min_area = PI * Sqr(group_delta_);
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
{
Path64::size_type pathLen = path_in_it->size();
path_out.clear();
if (pathLen == 1) // single point
{
if (deltaCallback64_)
{
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
if (group.is_reversed) group_delta_ = -group_delta_;
abs_delta = std::fabs(group_delta_);
}
if (group_delta_ < 1) continue;
const Point64& pt = (*path_in_it)[0];
//single vertex so build a circle or square ...
if (group.join_type == JoinType::Round)
{
double radius = abs_delta;
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
path_out = Ellipse(pt, radius, radius, steps);
#ifdef USINGZ
for (auto& p : path_out) p.z = pt.z;
#endif
}
else
{
int d = (int)std::ceil(abs_delta);
Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
path_out = r.AsPath();
#ifdef USINGZ
for (auto& p : path_out) p.z = pt.z;
#endif
}
solution->emplace_back(path_out);
continue;
} // end of offsetting a single point
if ((pathLen == 2) && (group.end_type == EndType::Joined))
end_type_ = (group.join_type == JoinType::Round) ?
EndType::Round :
EndType::Square;
BuildNormals(*path_in_it);
if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
else OffsetOpenPath(group, *path_in_it);
}
}
#ifdef USINGZ
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip)
{
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
}
#endif
size_t ClipperOffset::CalcSolutionCapacity()
{
size_t result = 0;
for (const Group& g : groups_)
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
return result;
}
bool ClipperOffset::CheckReverseOrientation()
{
// nb: this assumes there's consistency in orientation between groups
bool is_reversed_orientation = false;
for (const Group& g : groups_)
if (g.end_type == EndType::Polygon)
{
is_reversed_orientation = g.is_reversed;
break;
}
return is_reversed_orientation;
}
void ClipperOffset::ExecuteInternal(double delta)
{
error_code_ = 0;
if (groups_.size() == 0) return;
solution->reserve(CalcSolutionCapacity());
if (std::abs(delta) < 0.5) // ie: offset is insignificant
{
Paths64::size_type sol_size = 0;
for (const Group& group : groups_) sol_size += group.paths_in.size();
solution->reserve(sol_size);
for (const Group& group : groups_)
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
}
else
{
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
delta_ = delta;
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution->clear();
}
}
if (!solution->size()) return;
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
c.SetZCallback(fp);
#endif
c.AddSubject(*solution);
if (solution_tree)
{
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
else
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
}
else
{
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, *solution);
else
c.Execute(ClipType::Union, FillRule::Positive, *solution);
}
}
void ClipperOffset::Execute(double delta, Paths64& paths64)
{
paths64.clear();
solution = &paths64;
solution_tree = nullptr;
ExecuteInternal(delta);
}
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
{
polytree.Clear();
solution_tree = &polytree;
solution = new Paths64();
ExecuteInternal(delta);
delete solution;
solution = nullptr;
}
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
{
deltaCallback64_ = delta_cb;
Execute(1.0, paths);
}
} // namespace

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
// Enable the Z coordinate support.
#define USINGZ
// and let it compile
#include "clipper.engine.cpp"
#include "clipper.offset.cpp"
#include "clipper.rectclip.cpp"

View File

@@ -1,27 +0,0 @@
## Earcut.hpp changelog
### master
- Fixed a bunch of rare edge cases that led to bad triangulation (parity with Earcut v2.2.2)
- Removed use of deprecated `std::allocator::construct`
- Fixed a minor z-order hashing bug
- Improved visualization app, better docs
### v0.12.4
- Fixed a crash in Crash in Earcut::findHoleBridge
- Added coverage checks
- Added macOS, MinGW builds
### v0.12.3
- Fixed -Wunused-lambda-capture
### v0.12.2
- Fixed potential division by zero
- Fixed -fsanitize=integer warning
### v0.12.1
- Fixed cast precision warning

View File

@@ -1,13 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(earcut)
add_library(earcut INTERFACE)
target_include_directories(earcut SYSTEM
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(earcut INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/earcut.hpp
)

View File

@@ -1,15 +0,0 @@
ISC License
Copyright (c) 2015, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@@ -1,131 +0,0 @@
## Earcut
A C++ port of [earcut.js](https://github.com/mapbox/earcut), a fast, [header-only](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) polygon triangulation library.
[![Travis](https://img.shields.io/travis/com/mapbox/earcut.hpp.svg)](https://travis-ci.com/github/mapbox/earcut.hpp)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/a1ysrqd69mqn7coo/branch/master?svg=true)](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master)
[![Coverage](https://img.shields.io/coveralls/github/mapbox/earcut.hpp.svg)](https://coveralls.io/github/mapbox/earcut.hpp)
[![Coverity Scan](https://img.shields.io/coverity/scan/14000.svg)](https://scan.coverity.com/projects/14000)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open")
[![Mourner](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects)
The library implements a modified ear slicing algorithm, optimized by [z-order curve](http://en.wikipedia.org/wiki/Z-order_curve) hashing and extended to handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't _guarantee_ correctness of triangulation, but attempts to always produce acceptable results for practical data like geographical shapes.
It's based on ideas from [FIST: Fast Industrial-Strength Triangulation of Polygons](http://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) by Martin Held and [Triangulation by Ear Clipping](http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) by David Eberly.
## Usage
```cpp
#include <earcut.hpp>
```
```cpp
// The number type to use for tessellation
using Coord = double;
// The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your
// data won't have more than 65536 vertices.
using N = uint32_t;
// Create array
using Point = std::array<Coord, 2>;
std::vector<std::vector<Point>> polygon;
// Fill polygon structure with actual data. Any winding order works.
// The first polyline defines the main polygon.
polygon.push_back({{100, 0}, {100, 100}, {0, 100}, {0, 0}});
// Following polylines define holes.
polygon.push_back({{75, 25}, {75, 75}, {25, 75}, {25, 25}});
// Run tessellation
// Returns array of indices that refer to the vertices of the input polygon.
// e.g: the index 6 would refer to {25, 75} in this example.
// Three subsequent indices form a triangle. Output triangles are clockwise.
std::vector<N> indices = mapbox::earcut<N>(polygon);
```
Earcut can triangulate a simple, planar polygon of any winding order including holes. It will even return a robust, acceptable solution for non-simple poygons. Earcut works on a 2D plane. If you have three or more dimensions, you can project them onto a 2D surface before triangulation, or use a more suitable library for the task (e.g [CGAL](https://doc.cgal.org/latest/Triangulation_3/index.html)).
It is also possible to use your custom point type as input. There are default accessors defined for `std::tuple`, `std::pair`, and `std::array`. For a custom type (like Clipper's `IntPoint` type), do this:
```cpp
// struct IntPoint {
// int64_t X, Y;
// };
namespace mapbox {
namespace util {
template <>
struct nth<0, IntPoint> {
inline static auto get(const IntPoint &t) {
return t.X;
};
};
template <>
struct nth<1, IntPoint> {
inline static auto get(const IntPoint &t) {
return t.Y;
};
};
} // namespace util
} // namespace mapbox
```
You can also use a custom container type for your polygon. Similar to std::vector<T>, it has to meet the requirements of [Container](https://en.cppreference.com/w/cpp/named_req/Container), in particular `size()`, `empty()` and `operator[]`.
<p align="center">
<img src="https://camo.githubusercontent.com/01836f8ba21af844c93d8d3145f4e9976025a696/68747470733a2f2f692e696d6775722e636f6d2f67314e704c54712e706e67" alt="example triangulation"/>
</p>
## Additional build instructions
In case you just want to use the earcut triangulation library; copy and include the header file [`<earcut.hpp>`](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) in your project and follow the steps documented in the section [Usage](#usage).
If you want to build the test, benchmark and visualization programs instead, follow these instructions:
### Dependencies
Before you continue, make sure to have the following tools and libraries installed:
* git ([Ubuntu](https://help.ubuntu.com/lts/serverguide/git.html)/[Windows/macOS](http://git-scm.com/downloads))
* cmake 3.2+ ([Ubuntu](https://launchpad.net/~george-edison55/+archive/ubuntu/cmake-3.x)/[Windows/macOS](https://cmake.org/download/))
* OpenGL SDK ([Ubuntu](http://packages.ubuntu.com/de/trusty/libgl1-mesa-dev)/[Windows](https://dev.windows.com/en-us/downloads/windows-10-sdk)/[macOS](https://developer.apple.com/opengl/))
* Compiler such as [GCC 4.9+, Clang 3.4+](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test), [MSVC12+](https://www.visualstudio.com/)
Note: On some operating systems such as Windows, manual steps are required to add cmake and [git](http://blog.countableset.ch/2012/06/07/adding-git-to-windows-7-path/) to your PATH environment variable.
### Manual compilation
```bash
git clone --recursive https://github.com/mapbox/earcut.hpp.git
cd earcut.hpp
mkdir build
cd build
cmake ..
make
# ./tests
# ./bench
# ./viz
```
### [Visual Studio](https://www.visualstudio.com/), [Eclipse](https://eclipse.org/), [XCode](https://developer.apple.com/xcode/), ...
```batch
git clone --recursive https://github.com/mapbox/earcut.hpp.git
cd earcut.hpp
mkdir project
cd project
cmake .. -G "Visual Studio 14 2015"
::you can also generate projects for "Visual Studio 12 2013", "XCode", "Eclipse CDT4 - Unix Makefiles"
```
After completion, open the generated project with your IDE.
### [CLion](https://www.jetbrains.com/clion/), [Visual Studio 2017+](https://www.visualstudio.com/)
Import the project from https://github.com/mapbox/earcut.hpp.git and you should be good to go!
## Status
This is currently based on [earcut 2.2.4](https://github.com/mapbox/earcut#224-jul-5-2022).

View File

@@ -1,814 +0,0 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
namespace mapbox {
namespace util {
template <std::size_t I, typename T> struct nth {
inline static typename std::tuple_element<I, T>::type
get(const T& t) { return std::get<I>(t); };
};
}
namespace detail {
template <typename N = uint32_t>
class Earcut {
public:
std::vector<N> indices;
std::size_t vertices = 0;
template <typename Polygon>
void operator()(const Polygon& points);
private:
struct Node {
Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {}
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node(Node&&) = delete;
Node& operator=(Node&&) = delete;
const N i;
const double x;
const double y;
// previous and next vertice nodes in a polygon ring
Node* prev = nullptr;
Node* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
Node* prevZ = nullptr;
Node* nextZ = nullptr;
// indicates whether this is a steiner point
bool steiner = false;
};
template <typename Ring> Node* linkedList(const Ring& points, const bool clockwise);
Node* filterPoints(Node* start, Node* end = nullptr);
void earcutLinked(Node* ear, int pass = 0);
bool isEar(Node* ear);
bool isEarHashed(Node* ear);
Node* cureLocalIntersections(Node* start);
void splitEarcut(Node* start);
template <typename Polygon> Node* eliminateHoles(const Polygon& points, Node* outerNode);
Node* eliminateHole(Node* hole, Node* outerNode);
Node* findHoleBridge(Node* hole, Node* outerNode);
bool sectorContainsSector(const Node* m, const Node* p);
void indexCurve(Node* start);
Node* sortLinked(Node* list);
int32_t zOrder(const double x_, const double y_);
Node* getLeftmost(Node* start);
bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const;
bool isValidDiagonal(Node* a, Node* b);
double area(const Node* p, const Node* q, const Node* r) const;
bool equals(const Node* p1, const Node* p2);
bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2);
bool onSegment(const Node* p, const Node* q, const Node* r);
int sign(double val);
bool intersectsPolygon(const Node* a, const Node* b);
bool locallyInside(const Node* a, const Node* b);
bool middleInside(const Node* a, const Node* b);
Node* splitPolygon(Node* a, Node* b);
template <typename Point> Node* insertNode(std::size_t i, const Point& p, Node* last);
void removeNode(Node* p);
bool hashing;
double minX, maxX;
double minY, maxY;
double inv_size = 0;
template <typename T, typename Alloc = std::allocator<T>>
class ObjectPool {
public:
ObjectPool() { }
ObjectPool(std::size_t blockSize_) {
reset(blockSize_);
}
~ObjectPool() {
clear();
}
template <typename... Args>
T* construct(Args&&... args) {
if (currentIndex >= blockSize) {
currentBlock = alloc_traits::allocate(alloc, blockSize);
allocations.emplace_back(currentBlock);
currentIndex = 0;
}
T* object = &currentBlock[currentIndex++];
alloc_traits::construct(alloc, object, std::forward<Args>(args)...);
return object;
}
void reset(std::size_t newBlockSize) {
for (auto allocation : allocations) {
alloc_traits::deallocate(alloc, allocation, blockSize);
}
allocations.clear();
blockSize = std::max<std::size_t>(1, newBlockSize);
currentBlock = nullptr;
currentIndex = blockSize;
}
void clear() { reset(blockSize); }
private:
T* currentBlock = nullptr;
std::size_t currentIndex = 1;
std::size_t blockSize = 1;
std::vector<T*> allocations;
Alloc alloc;
typedef typename std::allocator_traits<Alloc> alloc_traits;
};
ObjectPool<Node> nodes;
};
template <typename N> template <typename Polygon>
void Earcut<N>::operator()(const Polygon& points) {
// reset
indices.clear();
vertices = 0;
if (points.empty()) return;
double x;
double y;
int threshold = 80;
std::size_t len = 0;
for (size_t i = 0; threshold >= 0 && i < points.size(); i++) {
threshold -= static_cast<int>(points[i].size());
len += points[i].size();
}
//estimate size of nodes and indices
nodes.reset(len * 3 / 2);
indices.reserve(len + points[0].size());
Node* outerNode = linkedList(points[0], true);
if (!outerNode || outerNode->prev == outerNode->next) return;
if (points.size() > 1) outerNode = eliminateHoles(points, outerNode);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
hashing = threshold < 0;
if (hashing) {
Node* p = outerNode->next;
minX = maxX = outerNode->x;
minY = maxY = outerNode->y;
do {
x = p->x;
y = p->y;
minX = std::min<double>(minX, x);
minY = std::min<double>(minY, y);
maxX = std::max<double>(maxX, x);
maxY = std::max<double>(maxY, y);
p = p->next;
} while (p != outerNode);
// minX, minY and inv_size are later used to transform coords into integers for z-order calculation
inv_size = std::max<double>(maxX - minX, maxY - minY);
inv_size = inv_size != .0 ? (32767. / inv_size) : .0;
}
earcutLinked(outerNode);
nodes.clear();
}
// create a circular doubly linked list from polygon points in the specified winding order
template <typename N> template <typename Ring>
typename Earcut<N>::Node*
Earcut<N>::linkedList(const Ring& points, const bool clockwise) {
using Point = typename Ring::value_type;
double sum = 0;
const std::size_t len = points.size();
std::size_t i, j;
Node* last = nullptr;
// calculate original winding order of a polygon ring
for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) {
const auto& p1 = points[i];
const auto& p2 = points[j];
const double p20 = util::nth<0, Point>::get(p2);
const double p10 = util::nth<0, Point>::get(p1);
const double p11 = util::nth<1, Point>::get(p1);
const double p21 = util::nth<1, Point>::get(p2);
sum += (p20 - p10) * (p11 + p21);
}
// link points into circular doubly-linked list in the specified winding order
if (clockwise == (sum > 0)) {
for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last);
} else {
for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last);
}
if (last && equals(last, last->next)) {
removeNode(last);
last = last->next;
}
vertices += len;
return last;
}
// eliminate colinear or duplicate points
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::filterPoints(Node* start, Node* end) {
if (!end) end = start;
Node* p = start;
bool again;
do {
again = false;
if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) {
removeNode(p);
p = end = p->prev;
if (p == p->next) break;
again = true;
} else {
p = p->next;
}
} while (again || p != end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
template <typename N>
void Earcut<N>::earcutLinked(Node* ear, int pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && hashing) indexCurve(ear);
Node* stop = ear;
Node* prev;
Node* next;
// iterate through ears, slicing them one by one
while (ear->prev != ear->next) {
prev = ear->prev;
next = ear->next;
if (hashing ? isEarHashed(ear) : isEar(ear)) {
// cut off the triangle
indices.emplace_back(prev->i);
indices.emplace_back(ear->i);
indices.emplace_back(next->i);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next->next;
stop = next->next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear == stop) {
// try filtering points and slicing again
if (!pass) earcutLinked(filterPoints(ear), 1);
// if this didn't work, try curing all small self-intersections locally
else if (pass == 1) {
ear = cureLocalIntersections(filterPoints(ear));
earcutLinked(ear, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass == 2) splitEarcut(ear);
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
template <typename N>
bool Earcut<N>::isEar(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
Node* p = ear->next->next;
while (p != ear->prev) {
if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->next;
}
return true;
}
template <typename N>
bool Earcut<N>::isEarHashed(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
const double minTX = std::min<double>(a->x, std::min<double>(b->x, c->x));
const double minTY = std::min<double>(a->y, std::min<double>(b->y, c->y));
const double maxTX = std::max<double>(a->x, std::max<double>(b->x, c->x));
const double maxTY = std::max<double>(a->y, std::max<double>(b->y, c->y));
// z-order range for the current triangle bbox;
const int32_t minZ = zOrder(minTX, minTY);
const int32_t maxZ = zOrder(maxTX, maxTY);
// first look for points inside the triangle in increasing z-order
Node* p = ear->nextZ;
while (p && p->z <= maxZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->nextZ;
}
// then look for points in decreasing z-order
p = ear->prevZ;
while (p && p->z >= minZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::cureLocalIntersections(Node* start) {
Node* p = start;
do {
Node* a = p->prev;
Node* b = p->next->next;
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) {
indices.emplace_back(a->i);
indices.emplace_back(p->i);
indices.emplace_back(b->i);
// remove two nodes involved
removeNode(p);
removeNode(p->next);
p = start = b;
}
p = p->next;
} while (p != start);
return filterPoints(p);
}
// try splitting polygon into two and triangulate them independently
template <typename N>
void Earcut<N>::splitEarcut(Node* start) {
// look for a valid diagonal that divides the polygon into two
Node* a = start;
do {
Node* b = a->next->next;
while (b != a->prev) {
if (a->i != b->i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
Node* c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a->next);
c = filterPoints(c, c->next);
// run earcut on each half
earcutLinked(a);
earcutLinked(c);
return;
}
b = b->next;
}
a = a->next;
} while (a != start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
template <typename N> template <typename Polygon>
typename Earcut<N>::Node*
Earcut<N>::eliminateHoles(const Polygon& points, Node* outerNode) {
const size_t len = points.size();
std::vector<Node*> queue;
for (size_t i = 1; i < len; i++) {
Node* list = linkedList(points[i], false);
if (list) {
if (list == list->next) list->steiner = true;
queue.push_back(getLeftmost(list));
}
}
std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) {
return a->x < b->x;
});
// process holes from left to right
for (size_t i = 0; i < queue.size(); i++) {
outerNode = eliminateHole(queue[i], outerNode);
}
return outerNode;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::eliminateHole(Node* hole, Node* outerNode) {
Node* bridge = findHoleBridge(hole, outerNode);
if (!bridge) {
return outerNode;
}
Node* bridgeReverse = splitPolygon(bridge, hole);
// filter collinear points around the cuts
filterPoints(bridgeReverse, bridgeReverse->next);
// Check if input node was removed by the filtering
return filterPoints(bridge, bridge->next);
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {
Node* p = outerNode;
double hx = hole->x;
double hy = hole->y;
double qx = -std::numeric_limits<double>::infinity();
Node* m = nullptr;
// find a segment intersected by a ray from the hole's leftmost Vertex to the left;
// segment's endpoint with lesser x will be potential connection Vertex
do {
if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) {
double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y);
if (x <= hx && x > qx) {
qx = x;
m = p->x < p->next->x ? p : p->next;
if (x == hx) return m; // hole touches outer segment; pick leftmost endpoint
}
}
p = p->next;
} while (p != outerNode);
if (!m) return 0;
// look for points inside the triangle of hole Vertex, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the Vertex of the minimum angle with the ray as connection Vertex
const Node* stop = m;
double tanMin = std::numeric_limits<double>::infinity();
double tanCur = 0;
p = m;
double mx = m->x;
double my = m->y;
do {
if (hx >= p->x && p->x >= mx && hx != p->x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) {
tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential
if (locallyInside(p, hole) &&
(tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
m = p;
tanMin = tanCur;
}
}
p = p->next;
} while (p != stop);
return m;
}
// whether sector in vertex m contains sector in vertex p in the same coordinates
template <typename N>
bool Earcut<N>::sectorContainsSector(const Node* m, const Node* p) {
return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0;
}
// interlink polygon nodes in z-order
template <typename N>
void Earcut<N>::indexCurve(Node* start) {
assert(start);
Node* p = start;
do {
p->z = p->z ? p->z : zOrder(p->x, p->y);
p->prevZ = p->prev;
p->nextZ = p->next;
p = p->next;
} while (p != start);
p->prevZ->nextZ = nullptr;
p->prevZ = nullptr;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::sortLinked(Node* list) {
assert(list);
Node* p;
Node* q;
Node* e;
Node* tail;
int i, numMerges, pSize, qSize;
int inSize = 1;
for (;;) {
p = list;
list = nullptr;
tail = nullptr;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q->nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize == 0) {
e = q;
q = q->nextZ;
qSize--;
} else if (qSize == 0 || !q) {
e = p;
p = p->nextZ;
pSize--;
} else if (p->z <= q->z) {
e = p;
p = p->nextZ;
pSize--;
} else {
e = q;
q = q->nextZ;
qSize--;
}
if (tail) tail->nextZ = e;
else list = e;
e->prevZ = tail;
tail = e;
}
p = q;
}
tail->nextZ = nullptr;
if (numMerges <= 1) return list;
inSize *= 2;
}
}
// z-order of a Vertex given coords and size of the data bounding box
template <typename N>
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
// coords are transformed into non-negative 15-bit integer range
int32_t x = static_cast<int32_t>((x_ - minX) * inv_size);
int32_t y = static_cast<int32_t>((y_ - minY) * inv_size);
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::getLeftmost(Node* start) {
Node* p = start;
Node* leftmost = start;
do {
if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y))
leftmost = p;
p = p->next;
} while (p != start);
return leftmost;
}
// check if a point lies within a convex triangle
template <typename N>
bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const {
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
(bx - px) * (cy - py) >= (cx - px) * (by - py);
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
template <typename N>
bool Earcut<N>::isValidDiagonal(Node* a, Node* b) {
return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges
((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
(area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors
(equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case
}
// signed area of a triangle
template <typename N>
double Earcut<N>::area(const Node* p, const Node* q, const Node* r) const {
return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y);
}
// check if two points are equal
template <typename N>
bool Earcut<N>::equals(const Node* p1, const Node* p2) {
return p1->x == p2->x && p1->y == p2->y;
}
// check if two segments intersect
template <typename N>
bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) {
int o1 = sign(area(p1, q1, p2));
int o2 = sign(area(p1, q1, q2));
int o3 = sign(area(p2, q2, p1));
int o4 = sign(area(p2, q2, q1));
if (o1 != o2 && o3 != o4) return true; // general case
if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
return false;
}
// for collinear points p, q, r, check if point q lies on segment pr
template <typename N>
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
return q->x <= std::max<double>(p->x, r->x) &&
q->x >= std::min<double>(p->x, r->x) &&
q->y <= std::max<double>(p->y, r->y) &&
q->y >= std::min<double>(p->y, r->y);
}
template <typename N>
int Earcut<N>::sign(double val) {
return (0.0 < val) - (val < 0.0);
}
// check if a polygon diagonal intersects any polygon segments
template <typename N>
bool Earcut<N>::intersectsPolygon(const Node* a, const Node* b) {
const Node* p = a;
do {
if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i &&
intersects(p, p->next, a, b)) return true;
p = p->next;
} while (p != a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
template <typename N>
bool Earcut<N>::locallyInside(const Node* a, const Node* b) {
return area(a->prev, a, a->next) < 0 ?
area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 :
area(a, b, a->prev) < 0 || area(a, a->next, b) < 0;
}
// check if the middle Vertex of a polygon diagonal is inside the polygon
template <typename N>
bool Earcut<N>::middleInside(const Node* a, const Node* b) {
const Node* p = a;
bool inside = false;
double px = (a->x + b->x) / 2;
double py = (a->y + b->y) / 2;
do {
if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y &&
(px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x))
inside = !inside;
p = p->next;
} while (p != a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits
// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a
// single ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::splitPolygon(Node* a, Node* b) {
Node* a2 = nodes.construct(a->i, a->x, a->y);
Node* b2 = nodes.construct(b->i, b->x, b->y);
Node* an = a->next;
Node* bp = b->prev;
a->next = b;
b->prev = a;
a2->next = an;
an->prev = a2;
b2->next = a2;
a2->prev = b2;
bp->next = b2;
b2->prev = bp;
return b2;
}
// create a node and util::optionally link it with previous one (in a circular doubly linked list)
template <typename N> template <typename Point>
typename Earcut<N>::Node*
Earcut<N>::insertNode(std::size_t i, const Point& pt, Node* last) {
Node* p = nodes.construct(static_cast<N>(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt));
if (!last) {
p->prev = p;
p->next = p;
} else {
assert(last);
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
}
template <typename N>
void Earcut<N>::removeNode(Node* p) {
p->next->prev = p->prev;
p->prev->next = p->next;
if (p->prevZ) p->prevZ->nextZ = p->nextZ;
if (p->nextZ) p->nextZ->prevZ = p->prevZ;
}
}
template <typename N = uint32_t, typename Polygon>
std::vector<N> earcut(const Polygon& poly) {
mapbox::detail::Earcut<N> earcut;
earcut(poly);
return std::move(earcut.indices);
}
}

View File

@@ -153,8 +153,6 @@ namespace ImGui
// const wchar_t CustomSupportsMarker = 0x1D;
// const wchar_t CustomSeamMarker = 0x1E;
// const wchar_t MmuSegmentationMarker = 0x1F;
//const wchar_t HorizontalHide = 0xB1; // ORCA use FoldButtonIcon / UnfoldButtonIcon
//const wchar_t HorizontalShow = 0xB2;
// Do not forget use following letters only in wstring
//BBS use 08xx to avoid unicode character which may be used
@@ -230,7 +228,5 @@ namespace ImGui
const wchar_t OpenHoverDarkButton = 0x085B;
// void MyFunction(const char* name, const MyMatrix44& v);
const wchar_t FilamentGreen = 0x0850;
}

View File

@@ -18,7 +18,7 @@ template <
typename DerivedF,
typename SType,
typename DerivedNF>
IGL_INLINE bool igl::loop(
IGL_INLINE void igl::loop(
const int n_verts,
const Eigen::PlainObjectBase<DerivedF> & F,
Eigen::SparseMatrix<SType>& S,
@@ -26,15 +26,15 @@ IGL_INLINE bool igl::loop(
{
typedef Eigen::SparseMatrix<SType> SparseMat;
typedef Eigen::Triplet<SType> Triplet_t;
//Ref. https://graphics.stanford.edu/~mdfisher/subdivision.html
//Heavily borrowing from igl::upsample
DerivedF FF, FFi;
triangle_triangle_adjacency(F, FF, FFi);
std::vector<std::vector<typename DerivedF::Scalar>> adjacencyList;
adjacency_list(F, adjacencyList, true);
//Compute the number and positions of the vertices to insert (on edges)
Eigen::MatrixXi NI = Eigen::MatrixXi::Constant(FF.rows(), FF.cols(), -1);
Eigen::MatrixXi NIdoubles = Eigen::MatrixXi::Zero(FF.rows(), FF.cols());
@@ -48,18 +48,12 @@ IGL_INLINE bool igl::loop(
{
NI(i,j) = counter;
NIdoubles(i,j) = 0;
if (FF(i,j) != -1)
if (FF(i,j) != -1)
{
//If it is not a boundary
int adj_triangle = FF(i, j);
int adj_edge = FFi(i, j);
if (adj_triangle >= 0 && adj_triangle < NI.rows() && adj_edge >= 0 && adj_edge < NI.cols()) {
NI(adj_triangle, adj_edge) = counter;
NIdoubles(i, j) = 1;
} else {
return false;
}
} else
NI(FF(i,j), FFi(i,j)) = counter;
NIdoubles(i,j) = 1;
} else
{
//Mark boundary vertices for later
vertIsOnBdry(F(i,j)) = 1;
@@ -69,24 +63,24 @@ IGL_INLINE bool igl::loop(
}
}
}
const int& n_odd = n_verts;
const int& n_even = counter;
const int n_newverts = n_odd + n_even;
//Construct vertex positions
std::vector<Triplet_t> tripletList;
for(int i=0; i<n_odd; ++i)
for(int i=0; i<n_odd; ++i)
{
//Old vertices
const std::vector<int>& localAdjList = adjacencyList[i];
if(vertIsOnBdry(i)==1)
if(vertIsOnBdry(i)==1)
{
//Boundary vertex
tripletList.emplace_back(i, localAdjList.front(), 1./8.);
tripletList.emplace_back(i, localAdjList.back(), 1./8.);
tripletList.emplace_back(i, i, 3./4.);
} else
} else
{
const int n = localAdjList.size();
const SType dn = n;
@@ -105,19 +99,19 @@ IGL_INLINE bool igl::loop(
tripletList.emplace_back(i, i, 1.-dn*beta);
}
}
for(int i=0; i<FF.rows(); ++i)
for(int i=0; i<FF.rows(); ++i)
{
//New vertices
for(int j=0; j<3; ++j)
for(int j=0; j<3; ++j)
{
if(NIdoubles(i,j)==0)
if(NIdoubles(i,j)==0)
{
if(FF(i,j)==-1)
if(FF(i,j)==-1)
{
//Boundary vertex
tripletList.emplace_back(NI(i,j) + n_odd, F(i,j), 1./2.);
tripletList.emplace_back(NI(i,j) + n_odd, F(i, (j+1)%3), 1./2.);
} else
} else
{
tripletList.emplace_back(NI(i,j) + n_odd, F(i,j), 3./8.);
tripletList.emplace_back(NI(i,j) + n_odd, F(i, (j+1)%3), 3./8.);
@@ -129,34 +123,33 @@ IGL_INLINE bool igl::loop(
}
S.resize(n_newverts, n_verts);
S.setFromTriplets(tripletList.begin(), tripletList.end());
// Build the new topology (Every face is replaced by four)
NF.resize(F.rows()*4, 3);
for(int i=0; i<F.rows();++i)
{
Eigen::VectorXi VI(6);
VI << F(i,0), F(i,1), F(i,2), NI(i,0) + n_odd, NI(i,1) + n_odd, NI(i,2) + n_odd;
Eigen::VectorXi f0(3), f1(3), f2(3), f3(3);
f0 << VI(0), VI(3), VI(5);
f1 << VI(1), VI(4), VI(3);
f2 << VI(3), VI(4), VI(5);
f3 << VI(4), VI(2), VI(5);
NF.row((i*4)+0) = f0;
NF.row((i*4)+1) = f1;
NF.row((i*4)+2) = f2;
NF.row((i*4)+3) = f3;
}
return true;
}
template <
typename DerivedV,
typename DerivedV,
typename DerivedF,
typename DerivedNV,
typename DerivedNF>
IGL_INLINE bool igl::loop(
IGL_INLINE void igl::loop(
const Eigen::PlainObjectBase<DerivedV>& V,
const Eigen::PlainObjectBase<DerivedF>& F,
Eigen::PlainObjectBase<DerivedNV>& NV,
@@ -165,19 +158,16 @@ IGL_INLINE bool igl::loop(
{
NV = V;
NF = F;
for(int i=0; i<number_of_subdivs; ++i)
for(int i=0; i<number_of_subdivs; ++i)
{
DerivedNF tempF = NF;
Eigen::SparseMatrix<typename DerivedV::Scalar> S;
if (!loop(NV.rows(), tempF, S, NF)) {
return false;
}
loop(NV.rows(), tempF, S, NF);
// This .eval is super important
NV = (S*NV).eval();
}
return true;
}
#ifdef IGL_STATIC_LIBRARY
template void igl::loop<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, int);
#endif
#endif

View File

@@ -29,7 +29,7 @@ namespace igl
typename DerivedF,
typename SType,
typename DerivedNF>
IGL_INLINE bool loop(
IGL_INLINE void loop(
const int n_verts,
const Eigen::PlainObjectBase<DerivedF> & F,
Eigen::SparseMatrix<SType>& S,
@@ -44,11 +44,11 @@ namespace igl
// NV a matrix containing the new vertices
// NF a matrix containing the new faces
template <
typename DerivedV,
typename DerivedV,
typename DerivedF,
typename DerivedNV,
typename DerivedNF>
IGL_INLINE bool loop(
IGL_INLINE void loop(
const Eigen::PlainObjectBase<DerivedV>& V,
const Eigen::PlainObjectBase<DerivedF>& F,
Eigen::PlainObjectBase<DerivedNV>& NV,

View File

@@ -1,71 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(MD4C C)
set(MD_VERSION_MAJOR 0)
set(MD_VERSION_MINOR 5)
set(MD_VERSION_RELEASE 2)
set(MD_VERSION "${MD_VERSION_MAJOR}.${MD_VERSION_MINOR}.${MD_VERSION_RELEASE}")
set(PROJECT_VERSION "${MD_VERSION}")
set(PROJECT_URL "https://github.com/mity/md4c")
#option(BUILD_MD2HTML_EXECUTABLE "Whether to compile the md2html executable" ON)
#if(WIN32)
# # On Windows, given there is no standard lib install dir etc., we rather
# # by default build static lib.
# option(BUILD_SHARED_LIBS "help string describing option" OFF)
#else()
# # On Linux, MD4C is slowly being adding into some distros which prefer
# # shared lib.
# option(BUILD_SHARED_LIBS "help string describing option" ON)
#endif()
add_definitions(
-DMD_VERSION_MAJOR=${MD_VERSION_MAJOR}
-DMD_VERSION_MINOR=${MD_VERSION_MINOR}
-DMD_VERSION_RELEASE=${MD_VERSION_RELEASE}
)
#set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo MinSizeRel)
#if("${CMAKE_BUILD_TYPE}" STREQUAL "")
# set(CMAKE_BUILD_TYPE $ENV{CMAKE_BUILD_TYPE})
#
# if("${CMAKE_BUILD_TYPE}" STREQUAL "")
# set(CMAKE_BUILD_TYPE "Release")
# endif()
#endif()
if(${CMAKE_C_COMPILER_ID} MATCHES GNU|Clang)
add_compile_options(-Wall -Wextra -Wshadow)
# We enforce -Wdeclaration-after-statement because Qt project needs to
# build MD4C with Integrity compiler which chokes whenever a declaration
# is not at the beginning of a block.
add_compile_options(-Wdeclaration-after-statement)
elseif(MSVC)
# Disable warnings about the so-called unsecured functions:
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
add_compile_options(/W3)
# Specify proper C runtime library:
string(REGEX REPLACE "/M[DT]d?" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
string(REGEX REPLACE "/M[DT]d?" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
string(REGEX REPLACE "/M[DT]d?" "" CMAKE_C_FLAGS_RELWITHDEBINFO "{$CMAKE_C_FLAGS_RELWITHDEBINFO}")
string(REGEX REPLACE "/M[DT]d?" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} /MT")
set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_RELEASE} /MT")
endif()
include(GNUInstallDirs)
add_subdirectory(src)
#if (BUILD_MD2HTML_EXECUTABLE)
# add_subdirectory(md2html)
#endif ()

View File

@@ -1,22 +0,0 @@
# The MIT License (MIT)
Copyright © 2016-2024 Martin Mitáš
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View File

@@ -1,297 +0,0 @@
# MD4C Readme
* Home: http://github.com/mity/md4c
* Wiki: http://github.com/mity/md4c/wiki
* Issue tracker: http://github.com/mity/md4c/issues
MD4C stands for "Markdown for C" and that's exactly what this project is about.
## What is Markdown
In short, Markdown is the markup language this `README.md` file is written in.
The following resources can explain more if you are unfamiliar with it:
* [Wikipedia article](http://en.wikipedia.org/wiki/Markdown)
* [CommonMark site](http://commonmark.org)
## What is MD4C
MD4C is Markdown parser implementation in C, with the following features:
* **Compliance:** Generally, MD4C aims to be compliant to the latest version of
[CommonMark specification](http://spec.commonmark.org/). Currently, we are
fully compliant to CommonMark 0.31.
* **Extensions:** MD4C supports some commonly requested and accepted extensions.
See below.
* **Performance:** MD4C is [very fast](https://talk.commonmark.org/t/2520).
* **Compactness:** MD4C parser is implemented in one source file and one header
file. There are no dependencies other than standard C library.
* **Embedding:** MD4C parser is easy to reuse in other projects, its API is
very straightforward: There is actually just one function, `md_parse()`.
* **Push model:** MD4C parses the complete document and calls few callback
functions provided by the application to inform it about a start/end of
every block, a start/end of every span, and with any textual contents.
* **Portability:** MD4C builds and works on Windows and POSIX-compliant OSes.
(It should be simple to make it run also on most other platforms, at least as
long as the platform provides C standard library, including a heap memory
management.)
* **Encoding:** MD4C by default expects UTF-8 encoding of the input document.
But it can be compiled to recognize ASCII-only control characters (i.e. to
disable all Unicode-specific code), or (on Windows) to expect UTF-16 (i.e.
what is on Windows commonly called just "Unicode"). See more details below.
* **Permissive license:** MD4C is available under the [MIT license](LICENSE.md).
## Using MD4C
### Parsing Markdown
If you need just to parse a Markdown document, you need to include `md4c.h`
and link against MD4C library (`-lmd4c`); or alternatively add `md4c.[hc]`
directly to your code base as the parser is only implemented in the single C
source file.
The main provided function is `md_parse()`. It takes a text in the Markdown
syntax and a pointer to a structure which provides pointers to several callback
functions.
As `md_parse()` processes the input, it calls the callbacks (when entering or
leaving any Markdown block or span; and when outputting any textual content of
the document), allowing application to convert it into another format or render
it onto the screen.
### Converting to HTML
If you need to convert Markdown to HTML, include `md4c-html.h` and link against
MD4C-HTML library (`-lmd4c-html`); or alternatively add the sources `md4c.[hc]`,
`md4c-html.[hc]` and `entity.[hc]` into your code base.
To convert a Markdown input, call `md_html()` function. It takes the Markdown
input and calls the provided callback function. The callback is fed with
chunks of the HTML output. Typical callback implementation just appends the
chunks into a buffer or writes them to a file.
## Markdown Extensions
The default behavior is to recognize only Markdown syntax defined by the
[CommonMark specification](http://spec.commonmark.org/).
However, with appropriate flags, the behavior can be tuned to enable some
extensions:
* With the flag `MD_FLAG_COLLAPSEWHITESPACE`, a non-trivial whitespace is
collapsed into a single space.
* With the flag `MD_FLAG_TABLES`, GitHub-style tables are supported.
* With the flag `MD_FLAG_TASKLISTS`, GitHub-style task lists are supported.
* With the flag `MD_FLAG_STRIKETHROUGH`, strike-through spans are enabled
(text enclosed in tilde marks, e.g. `~foo bar~`).
* With the flag `MD_FLAG_PERMISSIVEURLAUTOLINKS` permissive URL autolinks
(not enclosed in `<` and `>`) are supported.
* With the flag `MD_FLAG_PERMISSIVEEMAILAUTOLINKS`, permissive e-mail
autolinks (not enclosed in `<` and `>`) are supported.
* With the flag `MD_FLAG_PERMISSIVEWWWAUTOLINKS` permissive WWW autolinks
without any scheme specified (e.g. `www.example.com`) are supported. MD4C
then assumes `http:` scheme.
* With the flag `MD_FLAG_LATEXMATHSPANS` LaTeX math spans (`$...$`) and
LaTeX display math spans (`$$...$$`) are supported. (Note though that the
HTML renderer outputs them verbatim in a custom tag `<x-equation>`.)
* With the flag `MD_FLAG_WIKILINKS`, wiki-style links (`[[link label]]` and
`[[target article|link label]]`) are supported. (Note that the HTML renderer
outputs them in a custom tag `<x-wikilink>`.)
* With the flag `MD_FLAG_UNDERLINE`, underscore (`_`) denotes an underline
instead of an ordinary emphasis or strong emphasis.
Few features of CommonMark (those some people see as mis-features) may be
disabled with the following flags:
* With the flag `MD_FLAG_NOHTMLSPANS` or `MD_FLAG_NOHTMLBLOCKS`, raw inline
HTML or raw HTML blocks respectively are disabled.
* With the flag `MD_FLAG_NOINDENTEDCODEBLOCKS`, indented code blocks are
disabled.
## Input/Output Encoding
The CommonMark specification declares that any sequence of Unicode code points
is a valid CommonMark document.
But, under a closer inspection, Unicode plays any role in few very specific
situations when parsing Markdown documents:
1. For detection of word boundaries when processing emphasis and strong
emphasis, some classification of Unicode characters (whether it is
a whitespace or a punctuation) is needed.
2. For (case-insensitive) matching of a link reference label with the
corresponding link reference definition, Unicode case folding is used.
3. For translating HTML entities (e.g. `&amp;`) and numeric character
references (e.g. `&#35;` or `&#xcab;`) into their Unicode equivalents.
However note MD4C leaves this translation on the renderer/application; as
the renderer is supposed to really know output encoding and whether it
really needs to perform this kind of translation. (For example, when the
renderer outputs HTML, it may leave the entities untranslated and defer the
work to a web browser.)
MD4C relies on this property of the CommonMark and the implementation is, to
a large degree, encoding-agnostic. Most of MD4C code only assumes that the
encoding of your choice is compatible with ASCII. I.e. that the codepoints
below 128 have the same numeric values as ASCII.
Any input MD4C does not understand is simply seen as part of the document text
and sent to the renderer's callback functions unchanged.
The two situations (word boundary detection and link reference matching) where
MD4C has to understand Unicode are handled as specified by the following
preprocessor macros (as specified at the time MD4C is being built):
* If preprocessor macro `MD4C_USE_UTF8` is defined, MD4C assumes UTF-8 for the
word boundary detection and for the case-insensitive matching of link labels.
When none of these macros is explicitly used, this is the default behavior.
* On Windows, if preprocessor macro `MD4C_USE_UTF16` is defined, MD4C uses
`WCHAR` instead of `char` and assumes UTF-16 encoding in those situations.
(UTF-16 is what Windows developers usually call just "Unicode" and what
Win32API generally works with.)
Note that because this macro affects also the types in `md4c.h`, you have
to define the macro both when building MD4C as well as when including
`md4c.h`.
Also note this is only supported in the parser (`md4c.[hc]`). The HTML
renderer does not support this and you will have to write your own custom
renderer to use this feature.
* If preprocessor macro `MD4C_USE_ASCII` is defined, MD4C assumes nothing but
an ASCII input.
That effectively means that non-ASCII whitespace or punctuation characters
won't be recognized as such and that link reference matching will work in
a case-insensitive way only for ASCII letters (`[a-zA-Z]`).
## Documentation
The API of the parser is quite well documented in the comments in the `md4c.h`.
Similarly, the markdown-to-html API is described in its header `md4c-html.h`.
There is also [project wiki](http://github.com/mity/md4c/wiki) which provides
some more comprehensive documentation. However note it is incomplete and some
details may be somewhat outdated.
## FAQ
**Q: How does MD4C compare to other Markdown parsers?**
**A:** Some other implementations combine Markdown parser and HTML generator
into a single entangled code hidden behind an interface which just allows the
conversion from Markdown to HTML. They are often unusable if you want to
process the input in any other way.
Second, most parsers (if not all of them; at least within the scope of C/C++
language) are full DOM-like parsers: They construct abstract syntax tree (AST)
representation of the whole Markdown document. That takes time and it leads to
bigger memory footprint.
Building AST is completely fine as long as you need it. If you don't, there is
a very high chance that using MD4C will be substantially faster and less hungry
in terms of memory consumption.
Last but not least, some Markdown parsers are implemented in a naive way. When
fed with a [smartly crafted input pattern](test/pathological_tests.py), they
may exhibit quadratic (or even worse) parsing times. What MD4C can still parse
in a fraction of second may turn into long minutes or possibly hours with them.
Hence, when such a naive parser is used to process an input from an untrusted
source, the possibility of denial-of-service attacks becomes a real danger.
A lot of our effort went into providing linear parsing times no matter what
kind of crazy input MD4C parser is fed with. (If you encounter an input pattern
which leads to a sub-linear parsing times, please do not hesitate and report it
as a bug.)
**Q: Does MD4C perform any input validation?**
**A:** No. And we are proud of it. :-)
CommonMark specification states that any sequence of Unicode characters is
a valid Markdown document. (In practice, this more or less always means UTF-8
encoding.)
In other words, according to the specification, it does not matter whether some
Markdown syntax construction is in some way broken or not. If it's broken, it
won't be recognized and the parser should see it just as a verbatim text.
MD4C takes this a step further: It sees any sequence of bytes as a valid input,
following completely the GIGO philosophy (garbage in, garbage out). I.e. any
ill-formed UTF-8 byte sequence will propagate to the respective callback as
a part of the text.
If you need to validate that the input is, say, a well-formed UTF-8 document,
you have to do it on your own. The easiest way how to do this is to simply
validate the whole document before passing it to the MD4C parser.
## License
MD4C is covered with MIT license, see the file `LICENSE.md`.
## Links to Related Projects
Ports and bindings to other languages:
* [commonmark-d](https://github.com/AuburnSounds/commonmark-d):
Port of MD4C to D language.
* [markdown-wasm](https://github.com/rsms/markdown-wasm):
Port of MD4C to WebAssembly.
* [PyMD4C](https://github.com/dominickpastore/pymd4c):
Python bindings for MD4C
Software using MD4C:
* [imgui_md](https://github.com/mekhontsev/imgui_md):
Markdown renderer for [Dear ImGui](https://github.com/ocornut/imgui)
* [MarkDown Monolith Assembler](https://github.com/1Hyena/mdma):
A command line tool for building browser-based books.
* [QOwnNotes](https://www.qownnotes.org/):
A plain-text file notepad and todo-list manager with markdown support and
ownCloud / Nextcloud integration.
* [Qt](https://www.qt.io/):
Cross-platform C++ GUI framework.
* [Textosaurus](https://github.com/martinrotter/textosaurus):
Cross-platform text editor based on Qt and Scintilla.
* [8th](https://8th-dev.com/):
Cross-platform concatenative programming language.

View File

@@ -1,53 +0,0 @@
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG")
# Build rules for MD4C parser library
configure_file(md4c.pc.in md4c.pc @ONLY)
add_library(md4c md4c.c md4c.h)
set_target_properties(md4c PROPERTIES
COMPILE_FLAGS "-DMD4C_USE_UTF8"
VERSION ${MD_VERSION}
SOVERSION ${MD_VERSION_MAJOR}
PUBLIC_HEADER md4c.h
)
# Build rules for HTML renderer library
configure_file(md4c-html.pc.in md4c-html.pc @ONLY)
add_library(md4c-html md4c-html.c md4c-html.h entity.c entity.h)
set_target_properties(md4c-html PROPERTIES
VERSION ${MD_VERSION}
SOVERSION ${MD_VERSION_MAJOR}
PUBLIC_HEADER md4c-html.h
)
target_link_libraries(md4c-html md4c)
# Install rules
#install(
# TARGETS md4c
# EXPORT md4cConfig
# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
# INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
#)
#install(FILES ${CMAKE_BINARY_DIR}/src/md4c.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
#
#install(
# TARGETS md4c-html
# EXPORT md4cConfig
# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
# PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
#)
#install(FILES ${CMAKE_BINARY_DIR}/src/md4c-html.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
#
#install(EXPORT md4cConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/md4c/ NAMESPACE md4c::)
#

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_ENTITY_H
#define MD4C_ENTITY_H
#include <stdlib.h>
/* Most entities are formed by single Unicode codepoint, few by two codepoints.
* Single-codepoint entities have codepoints[1] set to zero. */
typedef struct ENTITY_tag ENTITY;
struct ENTITY_tag {
const char* name;
unsigned codepoints[2];
};
const ENTITY* entity_lookup(const char* name, size_t name_size);
#endif /* MD4C_ENTITY_H */

View File

@@ -1,567 +0,0 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include "md4c-html.h"
#include "entity.h"
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L
/* C89/90 or old compilers in general may not understand "inline". */
#if defined __GNUC__
#define inline __inline__
#elif defined _MSC_VER
#define inline __inline
#else
#define inline
#endif
#endif
#ifdef _WIN32
#define snprintf _snprintf
#endif
typedef struct MD_HTML_tag MD_HTML;
struct MD_HTML_tag {
void (*process_output)(const MD_CHAR*, MD_SIZE, void*);
void* userdata;
unsigned flags;
int image_nesting_level;
char escape_map[256];
};
#define NEED_HTML_ESC_FLAG 0x1
#define NEED_URL_ESC_FLAG 0x2
/*****************************************
*** HTML rendering helper functions ***
*****************************************/
#define ISDIGIT(ch) ('0' <= (ch) && (ch) <= '9')
#define ISLOWER(ch) ('a' <= (ch) && (ch) <= 'z')
#define ISUPPER(ch) ('A' <= (ch) && (ch) <= 'Z')
#define ISALNUM(ch) (ISLOWER(ch) || ISUPPER(ch) || ISDIGIT(ch))
static inline void
render_verbatim(MD_HTML* r, const MD_CHAR* text, MD_SIZE size)
{
r->process_output(text, size, r->userdata);
}
/* Keep this as a macro. Most compiler should then be smart enough to replace
* the strlen() call with a compile-time constant if the string is a C literal. */
#define RENDER_VERBATIM(r, verbatim) \
render_verbatim((r), (verbatim), (MD_SIZE) (strlen(verbatim)))
static void
render_html_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
{
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
/* Some characters need to be escaped in normal HTML text. */
#define NEED_HTML_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_HTML_ESC_FLAG)
while(1) {
/* Optimization: Use some loop unrolling. */
while(off + 3 < size && !NEED_HTML_ESC(data[off+0]) && !NEED_HTML_ESC(data[off+1])
&& !NEED_HTML_ESC(data[off+2]) && !NEED_HTML_ESC(data[off+3]))
off += 4;
while(off < size && !NEED_HTML_ESC(data[off]))
off++;
if(off > beg)
render_verbatim(r, data + beg, off - beg);
if(off < size) {
switch(data[off]) {
case '&': RENDER_VERBATIM(r, "&amp;"); break;
case '<': RENDER_VERBATIM(r, "&lt;"); break;
case '>': RENDER_VERBATIM(r, "&gt;"); break;
case '"': RENDER_VERBATIM(r, "&quot;"); break;
}
off++;
} else {
break;
}
beg = off;
}
}
static void
render_url_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
{
static const MD_CHAR hex_chars[] = "0123456789ABCDEF";
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
/* Some characters need to be escaped in URL attributes. */
#define NEED_URL_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_URL_ESC_FLAG)
while(1) {
while(off < size && !NEED_URL_ESC(data[off]))
off++;
if(off > beg)
render_verbatim(r, data + beg, off - beg);
if(off < size) {
char hex[3];
switch(data[off]) {
case '&': RENDER_VERBATIM(r, "&amp;"); break;
default:
hex[0] = '%';
hex[1] = hex_chars[((unsigned)data[off] >> 4) & 0xf];
hex[2] = hex_chars[((unsigned)data[off] >> 0) & 0xf];
render_verbatim(r, hex, 3);
break;
}
off++;
} else {
break;
}
beg = off;
}
}
static unsigned
hex_val(char ch)
{
if('0' <= ch && ch <= '9')
return ch - '0';
if('A' <= ch && ch <= 'Z')
return ch - 'A' + 10;
else
return ch - 'a' + 10;
}
static void
render_utf8_codepoint(MD_HTML* r, unsigned codepoint,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
static const MD_CHAR utf8_replacement_char[] = { (char)0xef, (char)0xbf, (char)0xbd };
unsigned char utf8[4];
size_t n;
if(codepoint <= 0x7f) {
n = 1;
utf8[0] = codepoint;
} else if(codepoint <= 0x7ff) {
n = 2;
utf8[0] = 0xc0 | ((codepoint >> 6) & 0x1f);
utf8[1] = 0x80 + ((codepoint >> 0) & 0x3f);
} else if(codepoint <= 0xffff) {
n = 3;
utf8[0] = 0xe0 | ((codepoint >> 12) & 0xf);
utf8[1] = 0x80 + ((codepoint >> 6) & 0x3f);
utf8[2] = 0x80 + ((codepoint >> 0) & 0x3f);
} else {
n = 4;
utf8[0] = 0xf0 | ((codepoint >> 18) & 0x7);
utf8[1] = 0x80 + ((codepoint >> 12) & 0x3f);
utf8[2] = 0x80 + ((codepoint >> 6) & 0x3f);
utf8[3] = 0x80 + ((codepoint >> 0) & 0x3f);
}
if(0 < codepoint && codepoint <= 0x10ffff)
fn_append(r, (char*)utf8, (MD_SIZE)n);
else
fn_append(r, utf8_replacement_char, 3);
}
/* Translate entity to its UTF-8 equivalent, or output the verbatim one
* if such entity is unknown (or if the translation is disabled). */
static void
render_entity(MD_HTML* r, const MD_CHAR* text, MD_SIZE size,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
if(r->flags & MD_HTML_FLAG_VERBATIM_ENTITIES) {
render_verbatim(r, text, size);
return;
}
/* We assume UTF-8 output is what is desired. */
if(size > 3 && text[1] == '#') {
unsigned codepoint = 0;
if(text[2] == 'x' || text[2] == 'X') {
/* Hexadecimal entity (e.g. "&#x1234abcd;")). */
MD_SIZE i;
for(i = 3; i < size-1; i++)
codepoint = 16 * codepoint + hex_val(text[i]);
} else {
/* Decimal entity (e.g. "&1234;") */
MD_SIZE i;
for(i = 2; i < size-1; i++)
codepoint = 10 * codepoint + (text[i] - '0');
}
render_utf8_codepoint(r, codepoint, fn_append);
return;
} else {
/* Named entity (e.g. "&nbsp;"). */
const ENTITY* ent;
ent = entity_lookup(text, size);
if(ent != NULL) {
render_utf8_codepoint(r, ent->codepoints[0], fn_append);
if(ent->codepoints[1])
render_utf8_codepoint(r, ent->codepoints[1], fn_append);
return;
}
}
fn_append(r, text, size);
}
static void
render_attribute(MD_HTML* r, const MD_ATTRIBUTE* attr,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
int i;
for(i = 0; attr->substr_offsets[i] < attr->size; i++) {
MD_TEXTTYPE type = attr->substr_types[i];
MD_OFFSET off = attr->substr_offsets[i];
MD_SIZE size = attr->substr_offsets[i+1] - off;
const MD_CHAR* text = attr->text + off;
switch(type) {
case MD_TEXT_NULLCHAR: render_utf8_codepoint(r, 0x0000, render_verbatim); break;
case MD_TEXT_ENTITY: render_entity(r, text, size, fn_append); break;
default: fn_append(r, text, size); break;
}
}
}
static void
render_open_ol_block(MD_HTML* r, const MD_BLOCK_OL_DETAIL* det)
{
char buf[64];
if(det->start == 1) {
RENDER_VERBATIM(r, "<ol>\n");
return;
}
snprintf(buf, sizeof(buf), "<ol start=\"%u\">\n", det->start);
RENDER_VERBATIM(r, buf);
}
static void
render_open_li_block(MD_HTML* r, const MD_BLOCK_LI_DETAIL* det)
{
if(det->is_task) {
RENDER_VERBATIM(r, "<li class=\"task-list-item\">"
"<input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled");
if(det->task_mark == 'x' || det->task_mark == 'X')
RENDER_VERBATIM(r, " checked");
RENDER_VERBATIM(r, ">");
} else {
RENDER_VERBATIM(r, "<li>");
}
}
static void
render_open_code_block(MD_HTML* r, const MD_BLOCK_CODE_DETAIL* det)
{
RENDER_VERBATIM(r, "<pre><code");
/* If known, output the HTML 5 attribute class="language-LANGNAME". */
if(det->lang.text != NULL) {
RENDER_VERBATIM(r, " class=\"language-");
render_attribute(r, &det->lang, render_html_escaped);
RENDER_VERBATIM(r, "\"");
}
RENDER_VERBATIM(r, ">");
}
static void
render_open_td_block(MD_HTML* r, const MD_CHAR* cell_type, const MD_BLOCK_TD_DETAIL* det)
{
RENDER_VERBATIM(r, "<");
RENDER_VERBATIM(r, cell_type);
switch(det->align) {
case MD_ALIGN_LEFT: RENDER_VERBATIM(r, " align=\"left\">"); break;
case MD_ALIGN_CENTER: RENDER_VERBATIM(r, " align=\"center\">"); break;
case MD_ALIGN_RIGHT: RENDER_VERBATIM(r, " align=\"right\">"); break;
default: RENDER_VERBATIM(r, ">"); break;
}
}
static void
render_open_a_span(MD_HTML* r, const MD_SPAN_A_DETAIL* det)
{
RENDER_VERBATIM(r, "<a href=\"");
render_attribute(r, &det->href, render_url_escaped);
if(det->title.text != NULL) {
RENDER_VERBATIM(r, "\" title=\"");
render_attribute(r, &det->title, render_html_escaped);
}
RENDER_VERBATIM(r, "\">");
}
static void
render_open_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
{
RENDER_VERBATIM(r, "<img src=\"");
render_attribute(r, &det->src, render_url_escaped);
RENDER_VERBATIM(r, "\" alt=\"");
}
static void
render_close_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
{
if(det->title.text != NULL) {
RENDER_VERBATIM(r, "\" title=\"");
render_attribute(r, &det->title, render_html_escaped);
}
RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\" />" : "\">");
}
static void
render_open_wikilink_span(MD_HTML* r, const MD_SPAN_WIKILINK_DETAIL* det)
{
RENDER_VERBATIM(r, "<x-wikilink data-target=\"");
render_attribute(r, &det->target, render_html_escaped);
RENDER_VERBATIM(r, "\">");
}
/**************************************
*** HTML renderer implementation ***
**************************************/
static int
enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
{
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_BLOCK_DOC: /* noop */ break;
case MD_BLOCK_QUOTE: RENDER_VERBATIM(r, "<blockquote>\n"); break;
case MD_BLOCK_UL: RENDER_VERBATIM(r, "<ul>\n"); break;
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
case MD_BLOCK_HR: RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr />\n" : "<hr>\n"); break;
case MD_BLOCK_H: RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: RENDER_VERBATIM(r, "<p>"); break;
case MD_BLOCK_TABLE: RENDER_VERBATIM(r, "<table>\n"); break;
case MD_BLOCK_THEAD: RENDER_VERBATIM(r, "<thead>\n"); break;
case MD_BLOCK_TBODY: RENDER_VERBATIM(r, "<tbody>\n"); break;
case MD_BLOCK_TR: RENDER_VERBATIM(r, "<tr>\n"); break;
case MD_BLOCK_TH: render_open_td_block(r, "th", (MD_BLOCK_TD_DETAIL*)detail); break;
case MD_BLOCK_TD: render_open_td_block(r, "td", (MD_BLOCK_TD_DETAIL*)detail); break;
}
return 0;
}
static int
leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
{
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_BLOCK_DOC: /*noop*/ break;
case MD_BLOCK_QUOTE: RENDER_VERBATIM(r, "</blockquote>\n"); break;
case MD_BLOCK_UL: RENDER_VERBATIM(r, "</ul>\n"); break;
case MD_BLOCK_OL: RENDER_VERBATIM(r, "</ol>\n"); break;
case MD_BLOCK_LI: RENDER_VERBATIM(r, "</li>\n"); break;
case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_H: RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: RENDER_VERBATIM(r, "</code></pre>\n"); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: RENDER_VERBATIM(r, "</p>\n"); break;
case MD_BLOCK_TABLE: RENDER_VERBATIM(r, "</table>\n"); break;
case MD_BLOCK_THEAD: RENDER_VERBATIM(r, "</thead>\n"); break;
case MD_BLOCK_TBODY: RENDER_VERBATIM(r, "</tbody>\n"); break;
case MD_BLOCK_TR: RENDER_VERBATIM(r, "</tr>\n"); break;
case MD_BLOCK_TH: RENDER_VERBATIM(r, "</th>\n"); break;
case MD_BLOCK_TD: RENDER_VERBATIM(r, "</td>\n"); break;
}
return 0;
}
static int
enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
int inside_img = (r->image_nesting_level > 0);
/* We are inside a Markdown image label. Markdown allows to use any emphasis
* and other rich contents in that context similarly as in any link label.
*
* However, unlike in the case of links (where that contents becomescontents
* of the <a>...</a> tag), in the case of images the contents is supposed to
* fall into the attribute alt: <img alt="...">.
*
* In that context we naturally cannot output nested HTML tags. So lets
* suppress them and only output the plain text (i.e. what falls into text()
* callback).
*
* CommonMark specification declares this a recommended practice for HTML
* output.
*/
if(type == MD_SPAN_IMG)
r->image_nesting_level++;
if(inside_img)
return 0;
switch(type) {
case MD_SPAN_EM: RENDER_VERBATIM(r, "<em>"); break;
case MD_SPAN_STRONG: RENDER_VERBATIM(r, "<strong>"); break;
case MD_SPAN_U: RENDER_VERBATIM(r, "<u>"); break;
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
case MD_SPAN_CODE: RENDER_VERBATIM(r, "<code>"); break;
case MD_SPAN_DEL: RENDER_VERBATIM(r, "<del>"); break;
case MD_SPAN_LATEXMATH: RENDER_VERBATIM(r, "<x-equation>"); break;
case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "<x-equation type=\"display\">"); break;
case MD_SPAN_WIKILINK: render_open_wikilink_span(r, (MD_SPAN_WIKILINK_DETAIL*) detail); break;
}
return 0;
}
static int
leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
if(type == MD_SPAN_IMG)
r->image_nesting_level--;
if(r->image_nesting_level > 0)
return 0;
switch(type) {
case MD_SPAN_EM: RENDER_VERBATIM(r, "</em>"); break;
case MD_SPAN_STRONG: RENDER_VERBATIM(r, "</strong>"); break;
case MD_SPAN_U: RENDER_VERBATIM(r, "</u>"); break;
case MD_SPAN_A: RENDER_VERBATIM(r, "</a>"); break;
case MD_SPAN_IMG: render_close_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
case MD_SPAN_CODE: RENDER_VERBATIM(r, "</code>"); break;
case MD_SPAN_DEL: RENDER_VERBATIM(r, "</del>"); break;
case MD_SPAN_LATEXMATH: /*fall through*/
case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "</x-equation>"); break;
case MD_SPAN_WIKILINK: RENDER_VERBATIM(r, "</x-wikilink>"); break;
}
return 0;
}
static int
text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_TEXT_NULLCHAR: render_utf8_codepoint(r, 0x0000, render_verbatim); break;
case MD_TEXT_BR: RENDER_VERBATIM(r, (r->image_nesting_level == 0
? ((r->flags & MD_HTML_FLAG_XHTML) ? "<br />\n" : "<br>\n")
: " "));
break;
case MD_TEXT_SOFTBR: RENDER_VERBATIM(r, (r->image_nesting_level == 0 ? "\n" : " ")); break;
case MD_TEXT_HTML: render_verbatim(r, text, size); break;
case MD_TEXT_ENTITY: render_entity(r, text, size, render_html_escaped); break;
default: render_html_escaped(r, text, size); break;
}
return 0;
}
static void
debug_log_callback(const char* msg, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
if(r->flags & MD_HTML_FLAG_DEBUG)
fprintf(stderr, "MD4C: %s\n", msg);
}
int
md_html(const MD_CHAR* input, MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata, unsigned parser_flags, unsigned renderer_flags)
{
MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } };
int i;
MD_PARSER parser = {
0,
parser_flags,
enter_block_callback,
leave_block_callback,
enter_span_callback,
leave_span_callback,
text_callback,
debug_log_callback,
NULL
};
/* Build map of characters which need escaping. */
for(i = 0; i < 256; i++) {
unsigned char ch = (unsigned char) i;
if(strchr("\"&<>", ch) != NULL)
render.escape_map[i] |= NEED_HTML_ESC_FLAG;
if(!ISALNUM(ch) && strchr("~-_.+!*(),%#@?=;:/,+$", ch) == NULL)
render.escape_map[i] |= NEED_URL_ESC_FLAG;
}
/* Consider skipping UTF-8 byte order mark (BOM). */
if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) {
static const MD_CHAR bom[3] = { (char)0xef, (char)0xbb, (char)0xbf };
if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) {
input += sizeof(bom);
input_size -= sizeof(bom);
}
}
return md_parse(input, input_size, &parser, (void*) &render);
}

View File

@@ -1,68 +0,0 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_HTML_H
#define MD4C_HTML_H
#include "md4c.h"
#ifdef __cplusplus
extern "C" {
#endif
/* If set, debug output from md_parse() is sent to stderr. */
#define MD_HTML_FLAG_DEBUG 0x0001
#define MD_HTML_FLAG_VERBATIM_ENTITIES 0x0002
#define MD_HTML_FLAG_SKIP_UTF8_BOM 0x0004
#define MD_HTML_FLAG_XHTML 0x0008
/* Render Markdown into HTML.
*
* Note only contents of <body> tag is generated. Caller must generate
* HTML header/footer manually before/after calling md_html().
*
* Params input and input_size specify the Markdown input.
* Callback process_output() gets called with chunks of HTML output.
* (Typical implementation may just output the bytes to a file or append to
* some buffer).
* Param userdata is just propagated back to process_output() callback.
* Param parser_flags are flags from md4c.h propagated to md_parse().
* Param render_flags is bitmask of MD_HTML_FLAG_xxxx.
*
* Returns -1 on error (if md_parse() fails.)
* Returns 0 on success.
*/
int md_html(const MD_CHAR* input, MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata, unsigned parser_flags, unsigned renderer_flags);
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* MD4C_HTML_H */

View File

@@ -1,13 +0,0 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@ HTML renderer
Description: Markdown to HTML converter library.
Version: @PROJECT_VERSION@
URL: @PROJECT_URL@
Requires: md4c = @PROJECT_VERSION@
Libs: -L${libdir} -lmd4c-html
Cflags: -I${includedir}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More