mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-04-06 00:32:05 +02:00
Compare commits
6 Commits
nightly-bu
...
v2.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2d10a0b57 | ||
|
|
d1b6ebcd06 | ||
|
|
37c5fcd908 | ||
|
|
cde79fc655 | ||
|
|
eba242953b | ||
|
|
7edeb8b277 |
@@ -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.
|
||||
@@ -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
|
||||
|
||||
---
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
14
.doxygen
14
.doxygen
@@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -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:
|
||||
|
||||
11
.github/actions/apt-install-deps/action.yml
vendored
11
.github/actions/apt-install-deps/action.yml
vendored
@@ -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 ./
|
||||
4
.github/workflows/assign.yml
vendored
4
.github/workflows/assign.yml
vendored
@@ -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'
|
||||
|
||||
31
.github/workflows/auto-close-duplicates.yml
vendored
31
.github/workflows/auto-close-duplicates.yml
vendored
@@ -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 }}
|
||||
@@ -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 }}
|
||||
149
.github/workflows/build_all.yml
vendored
149
.github/workflows/build_all.yml
vendored
@@ -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@v8
|
||||
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@v7
|
||||
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@v5
|
||||
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@v5
|
||||
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@v7
|
||||
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
|
||||
|
||||
|
||||
15
.github/workflows/build_check_cache.yml
vendored
15
.github/workflows/build_check_cache.yml
vendored
@@ -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: 'false'
|
||||
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 }}
|
||||
|
||||
104
.github/workflows/build_deps.yml
vendored
104
.github/workflows/build_deps.yml
vendored
@@ -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: 'false'
|
||||
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 }}
|
||||
|
||||
250
.github/workflows/build_orca.yml
vendored
250
.github/workflows/build_orca.yml
vendored
@@ -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: '2404'
|
||||
ubuntu-ver-str: '_Ubuntu2404'
|
||||
ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
lfs: 'false'
|
||||
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@v7
|
||||
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@v8
|
||||
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@v7
|
||||
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@v7
|
||||
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@v7
|
||||
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@v7
|
||||
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@v7
|
||||
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@v7
|
||||
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,35 +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
|
||||
${{ 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@v7
|
||||
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: |
|
||||
@@ -408,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@v7
|
||||
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@v7
|
||||
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"
|
||||
@@ -441,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
|
||||
@@ -452,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
|
||||
|
||||
2
.github/workflows/check_locale.yml
vendored
2
.github/workflows/check_locale.yml
vendored
@@ -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: |
|
||||
|
||||
93
.github/workflows/check_profiles.yml
vendored
93
.github/workflows/check_profiles.yml
vendored
@@ -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@v7
|
||||
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
|
||||
|
||||
46
.github/workflows/check_profiles_comment.yml
vendored
46
.github/workflows/check_profiles_comment.yml
vendored
@@ -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@v8
|
||||
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
|
||||
71
.github/workflows/claude-code-review.yml
vendored
Normal file
71
.github/workflows/claude-code-review.yml
vendored
Normal 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
64
.github/workflows/claude.yml
vendored
Normal 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
|
||||
|
||||
81
.github/workflows/dedupe-issues.yml
vendored
81
.github/workflows/dedupe-issues.yml
vendored
@@ -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
|
||||
78
.github/workflows/doxygen-docs.yml
vendored
78
.github/workflows/doxygen-docs.yml
vendored
@@ -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
73
.github/workflows/orca_bot.yml
vendored
Normal 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
|
||||
46
.github/workflows/publish_docs_to_wiki.yml
vendored
Normal file
46
.github/workflows/publish_docs_to_wiki.yml
vendored
Normal 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
|
||||
4
.github/workflows/shellcheck.yml
vendored
4
.github/workflows/shellcheck.yml
vendored
@@ -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
|
||||
|
||||
|
||||
4
.github/workflows/update-translation.yml
vendored
4
.github/workflows/update-translation.yml
vendored
@@ -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'
|
||||
|
||||
|
||||
335
.github/workflows/validate-documentation.yml
vendored
Normal file
335
.github/workflows/validate-documentation.yml
vendored
Normal 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
|
||||
})
|
||||
14
.github/workflows/winget_updater.yml
vendored
14
.github/workflows/winget_updater.yml
vendored
@@ -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
7
.gitignore
vendored
@@ -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/
|
||||
85
CLAUDE.md
85
CLAUDE.md
@@ -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
|
||||
168
CMakeLists.txt
168
CMakeLists.txt
@@ -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,25 +80,31 @@ 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})
|
||||
option(SLIC3R_GUI "Compile OrcaSlicer with GUI components (OpenGL, wxWidgets)" 1)
|
||||
option(SLIC3R_FHS "Assume OrcaSlicer is to be installed in a FHS directory structure" 0)
|
||||
option(SLIC3R_WX_STABLE "Build against wxWidgets stable (3.0) as oppsed to dev (3.1) on Linux" 0)
|
||||
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)
|
||||
|
||||
@@ -148,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)
|
||||
@@ -161,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 ()
|
||||
@@ -171,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 ()
|
||||
|
||||
@@ -198,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)
|
||||
|
||||
@@ -250,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}")
|
||||
@@ -345,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")
|
||||
@@ -434,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
|
||||
@@ -453,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)
|
||||
@@ -492,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()
|
||||
|
||||
@@ -581,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)
|
||||
@@ -699,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
|
||||
@@ -737,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})
|
||||
@@ -902,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()
|
||||
@@ -951,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}")
|
||||
|
||||
71
README.md
71
README.md
@@ -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>
|
||||
|
||||
[](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
|
||||
[](https://github.com/SoftFever/OrcaSlicer/stargazers) [](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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
130
build_linux.sh
130
build_linux.sh
@@ -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,34 +7,31 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
||||
pushd "${SCRIPT_PATH}" > /dev/null
|
||||
|
||||
function usage() {
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-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 " -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 "To build with tests: './${SCRIPT_NAME} -st' or './${SCRIPT_NAME} -dst'"
|
||||
}
|
||||
|
||||
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||
|
||||
unset name
|
||||
BUILD_DIR=build
|
||||
BUILD_CONFIG=Release
|
||||
while getopts ":1j:bcCdDehiprstulL" opt ; do
|
||||
while getopts ":1j:bcCdhiprstulL" opt ; do
|
||||
case ${opt} in
|
||||
1 )
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||
@@ -44,8 +40,7 @@ while getopts ":1j:bcCdDehiprstulL" opt ; do
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=$OPTARG
|
||||
;;
|
||||
b )
|
||||
BUILD_DIR=build-dbg
|
||||
BUILD_CONFIG=Debug
|
||||
BUILD_DEBUG="1"
|
||||
;;
|
||||
c )
|
||||
CLEAN_BUILD=1
|
||||
@@ -56,13 +51,6 @@ while getopts ":1j:bcCdDehiprstulL" opt ; do
|
||||
d )
|
||||
BUILD_DEPS="1"
|
||||
;;
|
||||
D )
|
||||
DRY_RUN="1"
|
||||
;;
|
||||
e )
|
||||
BUILD_DIR=build-dbginfo
|
||||
BUILD_CONFIG=RelWithDebInfo
|
||||
;;
|
||||
h ) usage
|
||||
exit 1
|
||||
;;
|
||||
@@ -124,21 +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
|
||||
}
|
||||
|
||||
# cmake 4.x compatibility workaround
|
||||
export CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
@@ -152,8 +125,6 @@ elif [[ "${DISTRIBUTION_LIKE}" == *"debian"* ]] || [[ "${DISTRIBUTION_LIKE}" ==
|
||||
DISTRIBUTION="debian"
|
||||
elif [[ "${DISTRIBUTION_LIKE}" == *"arch"* ]] ; then
|
||||
DISTRIBUTION="arch"
|
||||
elif [[ "${DISTRIBUTION_LIKE}" == *"suse"* ]] ; then
|
||||
DISTRIBUTION="suse"
|
||||
fi
|
||||
|
||||
if [ ! -f "./scripts/linux.d/${DISTRIBUTION}" ] ; then
|
||||
@@ -166,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"
|
||||
@@ -205,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)
|
||||
@@ -234,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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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 ..
|
||||
|
||||
175
cmake/modules/Catch2/Catch.cmake
Normal file
175
cmake/modules/Catch2/Catch.cmake
Normal 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
|
||||
)
|
||||
106
cmake/modules/Catch2/CatchAddTests.cmake
Normal file
106
cmake/modules/Catch2/CatchAddTests.cmake
Normal 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}")
|
||||
225
cmake/modules/Catch2/ParseAndAddCatchTests.cmake
Normal file
225
cmake/modules/Catch2/ParseAndAddCatchTests.cmake
Normal 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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
#
|
||||
# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or
|
||||
# don’t 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.
|
||||
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
|
||||
1
deps/Boost/Boost.cmake
vendored
1
deps/Boost/Boost.cmake
vendored
@@ -18,7 +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_ZSTD:BOOL=OFF
|
||||
"${_context_abi_line}"
|
||||
"${_context_arch_line}"
|
||||
)
|
||||
|
||||
59
deps/CGAL/0001-clang19.patch
vendored
Normal file
59
deps/CGAL/0001-clang19.patch
vendored
Normal 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);
|
||||
7
deps/CGAL/CGAL.cmake
vendored
7
deps/CGAL/CGAL.cmake
vendored
@@ -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
130
deps/CMakeLists.txt
vendored
@@ -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
|
||||
|
||||
4
deps/Draco/Draco.cmake
vendored
4
deps/Draco/Draco.cmake
vendored
@@ -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
|
||||
)
|
||||
2
deps/GLEW/GLEW.cmake
vendored
2
deps/GLEW/GLEW.cmake
vendored
@@ -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)
|
||||
|
||||
12
deps/GLEW/glew/CMakeLists.txt
vendored
12
deps/GLEW/glew/CMakeLists.txt
vendored
@@ -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
2
deps/GMP/GMP.cmake
vendored
@@ -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
|
||||
)
|
||||
|
||||
5
deps/MPFR/MPFR.cmake
vendored
5
deps/MPFR/MPFR.cmake
vendored
@@ -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
|
||||
|
||||
1
deps/OCCT/OCCT.cmake
vendored
1
deps/OCCT/OCCT.cmake
vendored
@@ -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
|
||||
|
||||
54
deps/OpenCV/0001-vs.patch
vendored
54
deps/OpenCV/0001-vs.patch
vendored
@@ -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
52
deps/OpenCV/0001-vs2022.patch
vendored
Normal 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
|
||||
|
||||
3
deps/OpenCV/OpenCV.cmake
vendored
3
deps/OpenCV/OpenCV.cmake
vendored
@@ -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
|
||||
|
||||
4
deps/OpenSSL/OpenSSL.cmake
vendored
4
deps/OpenSSL/OpenSSL.cmake
vendored
@@ -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})
|
||||
|
||||
6
deps/Qhull/Qhull.cmake
vendored
6
deps/Qhull/Qhull.cmake
vendored
@@ -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
2
deps/TBB/TBB.cmake
vendored
@@ -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 "")
|
||||
|
||||
4
deps/deps-windows.cmake
vendored
4
deps/deps-windows.cmake
vendored
@@ -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 ()
|
||||
|
||||
47
deps/wxWidgets/wxWidgets.cmake
vendored
47
deps/wxWidgets/wxWidgets.cmake
vendored
@@ -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,29 +17,34 @@ else ()
|
||||
set(_wx_edge "-DwxUSE_WEBVIEW_EDGE=OFF")
|
||||
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
|
||||
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
|
||||
GIT_TAG v3.3.2
|
||||
GIT_SHALLOW ON
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
|
||||
CMAKE_ARGS
|
||||
-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_PRIVATE_FONTS=ON
|
||||
-DwxUSE_UNICODE=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
|
||||
@@ -52,30 +53,8 @@ orcaslicer_add_cmake_project(
|
||||
-DwxUSE_ZLIB=sys
|
||||
-DwxUSE_LIBJPEG=sys
|
||||
-DwxUSE_LIBTIFF=OFF
|
||||
-DwxUSE_EXPAT=sys
|
||||
-DwxUSE_NANOSVG=OFF
|
||||
)
|
||||
|
||||
# wxWidgets 3.3 cmake install doesn't include private headers.
|
||||
# OrcaSlicer uses some of the private headers (for accessibility support).
|
||||
# Copy the private headers directory after install.
|
||||
if(MSVC)
|
||||
set(_wx_inc_dest ${DESTDIR}/include/wx)
|
||||
else()
|
||||
set(_wx_inc_dest ${DESTDIR}/include/wx-3.3/wx)
|
||||
endif()
|
||||
ExternalProject_Add_Step(dep_wxWidgets copy_private_headers
|
||||
DEPENDEES install
|
||||
COMMENT "Copying wxWidgets private headers"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/private
|
||||
${_wx_inc_dest}/private
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/generic/private
|
||||
${_wx_inc_dest}/generic/private
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/gtk/private
|
||||
${_wx_inc_dest}/gtk/private
|
||||
-DwxUSE_EXPAT=sys
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
@@ -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
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
[](https://travis-ci.com/github/mapbox/earcut.hpp)
|
||||
[](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master)
|
||||
[](https://coveralls.io/github/mapbox/earcut.hpp)
|
||||
[](https://scan.coverity.com/projects/14000)
|
||||
[](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue")
|
||||
[](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open")
|
||||
[](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).
|
||||
@@ -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 = ¤tBlock[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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ()
|
||||
@@ -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.
|
||||
@@ -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. `&`) and numeric character
|
||||
references (e.g. `#` or `ಫ`) 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.
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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, "&"); break;
|
||||
case '<': RENDER_VERBATIM(r, "<"); break;
|
||||
case '>': RENDER_VERBATIM(r, ">"); break;
|
||||
case '"': RENDER_VERBATIM(r, """); 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, "&"); 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. "�")). */
|
||||
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. " "). */
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
Reference in New Issue
Block a user