Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e2292bb24 | ||
|
|
4f7c1e4024 | ||
|
|
3881cae9c5 | ||
|
|
a863296cdd | ||
|
|
5f94740be0 | ||
|
|
07885cb51b | ||
|
|
cb30480865 | ||
|
|
80a81b7641 | ||
|
|
20a8f3d15d | ||
|
|
ce5d758f32 | ||
|
|
9a9f5a21d9 | ||
|
|
44a999a75e | ||
|
|
a97f6e94b0 | ||
|
|
edb4b25f64 | ||
|
|
f9d363497b | ||
|
|
95f40d1d14 | ||
|
|
2001b183f8 | ||
|
|
03fbf27478 | ||
|
|
0d93550ac5 | ||
|
|
2e59ba5da6 | ||
|
|
095dc65159 | ||
|
|
9cec6cd2e4 | ||
|
|
cc153b21d3 | ||
|
|
557effe165 | ||
|
|
ebd8fa2357 | ||
|
|
defb89b7c3 | ||
|
|
7f0c586629 | ||
|
|
7cbb4fc517 | ||
|
|
e7ca1e33d3 | ||
|
|
81464d30b1 | ||
|
|
07f860f6ad | ||
|
|
1d1ef6393a | ||
|
|
19eccdad39 | ||
|
|
d8c6509876 | ||
|
|
07ef876ac2 | ||
|
|
412b5cd937 | ||
|
|
c8a792a7a0 | ||
|
|
dbff2eb012 | ||
|
|
4b5ead381e | ||
|
|
8550f0546c | ||
|
|
59ab7c443d |
@@ -1,4 +0,0 @@
|
||||
# Carry-patches must keep LF line endings even when checked out on
|
||||
# Windows with default core.autocrlf=true; otherwise `git apply`
|
||||
# refuses them on EOL mismatch with the (non-CRLF) target tree.
|
||||
*.patch text eol=lf
|
||||
@@ -1,41 +0,0 @@
|
||||
name: download and cache git repo
|
||||
description: this simply dumps it in the current directory...
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
url:
|
||||
required: true
|
||||
rev:
|
||||
default: ''
|
||||
tag:
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: setup cache
|
||||
id: cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{inputs.name}}
|
||||
key: ${{inputs.url}}@${{inputs.rev}}${{inputs.tag}}
|
||||
|
||||
- name: download rev
|
||||
if: steps.cache.outputs.cache-hit != 'true' && inputs.rev != ''
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir ${{inputs.name}}
|
||||
cd ${{inputs.name}}
|
||||
git init
|
||||
git remote add origin ${{inputs.url}}
|
||||
git fetch --depth 1 origin ${{inputs.rev}}
|
||||
git checkout ${{inputs.rev}}
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: download tag
|
||||
if: steps.cache.outputs.cache-hit != 'true' && inputs.tag != ''
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --branch ${{inputs.tag}} --depth 1 ${{inputs.url}} ${{inputs.name}}
|
||||
cd ${{inputs.name}}
|
||||
git submodule update --init --recursive
|
||||
@@ -11,20 +11,18 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# macos-15-intel: x86-64
|
||||
# macos-15: arm64
|
||||
os: [ubuntu-22.04, ubuntu-22.04-arm, windows-2025, windows-11-arm, macos-15-intel, macos-15]
|
||||
# macos-13: x86-64
|
||||
# macos-14: arm64
|
||||
os: [ubuntu-22.04, ubuntu-22.04-arm, windows-2025, macos-14, macos-13]
|
||||
steps:
|
||||
- run: |
|
||||
git config --global submodule.fetchJobs 8
|
||||
git config --global core.longpaths true
|
||||
- if: matrix.os == 'macos-15'
|
||||
- if: matrix.os == 'macos-14'
|
||||
run: echo "CIBW_ARCHS=arm64 universal2" >> "$GITHUB_ENV"
|
||||
- if: matrix.os == 'windows-11-arm'
|
||||
run: echo "CIBW_BUILD=cp311-* cp312-* cp313-* cp314-*" >> $env:GITHUB_ENV
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v3.4.0
|
||||
uses: pypa/cibuildwheel@v2.23.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-${{ matrix.os }}
|
||||
@@ -34,14 +32,14 @@ jobs:
|
||||
name: Make SDist
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build SDist
|
||||
run: pipx run build --sdist
|
||||
- name: Confirm SDist can be built
|
||||
run: |
|
||||
# FIXME: why doesn't scikit_build_core automatically download ninja?
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build libtbb-dev
|
||||
sudo apt-get install ninja-build
|
||||
pip wheel dist/*.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -11,10 +11,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DoozyX/clang-format-lint-action@v0.20
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp,js,ts,html'
|
||||
clangFormatVersion: 20
|
||||
- uses: actions/setup-python@v5
|
||||
@@ -25,8 +26,8 @@ jobs:
|
||||
with:
|
||||
options: "--check --verbose"
|
||||
src: "./bindings/python/examples"
|
||||
# - name: "gersemi cmake check"
|
||||
# run: |
|
||||
# pip3 install gersemi
|
||||
# ./scripts/gersemi-check.sh
|
||||
- name: "gersemi cmake check"
|
||||
run: |
|
||||
pip3 install gersemi
|
||||
./scripts/gersemi-check.sh
|
||||
|
||||
|
||||
@@ -1,38 +1,18 @@
|
||||
name: Deploy documentation
|
||||
name: Deploy documentation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
types:
|
||||
- completed
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Branch or tag to build docs from (e.g. v3.4.0)'
|
||||
required: true
|
||||
destination_dir:
|
||||
description: 'Subdirectory to deploy to on gh-pages (e.g. v3.4.0)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
deploy_github_pages:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip when the CI run that triggered us failed.
|
||||
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ (github.event_name == 'workflow_dispatch' && inputs.ref) || (github.event_name == 'workflow_run' && github.event.workflow_run.head_sha) || github.ref }}
|
||||
|
||||
# github.sha is the ref the workflow was launched from, which on a manual
|
||||
# dispatch is not the ref being deployed. Use what checkout landed on so
|
||||
# the artifact matches the code.
|
||||
- name: Resolve deployed commit
|
||||
id: deployed
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Doxygen Action
|
||||
uses: mattnotmitt/doxygen-action@edge
|
||||
@@ -42,54 +22,31 @@ jobs:
|
||||
mkdir public
|
||||
mv ./samples ./public
|
||||
mv ./docs ./public
|
||||
mv ./dashboard ./public
|
||||
|
||||
# No continue-on-error: a missing artifact means the editor, examples and
|
||||
# TypeScript docs would be absent, and publishing that silently is worse
|
||||
# than failing.
|
||||
- name: Download manifoldCAD editor, examples and documentation
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: success
|
||||
# Pin to the artifact from the CI run that triggered us
|
||||
commit: ${{ (github.event_name != 'workflow_run' && steps.deployed.outputs.sha) || '' }}
|
||||
run_id: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.id) || '' }}
|
||||
check_artifacts: true
|
||||
name: wasmpublic
|
||||
workflow_conclusion: completed
|
||||
# specific to the triggering workflow
|
||||
run_id: ${{github.event.workflow_run.id}}
|
||||
# do not download from old run
|
||||
check_artifacts: true
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
# unreleased build for testing, not a version anyone should pin to.
|
||||
- name: Deploy master to alpha/
|
||||
if: github.event_name == 'workflow_run'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./public
|
||||
destination_dir: alpha
|
||||
- name: Deploy Javascript Docs to Github Pages
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
npm install
|
||||
npm run docs
|
||||
mv ./docs ../../public/jsdocs
|
||||
|
||||
- name: Deploy to vX.Y.Z/
|
||||
if: github.event_name != 'workflow_run'
|
||||
- name: Deploy to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./public
|
||||
destination_dir: ${{ (github.event_name == 'release' && github.ref_name) || inputs.destination_dir }}
|
||||
|
||||
# A release also publishes to the site root, so manifoldcad.org always
|
||||
# serves the current release without a redirect.(it is the latest release)
|
||||
#
|
||||
# keep_files is load-bearing here and must not be removed. With no
|
||||
# destination_dir the action's cleanup runs at the branch root, so
|
||||
# without this it would delete alpha/ and every vX.Y.Z/ directory.
|
||||
- name: Deploy to gh-pages root
|
||||
if: github.event_name == 'release'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./public
|
||||
keep_files: true
|
||||
force_orphan: true
|
||||
cname: manifoldcad.org
|
||||
publish_dir: ./public
|
||||
|
||||
@@ -15,6 +15,178 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: GCC ${{matrix.gcc}} (CrossSection:${{matrix.cross_section}}, TBB:${{matrix.parallelization}})
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
cross_section: [ON]
|
||||
parallelization: [OFF, ON]
|
||||
os: [ubuntu-24.04]
|
||||
gcc: [13, 14]
|
||||
include:
|
||||
- cross_section: OFF
|
||||
parallelization: ON
|
||||
os: ubuntu-24.04
|
||||
gcc: 14
|
||||
- cross_section: ON
|
||||
parallelization: ON
|
||||
os: ubuntu-22.04
|
||||
gcc: 11
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CC: gcc-${{ matrix.gcc }}
|
||||
CXX: g++-${{ matrix.gcc }}
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev lcov
|
||||
python -m pip install -U trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=ON -DMANIFOLD_FLAGS=-UNDEBUG -DMANIFOLD_CROSS_SECTION=${{matrix.cross_section}} -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
- name: Test ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF'
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
- name: Test Python bindings ${{matrix.parallelization}}
|
||||
if: matrix.parallelization != 'OFF' && matrix.cross_section == 'ON'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
python3 bindings/python/examples/run_all.py -e
|
||||
python3 -m pytest
|
||||
- name: Coverage Report
|
||||
# only do code coverage for default sequential backend, it seems that TBB
|
||||
# backend will cause failure
|
||||
# perhaps issue related to invalid memory access?
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_CROSS_SECTION=${{matrix.cross_section}} -DMANIFOLD_PAR=${{matrix.parallelization}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
cd ../
|
||||
lcov --capture --gcov-tool gcov-${{ matrix.gcc }} --ignore-errors mismatch --directory . --output-file ./code_coverage_test.info
|
||||
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
|
||||
lcov --extract ./code_coverage_total.info "/*/manifold/src/*" --output-file ./code_coverage.info
|
||||
cd ../
|
||||
- uses: codecov/codecov-action@v5
|
||||
if: matrix.parallelization == 'OFF'
|
||||
with:
|
||||
disable_search: true
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallelization}}
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
verbose: true
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
|
||||
build_wasm:
|
||||
name: WASM (TBB:${{matrix.parallelization}}, DEBUG:${{matrix.debug}})
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
debug: [OFF, ON]
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup WASM
|
||||
run: |
|
||||
# setup emscripten
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install 3.1.64
|
||||
./emsdk activate 3.1.64
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build WASM ${{matrix.parallelization}}
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh
|
||||
mkdir build
|
||||
cd build
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DMANIFOLD_PAR=${{matrix.parallelization}} -DMANIFOLD_DEBUG=${{matrix.debug}} -DMANIFOLD_ASSERT=${{matrix.debug}} .. && emmake make
|
||||
- name: Test WASM
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
- name: Test examples
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm/examples
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
cp ../manifold.* ./dist/
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && matrix.parallelization == 'OFF' && matrix.debug == 'OFF'
|
||||
with:
|
||||
name: wasm
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
|
||||
build_fuzzer:
|
||||
name: fuzzer
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev clang
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMANIFOLD_DEBUG=ON -DMANIFOLD_FUZZ=ON -DMANIFOLD_PYBIND=OFF -DCMAKE_CXX_COMPILER=clang++ ..
|
||||
make
|
||||
|
||||
build_fuzzer_mac:
|
||||
name: fuzzer mac
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 3.31.6
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMANIFOLD_DEBUG=ON -DMANIFOLD_FUZZ=ON -DMANIFOLD_PYBIND=OFF -DCMAKE_CXX_COMPILER=clang++ -DMANIFOLD_PAR=OFF ..
|
||||
ASAN_OPTIONS=detect_container_overflow=0 make
|
||||
|
||||
build_windows:
|
||||
name: Windows (TBB:${{matrix.parallelization}}, SHARED:${{matrix.shared}})
|
||||
timeout-minutes: 30
|
||||
@@ -24,46 +196,17 @@ jobs:
|
||||
shared: [OFF, ON]
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: lukka/get-cmake@v4.3.3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmakeVersion: 4.3.3
|
||||
- &clone-tbb
|
||||
uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: tbb
|
||||
url: https://github.com/oneapi-src/oneTBB.git
|
||||
tag: v2022.3.0
|
||||
- &clone-nanobind
|
||||
uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: nanobind
|
||||
url: https://github.com/wjakob/nanobind.git
|
||||
tag: v2.12.0
|
||||
- &clone-gtest
|
||||
uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: gtest
|
||||
url: https://github.com/google/googletest.git
|
||||
tag: v1.17.0
|
||||
cmake-version: 4.0.2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Build ${{matrix.parallelization}}
|
||||
shell: bash
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=${{matrix.shared}} \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_USE_BUILTIN_TBB=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_ASSERT=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
-DFETCHCONTENT_SOURCE_DIR_TBB=tbb \
|
||||
-DFETCHCONTENT_SOURCE_DIR_NANOBIND=nanobind \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=gtest \
|
||||
-A x64 -B build
|
||||
cmake --build build --target ALL_BUILD --config Release
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{matrix.shared}} -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} -A x64 -B build
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test dll location
|
||||
if: matrix.shared == 'ON'
|
||||
shell: bash
|
||||
@@ -75,383 +218,16 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cp ./build/lib/Release/* ./build/bin/Release
|
||||
./build/bin/Release/manifold_test.exe
|
||||
- name: Export determinism meshes
|
||||
if: matrix.parallelization == 'OFF' && matrix.shared == 'OFF'
|
||||
shell: bash
|
||||
env:
|
||||
MANIFOLD_OBJ_HEX_FLOAT: 1
|
||||
run: |
|
||||
cp ./build/lib/Release/* ./build/bin/Release
|
||||
bash ./scripts/export_determinism_meshes.sh build/bin/Release ./manifold_test.exe meshes-windows
|
||||
- name: Upload determinism meshes (Windows)
|
||||
if: matrix.parallelization == 'OFF' && matrix.shared == 'OFF'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshes-windows
|
||||
path: meshes-windows/
|
||||
build:
|
||||
name: ${{matrix.name}}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: GCC 11 (TBB:ON)
|
||||
gcc: 11
|
||||
parallelization: ON
|
||||
os: ubuntu-22.04
|
||||
std: 17
|
||||
- name: GCC 13 (TBB:OFF)
|
||||
gcc: 13
|
||||
parallelization: OFF
|
||||
os: ubuntu-24.04
|
||||
std: 17
|
||||
- name: GCC 13 (TBB:ON)
|
||||
gcc: 13
|
||||
parallelization: ON
|
||||
os: ubuntu-24.04
|
||||
std: 17
|
||||
- name: GCC 14 (TBB:OFF)
|
||||
gcc: 14
|
||||
parallelization: OFF
|
||||
os: ubuntu-24.04
|
||||
std: 17
|
||||
- name: GCC 14 (TBB:ON)
|
||||
gcc: 14
|
||||
parallelization: ON
|
||||
os: ubuntu-24.04
|
||||
std: 17
|
||||
# One strict lane at C++20, to catch what it deprecates or redefines.
|
||||
- name: GCC 14 (TBB:ON, C++20)
|
||||
gcc: 14
|
||||
parallelization: ON
|
||||
os: ubuntu-24.04
|
||||
std: 20
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CC: gcc-${{ matrix.gcc }}
|
||||
CXX: g++-${{ matrix.gcc }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- &setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: 'pip'
|
||||
- run: pip install -U trimesh pytest
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libgtest-dev libassimp-dev git libtbb-dev pkg-config cmake
|
||||
- *clone-nanobind
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_PYBIND=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_ASSERT=ON \
|
||||
-DFETCHCONTENT_SOURCE_DIR_NANOBIND=nanobind \
|
||||
-DASSIMP_ENABLE=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
. -B build
|
||||
cmake --build build
|
||||
- name: Test
|
||||
run: |
|
||||
./build/test/manifold_test
|
||||
# python tests
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
python3 bindings/python/examples/run_all.py -e
|
||||
python3 -m pytest
|
||||
- name: Export determinism meshes
|
||||
if: matrix.parallelization == 'OFF' && matrix.gcc == 14 && matrix.os == 'ubuntu-24.04'
|
||||
env:
|
||||
MANIFOLD_OBJ_HEX_FLOAT: 1
|
||||
run: |
|
||||
bash ./scripts/export_determinism_meshes.sh build/test ./manifold_test meshes-linux
|
||||
- name: Upload determinism meshes (Linux)
|
||||
if: matrix.parallelization == 'OFF' && matrix.gcc == 14 && matrix.os == 'ubuntu-24.04'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshes-linux
|
||||
path: meshes-linux/
|
||||
cd build/bin/Release
|
||||
./manifold_test.exe
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
sudo cmake --install build
|
||||
cd build
|
||||
cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
|
||||
build_no_iostream:
|
||||
name: NO_IOSTREAM (clang)
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: cmake clang libgtest-dev
|
||||
- name: Configure + build with MANIFOLD_NO_IOSTREAM=ON, MANIFOLD_TEST=ON
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DMANIFOLD_NO_IOSTREAM=ON \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_TEST=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
. -B build
|
||||
cmake --build build
|
||||
- name: Run manifold_test (iostream-using TESTs are gated out)
|
||||
run: ./build/test/manifold_test
|
||||
- name: Verify conflict guard fires (NO_IOSTREAM + DEBUG)
|
||||
run: |
|
||||
if cmake -DMANIFOLD_NO_IOSTREAM=ON -DMANIFOLD_DEBUG=ON \
|
||||
. -B /tmp/conflict-debug 2>&1; then
|
||||
echo "FAIL: configure should have failed for NO_IOSTREAM + DEBUG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_libcxx_hardened:
|
||||
name: Libc++ hardened (clang, EXTENSIVE, TBB:${{matrix.parallelization}})
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Match nixpkgs hardening profile (the trigger for #1735), plus
|
||||
# EXTENSIVE checks to catch latent preconditions beyond bounds.
|
||||
CXXFLAGS: "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -stdlib=libc++"
|
||||
LDFLAGS: "-stdlib=libc++"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
# tbb/gtest are built from the cloned sources below under the
|
||||
# lane's libc++ flags so they match the test binary's ABI; the distro
|
||||
# packages are libstdc++.
|
||||
packages: cmake clang libc++-dev libc++abi-dev
|
||||
- *clone-tbb
|
||||
- *clone-gtest
|
||||
- name: Configure + build with libc++ and EXTENSIVE hardening
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_TEST=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
-DMANIFOLD_USE_BUILTIN_TBB=ON \
|
||||
-DFETCHCONTENT_SOURCE_DIR_TBB=tbb \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=gtest \
|
||||
. -B build
|
||||
cmake --build build
|
||||
- name: Run manifold_test
|
||||
run: ./build/test/manifold_test
|
||||
|
||||
build_gcc_codecov:
|
||||
name: code coverage
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
# parallelization: [OFF, ON]
|
||||
parallelization: [OFF]
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libgtest-dev git libtbb-dev pkg-config lcov cmake
|
||||
- name: build
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_ASSERT=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
-DCODE_COVERAGE=ON \
|
||||
. -B build
|
||||
cmake --build build
|
||||
- name: Coverage Report
|
||||
run: |
|
||||
cd build
|
||||
lcov --capture --gcov-tool gcov-14 --ignore-errors mismatch --initial --directory . --output-file ./code_coverage_init.info
|
||||
test/manifold_test
|
||||
lcov --capture --gcov-tool gcov-14 --ignore-errors mismatch --directory . --output-file ./code_coverage_test.info
|
||||
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
|
||||
lcov --extract ./code_coverage_total.info "/*/manifold/src/*" --output-file ./code_coverage.info
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
disable_search: true
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallelization}}
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
verbose: true
|
||||
|
||||
build_wasm:
|
||||
name: WASM (TBB:${{matrix.parallelization}}, DEBUG:${{matrix.debug}})
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
debug: [OFF, ON]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
cache-dependency-path: bindings/**/package-lock.json
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: cmake
|
||||
version: 1
|
||||
- name: Setup EMSDK
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
version: 5.0.2
|
||||
- *clone-tbb
|
||||
- *clone-gtest
|
||||
- name: Build WASM ${{matrix.parallelization}}
|
||||
run: |
|
||||
emcmake cmake \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
-DMANIFOLD_USE_BUILTIN_TBB=ON \
|
||||
-DMANIFOLD_DEBUG=${{matrix.debug}} \
|
||||
-DMANIFOLD_ASSERT=${{matrix.debug}} \
|
||||
-DFETCHCONTENT_SOURCE_DIR_TBB=tbb \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=gtest \
|
||||
. -B build
|
||||
cd build
|
||||
emmake make
|
||||
- name: Test WASM
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
- name: Run unit tests
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
- name: Isolated npm smoke test
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
set -ev -o pipefail
|
||||
cd bindings/wasm
|
||||
wasmpath=$(pwd)
|
||||
npmtestpath=../npmtest
|
||||
# Create an npm package.
|
||||
npm ci && npm pack
|
||||
# Install it in an empty directory.
|
||||
mkdir -p ${npmtestpath} && cd ${npmtestpath}
|
||||
npm install ${wasmpath}/manifold-3d*.tgz
|
||||
cp ${wasmpath}/test/fixtures/npm/* .
|
||||
# Positive case smoke test.
|
||||
npx manifold-cad ./unitSphere.mjs ./unitSphere.glb && echo "Positive smoke test succeeded"
|
||||
# Negative case smoke test.
|
||||
npx manifold-cad ./unitSphereNoManifoldImport.mjs ./unitSphereNoManifoldImport.glb || echo "Negative smoke test failed as expected"
|
||||
- name: Build manifoldCAD editor
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
npm run ci:editor
|
||||
npm run build:editor
|
||||
- name: Build WASM examples
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm/examples
|
||||
npm run ci:examples
|
||||
npm run build:examples
|
||||
- name: Build WASM documentation and site
|
||||
if: matrix.parallelization == 'OFF'
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
npm run docs
|
||||
npm run public
|
||||
- name: Upload manifoldCAD editor, examples and documentation
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && matrix.parallelization == 'OFF' && matrix.debug == 'OFF'
|
||||
with:
|
||||
name: wasmpublic
|
||||
path: bindings/wasm/public/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
- name: Upload complete wasm build.
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'push' && matrix.parallelization == 'OFF' && matrix.debug == 'OFF'
|
||||
with:
|
||||
name: wasm
|
||||
path: bindings/wasm/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
|
||||
build_fuzzer:
|
||||
name: fuzzer
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, macos-latest]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install apt packages
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libgtest-dev clang cmake
|
||||
version: 1
|
||||
- name: Install brew packages
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
brew install googletest cmake
|
||||
- uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: fuzztest
|
||||
url: https://github.com/google/fuzztest.git
|
||||
rev: f1e26613f66997aa09d3026762e275de22b2daae
|
||||
- name: Build
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_FUZZ=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DFETCHCONTENT_SOURCE_DIR_FUZZTEST=fuzztest \
|
||||
. -B build
|
||||
ASAN_OPTIONS=detect_container_overflow=0 cmake --build build
|
||||
|
||||
sanitizer:
|
||||
uses: ./.github/workflows/sanitizer.yml
|
||||
with:
|
||||
subset: core
|
||||
retention-days: 1
|
||||
|
||||
build_mxe:
|
||||
name: MXE (TBB:${{matrix.parallelization == 'ON'}})
|
||||
timeout-minutes: 30
|
||||
@@ -461,129 +237,106 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: openscad/mxe-x86_64-gui:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
export MXEDIR=/mxe
|
||||
export MXE_TARGETS=x86_64-w64-mingw32.static.posix
|
||||
export MXEDIR=$HOME/openscad_deps/mxe
|
||||
export MXETARGETDIR=$MXEDIR/usr/$MXE_TARGETS
|
||||
export PATH=/mxe/usr/bin:$PATH
|
||||
export CMAKE=$MXE_TARGETS-cmake
|
||||
$CMAKE \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
-DMANIFOLD_CBIND=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
. -B build
|
||||
$CMAKE --build build
|
||||
$CMAKE -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_PYBIND=OFF -DMANIFOLD_CBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
|
||||
build_mac:
|
||||
name: MacOS (TBB:${{matrix.parallelization == 'ON'}}, shared lib:${{matrix.shared == 'ON'}}, ${{matrix.os}})
|
||||
name: MacOS (TBB:${{matrix.parallelization == 'ON'}}, ${{matrix.os}})
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallelization: [OFF, ON]
|
||||
shared: [OFF, ON]
|
||||
# macos-15-intel: intel
|
||||
# macos-13: intel
|
||||
# macos-latest: arm
|
||||
os: [macos-15-intel, macos-latest]
|
||||
os: [macos-13, macos-latest]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: 'pip'
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest assimp cmake
|
||||
brew install googletest assimp
|
||||
pip install trimesh pytest
|
||||
- name: Install TBB
|
||||
if: matrix.parallelization == 'ON'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: Build
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=${{matrix.shared}} \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_ASSERT=ON \
|
||||
-DMANIFOLD_PYBIND=ON \
|
||||
-DASSIMP_ENABLE=ON \
|
||||
-DMANIFOLD_PAR=${{matrix.parallelization}} \
|
||||
. -B build
|
||||
cmake --build build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallelization}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
build/test/manifold_test
|
||||
- name: Export determinism meshes
|
||||
if: matrix.parallelization == 'OFF' && matrix.shared == 'OFF' && matrix.os == 'macos-latest'
|
||||
env:
|
||||
MANIFOLD_OBJ_HEX_FLOAT: 1
|
||||
run: |
|
||||
bash ./scripts/export_determinism_meshes.sh build/test ./manifold_test meshes-mac
|
||||
- name: Upload determinism meshes (macOS)
|
||||
if: matrix.parallelization == 'OFF' && matrix.shared == 'OFF' && matrix.os == 'macos-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshes-mac
|
||||
path: meshes-mac/
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
sudo cmake --install build
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
- name: test pkg-config consumer
|
||||
if: matrix.shared == 'ON'
|
||||
run: |
|
||||
LDFLAGS=-Wl,-rpath,/usr/local/lib ./scripts/test-pkgconfig.sh
|
||||
|
||||
build_mac_builtin_tbb:
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: 'pip'
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest cmake
|
||||
brew install googletest
|
||||
pip install trimesh pytest
|
||||
- *clone-tbb
|
||||
- name: test cmake consumer subdir
|
||||
run: |
|
||||
export TBB=$(pwd)/tbb
|
||||
cd ..
|
||||
./manifold/scripts/test-cmake-subdir.sh
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: Build
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_USE_BUILTIN_TBB=ON \
|
||||
-DMANIFOLD_DEBUG=ON \
|
||||
-DMANIFOLD_ASSERT=ON \
|
||||
-DMANIFOLD_PYBIND=OFF \
|
||||
-DMANIFOLD_PAR=ON \
|
||||
-DFETCHCONTENT_SOURCE_DIR_TBB=tbb \
|
||||
. -B build
|
||||
cmake --build build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_ASSERT=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_PAR=ON .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
./build/test/manifold_test
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
sudo cmake --install build
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./scripts/test-cmake.sh
|
||||
LDFLAGS=-Wl,-rpath,/usr/local/lib ./scripts/test-pkgconfig.sh
|
||||
|
||||
build_mac_builtin_tbb_subdir:
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install googletest
|
||||
pip install trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: 4.0.2
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd ..
|
||||
./manifold/scripts/test-cmake-subdir.sh
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
@@ -591,67 +344,7 @@ jobs:
|
||||
variant: [manifold-none, manifold-tbb, manifold-js, manifold-js-tbb, manifold3d]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- run: nix build -L '.?submodules=1#${{matrix.variant}}'
|
||||
|
||||
determinism_check:
|
||||
name: Cross-platform determinism check
|
||||
needs: [build, build_windows, build_mac]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download Linux meshes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: meshes-linux
|
||||
path: meshes/linux
|
||||
- name: Download macOS meshes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: meshes-mac
|
||||
path: meshes/mac
|
||||
- name: Download Windows meshes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: meshes-windows
|
||||
path: meshes/windows
|
||||
- name: Compare meshes across platforms
|
||||
run: |
|
||||
bash ./scripts/compare_artifact.sh meshes
|
||||
|
||||
benchmark_guard:
|
||||
name: perfTest base-vs-head guard
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 45
|
||||
runs-on: macos-15
|
||||
env:
|
||||
PERF_REPEATS: 3
|
||||
REGRESSION_WARN_PERCENT: 20
|
||||
REGRESSION_WARN_ABS_MS: 10
|
||||
MEMORY_WARN_PERCENT: 20
|
||||
MEMORY_WARN_ABS_MB: 50
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install brew packages
|
||||
run: brew install googletest cmake
|
||||
- name: Benchmark base and head
|
||||
run: >
|
||||
python3 ./scripts/benchmark_pr_perf_guard.py
|
||||
--base-sha "${{ github.event.pull_request.base.sha }}"
|
||||
--head-sha "${{ github.event.pull_request.head.sha }}"
|
||||
--repeats "${PERF_REPEATS}"
|
||||
- name: Compare benchmark results
|
||||
run: >
|
||||
python3 ./scripts/compare_pr_perf_guard.py
|
||||
--base-dir ./bench/base
|
||||
--head-dir ./bench/head
|
||||
--warn-percent "${REGRESSION_WARN_PERCENT}"
|
||||
--warn-abs-ms "${REGRESSION_WARN_ABS_MS}"
|
||||
--memory-warn-percent "${MEMORY_WARN_PERCENT}"
|
||||
--memory-warn-abs-mb "${MEMORY_WARN_ABS_MB}"
|
||||
--markdown-out ./bench/summary.md
|
||||
--json-out ./bench/result.json
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Prepare release
|
||||
|
||||
# Bumps the version everywhere it appears and opens a PR with the result.
|
||||
# Run this before tagging, so the commit you tag already carries the right
|
||||
# version. Deriving it here rather than at publish time means what ships
|
||||
# always corresponds to a real commit in history.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version, without a leading v (e.g. 3.6.0)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
prepare_release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
# Nothing is built or tested here; that all happens on the resulting PR.
|
||||
# The lockfiles are edited in place rather than regenerated with npm
|
||||
# install, which keeps this fast and limits the commit to the version.
|
||||
- name: Set the version
|
||||
run: python3 scripts/set_version.py "${{ inputs.version }}"
|
||||
|
||||
# gh rather than a third-party action; release_file.yml already uses it.
|
||||
- name: Commit and open a pull request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
if git diff --quiet; then
|
||||
echo "Already at $VERSION; nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
git checkout -b "release/v$VERSION"
|
||||
git commit -am "Set version to $VERSION"
|
||||
# Force, so a re-run replaces an earlier attempt's branch rather than
|
||||
# being rejected. The branch is generated, so there is nothing to lose.
|
||||
git push --force origin "release/v$VERSION"
|
||||
# Heredoc so the body is not indented by the YAML block scalar,
|
||||
# which markdown would render as a code block.
|
||||
cat > /tmp/pr-body.md <<EOF
|
||||
Generated by the Prepare release workflow.
|
||||
|
||||
Sets the version to \`$VERSION\` across CMakeLists.txt, pyproject.toml,
|
||||
flake.nix, bindings/wasm/package.json and scripts/test-cmake.sh, and
|
||||
refreshes the package-lock files.
|
||||
|
||||
Once merged, tag the merge commit \`v$VERSION\` and publish the release.
|
||||
The tagged commit then carries the matching version, which is what
|
||||
publish_npm reads.
|
||||
EOF
|
||||
sed -i 's/^ //' /tmp/pr-body.md
|
||||
# --base is required. Without it gh targets the repository's default
|
||||
# branch, so a backport release dispatched against v3.5.x would open
|
||||
# a PR into master carrying the whole branch difference.
|
||||
gh pr create \
|
||||
--base "$GITHUB_REF_NAME" \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/pr-body.md
|
||||
@@ -4,75 +4,33 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Branch or tag to publish from (e.g. v3.5.2)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
publish_npm:
|
||||
# Matches the WASM build environment in manifold.yml.
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ (github.event_name == 'workflow_dispatch' && inputs.ref) || github.ref }}
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Update npm # Ensure npm 11.5.1 or later for trusted publishing
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
packages: cmake
|
||||
version: 1
|
||||
|
||||
- name: Setup EMSDK
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
version: 5.0.2
|
||||
|
||||
- uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: tbb
|
||||
url: https://github.com/oneapi-src/oneTBB.git
|
||||
tag: v2022.3.0
|
||||
|
||||
- uses: ./.github/actions/git-clone
|
||||
with:
|
||||
name: gtest
|
||||
url: https://github.com/google/googletest.git
|
||||
tag: v1.17.0
|
||||
|
||||
# Built here rather than downloaded from a CI artifact, so what ships is
|
||||
# always the checked-out commit.
|
||||
- name: Build WASM
|
||||
run: |
|
||||
emcmake cmake \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DMANIFOLD_STRICT=ON \
|
||||
-DMANIFOLD_PAR=OFF \
|
||||
-DMANIFOLD_USE_BUILTIN_TBB=ON \
|
||||
-DMANIFOLD_DEBUG=OFF \
|
||||
-DMANIFOLD_ASSERT=OFF \
|
||||
-DFETCHCONTENT_SOURCE_DIR_TBB=tbb \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=gtest \
|
||||
. -B build
|
||||
cd build
|
||||
emmake make
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
branch: master
|
||||
check_artifacts: true
|
||||
name: wasm
|
||||
path: ./bindings/wasm/
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
cd ./bindings/wasm/
|
||||
npm ci
|
||||
npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
- run: |
|
||||
ref=${{ github.ref_name }}
|
||||
echo "release_name=manifold-${ref#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.release_name }}
|
||||
submodules: recursive
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
name: Sanitizer
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
subset:
|
||||
description: Which gtest subset to run - core or extended.
|
||||
type: string
|
||||
required: true
|
||||
retention-days:
|
||||
description: >
|
||||
Retention for the shared sanitizer build artifact. It only exists to
|
||||
hand the build from the build job to the test job, so this is short.
|
||||
type: number
|
||||
default: 1
|
||||
test-timeout-sec:
|
||||
description: Per-run timeout applied inside sanitizer_test.sh.
|
||||
type: number
|
||||
default: 1200
|
||||
outputs:
|
||||
build-result:
|
||||
description: Result of the sanitizer build job.
|
||||
value: ${{ jobs.build_sanitizer.result }}
|
||||
test-result:
|
||||
description: Result of the sanitizer test job.
|
||||
value: ${{ jobs.test_sanitizer.result }}
|
||||
|
||||
jobs:
|
||||
build_sanitizer:
|
||||
name: Sanitizer build (ASan+UBSan)
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libgtest-dev cmake
|
||||
version: 1
|
||||
- name: Build (asan/ubsan)
|
||||
run: ./scripts/ci/sanitizer_build.sh
|
||||
- name: Upload sanitizer build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sanitizer-build
|
||||
path: build/
|
||||
retention-days: ${{ inputs.retention-days }}
|
||||
|
||||
test_sanitizer:
|
||||
name: Sanitizer ${{ inputs.subset }} subset (ASan+UBSan)
|
||||
needs: build_sanitizer
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
ASAN_OPTIONS: detect_container_overflow=0:strict_init_order=1
|
||||
UBSAN_OPTIONS: print_stacktrace=1
|
||||
SANITIZER_TEST_TIMEOUT_SEC: ${{ inputs.test-timeout-sec }}
|
||||
SANITIZER_SUBSET: ${{ inputs.subset }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download sanitizer build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sanitizer-build
|
||||
path: build
|
||||
- name: Test sanitizer subset
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/sanitizer_test.sh
|
||||
@@ -1,93 +0,0 @@
|
||||
name: TSAN
|
||||
|
||||
# Run ThreadSanitizer against the test suite using a TBB built with
|
||||
# TBB_SANITIZE=thread (so TBB's internal fences are swapped for acquire/release
|
||||
# ops TSAN can model — otherwise stock libtbb produces thousands of spurious
|
||||
# warnings). On a cold TBB build run takes ~22 min on CI; intent is to move
|
||||
# off per-PR onto a nightly/on-demand trigger once validated. For now it
|
||||
# runs per-PR to prove out the plumbing.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
tsan:
|
||||
name: TSAN (GCC 14, TBB_SANITIZE=thread)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
TBB_TAG: v2022.3.0
|
||||
TBB_PREFIX: ${{ github.workspace }}/.tbb-tsan
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libgtest-dev libassimp-dev pkg-config cmake
|
||||
|
||||
- name: Cache TBB (TSAN build)
|
||||
id: cache-tbb
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.TBB_PREFIX }}
|
||||
key: tbb-tsan-${{ env.TBB_TAG }}-gcc14-ubuntu-24.04-v1
|
||||
|
||||
- name: Build oneTBB with TBB_SANITIZE=thread
|
||||
if: steps.cache-tbb.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone --depth 1 --branch "$TBB_TAG" https://github.com/uxlfoundation/oneTBB.git tbb-src
|
||||
# -Wno-tsan: GCC 14's -Wtsan fires on TBB's own atomic_thread_fence
|
||||
# usage (which TBB_SANITIZE=thread mode replaces internally anyway).
|
||||
# oneTBB master adds this suppression in its own sanitize.cmake but
|
||||
# it didn't land until after v2022.3.0.
|
||||
cmake -S tbb-src -B tbb-build \
|
||||
-DTBB_TEST=OFF \
|
||||
-DTBB_SANITIZE=thread \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS=-Wno-tsan \
|
||||
-DCMAKE_INSTALL_PREFIX="$TBB_PREFIX"
|
||||
cmake --build tbb-build -j$(nproc)
|
||||
cmake --install tbb-build
|
||||
|
||||
- name: Configure manifold with TSAN
|
||||
run: |
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DMANIFOLD_CBIND=ON \
|
||||
-DMANIFOLD_TEST=ON \
|
||||
-DMANIFOLD_PAR=ON \
|
||||
-DCMAKE_PREFIX_PATH="$TBB_PREFIX" \
|
||||
-DCMAKE_CXX_FLAGS=-fsanitize=thread \
|
||||
-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=thread \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS=-fsanitize=thread \
|
||||
. -B build
|
||||
|
||||
- name: Build manifold
|
||||
run: cmake --build build -j$(nproc)
|
||||
|
||||
- name: Run tests under TSAN
|
||||
env:
|
||||
LD_LIBRARY_PATH: ${{ env.TBB_PREFIX }}/lib
|
||||
TSAN_OPTIONS: halt_on_error=1 report_thread_leaks=0
|
||||
run: |
|
||||
set -o pipefail
|
||||
./build/test/manifold_test 2>&1 | tee tsan.log
|
||||
|
||||
- name: Upload TSAN log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tsan-log
|
||||
path: tsan.log
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Weekly Benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: "0 2 * * 0"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: weekly-benchmark
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sanitizer:
|
||||
uses: ./.github/workflows/sanitizer.yml
|
||||
with:
|
||||
subset: extended
|
||||
retention-days: 1
|
||||
|
||||
weekly_benchmark:
|
||||
name: Weekly benchmarks
|
||||
needs: sanitizer
|
||||
if: ${{ !cancelled() }}
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-15
|
||||
env:
|
||||
WEEKLY_BENCHMARK_REPEATS: 5
|
||||
WEEKLY_BENCHMARK_THREADS: 1
|
||||
CMAKE_OSX_ARCHITECTURES: arm64
|
||||
DATA_BRANCH: benchmark-data
|
||||
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
|
||||
SANITIZER_SUMMARY_SUBSET: extended
|
||||
SANITIZER_SUMMARY_RUNNER: ubuntu-24.04
|
||||
SANITIZER_SUMMARY_OS: Linux
|
||||
SANITIZER_BUILD_RESULT: ${{ needs.sanitizer.outputs.build-result }}
|
||||
SANITIZER_TEST_RESULT: ${{ needs.sanitizer.outputs.test-result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install brew packages
|
||||
run: brew install googletest assimp tbb cmake
|
||||
- name: Run weekly benchmarks
|
||||
shell: bash
|
||||
run: python3 ./scripts/run_weekly_benchmarks.py . ./bench/weekly "${WEEKLY_BENCHMARK_REPEATS}"
|
||||
- name: Report weekly benchmarks
|
||||
run: python3 ./scripts/report_weekly_benchmarks.py . ./bench/weekly "${WEEKLY_BENCHMARK_REPEATS}"
|
||||
- name: Publish weekly benchmark data
|
||||
if: github.event_name == 'release' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
||||
shell: bash
|
||||
run: |
|
||||
# github-actions[bot]'s identity for committing with the built-in
|
||||
# token, see https://github.com/actions/checkout#push-a-commit-using-the-built-in-token
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git fetch origin "${DATA_BRANCH}"
|
||||
git worktree add benchmark-data "origin/${DATA_BRANCH}"
|
||||
|
||||
python3 ./scripts/publish_weekly_benchmark_results.py \
|
||||
./bench/weekly ./benchmark-data \
|
||||
--release-tag "${RELEASE_TAG}" \
|
||||
--sanitizer-subset "${SANITIZER_SUMMARY_SUBSET}" \
|
||||
--sanitizer-build-result "${SANITIZER_BUILD_RESULT}" \
|
||||
--sanitizer-test-result "${SANITIZER_TEST_RESULT}" \
|
||||
--sanitizer-runner "${SANITIZER_SUMMARY_RUNNER}" \
|
||||
--sanitizer-os "${SANITIZER_SUMMARY_OS}"
|
||||
|
||||
git -C benchmark-data add weekly
|
||||
git -C benchmark-data commit -m "add weekly benchmark result ${GITHUB_RUN_ID}"
|
||||
git -C benchmark-data push origin "HEAD:${DATA_BRANCH}"
|
||||
@@ -133,8 +133,5 @@ CMakeSettings.json
|
||||
[._]sw[a-p]
|
||||
|
||||
# Benchmark stl files
|
||||
extras/ember_tests/testfiles/raw_meshes/*
|
||||
extras/ember_tests/testfiles/raw_meshes
|
||||
|
||||
#Eclipse project files
|
||||
/.project
|
||||
/.cproject
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_catch_exceptions=0",
|
||||
"--gtest_filter=Smooth.Csaszar"
|
||||
"--gtest_filter=Manifold.Simplify"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Minkowski",
|
||||
"Tris",
|
||||
"Verts",
|
||||
"Voxel",
|
||||
|
||||
@@ -15,11 +15,9 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(manifold LANGUAGES CXX)
|
||||
|
||||
# C++17 by default, guarded so -DCMAKE_CXX_STANDARD and parent projects work.
|
||||
# Use C++17
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
@@ -31,7 +29,7 @@ include(CMakeDependentOption)
|
||||
|
||||
# Define Manifold version
|
||||
set(MANIFOLD_VERSION_MAJOR 3)
|
||||
set(MANIFOLD_VERSION_MINOR 5)
|
||||
set(MANIFOLD_VERSION_MINOR 2)
|
||||
set(MANIFOLD_VERSION_PATCH 1)
|
||||
set(
|
||||
MANIFOLD_VERSION
|
||||
@@ -39,20 +37,16 @@ set(
|
||||
)
|
||||
|
||||
# Primary user facing options
|
||||
option(MANIFOLD_CROSS_SECTION "Build CrossSection for 2D support" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_ASSERT "Enable assertions - requires MANIFOLD_DEBUG" OFF)
|
||||
option(MANIFOLD_TIMING "Enable Boolean3 phase timing without debug overhead" OFF)
|
||||
option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" OFF)
|
||||
option(
|
||||
MANIFOLD_NO_IOSTREAM
|
||||
"Strip iostream- and filesystem-using bits from the public API and tests; useful for freestanding/embedded builds. When ON, defines both MANIFOLD_NO_IOSTREAM and MANIFOLD_NO_FILESYSTEM."
|
||||
OFF
|
||||
)
|
||||
option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" ON)
|
||||
option(
|
||||
MANIFOLD_DOWNLOADS
|
||||
"Allow Manifold build to download missing dependencies"
|
||||
ON
|
||||
)
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_PAR "Enable Parallel backend" OFF)
|
||||
option(
|
||||
MANIFOLD_OPTIMIZED
|
||||
@@ -61,7 +55,14 @@ option(
|
||||
)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
option(MANIFOLD_PYBIND "Build python bindings" OFF)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" ON)
|
||||
# MANIFOLD_CBIND is only available when MANIFOLD_CROSS_SECTION is enabled
|
||||
cmake_dependent_option(
|
||||
MANIFOLD_CBIND
|
||||
"Build C (FFI) bindings"
|
||||
ON
|
||||
"MANIFOLD_CROSS_SECTION"
|
||||
OFF
|
||||
)
|
||||
# MANIFOLD_JSBIND is only available when building with Emscripten
|
||||
cmake_dependent_option(
|
||||
MANIFOLD_JSBIND
|
||||
@@ -79,6 +80,7 @@ cmake_dependent_option(
|
||||
# When the system package is unavailable, the option will be automatically set
|
||||
# to true.
|
||||
option(MANIFOLD_USE_BUILTIN_TBB "Use builtin tbb" OFF)
|
||||
option(MANIFOLD_USE_BUILTIN_CLIPPER2 "Use builtin clipper2" OFF)
|
||||
option(MANIFOLD_USE_BUILTIN_NANOBIND "Use builtin nanobind" OFF)
|
||||
|
||||
# default to Release build
|
||||
@@ -92,24 +94,9 @@ set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
|
||||
option(MANIFOLD_FUZZ "Enable fuzzing tests" OFF)
|
||||
option(ASSIMP_ENABLE "Enable ASSIMP for utilities in extras" OFF)
|
||||
mark_as_advanced(TRACY_ENABLE)
|
||||
mark_as_advanced(TRACY_MEMORY_USAGE)
|
||||
mark_as_advanced(MANIFOLD_FUZZ)
|
||||
mark_as_advanced(ASSIMP_ENABLE)
|
||||
|
||||
# MANIFOLD_NO_IOSTREAM strips iostream- and filesystem-using bits
|
||||
# from the public API and from the test files that use them.
|
||||
# Incompatible with options that emit diagnostic output via iostream:
|
||||
# MANIFOLD_DEBUG and MANIFOLD_TIMING (both use std::cout).
|
||||
# MANIFOLD_TEST=ON is supported — iostream-using TEST(...) blocks
|
||||
# in manifold_test/polygon_test/manifoldc_test are gated out under
|
||||
# the macro; the test executable still builds and runs the rest.
|
||||
if(MANIFOLD_NO_IOSTREAM AND (MANIFOLD_DEBUG OR MANIFOLD_TIMING))
|
||||
message(FATAL_ERROR
|
||||
"MANIFOLD_NO_IOSTREAM is incompatible with MANIFOLD_DEBUG / MANIFOLD_TIMING "
|
||||
"(those options use std::cout for diagnostic output).")
|
||||
endif()
|
||||
|
||||
# Always build position independent code for relocatability
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
@@ -162,10 +149,7 @@ if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
list(APPEND MANIFOLD_FLAGS /DNOMINMAX /bigobj /fp:precise)
|
||||
if(MANIFOLD_STRICT)
|
||||
list(APPEND MANIFOLD_FLAGS /WX)
|
||||
endif()
|
||||
list(APPEND MANIFOLD_FLAGS /DNOMINMAX /bigobj)
|
||||
else()
|
||||
list(
|
||||
APPEND
|
||||
@@ -174,8 +158,6 @@ else()
|
||||
-Wno-unknown-warning-option
|
||||
-Wno-unused
|
||||
-Wno-shorten-64-to-32
|
||||
-Wno-sign-compare
|
||||
-Wno-alloc-size-larger-than
|
||||
)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
@@ -199,16 +181,19 @@ else()
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||
list(APPEND MANIFOLD_FLAGS -fno-omit-frame-pointer)
|
||||
endif()
|
||||
# disable fp math optimizations, e.g. FMA for supported architectures, as
|
||||
# this changes floating-point results
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-ffp-contract=off HAS_FP_CONTRACT)
|
||||
check_cxx_compiler_flag(-fexcess-precision=standard HAS_FP_PRECISION)
|
||||
if(HAS_FP_CONTRACT)
|
||||
list(APPEND MANIFOLD_FLAGS -ffp-contract=off)
|
||||
endif()
|
||||
if(HAS_FP_PRECISION)
|
||||
list(APPEND MANIFOLD_FLAGS -fexcess-precision=standard)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
# disable fp math optimizations, e.g. FMA for supported architectures, as
|
||||
# this changes floating-point results
|
||||
# clang defaults to -ffp-contract=off so we don't have to set that
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-ffp-contract=off HAS_FP_CONTRACT)
|
||||
check_cxx_compiler_flag(-fexcess-precision=standard HAS_FP_PRECISION)
|
||||
if(HAS_FP_CONTRACT)
|
||||
list(APPEND MANIFOLD_FLAGS -ffp-contract=off)
|
||||
endif()
|
||||
if(HAS_FP_PRECISION)
|
||||
list(APPEND MANIFOLD_FLAGS -fexcess-precision=standard)
|
||||
endif()
|
||||
endif()
|
||||
if(CODE_COVERAGE)
|
||||
list(
|
||||
@@ -264,6 +249,11 @@ set_source_files_properties(
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
# If it's an EMSCRIPTEN build, we're done
|
||||
if(EMSCRIPTEN)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# CMake exports
|
||||
configure_file(
|
||||
cmake/manifoldConfig.cmake.in
|
||||
@@ -297,14 +287,13 @@ install(
|
||||
set(
|
||||
MANIFOLD_PUBLIC_HDRS
|
||||
common.h
|
||||
math.h
|
||||
mesh.h
|
||||
linalg.h
|
||||
manifold.h
|
||||
optional_assert.h
|
||||
polygon.h
|
||||
vec_view.h
|
||||
cross_section.h
|
||||
$<$<BOOL:${MANIFOLD_CROSS_SECTION}>:cross_section.h>
|
||||
$<$<BOOL:${MANIFOLD_EXPORT}>:meshIO.h>
|
||||
)
|
||||
list(TRANSFORM MANIFOLD_PUBLIC_HDRS PREPEND include/manifold/)
|
||||
list(
|
||||
@@ -322,6 +311,9 @@ install(
|
||||
if(MANIFOLD_PAR)
|
||||
set(TEMPLATE_OPTIONAL_TBB "tbb")
|
||||
endif()
|
||||
if(MANIFOLD_CROSS_SECTION)
|
||||
set(TEMPLATE_OPTIONAL_CLIPPER "Clipper2")
|
||||
endif()
|
||||
configure_file(
|
||||
cmake/manifold.pc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.pc
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement (CLA). You (or your employer) retain the copyright to your
|
||||
contribution; this simply gives us permission to use and redistribute your
|
||||
contributions as part of the project. Head over to
|
||||
<https://cla.developers.google.com/> to see your current agreements on file or
|
||||
to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code Reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
@@ -13,4 +26,4 @@ information on using pull requests.
|
||||
## Community Guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
@@ -966,17 +966,18 @@ WARN_LOGFILE =
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = include/manifold/manifold.h \
|
||||
include/manifold/mesh.h \
|
||||
include/manifold/cross_section.h \
|
||||
include/manifold/polygon.h \
|
||||
include/manifold/common.h \
|
||||
include/manifold/meshIO.h \
|
||||
include/manifold/linalg.h \
|
||||
include/manifold/vec_view.h \
|
||||
include/manifold/optional_assert.h \
|
||||
src/manifold.cpp \
|
||||
src/constructors.cpp \
|
||||
src/polygon.cpp \
|
||||
src/cross_section.cpp \
|
||||
src/cross_section/cross_section.cpp \
|
||||
src/meshIO/meshIO.cpp \
|
||||
src/sdf.cpp \
|
||||
README.md \
|
||||
samples/include/samples.h \
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
[**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**ManifoldCAD User Guide**](https://manifoldcad.org/docs/jsuser/) | [**JS/TS/WASM API**](https://manifoldcad.org/docs/jsapi/) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://manifoldcad.org/model-viewer.html)
|
||||
[**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**TS Documentation**](https://manifoldcad.org/jsdocs) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://manifoldcad.org/model-viewer.html)
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Manifold also supports arbitrary vertex properties and enables mapping of materials for rendering use-cases. Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
|
||||
|
||||
@@ -22,15 +22,11 @@ Here is an incomplete list of our users, whose integrations may be anywhere from
|
||||
| [Babylon.js](https://doc.babylonjs.com/features/featuresDeepDive/mesh/mergeMeshes#merging-meshes-with-constructive-solid-geometry) | [trimesh](https://trimesh.org/) | [Gypsum](https://github.com/playkostudios/gypsum) |
|
||||
| [Valence 3D](https://apps.apple.com/us/app/valence-3d/id6450967410?mt=8&platform=ipad) | [bitbybit.dev](https://bitbybit.dev) | [PythonOpenSCAD](https://github.com/owebeeone/pythonopenscad) |
|
||||
| [Conversation](https://james-bern.github.io/conversation.html) | [AnchorSCAD](https://github.com/owebeeone/anchorscad-core) | [Dactyl Web Configurator](https://github.com/rianadon/dactyl-configurator) |
|
||||
| [Arcol](https://arcol.io) | [Bento3D](https://bento3d.design) | [SKÅPA](https://skapa.build) |
|
||||
| [Cadova](https://github.com/tomasf/Cadova) | [BREP.io](https://github.com/mmiscool/BREP) | [Otterplans](https://otterplans.com) |
|
||||
| [Bracket Engineer](https://bracket.engineer) | [Nodillo](https://nodillo3d.com) | [CaDoodle CAD](https://cadoodlecad.com/) |
|
||||
| [Bridge Designer](https://www.asce.org/career-growth/pre-college-outreach/bridge-designer) |[AdaShape](https://adashape.com)| [PyVista](https://github.com/pyvista/pyvista-manifold) |
|
||||
| [NASSCAD](https://www.nasscad.com) — Free browser-based parametric CAD in a single HTML file. Boolean CSG, 18 watertight primitives, offline-ready. ([GitHub](https://github.com/Nx-Nass/Nasscad_4.2.7)) | [SolidSKeleton](https://github.com/FerroIT/SolidSKeleton) | [Piecad (Python-based)](https://github.com/briansturgill/Piecad) |
|
||||
| [Arcol](https://arcol.io) | [Bento3D](https://bento3d.design) | [Cadova](https://github.com/tomasf/Cadova) |
|
||||
|
||||
### Bindings & Packages
|
||||
|
||||
Manifold has bindings to many other languages, some maintained in this repository, and others elsewhere. It can also be built in C++ via [vcpkg](https://github.com/microsoft/vcpkg.git). There is also a native Rust port, [manifold-rust](https://github.com/larsbrubaker/manifold-rust), which passes Manifold's full test suite with identical results and has no C++ dependency — from a user's perspective it works like a binding.
|
||||
Manifold has bindings to many other languages, some maintained in this repository, and others elsewhere. It can also be built in C++ via [vcpkg](https://github.com/microsoft/vcpkg.git).
|
||||
|
||||
| Language | Packager | Name | Maintenance |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -38,27 +34,22 @@ Manifold has bindings to many other languages, some maintained in this repositor
|
||||
| C++ | vcpkg | [manifold](https://github.com/microsoft/vcpkg/tree/master/ports/manifold) | external |
|
||||
| TS/JS | npm | [manifold-3d](https://www.npmjs.com/package/manifold-3d) | internal |
|
||||
| Python | PyPI | [manifold3d](https://pypi.org/project/manifold3d/) | internal |
|
||||
| Java | Maven | [manifold3d](https://github.com/CommonWealthRobotics/manifold3d-java/blob/development/bindings/java/README.md) | external |
|
||||
| Java | N/A | [manifold](https://github.com/SovereignShop/manifold) | external |
|
||||
| Clojure | N/A | [clj-manifold3d](https://github.com/SovereignShop/clj-manifold3d) | external |
|
||||
| C# | NuGet | [ManifoldNET](https://www.nuget.org/packages/ManifoldNET) | external |
|
||||
| Julia | Packages | [ManifoldBindings.jl](https://juliapackages.com/p/manifoldbindings) | external |
|
||||
| OCaml | N/A | [OManifold](https://ocadml.github.io/OManifold/OManifold/index.html) | external |
|
||||
| Rust | crates.io | [manifold-csg](https://github.com/zmerlynn/manifold-csg) | external |
|
||||
| Rust (native port) | crates.io | [manifold-rust](https://crates.io/crates/manifold-rust) | external |
|
||||
| Swift | SPM | [Manifold-Swift](https://github.com/tomasf/manifold-swift) | external |
|
||||
|
||||
## Frontend Sandboxes
|
||||
|
||||
[ManifoldCAD.org]: https://manifoldcad.org
|
||||
[Python Colab Example]: https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing
|
||||
### [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
### [ManifoldCAD.org]
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like [ManifoldCAD][ManifoldCAD.org] - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
|
||||
### [Python Colab Example](https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing)
|
||||
|
||||
### [Python Colab Example]
|
||||
|
||||
If you prefer Python to JS/TS, make your own copy of [the notebook][Python Colab Example]. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
|
||||
If you prefer Python to JS/TS, make your own copy of the example notebook above. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
|
||||
|
||||

|
||||
|
||||
@@ -74,7 +65,7 @@ Also included are a novel and powerful suite of refining functions for smooth me
|
||||
|
||||
To aid in speed, this library makes extensive use of parallelization through TBB, if enabled. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
|
||||
|
||||
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bear a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
|
||||
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -82,12 +73,11 @@ Manifold no longer has **any** required dependencies! However, we do have severa
|
||||
| Name | CMake Flag | Provides |
|
||||
| --- | --- | --- |
|
||||
| [`TBB`](https://github.com/oneapi-src/oneTBB/) |`MANIFOLD_PAR=ON` | Parallel acceleration |
|
||||
| [`Clipper2`](https://github.com/AngusJohnson/Clipper2) | `MANIFOLD_CROSS_SECTION=ON` | 2D: [`CrossSection`](https://manifoldcad.org/docs/html/classmanifold_1_1_cross_section.html) |
|
||||
| [`Assimp`](https://github.com/assimp/assimp) | `MANIFOLD_EXPORT=ON` | Basic Mesh I/O |
|
||||
| [`Nanobind`](https://github.com/wjakob/nanobind) | `MANIFOLD_PYBIND=ON` | Python bindings |
|
||||
| [`Emscripten`](https://github.com/emscripten-core/emscripten) | `MANIFOLD_JSBIND=ON` | JS bindings via WASM |
|
||||
| [`GTest`](https://github.com/google/googletest/) | `MANIFOLD_TEST=ON` | Testing framework |
|
||||
| [`Assimp`](https://github.com/assimp/assimp) | `ASSIMP_ENABLE=ON` | Utilities in `extras` |
|
||||
| [`Tracy`](https://github.com/wolfpld/tracy) | `TRACY_ENABLE=ON` | Performance analysis |
|
||||
|
||||
|
||||
### 3D Formats
|
||||
|
||||
@@ -95,8 +85,7 @@ Please avoid saving to STL files! They are lossy and inefficient - when saving a
|
||||
|
||||
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
|
||||
|
||||
Manifold provides high precision OBJ file IO, but it is limited in functionality and is primarily to aid in testing. If you are using our npm module, we have a much more capable [gltf-io.ts](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/gltf-io.ts) you can use instead. For other languages we strongly recommend using existing packages that focus on 3D file I/O, e.g. [trimesh](https://trimesh.org/) for Python, particularly when using vertex properties or materials.
|
||||
Example for integrating with [Assimp](https://github.com/assimp/assimp) is in `extras/meshIO.cpp`, which is used by files such as `extras/convert_file.cpp`.
|
||||
Manifold provides an optional [`MeshIO`](https://manifoldcad.org/docs/html/group___mesh_i_o.html) component based on [Assimp](https://assimp.org/), but it is limited in functionality and is primarily to aid in testing. If you are using our npm module, we have a much more capable [gltf-io.ts](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/gltf-io.ts) you can use instead. For other languages we strongly recommend using existing packages that focus on 3D file I/O, e.g. [trimesh](https://trimesh.org/) for Python, particularly when using vertex properties or materials.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -117,44 +106,31 @@ CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
|
||||
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
|
||||
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding, requires `nanobind`.
|
||||
- `MANIFOLD_PAR=[<OFF>, ON]`: Enables multi-thread parallelization, requires `tbb`.
|
||||
- `MANIFOLD_CROSS_SECTION=[OFF, <ON>]`: Build CrossSection for 2D support (needed by language bindings), requires `Clipper2`.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables `MeshIO` and GLB export of 3D models from the tests, requires `assimp`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables exceptions, timing, verbosity, OBJ test dumps. Has almost no effect on its own, but enables further runtime parameters to dump various outputs.
|
||||
- `MANIFOLD_ASSERT=[<OFF>, ON]`: Enables internal assertions. This incurs around 20% runtime overhead. Requires MANIFOLD_DEBUG to work.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unit tests, requires `GTest`.
|
||||
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
|
||||
See profiling section below.
|
||||
- `ASSIMP_ENABLE=[<OFF>, ON]`: Enable integration with assimp, which is needed for some of the utilities in `extras`.
|
||||
- `MANIFOLD_STRICT=[<OFF>, ON]`: Treat compile warnings as fatal build errors.
|
||||
- `MANIFOLD_NO_IOSTREAM=[<OFF>, ON]`: Strip iostream- and filesystem-using
|
||||
bits from the public API and tests; useful for freestanding/embedded
|
||||
builds (e.g., `wasm32-unknown-unknown`). Defines both
|
||||
`MANIFOLD_NO_IOSTREAM` and `MANIFOLD_NO_FILESYSTEM` as PUBLIC compile
|
||||
definitions. The test suite still builds + runs — iostream-using
|
||||
TESTs in `manifold_test`/`polygon_test`/`manifoldc_test` are gated
|
||||
out under the macro. Incompatible with `MANIFOLD_DEBUG` /
|
||||
`MANIFOLD_TIMING` (which use `std::cout` for diagnostic output).
|
||||
|
||||
Dependency version override:
|
||||
- `MANIFOLD_USE_BUILTIN_TBB=[<OFF>, ON]`: Use builtin version of tbb.
|
||||
- `MANIFOLD_USE_BUILTIN_CLIPPER2=[<OFF>, ON]`: Use builtin version of clipper2.
|
||||
- `MANIFOLD_USE_BUILTIN_NANOBIND=[<OFF>, ON]`: Use builtin version of nanobind.
|
||||
|
||||
> Note: These two options can force the build to avoid using the system
|
||||
> Note: These three options can force the build to avoid using the system
|
||||
> version of the dependency. This will either use the provided source directory
|
||||
> via `FETCHCONTENT_SOURCE_DIR_*` (see below), or fetch the source from GitHub.
|
||||
> Note that the dependency will be built as static dependency to avoid dynamic
|
||||
> library conflict. When the system package is unavailable, the option will be
|
||||
> automatically set to true (except for tbb).
|
||||
|
||||
> WARNING: These packages are statically linked to the library, which may be
|
||||
> unexpected for other consumers of the library. In particular, for tbb, this
|
||||
> create two versions of tbb when another library also bring their own tbb,
|
||||
> which may cause performance issues or crash the system.
|
||||
> It is not recommended to install manifold compiled with builtin tbb, and this
|
||||
> option requires explicit opt-in now.
|
||||
> automatically set to true.
|
||||
|
||||
Offline building (with missing dependencies/dependency version override):
|
||||
- `MANIFOLD_DOWNLOADS=[OFF, <ON>]`: Automatically download missing dependencies.
|
||||
Need to set `FETCHCONTENT_SOURCE_DIR_*` if the dependency `*` is missing.
|
||||
- `FETCHCONTENT_SOURCE_DIR_TBB`: path to tbb source (if `MANIFOLD_PAR` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_CLIPPER2`: path to tbb source (if `MANIFOLD_CROSS_SECTION` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_NANOBIND`: path to nanobind source (if `MANIFOLD_PYBIND` is enabled).
|
||||
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source (if `MANIFOLD_TEST` is enabled).
|
||||
|
||||
@@ -164,18 +140,6 @@ Offline building (with missing dependencies/dependency version override):
|
||||
|
||||
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
|
||||
|
||||
#### Sanitizer CI Reproduction
|
||||
|
||||
To reproduce the sanitizer CI test locally from repo root:
|
||||
|
||||
```bash
|
||||
bash ./scripts/ci/sanitizer_build.sh
|
||||
source ./scripts/sanitizer_cases.sh
|
||||
ASAN_OPTIONS=detect_container_overflow=0:strict_init_order=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
./build/test/manifold_test --gtest_filter="${SANITIZER_GTEST_FILTER_CORE}"
|
||||
```
|
||||
|
||||
### WASM
|
||||
|
||||
> Note: While we support compiling with `MANIFOLD_PAR=ON` in recent emscripten
|
||||
|
||||
@@ -3,60 +3,12 @@
|
||||
1. Verify our three.js [example](https://manifoldcad.org/three) is functional.
|
||||
1. Verify our model-viewer [example](https://manifoldcad.org/model-viewer) is functional - select Union and Intersection.
|
||||
1. Verify [make-manifold](https://manifoldcad.org/make-manifold) is functional. Try dropping [DragonAttenuation.glb](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/DragonAttenuation/glTF-Binary/DragonAttenuation.glb) in and verify you can select "View Manifold GLB" and that the dragon is still present while the backdrop is removed. Download the GLB.
|
||||
1. Run the Prepare release workflow from the Actions tab with the new version number. It sets the version in CMakeLists.txt, pyproject.toml, flake.nix, package.json and test-cmake.sh, refreshes the package-lock files, and opens a PR.
|
||||
1. Verify tests pass on that PR, manually trigger build_wheels CI, merge.
|
||||
1. Make a new branch called the version, e.g. v2.3.0.
|
||||
1. Use VSCode to search and replace the old version with the new - so far in test-cmake.sh, flake.nix, pyproject.toml, and package.json.
|
||||
1. Also update CMakeLists.txt version by searching for "set(MANIFOLD_VERSION_".
|
||||
1. Commit, push, open a PR, verify tests pass, manually trigger PyPI CI, merge.
|
||||
1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish.
|
||||
1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully.
|
||||
1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB.
|
||||
1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB.
|
||||
1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab. See [Deploying manually](#deploying-manually) for the site deploy in particular.
|
||||
|
||||
## Where the site is published
|
||||
|
||||
`manifoldcad.org` is served from the `gh-pages` branch, which holds several
|
||||
builds side by side:
|
||||
|
||||
| Path | Updated | Contents |
|
||||
| --- | --- | --- |
|
||||
| `/` (root) | each published release | the current release, served at manifoldcad.org |
|
||||
| `alpha/` | every master commit that passes CI | unreleased build, for testing |
|
||||
| `vX.Y.Z/` | once, when that release is published | a frozen snapshot of that version |
|
||||
|
||||
Each is a complete copy of the site: the ManifoldCAD editor, the examples,
|
||||
the benchmark dashboard, and both the C++ and TypeScript docs. A release
|
||||
publishes twice, once to its own `vX.Y.Z/` directory and once to the root,
|
||||
so manifoldcad.org always serves the current release directly.
|
||||
|
||||
Subdirectory deploys erase their target before writing, so each holds exactly
|
||||
one build. The root deploy cannot, because clearing it would take the
|
||||
subdirectories with it, so there it overwrites in place instead.
|
||||
|
||||
## Deploying manually
|
||||
|
||||
The Deploy documentation workflow can be run by hand from the Actions tab for
|
||||
a quick fix or a docs-only update, without cutting a release. It takes two
|
||||
inputs:
|
||||
|
||||
- **ref** - the branch or tag to build from, e.g. `v3.4.0`
|
||||
- **destination_dir** - the directory on `gh-pages` to publish into, e.g. `v3.4.0`
|
||||
|
||||
The target directory is erased and rebuilt, so the result is exactly what the
|
||||
chosen ref produces. Nothing outside that directory is affected.
|
||||
|
||||
Keep `ref` and `destination_dir` describing the same version. Publishing
|
||||
master into `v3.4.0/` would leave that directory claiming to be a release it
|
||||
is not.
|
||||
|
||||
Typical uses:
|
||||
|
||||
- **Fix a published version.** Branch from its tag, commit the fix, then
|
||||
deploy that branch into the version's own directory. Useful for correcting
|
||||
documentation without a patch release.
|
||||
- **Backfill an old release.** Deploy its tag into a matching directory so the
|
||||
version has a browsable copy of the site.
|
||||
- **Preview a branch.** Deploy it somewhere disposable rather than over a
|
||||
directory that matters.
|
||||
|
||||
Builds from before the versioned layout existed assume they are served from
|
||||
the site root and will load the wrong assets from a subdirectory. Deploying
|
||||
such a tag needs the relative-path fix applied on top first.
|
||||
1. If there's a problem with release deployment, the release workflows can be triggered separately, manually for any branch, under the Actions tab.
|
||||
@@ -12,6 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT MANIFOLD_CROSS_SECTION)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
@@ -28,6 +28,11 @@ add_library(
|
||||
)
|
||||
exportlib(manifoldc)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_sources(manifoldc PRIVATE meshIOc.cpp)
|
||||
target_compile_options(manifoldc PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(manifoldc PRIVATE manifold)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@@ -55,7 +55,7 @@ int manifold_box_contains_box(ManifoldBox* a, ManifoldBox* b) {
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox* b, double x, double y, double z) {
|
||||
auto& box = *from_c(b);
|
||||
auto box = *from_c(b);
|
||||
auto p = vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
@@ -37,14 +37,6 @@ ManifoldCrossSectionVec* to_c(CrossSectionVec* csv) {
|
||||
return reinterpret_cast<ManifoldCrossSectionVec*>(csv);
|
||||
}
|
||||
|
||||
ManifoldRayHitVec* to_c(RayHitVec* v) {
|
||||
return reinterpret_cast<ManifoldRayHitVec*>(v);
|
||||
}
|
||||
|
||||
ManifoldExecutionContext* to_c(ExecutionContext* ctx) {
|
||||
return reinterpret_cast<ManifoldExecutionContext*>(ctx);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon* to_c(manifold::SimplePolygon* m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon*>(m);
|
||||
}
|
||||
@@ -117,12 +109,6 @@ ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
case Manifold::Error::ResultTooLarge:
|
||||
e = MANIFOLD_RESULT_TOO_LARGE;
|
||||
break;
|
||||
case Manifold::Error::InvalidTangents:
|
||||
e = MANIFOLD_INVALID_TANGENTS;
|
||||
break;
|
||||
case Manifold::Error::Cancelled:
|
||||
e = MANIFOLD_CANCELLED;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
@@ -145,15 +131,15 @@ ManifoldTriangulation* to_c(std::vector<ivec3>* m) {
|
||||
return reinterpret_cast<ManifoldTriangulation*>(m);
|
||||
}
|
||||
|
||||
manifold::Manifold* from_c(ManifoldManifold* m) {
|
||||
return reinterpret_cast<manifold::Manifold*>(m);
|
||||
const manifold::Manifold* from_c(ManifoldManifold* m) {
|
||||
return reinterpret_cast<manifold::Manifold const*>(m);
|
||||
}
|
||||
|
||||
ManifoldVec* from_c(ManifoldManifoldVec* ms) {
|
||||
return reinterpret_cast<ManifoldVec*>(ms);
|
||||
}
|
||||
|
||||
manifold::CrossSection* from_c(ManifoldCrossSection* cs) {
|
||||
const manifold::CrossSection* from_c(ManifoldCrossSection* cs) {
|
||||
return reinterpret_cast<manifold::CrossSection*>(cs);
|
||||
}
|
||||
|
||||
@@ -161,28 +147,20 @@ CrossSectionVec* from_c(ManifoldCrossSectionVec* csv) {
|
||||
return reinterpret_cast<CrossSectionVec*>(csv);
|
||||
}
|
||||
|
||||
RayHitVec* from_c(ManifoldRayHitVec* v) {
|
||||
return reinterpret_cast<RayHitVec*>(v);
|
||||
const manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const*>(m);
|
||||
}
|
||||
|
||||
ExecutionContext* from_c(ManifoldExecutionContext* ctx) {
|
||||
return reinterpret_cast<ExecutionContext*>(ctx);
|
||||
const manifold::Polygons* from_c(ManifoldPolygons* m) {
|
||||
return reinterpret_cast<manifold::Polygons const*>(m);
|
||||
}
|
||||
|
||||
manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon*>(m);
|
||||
const manifold::MeshGL* from_c(ManifoldMeshGL* m) {
|
||||
return reinterpret_cast<manifold::MeshGL const*>(m);
|
||||
}
|
||||
|
||||
manifold::Polygons* from_c(ManifoldPolygons* m) {
|
||||
return reinterpret_cast<manifold::Polygons*>(m);
|
||||
}
|
||||
|
||||
manifold::MeshGL* from_c(ManifoldMeshGL* m) {
|
||||
return reinterpret_cast<manifold::MeshGL*>(m);
|
||||
}
|
||||
|
||||
manifold::MeshGL64* from_c(ManifoldMeshGL64* m) {
|
||||
return reinterpret_cast<manifold::MeshGL64*>(m);
|
||||
const manifold::MeshGL64* from_c(ManifoldMeshGL64* m) {
|
||||
return reinterpret_cast<manifold::MeshGL64 const*>(m);
|
||||
}
|
||||
|
||||
OpType from_c(ManifoldOpType optype) {
|
||||
@@ -200,6 +178,24 @@ OpType from_c(ManifoldOpType optype) {
|
||||
return op;
|
||||
}
|
||||
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
|
||||
auto fr = CrossSection::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case MANIFOLD_FILL_RULE_EVEN_ODD:
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NON_ZERO:
|
||||
fr = CrossSection::FillRule::NonZero;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_POSITIVE:
|
||||
fr = CrossSection::FillRule::Positive;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NEGATIVE:
|
||||
fr = CrossSection::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
auto jt = CrossSection::JoinType::Square;
|
||||
switch (join_type) {
|
||||
@@ -218,12 +214,12 @@ CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
return jt;
|
||||
}
|
||||
|
||||
manifold::Box* from_c(ManifoldBox* m) {
|
||||
return reinterpret_cast<manifold::Box*>(m);
|
||||
const manifold::Box* from_c(ManifoldBox* m) {
|
||||
return reinterpret_cast<manifold::Box const*>(m);
|
||||
}
|
||||
|
||||
manifold::Rect* from_c(ManifoldRect* m) {
|
||||
return reinterpret_cast<manifold::Rect*>(m);
|
||||
const manifold::Rect* from_c(ManifoldRect* m) {
|
||||
return reinterpret_cast<manifold::Rect const*>(m);
|
||||
}
|
||||
|
||||
vec2 from_c(ManifoldVec2 v) { return vec2(v.x, v.y); }
|
||||
@@ -234,8 +230,8 @@ ivec3 from_c(ManifoldIVec3 v) { return ivec3(v.x, v.y, v.z); }
|
||||
|
||||
vec4 from_c(ManifoldVec4 v) { return vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<ivec3>* from_c(ManifoldTriangulation* m) {
|
||||
return reinterpret_cast<std::vector<ivec3>*>(m);
|
||||
const std::vector<ivec3>* from_c(ManifoldTriangulation* m) {
|
||||
return reinterpret_cast<std::vector<ivec3> const*>(m);
|
||||
}
|
||||
|
||||
std::vector<vec3> vector_of_vec_array(ManifoldVec3* vs, size_t length) {
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
using namespace manifold;
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
using RayHitVec = std::vector<RayHit>;
|
||||
|
||||
ManifoldManifold* to_c(manifold::Manifold* m);
|
||||
ManifoldManifoldVec* to_c(ManifoldVec* ms);
|
||||
@@ -41,28 +40,25 @@ ManifoldVec2 to_c(vec2 v);
|
||||
ManifoldVec3 to_c(vec3 v);
|
||||
ManifoldIVec3 to_c(ivec3 v);
|
||||
ManifoldTriangulation* to_c(std::vector<ivec3>* m);
|
||||
ManifoldRayHitVec* to_c(RayHitVec* v);
|
||||
ManifoldExecutionContext* to_c(ExecutionContext* ctx);
|
||||
|
||||
manifold::Manifold* from_c(ManifoldManifold* m);
|
||||
const manifold::Manifold* from_c(ManifoldManifold* m);
|
||||
ManifoldVec* from_c(ManifoldManifoldVec* ms);
|
||||
manifold::CrossSection* from_c(ManifoldCrossSection* cs);
|
||||
const manifold::CrossSection* from_c(ManifoldCrossSection* cs);
|
||||
CrossSectionVec* from_c(ManifoldCrossSectionVec* csv);
|
||||
manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m);
|
||||
manifold::Polygons* from_c(ManifoldPolygons* m);
|
||||
manifold::MeshGL* from_c(ManifoldMeshGL* m);
|
||||
manifold::MeshGL64* from_c(ManifoldMeshGL64* m);
|
||||
const manifold::SimplePolygon* from_c(ManifoldSimplePolygon* m);
|
||||
const manifold::Polygons* from_c(ManifoldPolygons* m);
|
||||
const manifold::MeshGL* from_c(ManifoldMeshGL* m);
|
||||
const manifold::MeshGL64* from_c(ManifoldMeshGL64* m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
manifold::Box* from_c(ManifoldBox* m);
|
||||
manifold::Rect* from_c(ManifoldRect* r);
|
||||
const manifold::Box* from_c(ManifoldBox* m);
|
||||
const manifold::Rect* from_c(ManifoldRect* r);
|
||||
vec2 from_c(ManifoldVec2 v);
|
||||
vec3 from_c(ManifoldVec3 v);
|
||||
ivec3 from_c(ManifoldIVec3 v);
|
||||
vec4 from_c(ManifoldVec4 v);
|
||||
std::vector<ivec3>* from_c(ManifoldTriangulation* m);
|
||||
RayHitVec* from_c(ManifoldRayHitVec* v);
|
||||
ExecutionContext* from_c(ManifoldExecutionContext* ctx);
|
||||
const std::vector<ivec3>* from_c(ManifoldTriangulation* m);
|
||||
|
||||
std::vector<vec3> vector_of_vec_array(ManifoldVec3* vs, size_t length);
|
||||
std::vector<ivec3> vector_of_vec_array(ManifoldIVec3* vs, size_t length);
|
||||
|
||||
@@ -31,23 +31,14 @@ ManifoldCrossSection* manifold_cross_section_copy(void* mem,
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_of_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p)));
|
||||
void* mem, ManifoldSimplePolygon* p, ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_of_polygons(void* mem,
|
||||
ManifoldPolygons* p) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p)));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_even_odd_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p) {
|
||||
return to_c(new (mem) CrossSection(CrossSection::EvenOdd(*from_c(p))));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_even_odd_polygons(
|
||||
void* mem, ManifoldPolygons* p) {
|
||||
return to_c(new (mem) CrossSection(CrossSection::EvenOdd(*from_c(p))));
|
||||
ManifoldPolygons* p,
|
||||
ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_empty_vec(void* mem) {
|
||||
@@ -190,7 +181,7 @@ ManifoldCrossSection* manifold_cross_section_transform(void* mem,
|
||||
return to_c(new (mem) CrossSection(transformed));
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_warp_context(
|
||||
ManifoldCrossSection* manifold_cross_section_warp(
|
||||
void* mem, ManifoldCrossSection* cs,
|
||||
ManifoldVec2 (*fun)(double, double, void*), void* ctx) {
|
||||
// Bind function with context argument to one without
|
||||
@@ -205,8 +196,8 @@ ManifoldCrossSection* manifold_cross_section_warp_context(
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_simplify(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double tolerance) {
|
||||
auto simplified = from_c(cs)->Simplify(tolerance);
|
||||
double epsilon) {
|
||||
auto simplified = from_c(cs)->Simplify(epsilon);
|
||||
return to_c(new (mem) CrossSection(simplified));
|
||||
}
|
||||
|
||||
@@ -246,14 +237,10 @@ ManifoldPolygons* manifold_cross_section_to_polygons(void* mem,
|
||||
return to_c(new (mem) Polygons(ps));
|
||||
}
|
||||
|
||||
double manifold_cross_section_get_tolerance(ManifoldCrossSection* cs) {
|
||||
return from_c(cs)->GetTolerance();
|
||||
}
|
||||
|
||||
ManifoldCrossSection* manifold_cross_section_set_tolerance(
|
||||
void* mem, ManifoldCrossSection* cs, double tolerance) {
|
||||
auto out = from_c(cs)->SetTolerance(tolerance);
|
||||
return to_c(new (mem) CrossSection(out));
|
||||
ManifoldCrossSection* manifold_cross_section_compose(
|
||||
void* mem, ManifoldCrossSectionVec* csv) {
|
||||
auto cs = CrossSection::Compose(*from_c(csv));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec* manifold_cross_section_decompose(
|
||||
|
||||
@@ -49,13 +49,7 @@ ManifoldMeshGL* manifold_meshgl_w_tangents(void* mem, float* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t* tri_verts, size_t n_tris,
|
||||
float* halfedge_tangent);
|
||||
ManifoldMeshGL* manifold_meshgl_w_options(void* mem, float* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t* tri_verts, size_t n_tris,
|
||||
ManifoldMeshGLOptions* options);
|
||||
ManifoldMeshGL* manifold_get_meshgl(void* mem, ManifoldManifold* m);
|
||||
ManifoldMeshGL* manifold_get_meshgl_w_normals(void* mem, ManifoldManifold* m,
|
||||
int32_t normalIdx);
|
||||
ManifoldMeshGL* manifold_meshgl_copy(void* mem, ManifoldMeshGL* m);
|
||||
ManifoldMeshGL* manifold_meshgl_merge(void* mem, ManifoldMeshGL* m);
|
||||
|
||||
@@ -68,15 +62,7 @@ ManifoldMeshGL64* manifold_meshgl64_w_tangents(void* mem, double* vert_props,
|
||||
uint64_t* tri_verts,
|
||||
size_t n_tris,
|
||||
double* halfedge_tangent);
|
||||
ManifoldMeshGL64* manifold_meshgl64_w_options(void* mem, double* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t* tri_verts,
|
||||
size_t n_tris,
|
||||
ManifoldMeshGL64Options* options);
|
||||
ManifoldMeshGL64* manifold_get_meshgl64(void* mem, ManifoldManifold* m);
|
||||
ManifoldMeshGL64* manifold_get_meshgl64_w_normals(void* mem,
|
||||
ManifoldManifold* m,
|
||||
int32_t normalIdx);
|
||||
ManifoldMeshGL64* manifold_meshgl64_copy(void* mem, ManifoldMeshGL64* m);
|
||||
ManifoldMeshGL64* manifold_meshgl64_merge(void* mem, ManifoldMeshGL64* m);
|
||||
|
||||
@@ -129,10 +115,6 @@ ManifoldManifoldPair manifold_split_by_plane(void* mem_first, void* mem_second,
|
||||
ManifoldManifold* manifold_trim_by_plane(void* mem, ManifoldManifold* m,
|
||||
double normal_x, double normal_y,
|
||||
double normal_z, double offset);
|
||||
ManifoldManifold* manifold_minkowski_sum(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b);
|
||||
ManifoldManifold* manifold_minkowski_difference(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b);
|
||||
|
||||
// 3D to 2D
|
||||
|
||||
@@ -165,15 +147,14 @@ ManifoldManifold* manifold_warp(void* mem, ManifoldManifold* m,
|
||||
void* ctx);
|
||||
ManifoldManifold* manifold_smooth_by_normals(void* mem, ManifoldManifold* m,
|
||||
int normalIdx);
|
||||
ManifoldManifold* manifold_smooth_out(void* mem, ManifoldManifold* m,
|
||||
double minSharpAngle,
|
||||
double minSmoothness);
|
||||
ManifoldManifold* manifold_refine(void* mem, ManifoldManifold* m, int refine);
|
||||
ManifoldManifold* manifold_refine_to_length(void* mem, ManifoldManifold* m,
|
||||
double length);
|
||||
ManifoldManifold* manifold_refine_to_tolerance(void* mem, ManifoldManifold* m,
|
||||
double tolerance);
|
||||
ManifoldManifold* manifold_set_tolerance(void* mem, ManifoldManifold* m,
|
||||
double tolerance);
|
||||
ManifoldManifold* manifold_simplify(void* mem, ManifoldManifold* m,
|
||||
double tolerance);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
|
||||
@@ -189,6 +170,12 @@ ManifoldManifold* manifold_sphere(void* mem, double radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold* manifold_of_meshgl(void* mem, ManifoldMeshGL* mesh);
|
||||
ManifoldManifold* manifold_of_meshgl64(void* mem, ManifoldMeshGL64* mesh);
|
||||
ManifoldManifold* manifold_smooth(void* mem, ManifoldMeshGL* mesh,
|
||||
size_t* half_edges, double* smoothness,
|
||||
size_t n_idxs);
|
||||
ManifoldManifold* manifold_smooth64(void* mem, ManifoldMeshGL64* mesh,
|
||||
size_t* half_edges, double* smoothness,
|
||||
size_t n_idxs);
|
||||
ManifoldManifold* manifold_extrude(void* mem, ManifoldPolygons* cs,
|
||||
double height, int slices,
|
||||
double twist_degrees, double scale_x,
|
||||
@@ -205,22 +192,12 @@ ManifoldManifold* manifold_as_original(void* mem, ManifoldManifold* m);
|
||||
|
||||
int manifold_is_empty(ManifoldManifold* m);
|
||||
ManifoldError manifold_status(ManifoldManifold* m);
|
||||
// Returns a copy of the manifold with the given ExecutionContext attached.
|
||||
// The attachment is consumed by the next eager op invoked on the result —
|
||||
// manifold_status, or one of the manifold_refine* family. Deferred ops
|
||||
// (Boolean operators, transforms, batch ops) ignore any attached ctx and
|
||||
// produce a result with no attached ctx. See ExecutionContext in common.h
|
||||
// for the full model.
|
||||
ManifoldManifold* manifold_with_context(void* mem, ManifoldManifold* m,
|
||||
ManifoldExecutionContext* ctx);
|
||||
size_t manifold_num_vert(ManifoldManifold* m);
|
||||
size_t manifold_num_edge(ManifoldManifold* m);
|
||||
size_t manifold_num_tri(ManifoldManifold* m);
|
||||
size_t manifold_num_prop(ManifoldManifold* m);
|
||||
ManifoldBox* manifold_bounding_box(void* mem, ManifoldManifold* m);
|
||||
double manifold_epsilon(ManifoldManifold* m);
|
||||
double manifold_get_tolerance(ManifoldManifold* m);
|
||||
size_t manifold_num_prop_vert(ManifoldManifold* m);
|
||||
int manifold_genus(ManifoldManifold* m);
|
||||
double manifold_surface_area(ManifoldManifold* m);
|
||||
double manifold_volume(ManifoldManifold* m);
|
||||
@@ -240,62 +217,21 @@ ManifoldManifold* manifold_calculate_normals(void* mem, ManifoldManifold* m,
|
||||
int normal_idx,
|
||||
double min_sharp_angle);
|
||||
|
||||
// Ray Casting
|
||||
ManifoldRayHitVec* manifold_ray_cast(void* mem, ManifoldManifold* m,
|
||||
double origin_x, double origin_y,
|
||||
double origin_z, double end_x,
|
||||
double end_y, double end_z);
|
||||
size_t manifold_ray_hit_vec_length(ManifoldRayHitVec* v);
|
||||
ManifoldRayHit manifold_ray_hit_vec_get(ManifoldRayHitVec* v, size_t idx);
|
||||
|
||||
// Point containment
|
||||
int manifold_winding_number(ManifoldManifold* m, double x, double y, double z);
|
||||
|
||||
// ExecutionContext: observe progress and request cancellation of a
|
||||
// long-running Manifold evaluation. Attach to a manifold via
|
||||
// manifold_with_context; subsequent operations on that manifold observe
|
||||
// the context. Safe to read/write from any thread. See the ExecutionContext
|
||||
// class in common.h for full semantics (sticky cancel, per-boolean
|
||||
// granularity).
|
||||
ManifoldExecutionContext* manifold_execution_context(void* mem);
|
||||
void manifold_execution_context_cancel(ManifoldExecutionContext* ctx);
|
||||
int manifold_execution_context_cancelled(ManifoldExecutionContext* ctx);
|
||||
double manifold_execution_context_progress(ManifoldExecutionContext* ctx);
|
||||
|
||||
// ctx-aware static factories. These ops have no source manifold to attach via
|
||||
// manifold_with_context, so they run on the ExecutionContext directly to report
|
||||
// progress / observe cancellation. Mirror manifold_level_set /
|
||||
// manifold_of_meshgl; `sdf_context` is the SDF callback's
|
||||
// user-data.
|
||||
ManifoldManifold* manifold_execution_context_level_set(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldSdf sdf,
|
||||
ManifoldBox* bounds, double edge_length, double level, double tolerance,
|
||||
void* sdf_context);
|
||||
ManifoldManifold* manifold_execution_context_level_set_seq(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldSdf sdf,
|
||||
ManifoldBox* bounds, double edge_length, double level, double tolerance,
|
||||
void* sdf_context);
|
||||
ManifoldManifold* manifold_execution_context_of_meshgl(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldMeshGL* mesh);
|
||||
ManifoldManifold* manifold_execution_context_of_meshgl64(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldMeshGL64* mesh);
|
||||
|
||||
// CrossSection Shapes/Constructors
|
||||
ManifoldCrossSection* manifold_cross_section_empty(void* mem);
|
||||
ManifoldCrossSection* manifold_cross_section_copy(void* mem,
|
||||
ManifoldCrossSection* cs);
|
||||
ManifoldCrossSection* manifold_cross_section_of_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p);
|
||||
void* mem, ManifoldSimplePolygon* p, ManifoldFillRule fr);
|
||||
ManifoldCrossSection* manifold_cross_section_of_polygons(void* mem,
|
||||
ManifoldPolygons* p);
|
||||
ManifoldCrossSection* manifold_cross_section_even_odd_simple_polygon(
|
||||
void* mem, ManifoldSimplePolygon* p);
|
||||
ManifoldCrossSection* manifold_cross_section_even_odd_polygons(
|
||||
void* mem, ManifoldPolygons* p);
|
||||
ManifoldPolygons* p,
|
||||
ManifoldFillRule fr);
|
||||
ManifoldCrossSection* manifold_cross_section_square(void* mem, double x,
|
||||
double y, int center);
|
||||
ManifoldCrossSection* manifold_cross_section_circle(void* mem, double radius,
|
||||
int circular_segments);
|
||||
ManifoldCrossSection* manifold_cross_section_compose(
|
||||
void* mem, ManifoldCrossSectionVec* csv);
|
||||
ManifoldCrossSectionVec* manifold_cross_section_decompose(
|
||||
void* mem, ManifoldCrossSection* cs);
|
||||
|
||||
@@ -359,14 +295,14 @@ ManifoldCrossSection* manifold_cross_section_transform(void* mem,
|
||||
double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3);
|
||||
ManifoldCrossSection* manifold_cross_section_warp(
|
||||
void* mem, ManifoldCrossSection* cs, ManifoldVec2 (*fun)(double, double));
|
||||
ManifoldCrossSection* manifold_cross_section_warp_context(
|
||||
void* mem, ManifoldCrossSection* cs,
|
||||
ManifoldVec2 (*fun)(double, double, void*), void* ctx);
|
||||
ManifoldCrossSection* manifold_cross_section_simplify(void* mem,
|
||||
ManifoldCrossSection* cs,
|
||||
double tolerance);
|
||||
ManifoldCrossSection* manifold_cross_section_set_tolerance(
|
||||
void* mem, ManifoldCrossSection* cs, double tolerance);
|
||||
double epsilon);
|
||||
ManifoldCrossSection* manifold_cross_section_offset(
|
||||
void* mem, ManifoldCrossSection* cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, int circular_segments);
|
||||
@@ -374,7 +310,6 @@ ManifoldCrossSection* manifold_cross_section_offset(
|
||||
// CrossSection Info
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection* cs);
|
||||
double manifold_cross_section_get_tolerance(ManifoldCrossSection* cs);
|
||||
size_t manifold_cross_section_num_vert(ManifoldCrossSection* cs);
|
||||
size_t manifold_cross_section_num_contour(ManifoldCrossSection* cs);
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection* cs);
|
||||
@@ -440,9 +375,9 @@ void manifold_reset_to_circular_defaults();
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
|
||||
size_t manifold_meshgl_num_prop(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_num_vert(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_num_tri(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL* m);
|
||||
@@ -460,12 +395,6 @@ uint32_t* manifold_meshgl_run_original_id(void* mem, ManifoldMeshGL* m);
|
||||
float* manifold_meshgl_run_transform(void* mem, ManifoldMeshGL* m);
|
||||
uint32_t* manifold_meshgl_face_id(void* mem, ManifoldMeshGL* m);
|
||||
float* manifold_meshgl_halfedge_tangent(void* mem, ManifoldMeshGL* m);
|
||||
float manifold_meshgl_tolerance(ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_run_flags_length(ManifoldMeshGL* m);
|
||||
uint8_t* manifold_meshgl_run_flags(void* mem, ManifoldMeshGL* m);
|
||||
size_t manifold_meshgl_num_run(ManifoldMeshGL* m);
|
||||
int manifold_meshgl_backside(ManifoldMeshGL* m, size_t run);
|
||||
int manifold_meshgl_has_normals(ManifoldMeshGL* m, size_t run);
|
||||
|
||||
size_t manifold_meshgl64_num_prop(ManifoldMeshGL64* m);
|
||||
size_t manifold_meshgl64_num_vert(ManifoldMeshGL64* m);
|
||||
@@ -487,12 +416,6 @@ uint32_t* manifold_meshgl64_run_original_id(void* mem, ManifoldMeshGL64* m);
|
||||
double* manifold_meshgl64_run_transform(void* mem, ManifoldMeshGL64* m);
|
||||
uint64_t* manifold_meshgl64_face_id(void* mem, ManifoldMeshGL64* m);
|
||||
double* manifold_meshgl64_halfedge_tangent(void* mem, ManifoldMeshGL64* m);
|
||||
double manifold_meshgl64_tolerance(ManifoldMeshGL64* m);
|
||||
size_t manifold_meshgl64_run_flags_length(ManifoldMeshGL64* m);
|
||||
uint8_t* manifold_meshgl64_run_flags(void* mem, ManifoldMeshGL64* m);
|
||||
size_t manifold_meshgl64_num_run(ManifoldMeshGL64* m);
|
||||
int manifold_meshgl64_backside(ManifoldMeshGL64* m, size_t run);
|
||||
int manifold_meshgl64_has_normals(ManifoldMeshGL64* m, size_t run);
|
||||
|
||||
// Triangulation
|
||||
|
||||
@@ -507,7 +430,6 @@ size_t manifold_manifold_size();
|
||||
size_t manifold_manifold_vec_size();
|
||||
size_t manifold_cross_section_size();
|
||||
size_t manifold_cross_section_vec_size();
|
||||
size_t manifold_ray_hit_vec_size();
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
@@ -515,8 +437,8 @@ size_t manifold_meshgl_size();
|
||||
size_t manifold_meshgl64_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_rect_size();
|
||||
size_t manifold_curvature_size();
|
||||
size_t manifold_triangulation_size();
|
||||
size_t manifold_execution_context_size();
|
||||
|
||||
// allocation
|
||||
|
||||
@@ -524,7 +446,6 @@ ManifoldManifold* manifold_alloc_manifold();
|
||||
ManifoldManifoldVec* manifold_alloc_manifold_vec();
|
||||
ManifoldCrossSection* manifold_alloc_cross_section();
|
||||
ManifoldCrossSectionVec* manifold_alloc_cross_section_vec();
|
||||
ManifoldRayHitVec* manifold_alloc_ray_hit_vec();
|
||||
ManifoldSimplePolygon* manifold_alloc_simple_polygon();
|
||||
ManifoldPolygons* manifold_alloc_polygons();
|
||||
ManifoldMeshGL* manifold_alloc_meshgl();
|
||||
@@ -532,7 +453,6 @@ ManifoldMeshGL64* manifold_alloc_meshgl64();
|
||||
ManifoldBox* manifold_alloc_box();
|
||||
ManifoldRect* manifold_alloc_rect();
|
||||
ManifoldTriangulation* manifold_alloc_triangulation();
|
||||
ManifoldExecutionContext* manifold_alloc_execution_context();
|
||||
|
||||
// destruction
|
||||
|
||||
@@ -540,7 +460,6 @@ void manifold_destruct_manifold(ManifoldManifold* m);
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec* ms);
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection* m);
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec* csv);
|
||||
void manifold_destruct_ray_hit_vec(ManifoldRayHitVec* v);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon* p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons* p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL* m);
|
||||
@@ -548,7 +467,6 @@ void manifold_destruct_meshgl64(ManifoldMeshGL64* m);
|
||||
void manifold_destruct_box(ManifoldBox* b);
|
||||
void manifold_destruct_rect(ManifoldRect* b);
|
||||
void manifold_destruct_triangulation(ManifoldTriangulation* M);
|
||||
void manifold_destruct_execution_context(ManifoldExecutionContext* ctx);
|
||||
|
||||
// pointer free + destruction
|
||||
|
||||
@@ -556,7 +474,6 @@ void manifold_delete_manifold(ManifoldManifold* m);
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec* ms);
|
||||
void manifold_delete_cross_section(ManifoldCrossSection* cs);
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec* csv);
|
||||
void manifold_delete_ray_hit_vec(ManifoldRayHitVec* v);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon* p);
|
||||
void manifold_delete_polygons(ManifoldPolygons* p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL* m);
|
||||
@@ -564,38 +481,34 @@ void manifold_delete_meshgl64(ManifoldMeshGL64* m);
|
||||
void manifold_delete_box(ManifoldBox* b);
|
||||
void manifold_delete_rect(ManifoldRect* b);
|
||||
void manifold_delete_triangulation(ManifoldTriangulation* m);
|
||||
void manifold_delete_execution_context(ManifoldExecutionContext* ctx);
|
||||
|
||||
#ifndef MANIFOLD_NO_IOSTREAM
|
||||
// MeshIO / Export
|
||||
|
||||
// Import a manifold from a Wavefront obj file.
|
||||
// The obj_file parameter is the content of the obj file, not the filename,
|
||||
// and should be null-terminated.
|
||||
ManifoldManifold* manifold_read_obj(void* mem, char* obj_file);
|
||||
// Import a meshgl from a Wavefront obj file.
|
||||
// The obj_file parameter is the content of the obj file, not the filename,
|
||||
// and should be null-terminated.
|
||||
ManifoldMeshGL64* manifold_meshgl64_read_obj(void* mem, char* obj_file);
|
||||
// Export a manifold to a Wavefront obj file.
|
||||
// The callback accepts two parameters:
|
||||
// 1. Temporary null-terminated string buffer, containing the content of the
|
||||
// file. This buffer will be freed automatically after returning from the
|
||||
// callback.
|
||||
// 2. An arg value (the third parameter in the manifold_write_obj function), for
|
||||
// passing additional data into the callback.
|
||||
void manifold_write_obj(ManifoldManifold* manifold,
|
||||
void (*callback)(char*, void*), void* args);
|
||||
// Export a MeshGL64 to a Wavefront obj file.
|
||||
// The callback accepts two parameters:
|
||||
// 1. Temporary null-terminated string buffer, containing the content of the
|
||||
// file. This buffer will be freed automatically after returning from the
|
||||
// callback.
|
||||
// 2. An arg value (the third parameter in the manifold_write_obj function), for
|
||||
// passing additional data into the callback.
|
||||
void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
|
||||
void (*callback)(char*, void*), void* args);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ManifoldMaterial* manifold_material(void* mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial* mat, double roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial* mat, double metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial* mat, ManifoldVec3 color);
|
||||
ManifoldExportOptions* manifold_export_options(void* mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions* options,
|
||||
int faceted);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions* options,
|
||||
ManifoldMaterial* mat);
|
||||
void manifold_export_meshgl(const char* filename, ManifoldMeshGL* mesh,
|
||||
ManifoldExportOptions* options);
|
||||
ManifoldMeshGL* manifold_import_meshgl(void* mem, const char* filename,
|
||||
int force_cleanup);
|
||||
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
void manifold_destruct_material(ManifoldMaterial* m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions* options);
|
||||
|
||||
void manifold_delete_material(ManifoldMaterial* m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions* options);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// opaque pointers
|
||||
|
||||
@@ -22,7 +21,6 @@ typedef struct ManifoldManifold ManifoldManifold;
|
||||
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
|
||||
typedef struct ManifoldCrossSection ManifoldCrossSection;
|
||||
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
|
||||
typedef struct ManifoldRayHitVec ManifoldRayHitVec;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct ManifoldMeshGL ManifoldMeshGL;
|
||||
@@ -30,7 +28,11 @@ typedef struct ManifoldMeshGL64 ManifoldMeshGL64;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
typedef struct ManifoldTriangulation ManifoldTriangulation;
|
||||
typedef struct ManifoldExecutionContext ManifoldExecutionContext;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
#endif
|
||||
|
||||
// structs
|
||||
|
||||
@@ -68,35 +70,6 @@ typedef struct ManifoldProperties {
|
||||
double volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
typedef struct ManifoldMeshGLOptions {
|
||||
uint32_t* run_indices;
|
||||
size_t run_indices_length;
|
||||
uint32_t* run_original_ids;
|
||||
size_t run_original_ids_length;
|
||||
uint32_t* merge_from_vert;
|
||||
uint32_t* merge_to_vert;
|
||||
size_t merge_verts_length;
|
||||
float* halfedge_tangents;
|
||||
} ManifoldMeshGLOptions;
|
||||
|
||||
typedef struct ManifoldMeshGL64Options {
|
||||
uint64_t* run_indices;
|
||||
size_t run_indices_length;
|
||||
uint32_t* run_original_ids;
|
||||
size_t run_original_ids_length;
|
||||
uint64_t* merge_from_vert;
|
||||
uint64_t* merge_to_vert;
|
||||
size_t merge_verts_length;
|
||||
double* halfedge_tangents;
|
||||
} ManifoldMeshGL64Options;
|
||||
|
||||
typedef struct ManifoldRayHit {
|
||||
uint64_t face_id;
|
||||
double distance;
|
||||
ManifoldVec3 position;
|
||||
ManifoldVec3 normal;
|
||||
} ManifoldRayHit;
|
||||
|
||||
// enums
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
@@ -119,10 +92,15 @@ typedef enum ManifoldError {
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
MANIFOLD_INVALID_CONSTRUCTION,
|
||||
MANIFOLD_RESULT_TOO_LARGE,
|
||||
MANIFOLD_INVALID_TANGENTS,
|
||||
MANIFOLD_CANCELLED,
|
||||
} ManifoldError;
|
||||
|
||||
typedef enum ManifoldFillRule {
|
||||
MANIFOLD_FILL_RULE_EVEN_ODD,
|
||||
MANIFOLD_FILL_RULE_NON_ZERO,
|
||||
MANIFOLD_FILL_RULE_POSITIVE,
|
||||
MANIFOLD_FILL_RULE_NEGATIVE
|
||||
} ManifoldFillRule;
|
||||
|
||||
typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_SQUARE,
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
|
||||
#include "manifold/manifoldc.h"
|
||||
|
||||
#ifndef MANIFOLD_NO_IOSTREAM
|
||||
#include <sstream>
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
#include "conv.h"
|
||||
@@ -32,7 +29,7 @@ namespace {
|
||||
ManifoldManifold* level_set(
|
||||
void* mem, double (*sdf_context)(double, double, double, void*),
|
||||
ManifoldBox* bounds, double edge_length, double level, double tolerance,
|
||||
bool seq, void* ctx, ExecutionContext* ec = nullptr) {
|
||||
bool seq, void* ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<double(double, double, double)> sdf =
|
||||
@@ -40,24 +37,8 @@ ManifoldManifold* level_set(
|
||||
std::function<double(vec3)> fun = [sdf](vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
// Run under the ExecutionContext when given, so progress/cancel are observed.
|
||||
Manifold result = ec ? ec->LevelSet(fun, *from_c(bounds), edge_length, level,
|
||||
tolerance, !seq)
|
||||
: Manifold::LevelSet(fun, *from_c(bounds), edge_length,
|
||||
level, tolerance, !seq);
|
||||
return to_c(new (mem) Manifold(result));
|
||||
}
|
||||
|
||||
// Raw uninitialized storage for a T — callers must placement-new into it
|
||||
// before any use or before passing to manifold_destruct_* / manifold_delete_*.
|
||||
// Pairs with `delete T*`, which uses the plain `::operator delete` for
|
||||
// types with default alignment.
|
||||
template <typename T>
|
||||
T* alloc_raw() {
|
||||
static_assert(alignof(T) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__,
|
||||
"over-aligned types need the aligned operator new/delete "
|
||||
"pair; extend this helper if a binding type ever needs it");
|
||||
return static_cast<T*>(::operator new(sizeof(T)));
|
||||
return to_c(new (mem) Manifold(Manifold::LevelSet(
|
||||
fun, *from_c(bounds), edge_length, level, tolerance, !seq)));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -202,18 +183,6 @@ ManifoldManifold* manifold_trim_by_plane(void* mem, ManifoldManifold* m,
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_minkowski_sum(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b) {
|
||||
auto m = (*from_c(a)).MinkowskiSum(*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_minkowski_difference(void* mem, ManifoldManifold* a,
|
||||
ManifoldManifold* b) {
|
||||
auto m = (*from_c(a)).MinkowskiDifference(*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m,
|
||||
double height) {
|
||||
auto poly = from_c(m)->Slice(height);
|
||||
@@ -315,6 +284,13 @@ ManifoldManifold* manifold_smooth_by_normals(void* mem, ManifoldManifold* m,
|
||||
return to_c(new (mem) Manifold(smoothed));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_smooth_out(void* mem, ManifoldManifold* m,
|
||||
double minSharpAngle,
|
||||
double minSmoothness) {
|
||||
auto smoothed = from_c(m)->SmoothOut(minSharpAngle, minSmoothness);
|
||||
return to_c(new (mem) Manifold(smoothed));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_refine(void* mem, ManifoldManifold* m, int refine) {
|
||||
auto refined = from_c(m)->Refine(refine);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
@@ -332,18 +308,6 @@ ManifoldManifold* manifold_refine_to_tolerance(void* mem, ManifoldManifold* m,
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_set_tolerance(void* mem, ManifoldManifold* m,
|
||||
double tolerance) {
|
||||
auto result = from_c(m)->SetTolerance(tolerance);
|
||||
return to_c(new (mem) Manifold(result));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_simplify(void* mem, ManifoldManifold* m,
|
||||
double tolerance) {
|
||||
auto simplified = from_c(m)->Simplify(tolerance);
|
||||
return to_c(new (mem) Manifold(simplified));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_empty(void* mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
@@ -400,43 +364,6 @@ ManifoldMeshGL* manifold_meshgl_w_tangents(void* mem, float* vert_props,
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_w_options(void* mem, float* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t* tri_verts, size_t n_tris,
|
||||
ManifoldMeshGLOptions* options) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
|
||||
if (options->halfedge_tangents != nullptr) {
|
||||
mesh->halfedgeTangent =
|
||||
vector_of_array(options->halfedge_tangents, n_tris * 3 * 4);
|
||||
}
|
||||
|
||||
if (options->run_indices != nullptr) {
|
||||
mesh->runIndex =
|
||||
vector_of_array(options->run_indices, options->run_indices_length);
|
||||
}
|
||||
|
||||
if (options->run_original_ids != nullptr) {
|
||||
mesh->runOriginalID = vector_of_array(options->run_original_ids,
|
||||
options->run_original_ids_length);
|
||||
}
|
||||
|
||||
if (options->merge_from_vert != nullptr) {
|
||||
mesh->mergeFromVert =
|
||||
vector_of_array(options->merge_from_vert, options->merge_verts_length);
|
||||
}
|
||||
|
||||
if (options->merge_to_vert != nullptr) {
|
||||
mesh->mergeToVert =
|
||||
vector_of_array(options->merge_to_vert, options->merge_verts_length);
|
||||
}
|
||||
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_meshgl64(void* mem, double* vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint64_t* tri_verts, size_t n_tris) {
|
||||
@@ -460,40 +387,26 @@ ManifoldMeshGL64* manifold_meshgl64_w_tangents(void* mem, double* vert_props,
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_meshgl64_w_options(
|
||||
void* mem, double* vert_props, size_t n_verts, size_t n_props,
|
||||
uint64_t* tri_verts, size_t n_tris, ManifoldMeshGL64Options* options) {
|
||||
auto mesh = new (mem) MeshGL64();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
|
||||
if (options->halfedge_tangents != nullptr) {
|
||||
mesh->halfedgeTangent =
|
||||
vector_of_array(options->halfedge_tangents, n_tris * 3 * 4);
|
||||
ManifoldManifold* manifold_smooth(void* mem, ManifoldMeshGL* mesh,
|
||||
size_t* half_edges, double* smoothness,
|
||||
size_t n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
if (options->run_indices != nullptr) {
|
||||
mesh->runIndex =
|
||||
vector_of_array(options->run_indices, options->run_indices_length);
|
||||
ManifoldManifold* manifold_smooth64(void* mem, ManifoldMeshGL64* mesh,
|
||||
size_t* half_edges, double* smoothness,
|
||||
size_t n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
|
||||
if (options->run_original_ids != nullptr) {
|
||||
mesh->runOriginalID = vector_of_array(options->run_original_ids,
|
||||
options->run_original_ids_length);
|
||||
}
|
||||
|
||||
if (options->merge_from_vert != nullptr) {
|
||||
mesh->mergeFromVert =
|
||||
vector_of_array(options->merge_from_vert, options->merge_verts_length);
|
||||
}
|
||||
|
||||
if (options->merge_to_vert != nullptr) {
|
||||
mesh->mergeToVert =
|
||||
vector_of_array(options->merge_to_vert, options->merge_verts_length);
|
||||
}
|
||||
|
||||
return to_c(mesh);
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_of_meshgl(void* mem, ManifoldMeshGL* mesh) {
|
||||
@@ -523,7 +436,7 @@ ManifoldManifold* manifold_revolve(void* mem, ManifoldPolygons* cs,
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_compose(void* mem, ManifoldManifoldVec* ms) {
|
||||
auto composed = Manifold::BatchBoolean(*from_c(ms), OpType::Add);
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
@@ -537,20 +450,17 @@ ManifoldMeshGL* manifold_get_meshgl(void* mem, ManifoldManifold* m) {
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_get_meshgl_w_normals(void* mem, ManifoldManifold* m,
|
||||
int32_t normalIdx) {
|
||||
auto mesh = from_c(m)->GetMeshGL(normalIdx);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_copy(void* mem, ManifoldMeshGL* m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_meshgl_merge(void* mem, ManifoldMeshGL* m) {
|
||||
auto duplicate = new (mem) MeshGL(*from_c(m));
|
||||
duplicate->Merge();
|
||||
return to_c(duplicate);
|
||||
if (duplicate->Merge()) {
|
||||
return to_c(duplicate);
|
||||
}
|
||||
duplicate->~MeshGL();
|
||||
return m;
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_get_meshgl64(void* mem, ManifoldManifold* m) {
|
||||
@@ -558,32 +468,22 @@ ManifoldMeshGL64* manifold_get_meshgl64(void* mem, ManifoldManifold* m) {
|
||||
return to_c(new (mem) MeshGL64(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_get_meshgl64_w_normals(void* mem,
|
||||
ManifoldManifold* m,
|
||||
int32_t normalIdx) {
|
||||
auto mesh = from_c(m)->GetMeshGL64(normalIdx);
|
||||
return to_c(new (mem) MeshGL64(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_meshgl64_copy(void* mem, ManifoldMeshGL64* m) {
|
||||
return to_c(new (mem) MeshGL64(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_meshgl64_merge(void* mem, ManifoldMeshGL64* m) {
|
||||
auto duplicate = new (mem) MeshGL64(*from_c(m));
|
||||
duplicate->Merge();
|
||||
return to_c(duplicate);
|
||||
if (duplicate->Merge()) {
|
||||
return to_c(duplicate);
|
||||
}
|
||||
duplicate->~MeshGL64();
|
||||
return m;
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_num_prop(ManifoldMeshGL* m) {
|
||||
return from_c(m)->numProp;
|
||||
}
|
||||
size_t manifold_meshgl_num_vert(ManifoldMeshGL* m) {
|
||||
return from_c(m)->NumVert();
|
||||
}
|
||||
size_t manifold_meshgl_num_tri(ManifoldMeshGL* m) {
|
||||
return from_c(m)->NumTri();
|
||||
}
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL* m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL* m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL* m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
@@ -653,30 +553,6 @@ float* manifold_meshgl_halfedge_tangent(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
float manifold_meshgl_tolerance(ManifoldMeshGL* m) {
|
||||
return from_c(m)->tolerance;
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_flags_length(ManifoldMeshGL* m) {
|
||||
return from_c(m)->runFlags.size();
|
||||
}
|
||||
|
||||
uint8_t* manifold_meshgl_run_flags(void* mem, ManifoldMeshGL* m) {
|
||||
return copy_data(mem, from_c(m)->runFlags);
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_num_run(ManifoldMeshGL* m) {
|
||||
return from_c(m)->NumRun();
|
||||
}
|
||||
|
||||
int manifold_meshgl_backside(ManifoldMeshGL* m, size_t run) {
|
||||
return from_c(m)->Backside(run) ? 1 : 0;
|
||||
}
|
||||
|
||||
int manifold_meshgl_has_normals(ManifoldMeshGL* m, size_t run) {
|
||||
return from_c(m)->HasNormals(run) ? 1 : 0;
|
||||
}
|
||||
|
||||
size_t manifold_meshgl64_num_prop(ManifoldMeshGL64* m) {
|
||||
return from_c(m)->numProp;
|
||||
}
|
||||
@@ -738,30 +614,6 @@ double* manifold_meshgl64_halfedge_tangent(void* mem, ManifoldMeshGL64* m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
double manifold_meshgl64_tolerance(ManifoldMeshGL64* m) {
|
||||
return from_c(m)->tolerance;
|
||||
}
|
||||
|
||||
size_t manifold_meshgl64_run_flags_length(ManifoldMeshGL64* m) {
|
||||
return from_c(m)->runFlags.size();
|
||||
}
|
||||
|
||||
uint8_t* manifold_meshgl64_run_flags(void* mem, ManifoldMeshGL64* m) {
|
||||
return copy_data(mem, from_c(m)->runFlags);
|
||||
}
|
||||
|
||||
size_t manifold_meshgl64_num_run(ManifoldMeshGL64* m) {
|
||||
return from_c(m)->NumRun();
|
||||
}
|
||||
|
||||
int manifold_meshgl64_backside(ManifoldMeshGL64* m, size_t run) {
|
||||
return from_c(m)->Backside(run) ? 1 : 0;
|
||||
}
|
||||
|
||||
int manifold_meshgl64_has_normals(ManifoldMeshGL64* m, size_t run) {
|
||||
return from_c(m)->HasNormals(run) ? 1 : 0;
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_as_original(void* mem, ManifoldManifold* m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
@@ -778,12 +630,6 @@ ManifoldError manifold_status(ManifoldManifold* m) {
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_with_context(void* mem, ManifoldManifold* m,
|
||||
ManifoldExecutionContext* ctx) {
|
||||
auto attached = from_c(m)->WithContext(*from_c(ctx));
|
||||
return to_c(new (mem) Manifold(attached));
|
||||
}
|
||||
|
||||
size_t manifold_num_vert(ManifoldManifold* m) { return from_c(m)->NumVert(); }
|
||||
size_t manifold_num_edge(ManifoldManifold* m) { return from_c(m)->NumEdge(); }
|
||||
size_t manifold_num_tri(ManifoldManifold* m) { return from_c(m)->NumTri(); }
|
||||
@@ -802,14 +648,6 @@ ManifoldBox* manifold_bounding_box(void* mem, ManifoldManifold* m) {
|
||||
|
||||
double manifold_epsilon(ManifoldManifold* m) { return from_c(m)->GetEpsilon(); }
|
||||
|
||||
double manifold_get_tolerance(ManifoldManifold* m) {
|
||||
return from_c(m)->GetTolerance();
|
||||
}
|
||||
|
||||
size_t manifold_num_prop_vert(ManifoldManifold* m) {
|
||||
return from_c(m)->NumPropVert();
|
||||
}
|
||||
|
||||
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
|
||||
|
||||
ManifoldManifold* manifold_set_properties(
|
||||
@@ -840,76 +678,6 @@ double manifold_min_gap(ManifoldManifold* m, ManifoldManifold* other,
|
||||
return from_c(m)->MinGap(*from_c(other), searchLength);
|
||||
}
|
||||
|
||||
ManifoldRayHitVec* manifold_ray_cast(void* mem, ManifoldManifold* m,
|
||||
double origin_x, double origin_y,
|
||||
double origin_z, double end_x,
|
||||
double end_y, double end_z) {
|
||||
auto hits = from_c(m)->RayCast(vec3(origin_x, origin_y, origin_z),
|
||||
vec3(end_x, end_y, end_z));
|
||||
return to_c(new (mem) RayHitVec(std::move(hits)));
|
||||
}
|
||||
|
||||
size_t manifold_ray_hit_vec_length(ManifoldRayHitVec* v) {
|
||||
return from_c(v)->size();
|
||||
}
|
||||
|
||||
ManifoldRayHit manifold_ray_hit_vec_get(ManifoldRayHitVec* v, size_t idx) {
|
||||
const auto& hit = (*from_c(v))[idx];
|
||||
return {hit.faceID, hit.distance, to_c(hit.position), to_c(hit.normal)};
|
||||
}
|
||||
|
||||
int manifold_winding_number(ManifoldManifold* m, double x, double y, double z) {
|
||||
const auto result = from_c(m)->WindingNumber({{x, y, z}});
|
||||
return result.empty() ? 0 : result[0];
|
||||
}
|
||||
|
||||
ManifoldExecutionContext* manifold_execution_context(void* mem) {
|
||||
return to_c(new (mem) ExecutionContext());
|
||||
}
|
||||
|
||||
void manifold_execution_context_cancel(ManifoldExecutionContext* ctx) {
|
||||
from_c(ctx)->Cancel();
|
||||
}
|
||||
|
||||
int manifold_execution_context_cancelled(ManifoldExecutionContext* ctx) {
|
||||
return from_c(ctx)->Cancelled() ? 1 : 0;
|
||||
}
|
||||
|
||||
double manifold_execution_context_progress(ManifoldExecutionContext* ctx) {
|
||||
return from_c(ctx)->Progress();
|
||||
}
|
||||
|
||||
// ctx-aware static factories: the FromMeshGL / LevelSet ops have no
|
||||
// source Manifold to attach via manifold_with_context, so they run directly on
|
||||
// the ExecutionContext. Each mirrors its plain factory but observes progress /
|
||||
// cancellation. `sdf_context` is the SDF callback's user-data (distinct from
|
||||
// the ExecutionContext `ec`).
|
||||
ManifoldManifold* manifold_execution_context_level_set(
|
||||
void* mem, ManifoldExecutionContext* ec,
|
||||
double (*sdf)(double, double, double, void*), ManifoldBox* bounds,
|
||||
double edge_length, double level, double tolerance, void* sdf_context) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, tolerance, false,
|
||||
sdf_context, from_c(ec));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_execution_context_level_set_seq(
|
||||
void* mem, ManifoldExecutionContext* ec,
|
||||
double (*sdf)(double, double, double, void*), ManifoldBox* bounds,
|
||||
double edge_length, double level, double tolerance, void* sdf_context) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, tolerance, true,
|
||||
sdf_context, from_c(ec));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_execution_context_of_meshgl(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldMeshGL* mesh) {
|
||||
return to_c(new (mem) Manifold(from_c(ec)->FromMeshGL(*from_c(mesh))));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_execution_context_of_meshgl64(
|
||||
void* mem, ManifoldExecutionContext* ec, ManifoldMeshGL64* mesh) {
|
||||
return to_c(new (mem) Manifold(from_c(ec)->FromMeshGL(*from_c(mesh))));
|
||||
}
|
||||
|
||||
ManifoldManifold* manifold_calculate_normals(void* mem, ManifoldManifold* m,
|
||||
int normal_idx,
|
||||
double min_sharp_angle) {
|
||||
@@ -956,7 +724,6 @@ size_t manifold_cross_section_size() { return sizeof(CrossSection); }
|
||||
size_t manifold_cross_section_vec_size() {
|
||||
return sizeof(std::vector<CrossSection>);
|
||||
}
|
||||
size_t manifold_ray_hit_vec_size() { return sizeof(RayHitVec); }
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
@@ -967,48 +734,32 @@ size_t manifold_meshgl64_size() { return sizeof(MeshGL64); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
size_t manifold_triangulation_size() { return sizeof(std::vector<ivec3>); }
|
||||
size_t manifold_execution_context_size() { return sizeof(ExecutionContext); }
|
||||
|
||||
// allocation
|
||||
//
|
||||
// Returns raw uninitialized storage. Callers must placement-new into it
|
||||
// via one of the constructor functions (e.g. manifold_cube) before any
|
||||
// use or before passing to manifold_destruct_* / manifold_delete_*.
|
||||
// Using `new T` here would silently leak the default-constructed object
|
||||
// every time a constructor function placement-new'd over it (see
|
||||
// Manifold's pNode_ and CrossSection's paths_ shared_ptrs).
|
||||
ManifoldManifold* manifold_alloc_manifold() {
|
||||
return to_c(alloc_raw<manifold::Manifold>());
|
||||
return to_c(new manifold::Manifold);
|
||||
}
|
||||
ManifoldManifoldVec* manifold_alloc_manifold_vec() {
|
||||
return to_c(alloc_raw<std::vector<manifold::Manifold>>());
|
||||
return to_c(new std::vector<manifold::Manifold>);
|
||||
}
|
||||
ManifoldCrossSection* manifold_alloc_cross_section() {
|
||||
return to_c(alloc_raw<CrossSection>());
|
||||
return to_c(new CrossSection);
|
||||
}
|
||||
ManifoldCrossSectionVec* manifold_alloc_cross_section_vec() {
|
||||
return to_c(alloc_raw<std::vector<CrossSection>>());
|
||||
}
|
||||
ManifoldRayHitVec* manifold_alloc_ray_hit_vec() {
|
||||
return to_c(alloc_raw<RayHitVec>());
|
||||
return to_c(new std::vector<CrossSection>);
|
||||
}
|
||||
ManifoldSimplePolygon* manifold_alloc_simple_polygon() {
|
||||
return to_c(alloc_raw<SimplePolygon>());
|
||||
return to_c(new SimplePolygon);
|
||||
}
|
||||
ManifoldPolygons* manifold_alloc_polygons() {
|
||||
return to_c(alloc_raw<std::vector<SimplePolygon>>());
|
||||
return to_c(new std::vector<SimplePolygon>);
|
||||
}
|
||||
ManifoldMeshGL* manifold_alloc_meshgl() { return to_c(alloc_raw<MeshGL>()); }
|
||||
ManifoldMeshGL64* manifold_alloc_meshgl64() {
|
||||
return to_c(alloc_raw<MeshGL64>());
|
||||
}
|
||||
ManifoldBox* manifold_alloc_box() { return to_c(alloc_raw<Box>()); }
|
||||
ManifoldRect* manifold_alloc_rect() { return to_c(alloc_raw<Rect>()); }
|
||||
ManifoldMeshGL* manifold_alloc_meshgl() { return to_c(new MeshGL); }
|
||||
ManifoldMeshGL64* manifold_alloc_meshgl64() { return to_c(new MeshGL64); }
|
||||
ManifoldBox* manifold_alloc_box() { return to_c(new Box); }
|
||||
ManifoldRect* manifold_alloc_rect() { return to_c(new Rect); }
|
||||
ManifoldTriangulation* manifold_alloc_triangulation() {
|
||||
return to_c(alloc_raw<std::vector<ivec3>>());
|
||||
}
|
||||
ManifoldExecutionContext* manifold_alloc_execution_context() {
|
||||
return to_c(alloc_raw<ExecutionContext>());
|
||||
return to_c(new std::vector<ivec3>);
|
||||
}
|
||||
|
||||
// pointer free + destruction
|
||||
@@ -1018,7 +769,6 @@ void manifold_delete_cross_section(ManifoldCrossSection* c) {
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec* csv) {
|
||||
delete from_c(csv);
|
||||
}
|
||||
void manifold_delete_ray_hit_vec(ManifoldRayHitVec* v) { delete from_c(v); }
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon* p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
@@ -1034,9 +784,6 @@ void manifold_delete_rect(ManifoldRect* r) { delete from_c(r); }
|
||||
void manifold_delete_triangulation(ManifoldTriangulation* m) {
|
||||
delete from_c(m);
|
||||
}
|
||||
void manifold_delete_execution_context(ManifoldExecutionContext* ctx) {
|
||||
delete from_c(ctx);
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection* cs) {
|
||||
@@ -1045,9 +792,6 @@ void manifold_destruct_cross_section(ManifoldCrossSection* cs) {
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec* csv) {
|
||||
from_c(csv)->~CrossSectionVec();
|
||||
}
|
||||
void manifold_destruct_ray_hit_vec(ManifoldRayHitVec* v) {
|
||||
from_c(v)->~RayHitVec();
|
||||
}
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon* p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
@@ -1063,41 +807,6 @@ void manifold_destruct_rect(ManifoldRect* r) { from_c(r)->~Rect(); }
|
||||
void manifold_destruct_triangulation(ManifoldTriangulation* m) {
|
||||
from_c(m)->~vector<ivec3>();
|
||||
}
|
||||
void manifold_destruct_execution_context(ManifoldExecutionContext* ctx) {
|
||||
from_c(ctx)->~ExecutionContext();
|
||||
}
|
||||
|
||||
// IO
|
||||
|
||||
#ifndef MANIFOLD_NO_IOSTREAM
|
||||
ManifoldManifold* manifold_read_obj(void* mem, char* obj_file) {
|
||||
std::istringstream iss(obj_file);
|
||||
Manifold m = Manifold::ReadOBJ(iss);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL64* manifold_meshgl64_read_obj(void* mem, char* obj_file) {
|
||||
std::istringstream iss(obj_file);
|
||||
MeshGL64 m = ReadOBJ(iss);
|
||||
return to_c(new (mem) MeshGL64(std::move(m)));
|
||||
}
|
||||
|
||||
void manifold_write_obj(ManifoldManifold* manifold,
|
||||
void (*callback)(char*, void*), void* args) {
|
||||
std::stringstream ss;
|
||||
const Manifold* m = from_c(manifold);
|
||||
m->WriteOBJ(ss);
|
||||
callback(ss.str().data(), args);
|
||||
}
|
||||
|
||||
void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
|
||||
void (*callback)(char*, void*), void* args) {
|
||||
std::stringstream ss;
|
||||
const MeshGL64* m = from_c(mesh);
|
||||
WriteOBJ(ss, *m);
|
||||
callback(ss.str().data(), args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold/meshIO.h"
|
||||
#include "manifold/types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
ManifoldMaterial* to_c(manifold::Material* m) {
|
||||
return reinterpret_cast<ManifoldMaterial*>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions* to_c(manifold::ExportOptions* m) {
|
||||
return reinterpret_cast<ManifoldExportOptions*>(m);
|
||||
}
|
||||
|
||||
manifold::Material* from_c(ManifoldMaterial* mat) {
|
||||
return reinterpret_cast<manifold::Material*>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions* from_c(ManifoldExportOptions* options) {
|
||||
return reinterpret_cast<manifold::ExportOptions*>(options);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// material
|
||||
|
||||
ManifoldMaterial* manifold_material(void* mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial* mat, double roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial* mat, double metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial* mat, ManifoldVec3 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions* manifold_export_options(void* mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions* options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions* options,
|
||||
ManifoldMaterial* mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
// mesh IO
|
||||
|
||||
void manifold_export_meshgl(const char* filename, ManifoldMeshGL* mesh,
|
||||
ManifoldExportOptions* options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
ManifoldMeshGL* manifold_import_meshgl(void* mem, const char* filename,
|
||||
int force_cleanup) {
|
||||
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
|
||||
return to_c(new (mem) MeshGL(m));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial* m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions* m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial* m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions* m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -60,7 +60,7 @@ int manifold_rect_contains_rect(ManifoldRect* a, ManifoldRect* b) {
|
||||
}
|
||||
|
||||
void manifold_rect_include_pt(ManifoldRect* r, double x, double y) {
|
||||
auto& rect = *from_c(r);
|
||||
auto rect = *from_c(r);
|
||||
auto p = vec2(x, y);
|
||||
rect.Union(p);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ set(
|
||||
${PROJECT_SOURCE_DIR}/src/manifold.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/constructors.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/sort.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/cross_section.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/cross_section/cross_section.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/polygon.cpp
|
||||
${PROJECT_SOURCE_DIR}/include/manifold/common.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
|
||||
|
||||
@@ -24,3 +24,28 @@ manifold__warp_batch__warp_func:
|
||||
:param warp_func: A function that takes multiple vertex positions as an
|
||||
ndarray[n, 3] and returns the new vertex positions. The result should have the
|
||||
same shape as the input.
|
||||
manifold__smooth__mesh_gl__sharpened_edges:
|
||||
Constructs a smooth version of the input mesh by creating tangents; this
|
||||
method will throw if you have supplied tangents with your mesh already. The
|
||||
actual triangle resolution is unchanged; use the Refine() method to
|
||||
interpolate to a higher-resolution curve.
|
||||
By default, every edge is calculated for maximum smoothness (very much
|
||||
approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
No higher-order derivatives are considered, as the interpolation is
|
||||
independent per triangle, only sharing constraints on their boundaries.
|
||||
:param mesh: input Mesh.
|
||||
:param sharpened_edges: If desired, you can supply a vector of sharpened
|
||||
halfedges, which should in general be a small subset of all halfedges. Order
|
||||
of entries doesn't matter, as each one specifies the desired smoothness
|
||||
(between zero and one, with one the default for all unspecified halfedges)
|
||||
and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
between triVert 0 and 1, etc).
|
||||
:param edge_smoothness: Smoothness values associated to each halfedge defined
|
||||
in sharpened_edges. At a smoothness value of zero, a sharp crease is made. The
|
||||
smoothness is interpolated along each edge, so the specified value should be
|
||||
thought of as an average. Where exactly two sharpened edges meet at a vertex,
|
||||
their tangents are rotated to be colinear so that the sharpened edge can be
|
||||
continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
can be sharpened by sharping all edges that are incident on it, allowing cones
|
||||
to be formed.
|
||||
|
||||
@@ -17,7 +17,6 @@ def all_cross_section():
|
||||
poly = [[0, 0], [1, 0], [1, 1]]
|
||||
c = CrossSection([np.array(poly)])
|
||||
c = CrossSection([poly])
|
||||
c = CrossSection.even_odd([poly])
|
||||
c = CrossSection() + c
|
||||
a = c.area()
|
||||
c = CrossSection.batch_boolean(
|
||||
@@ -31,7 +30,7 @@ def all_cross_section():
|
||||
c = CrossSection.batch_hull([c, c.translate((1, 0))])
|
||||
b = c.bounds()
|
||||
c = CrossSection.circle(1)
|
||||
c = CrossSection.batch_boolean([c, c.translate((1, 0))], OpType.Add)
|
||||
c = CrossSection.compose([c, c.translate((1, 0))])
|
||||
cs = c.decompose()
|
||||
m = c.extrude(1)
|
||||
c = c.hull()
|
||||
@@ -46,8 +45,6 @@ def all_cross_section():
|
||||
c = c.rotate(90)
|
||||
c = c.scale((2, 2))
|
||||
c = c.simplify()
|
||||
t = c.get_tolerance()
|
||||
c = c.set_tolerance(t)
|
||||
c = CrossSection.square((1, 1))
|
||||
p = c.to_polygons()
|
||||
c = c.transform([[1, 0, 0], [0, 1, 0]])
|
||||
@@ -100,44 +97,16 @@ def all_manifold():
|
||||
m = m.refine(2)
|
||||
m = m.refine_to_length(0.1)
|
||||
m = m.refine_to_tolerance(0.01)
|
||||
m = m.smooth_out()
|
||||
i = Manifold.reserve_ids(1)
|
||||
m = m.scale((1, 2, 3))
|
||||
m = m.set_properties(3, lambda pos, prop: pos)
|
||||
c = m.slice(0.5)
|
||||
m = Manifold.smooth(mesh, [0], [0.5])
|
||||
m = Manifold.sphere(1)
|
||||
m, n = m.split(m.translate((1, 0, 0)))
|
||||
m, n = m.split_by_plane((0, 0, 1), 0)
|
||||
e = m.status()
|
||||
ctx = ExecutionContext()
|
||||
_ = ctx.cancelled()
|
||||
_ = ctx.progress()
|
||||
ctx.cancel()
|
||||
m = m.with_context(ctx)
|
||||
# Real cancel assertion: a pre-cancelled ctx on a CsgOpNode returns
|
||||
# Cancelled (not NoError). Leaf nodes are trivially evaluated so use a
|
||||
# fresh union to ensure an actual CSG tree is traversed.
|
||||
cancelled_ctx = ExecutionContext()
|
||||
cancelled_ctx.cancel()
|
||||
tree = (Manifold.cube() + Manifold.sphere(1)).with_context(cancelled_ctx)
|
||||
assert tree.status() == Error.Cancelled
|
||||
# ctx-aware static factories: these ops have no source Manifold to attach a
|
||||
# context to via with_context, so they run on the context directly.
|
||||
ingest_ctx = ExecutionContext()
|
||||
assert ingest_ctx.from_mesh(Manifold.cube().to_mesh()).status() == Error.NoError
|
||||
assert ingest_ctx.progress() == 1.0
|
||||
sdf_ctx = ExecutionContext()
|
||||
sphere = sdf_ctx.level_set(
|
||||
lambda x, y, z: 1 - (x * x + y * y + z * z) ** 0.5,
|
||||
[-1.1, -1.1, -1.1, 1.1, 1.1, 1.1],
|
||||
0.2,
|
||||
)
|
||||
assert sphere.status() == Error.NoError
|
||||
# A pre-cancelled ctx propagates through a factory; this proves the ctx is
|
||||
# wired, since NoError + progress==1 also hold for an untouched ctx.
|
||||
cancel_factory_ctx = ExecutionContext()
|
||||
cancel_factory_ctx.cancel()
|
||||
cancelled = cancel_factory_ctx.from_mesh(Manifold.cube().to_mesh())
|
||||
assert cancelled.status() == Error.Cancelled
|
||||
m = Manifold.tetrahedron()
|
||||
mesh = m.to_mesh()
|
||||
ok = mesh.merge()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import numpy as np
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
small_cube = Manifold.cube([0.1, 0.1, 0.1], True)
|
||||
cube_vertices = small_cube.to_mesh().vert_properties[:, :3]
|
||||
star = Manifold.as_original(small_cube)
|
||||
for offset in [
|
||||
[[0.2, 0.0, 0.0]],
|
||||
[[-0.2, 0.0, 0.0]],
|
||||
[[0.0, 0.2, 0.0]],
|
||||
[[0.0, -0.2, 0.0]],
|
||||
[[0.0, 0.0, 0.2]],
|
||||
[[0.0, 0.0, -0.2]],
|
||||
]:
|
||||
star += Manifold.hull_points(np.concatenate((cube_vertices, offset), axis=0))
|
||||
|
||||
sphere = Manifold.sphere(0.6, 15)
|
||||
cube = Manifold.cube([1.0, 1.0, 1.0], True)
|
||||
sphereless_cube = cube - sphere
|
||||
return sphereless_cube.minkowski_sum(star)
|
||||
@@ -19,79 +19,50 @@ import numpy as np
|
||||
|
||||
|
||||
def run():
|
||||
# A smoothed manifold demonstrating manual smoothing by assigning tangents to
|
||||
# halfedges. Use refine() before export to see the curvature.
|
||||
# A smoothed manifold demonstrating selective edge sharpening with
|
||||
# smooth() and refine(), see more details at:
|
||||
# https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
height = 10
|
||||
radius = 30
|
||||
offset = 20
|
||||
wiggles = 12
|
||||
lean = 0.3
|
||||
frontal_sharpness = 1
|
||||
sharpness = 0.8
|
||||
n = 50
|
||||
|
||||
def lerp(a, b, t):
|
||||
return np.array(a) + (np.array(b) - np.array(a)) * t
|
||||
|
||||
triangles = []
|
||||
positions = [[-offset, 0, height], [-offset, 0, -height]]
|
||||
halfedge_tangent = []
|
||||
sharpenedEdges = []
|
||||
|
||||
len_ = np.pi * radius / (2 * wiggles)
|
||||
top_normal = np.array([-lean, 0, np.sqrt(1 - lean * lean)])
|
||||
delta = np.pi / wiggles
|
||||
center_tangents = [None] * (2 * wiggles)
|
||||
edge_tangents = [None] * (2 * wiggles)
|
||||
|
||||
for i in range(2 * wiggles):
|
||||
theta = i * delta
|
||||
amp = 0.5 * height * (np.cos(theta) + 1) / 2
|
||||
v = np.array(
|
||||
theta = (i - wiggles) * delta
|
||||
amp = 0.5 * height * max(np.cos(0.8 * theta), 0)
|
||||
|
||||
positions.append(
|
||||
[
|
||||
radius * np.cos(theta),
|
||||
radius * np.sin(theta),
|
||||
amp * (1 if i % 2 == 0 else -1),
|
||||
],
|
||||
dtype=np.float64,
|
||||
]
|
||||
)
|
||||
|
||||
positions.append(v.tolist())
|
||||
j = i + 1
|
||||
if j == 2 * wiggles:
|
||||
j = 0
|
||||
|
||||
center_tan = np.array([(v[0] + offset) / 2, v[1] / 2, (v[2] - height) / 2])
|
||||
center_tangents[i] = center_tan - np.dot(center_tan, top_normal) * top_normal
|
||||
edge_tangents[i] = np.array([-len_ * np.sin(theta), len_ * np.cos(theta), 0.0])
|
||||
smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2)
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([0, 2 + i, 2 + j])
|
||||
|
||||
for i in range(2 * wiggles):
|
||||
next_i = 0 if i + 1 == 2 * wiggles else i + 1
|
||||
|
||||
radial = center_tangents[i]
|
||||
next_radial = center_tangents[next_i]
|
||||
edge = edge_tangents[i]
|
||||
next_edge = -edge_tangents[next_i]
|
||||
sharpness = frontal_sharpness * (np.cos(i * delta) + 1) / 4
|
||||
up = lerp(
|
||||
np.array([0, 0, len_]),
|
||||
np.array([next_edge[1], -next_edge[0], 0]),
|
||||
sharpness,
|
||||
)
|
||||
down = lerp(
|
||||
np.array([0, 0, -len_]), np.array([-edge[1], edge[0], 0]), sharpness
|
||||
)
|
||||
|
||||
triangles.append([0, 2 + i, 2 + next_i])
|
||||
halfedge_tangent.extend([*radial.tolist(), 1])
|
||||
halfedge_tangent.extend([*edge.tolist(), 1])
|
||||
halfedge_tangent.extend([*up.tolist(), 1])
|
||||
|
||||
triangles.append([1, 2 + next_i, 2 + i])
|
||||
halfedge_tangent.extend([next_radial[0], next_radial[1], -next_radial[2], 1])
|
||||
halfedge_tangent.extend([*next_edge.tolist(), 1])
|
||||
halfedge_tangent.extend([*down.tolist(), 1])
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([1, 2 + j, 2 + i])
|
||||
|
||||
scallop = Mesh(
|
||||
tri_verts=np.array(triangles, np.int32),
|
||||
vert_properties=np.array(positions, np.float32),
|
||||
halfedge_tangent=np.array(halfedge_tangent, np.float32).reshape(-1, 3, 4),
|
||||
)
|
||||
|
||||
def colorCurvature(_pos, oldProp):
|
||||
@@ -101,8 +72,9 @@ def run():
|
||||
blue = [0, 0, 1]
|
||||
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]
|
||||
|
||||
edges, smoothing = zip(*sharpenedEdges)
|
||||
return (
|
||||
Manifold(scallop)
|
||||
Manifold.smooth(scallop, edges, smoothing)
|
||||
.refine(n)
|
||||
.calculate_curvature(-1, 0)
|
||||
.set_properties(3, colorCurvature)
|
||||
|
||||
@@ -92,7 +92,7 @@ collect(f"{base}/src/manifold.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/constructors.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/sort.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/sdf.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/cross_section.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/cross_section/cross_section.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/polygon.cpp", select_functions)
|
||||
collect(f"{base}/include/manifold/common.h", select_functions)
|
||||
|
||||
|
||||
@@ -320,17 +320,14 @@ NB_MODULE(manifold3d, m) {
|
||||
nb::arg("search_length"),
|
||||
"Returns the minimum gap between two manifolds."
|
||||
"Returns a double between 0 and searchLength.")
|
||||
.def("ray_cast", &Manifold::RayCast, nb::arg("origin"),
|
||||
nb::arg("endpoint"),
|
||||
"Cast a ray segment, returning all hits sorted by distance.")
|
||||
.def("winding_number", &Manifold::WindingNumber, nb::arg("points"),
|
||||
"Returns the winding number of this manifold around each of the "
|
||||
"given points. 0 means outside, non-zero means inside.")
|
||||
.def("calculate_normals", &Manifold::CalculateNormals,
|
||||
nb::arg("normal_idx") = 0, nb::arg("min_sharp_angle") = 52.5,
|
||||
nb::arg("normal_idx"), nb::arg("min_sharp_angle") = 60,
|
||||
manifold__calculate_normals__normal_idx__min_sharp_angle)
|
||||
.def("smooth_by_normals", &Manifold::SmoothByNormals,
|
||||
nb::arg("normal_idx") = 0, manifold__smooth_by_normals__normal_idx)
|
||||
nb::arg("normal_idx"), manifold__smooth_by_normals__normal_idx)
|
||||
.def("smooth_out", &Manifold::SmoothOut, nb::arg("min_sharp_angle") = 60,
|
||||
nb::arg("min_smoothness") = 0,
|
||||
manifold__smooth_out__min_sharp_angle__min_smoothness)
|
||||
.def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
|
||||
.def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
|
||||
manifold__refine_to_length__length)
|
||||
@@ -371,10 +368,6 @@ NB_MODULE(manifold3d, m) {
|
||||
.def("trim_by_plane", &Manifold::TrimByPlane, nb::arg("normal"),
|
||||
nb::arg("origin_offset"),
|
||||
manifold__trim_by_plane__normal__origin_offset)
|
||||
.def("minkowski_sum", &Manifold::MinkowskiSum, nb::arg("other"),
|
||||
manifold__minkowski_sum__other)
|
||||
.def("minkowski_difference", &Manifold::MinkowskiDifference,
|
||||
nb::arg("other"), manifold__minkowski_difference__other)
|
||||
.def(
|
||||
"slice",
|
||||
[](const Manifold& self, double height) {
|
||||
@@ -388,8 +381,6 @@ NB_MODULE(manifold3d, m) {
|
||||
},
|
||||
manifold__project)
|
||||
.def("status", &Manifold::Status, manifold__status)
|
||||
.def("with_context", &Manifold::WithContext, nb::arg("ctx"),
|
||||
manifold__with_context__ctx)
|
||||
.def(
|
||||
"bounding_box",
|
||||
[](const Manifold& self) {
|
||||
@@ -399,15 +390,48 @@ NB_MODULE(manifold3d, m) {
|
||||
},
|
||||
"Gets the manifold bounding box as a tuple "
|
||||
"(xmin, ymin, zmin, xmax, ymax, zmax).")
|
||||
.def_static(
|
||||
"smooth",
|
||||
[](const MeshGL& mesh, std::vector<size_t> sharpened_edges,
|
||||
std::vector<double> edge_smoothness) {
|
||||
if (sharpened_edges.size() != edge_smoothness.size()) {
|
||||
throw std::runtime_error(
|
||||
"sharpened_edges.size() != edge_smoothness.size()");
|
||||
}
|
||||
std::vector<Smoothness> vec(sharpened_edges.size());
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
|
||||
}
|
||||
return Manifold::Smooth(mesh, vec);
|
||||
},
|
||||
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
|
||||
nb::arg("edge_smoothness") = nb::list(),
|
||||
manifold__smooth__mesh_gl__sharpened_edges)
|
||||
.def_static(
|
||||
"smooth",
|
||||
[](const MeshGL64& mesh, std::vector<size_t> sharpened_edges,
|
||||
std::vector<double> edge_smoothness) {
|
||||
if (sharpened_edges.size() != edge_smoothness.size()) {
|
||||
throw std::runtime_error(
|
||||
"sharpened_edges.size() != edge_smoothness.size()");
|
||||
}
|
||||
std::vector<Smoothness> vec(sharpened_edges.size());
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
|
||||
}
|
||||
return Manifold::Smooth(mesh, vec);
|
||||
},
|
||||
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
|
||||
nb::arg("edge_smoothness") = nb::list(),
|
||||
// note: this is not a typo, the documentation is essentially the same
|
||||
// so we just use the 32 byte variant to avoid duplicating docstring
|
||||
// override...
|
||||
manifold__smooth__mesh_gl__sharpened_edges)
|
||||
.def_static("batch_boolean", &Manifold::BatchBoolean,
|
||||
nb::arg("manifolds"), nb::arg("op"),
|
||||
manifold__batch_boolean__manifolds__op)
|
||||
.def_static(
|
||||
"compose",
|
||||
[](const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Add);
|
||||
},
|
||||
nb::arg("manifolds"), manifold__compose__manifolds)
|
||||
.def_static("compose", &Manifold::Compose, nb::arg("manifolds"),
|
||||
manifold__compose__manifolds)
|
||||
.def_static("tetrahedron", &Manifold::Tetrahedron, manifold__tetrahedron)
|
||||
.def_static("cube", &Manifold::Cube,
|
||||
nb::arg("size") = std::make_tuple(1.0, 1.0, 1.0),
|
||||
@@ -480,8 +504,6 @@ NB_MODULE(manifold3d, m) {
|
||||
nb::c_contig>>& runOriginalID,
|
||||
std::optional<nb::ndarray<float, nb::shape<-1, 4, 3>,
|
||||
nb::c_contig>>& runTransform,
|
||||
const std::optional<
|
||||
nb::ndarray<uint8_t, nb::shape<-1>, nb::c_contig>>& runFlags,
|
||||
const std::optional<
|
||||
nb::ndarray<uint32_t, nb::shape<-1>, nb::c_contig>>& faceID,
|
||||
const std::optional<nb::ndarray<float, nb::shape<-1, 3, 4>,
|
||||
@@ -514,10 +536,6 @@ NB_MODULE(manifold3d, m) {
|
||||
toVector(runTransform->data(), runTransform->size());
|
||||
}
|
||||
|
||||
if (runFlags.has_value()) {
|
||||
out.runFlags = toVector(runFlags->data(), runFlags->size());
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector(faceID->data(), faceID->size());
|
||||
|
||||
@@ -532,7 +550,7 @@ NB_MODULE(manifold3d, m) {
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("run_flags") = nb::none(), nb::arg("face_id") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("tolerance") = 0)
|
||||
.def_prop_ro(
|
||||
"vert_properties",
|
||||
@@ -571,10 +589,7 @@ NB_MODULE(manifold3d, m) {
|
||||
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_ro("run_flags", &MeshGL::runFlags)
|
||||
.def_ro("face_id", &MeshGL::faceID)
|
||||
.def("backside", &MeshGL::Backside, nb::arg("run"))
|
||||
.def("has_normals", &MeshGL::HasNormals, nb::arg("run"))
|
||||
.def("merge", &MeshGL::Merge, mesh_gl__merge);
|
||||
|
||||
nb::class_<MeshGL64>(m, "Mesh64")
|
||||
@@ -595,8 +610,6 @@ NB_MODULE(manifold3d, m) {
|
||||
nb::c_contig>>& runOriginalID,
|
||||
std::optional<nb::ndarray<double, nb::shape<-1, 4, 3>,
|
||||
nb::c_contig>>& runTransform,
|
||||
const std::optional<
|
||||
nb::ndarray<uint8_t, nb::shape<-1>, nb::c_contig>>& runFlags,
|
||||
const std::optional<
|
||||
nb::ndarray<uint64_t, nb::shape<-1>, nb::c_contig>>& faceID,
|
||||
const std::optional<nb::ndarray<double, nb::shape<-1, 3, 4>,
|
||||
@@ -629,10 +642,6 @@ NB_MODULE(manifold3d, m) {
|
||||
toVector(runTransform->data(), runTransform->size());
|
||||
}
|
||||
|
||||
if (runFlags.has_value()) {
|
||||
out.runFlags = toVector(runFlags->data(), runFlags->size());
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector(faceID->data(), faceID->size());
|
||||
|
||||
@@ -647,7 +656,7 @@ NB_MODULE(manifold3d, m) {
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("run_flags") = nb::none(), nb::arg("face_id") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("tolerance") = 0)
|
||||
.def_prop_ro(
|
||||
"vert_properties",
|
||||
@@ -686,61 +695,9 @@ NB_MODULE(manifold3d, m) {
|
||||
.def_ro("merge_to_vert", &MeshGL64::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL64::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL64::runOriginalID)
|
||||
.def_ro("run_flags", &MeshGL64::runFlags)
|
||||
.def_ro("face_id", &MeshGL64::faceID)
|
||||
.def("backside", &MeshGL64::Backside, nb::arg("run"))
|
||||
.def("has_normals", &MeshGL64::HasNormals, nb::arg("run"))
|
||||
.def("merge", &MeshGL64::Merge, mesh_gl__merge);
|
||||
|
||||
nb::class_<RayHit>(m, "RayHit")
|
||||
.def_ro("face_id", &RayHit::faceID)
|
||||
.def_ro("distance", &RayHit::distance)
|
||||
.def_ro("position", &RayHit::position)
|
||||
.def_ro("normal", &RayHit::normal);
|
||||
|
||||
nb::class_<ExecutionContext>(m, "ExecutionContext")
|
||||
.def(nb::init<>())
|
||||
.def("cancel", &ExecutionContext::Cancel)
|
||||
.def("cancelled", &ExecutionContext::Cancelled)
|
||||
.def("progress", &ExecutionContext::Progress)
|
||||
// ctx-aware static factories: like Manifold.from_mesh / level_set, but
|
||||
// run under this context so progress / cancellation are observed (these
|
||||
// ops have no source Manifold to attach via with_context).
|
||||
.def(
|
||||
"from_mesh",
|
||||
[](ExecutionContext& ctx, const MeshGL& mesh) {
|
||||
return ctx.FromMeshGL(mesh);
|
||||
},
|
||||
nb::arg("mesh"),
|
||||
"Ingest a MeshGL into a Manifold under this context, so the ingest "
|
||||
"reports progress and observes cancellation.")
|
||||
.def(
|
||||
"from_mesh",
|
||||
[](ExecutionContext& ctx, const MeshGL64& mesh) {
|
||||
return ctx.FromMeshGL(mesh);
|
||||
},
|
||||
nb::arg("mesh"),
|
||||
"Ingest a MeshGL64 into a Manifold under this context, so the ingest "
|
||||
"reports progress and observes cancellation.")
|
||||
.def(
|
||||
"level_set",
|
||||
[](ExecutionContext& ctx,
|
||||
const std::function<double(double, double, double)>& f,
|
||||
std::vector<double> bounds, double edgeLength, double level,
|
||||
double tolerance) {
|
||||
Box bound = {vec3(bounds[0], bounds[1], bounds[2]),
|
||||
vec3(bounds[3], bounds[4], bounds[5])};
|
||||
std::function<double(vec3)> cppToPython = [&f](vec3 v) {
|
||||
return f(v.x, v.y, v.z);
|
||||
};
|
||||
return ctx.LevelSet(cppToPython, bound, edgeLength, level,
|
||||
tolerance, false);
|
||||
},
|
||||
nb::arg("f"), nb::arg("bounds"), nb::arg("edgeLength"),
|
||||
nb::arg("level") = 0.0, nb::arg("tolerance") = -1,
|
||||
"Like Manifold.level_set, run under this context so the level-set "
|
||||
"evaluation reports progress and observes cancellation.");
|
||||
|
||||
nb::enum_<Manifold::Error>(m, "Error")
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
@@ -755,10 +712,17 @@ NB_MODULE(manifold3d, m) {
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction)
|
||||
.value("ResultTooLarge", Manifold::Error::ResultTooLarge)
|
||||
.value("InvalidTangents", Manifold::Error::InvalidTangents)
|
||||
.value("Cancelled", Manifold::Error::Cancelled);
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
nb::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
|
||||
"Only odd numbered sub-regions are filled.")
|
||||
.value("NonZero", CrossSection::FillRule::NonZero,
|
||||
"Only non-zero sub-regions are filled.")
|
||||
.value("Positive", CrossSection::FillRule::Positive,
|
||||
"Only sub-regions with winding counts > 0 are filled.")
|
||||
.value("Negative", CrossSection::FillRule::Negative,
|
||||
"Only sub-regions with winding counts < 0 are filled.");
|
||||
|
||||
nb::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
.value("Square", CrossSection::JoinType::Square,
|
||||
@@ -797,14 +761,14 @@ NB_MODULE(manifold3d, m) {
|
||||
m, "CrossSection",
|
||||
"Two-dimensional cross sections guaranteed to be without "
|
||||
"self-intersections, or overlaps between polygons (from construction "
|
||||
"onwards). Polygon clipping (boolean) and offsetting use Manifold's own "
|
||||
"robust floating-point predicates.")
|
||||
"onwards). This class makes use of the "
|
||||
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
|
||||
"for polygon clipping (boolean) and offsetting operations.")
|
||||
.def(nb::init<>(), cross_section__cross_section)
|
||||
.def(nb::init<std::vector<std::vector<vec2>>>(), nb::arg("contours"),
|
||||
cross_section__cross_section__contours)
|
||||
.def_static("even_odd",
|
||||
nb::overload_cast<const Polygons&>(&CrossSection::EvenOdd),
|
||||
nb::arg("contours"), cross_section__even_odd__contours)
|
||||
.def(nb::init<std::vector<std::vector<vec2>>, CrossSection::FillRule>(),
|
||||
nb::arg("contours"),
|
||||
nb::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
cross_section__cross_section__contours__fillrule)
|
||||
.def("area", &CrossSection::Area, cross_section__area)
|
||||
.def("num_vert", &CrossSection::NumVert, cross_section__num_vert)
|
||||
.def("num_contour", &CrossSection::NumContour, cross_section__num_contour)
|
||||
@@ -861,12 +825,8 @@ NB_MODULE(manifold3d, m) {
|
||||
});
|
||||
},
|
||||
nb::arg("warp_func"), cross_section__warp_batch__warp_func)
|
||||
.def("simplify", &CrossSection::Simplify, nb::arg("tolerance") = 0,
|
||||
cross_section__simplify__tolerance)
|
||||
.def("get_tolerance", &CrossSection::GetTolerance,
|
||||
cross_section__get_tolerance)
|
||||
.def("set_tolerance", &CrossSection::SetTolerance, nb::arg("tolerance"),
|
||||
cross_section__set_tolerance__tolerance)
|
||||
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
|
||||
cross_section__simplify__epsilon)
|
||||
.def(
|
||||
"offset", &CrossSection::Offset, nb::arg("delta"),
|
||||
nb::arg("join_type") = CrossSection::JoinType::Round,
|
||||
@@ -890,6 +850,8 @@ NB_MODULE(manifold3d, m) {
|
||||
.def_static("batch_boolean", &CrossSection::BatchBoolean,
|
||||
nb::arg("cross_sections"), nb::arg("op"),
|
||||
cross_section__batch_boolean__cross_sections__op)
|
||||
.def_static("compose", &CrossSection::Compose, nb::arg("cross_sections"),
|
||||
cross_section__compose__cross_sections)
|
||||
.def("to_polygons", &CrossSection::ToPolygons, cross_section__to_polygons)
|
||||
.def(
|
||||
"extrude",
|
||||
|
||||
@@ -1,16 +1,2 @@
|
||||
/manifold.js
|
||||
/manifold.wasm
|
||||
/manifold-3d-*.tgz
|
||||
/manifold.d.ts
|
||||
/lib/*.js
|
||||
/lib/*.js.map
|
||||
/lib/*.d.ts
|
||||
/lib/*.d.ts.map
|
||||
/test/*.js
|
||||
/test/*.js.map
|
||||
/test/*.d.ts
|
||||
/test/*.d.ts.map
|
||||
/test/results
|
||||
/coverage
|
||||
/public
|
||||
dist
|
||||
manifold.js
|
||||
manifold.wasm
|
||||
|
||||
@@ -37,6 +37,8 @@ target_link_options(
|
||||
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
|
||||
|
||||
# ensure that interface files are copied over when modified
|
||||
add_custom_target(
|
||||
js_deps
|
||||
@@ -51,4 +53,12 @@ add_custom_command(
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# copy WASM build and TS declarations for Vite to package into ManifoldCAD.org
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/
|
||||
# copy TS declarations to public so they can be accessed by our editor
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/
|
||||
)
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
# About Manifold
|
||||
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
[**ManifoldCAD User Guide**](https://manifoldcad.org/docs/jsuser/) | [**JS/TS/WASM API**](https://manifoldcad.org/docs/jsapi/) | [**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold)
|
||||
[**TS Documentation**](https://manifoldcad.org/jsdocs) | [**C++ Documentation**](https://manifoldcad.org/docs/html/classmanifold_1_1_manifold.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://manifoldcad.org/model-viewer.html)
|
||||
|
||||
<!-- #region IncludeInUserGuide -->
|
||||
|
||||
A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. 'Manifold' implies that there are no gaps or tears, and that all faces are oriented outwards.
|
||||
|
||||
## Manifold Library
|
||||
|
||||
[ [Using Manifold](https://manifoldcad.org/docs/jsapi/documents/Using_Manifold.html) | [Examples](https://manifoldcad.org/docs/jsapi/documents/Manifold_Examples.html) | [Manifold WASM Developer Guide](https://manifoldcad.org/docs/jsapi/) ]
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. It is available as a WASM module that runs in any modern browser.
|
||||
|
||||
Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Manifold also supports arbitrary vertex properties and enables mapping of materials for rendering use-cases. Our primary goal is reliability: guaranteed manifold output without caveats or edge cases. Our secondary goal is performance: efficient algorithms that make extensive use of parallelization, or pipelining when only a single thread is available.
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
<img alt="A metallic Menger sponge" src="https://manifoldcad.org/samples/models/mengerSponge192.png" style="float:right">
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS and save a GLB or 3MF file. It contains several examples showing how to use our API to make interesting shapes. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, this library is dramatically faster and the code is more flexible.
|
||||
|
||||
[ [ManifoldCAD](https://manifoldcad.org) | [ManifoldCAD User Guide](https://manifoldcad.org/docs/jsuser/) ]
|
||||

|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS and save a GLB or 3MF file. It contains several examples showing how to use our API to make interesting shapes. You may notice that some of these examples bear a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, this library is dramatically faster and the code is more flexible.
|
||||
## Manifold Library
|
||||
|
||||
manifoldCAD = manifold + [TypeScript](https://www.typescriptlang.org/) + [glTF](https://www.khronos.org/gltf/)
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as a level set function for evaluating signed-distance functions (SDF) that improves significantly over Marching Cubes. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. We provide a [`Merge`](https://manifoldcad.org/docs/html/structmanifold_1_1_mesh_g_l_p.html) function to fix slightly non-manifold meshes, but in general you may need one of the automated repair tools that exist mostly for 3D printing.
|
||||
|
||||
<div style="clear:both"></div>
|
||||
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
|
||||
|
||||
<!-- #endregion IncludeInUserGuide -->
|
||||
<!-- #region NoStlPlease -->
|
||||
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. See our [web example](https://manifoldcad.org/model-viewer.html) for a simple demonstration of combining objects with unique textures.
|
||||
|
||||
Also included are a novel and powerful suite of refining functions for smooth mesh interpolation. They handle smoothing both triangles and quads, as well as keeping polygonal faces flat. You can easily create sharp or small-radius edges where desired, or even drive the curvature by normal vectors.
|
||||
|
||||
## Note on memory management
|
||||
|
||||
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each object constructed by your scripts (both `Manifold` and `CrossSection`), see [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_mesh_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## 3D Formats
|
||||
|
||||
[ [EXT_mesh_manifold](https://manifoldcad.org/docs/jsapi/modules/manifold-gltf.html) ]
|
||||
|
||||
Please avoid saving to STL files! They are lossy and inefficient - when saving a manifold mesh to STL there is no guarantee that the re-imported mesh will still be manifold, as the topology is lost. Please consider using [3MF](https://3mf.io/) instead, as this format was designed from the beginning for manifold meshes representing solid objects.
|
||||
|
||||
If you use vertex properties for things like interpolated normals or texture UV coordinates, [glTF](https://www.khronos.org/Gltf) is recommended, specifically using the [`EXT_mesh_manifold`](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md) extension. This allows for the lossless and efficient transmission of manifoldness even with property boundaries. Try our [make-manifold](https://manifoldcad.org/make-manifold) page to add this extension to your existing glTF/GLB models.
|
||||
|
||||
<!-- #endregion NoStlPlease -->
|
||||
|
||||
## About the author
|
||||
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/), currently a senior rendering engineer at Wētā FX. This was my 20% project when I was a Google employee, though my day job was maintaining [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "./manifold-root.d.ts",
|
||||
"compiler": {
|
||||
"overrideTsconfig": {
|
||||
"include": [
|
||||
"./manifold-global-types.d.ts",
|
||||
"./manifold-encapsulated-types.d.ts",
|
||||
"./manifold-root.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "./manifold.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-unresolved-link": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-incompatible-release-tags": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-internal-missing-underscore": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"tsdoc-malformed-html-name": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-param-tag-missing-hyphen": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-right-brace": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-greater-than": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-malformed-inline-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-undefined-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-reference-missing-hash": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "./types/manifoldCAD.d.ts",
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "./dist/manifoldCAD.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-unresolved-link": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-incompatible-release-tags": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-internal-missing-underscore": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"tsdoc-malformed-html-name": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-param-tag-missing-hyphen": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-right-brace": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-greater-than": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-malformed-inline-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-undefined-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-reference-missing-hash": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "./types/manifoldCADGlobals.d.ts",
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "./dist/manifoldCADGlobals.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-unresolved-link": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-incompatible-release-tags": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"ae-internal-missing-underscore": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"tsdoc-malformed-html-name": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-param-tag-missing-hyphen": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-right-brace": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-greater-than": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-malformed-inline-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-undefined-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-reference-missing-hash": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// Copyright 2025 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {readFile} from 'node:fs/promises';
|
||||
import {Command} from 'commander';
|
||||
|
||||
import {bundleFile} from '../lib/bundler.js';
|
||||
import * as worker from '../lib/worker.js';
|
||||
import * as exportModel from '../lib/export-model.js';
|
||||
|
||||
const program = new Command();
|
||||
program.name('manifold-cad')
|
||||
.description('Compile a ManifoldCAD program into a mesh.')
|
||||
.argument('<infile.[js|ts]>', 'Input file')
|
||||
.argument('<outfile>', 'Output file')
|
||||
.option('--jscdn <string>', 'Fetch npm packages from a CDN', 'jsDelivr')
|
||||
|
||||
// True means '--bundle' is implied by default, and '--no-bundle'
|
||||
// must be specified to disable bundling.
|
||||
.option('--no-bundle', 'Do not bundle input file before building model', true)
|
||||
.action(async(infile, outfile, options) => {
|
||||
// Choose an exporter. It will throw if we cannot match the extension.
|
||||
exportModel.supports(outfile, true);
|
||||
|
||||
// Build the model.
|
||||
let bundle = '';
|
||||
if (options.bundle) {
|
||||
bundle = await bundleFile(infile, {jsCDN: options.jscdn});
|
||||
} else {
|
||||
bundle = await readFile(infile, 'utf-8');
|
||||
}
|
||||
const doc = await worker.evaluate(bundle, {doNotBundle:true});
|
||||
|
||||
// Convert it and write it to disk.
|
||||
await exportModel.writeFile(outfile, doc);
|
||||
});
|
||||
|
||||
program.parseAsync(process.argv);
|
||||
@@ -15,8 +15,6 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.cpp"
|
||||
#include "manifold/cross_section.h"
|
||||
#include "manifold/manifold.h"
|
||||
@@ -81,10 +79,13 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction)
|
||||
.value("ResultTooLarge", Manifold::Error::ResultTooLarge)
|
||||
.value("InvalidTangents", Manifold::Error::InvalidTangents)
|
||||
.value("Cancelled", Manifold::Error::Cancelled);
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
enum_<CrossSection::FillRule>("fillrule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
|
||||
.value("NonZero", CrossSection::FillRule::NonZero)
|
||||
.value("Positive", CrossSection::FillRule::Positive)
|
||||
.value("Negative", CrossSection::FillRule::Negative);
|
||||
|
||||
enum_<CrossSection::JoinType>("jointype")
|
||||
.value("Square", CrossSection::JoinType::Square)
|
||||
@@ -95,29 +96,18 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
|
||||
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
value_object<RayHit>("rayHit")
|
||||
.field("faceID", &RayHit::faceID)
|
||||
.field("distance", &RayHit::distance)
|
||||
.field("position", &RayHit::position)
|
||||
.field("normal", &RayHit::normal);
|
||||
value_object<Smoothness>("smoothness")
|
||||
.field("halfedge", &Smoothness::halfedge)
|
||||
.field("smoothness", &Smoothness::smoothness);
|
||||
|
||||
class_<ExecutionContext>("ExecutionContext")
|
||||
.constructor<>()
|
||||
.function("cancel", &ExecutionContext::Cancel)
|
||||
.function("cancelled", &ExecutionContext::Cancelled)
|
||||
.function("progress", &ExecutionContext::Progress)
|
||||
.function("_FromMesh", &man_js::ExecutionContextFromMesh)
|
||||
.function("_LevelSet", &man_js::ExecutionContextLevelSet);
|
||||
|
||||
register_vector<RayHit>("Vector_rayHit");
|
||||
register_vector<ivec3>("Vector_ivec3");
|
||||
register_vector<vec3>("Vector_vec3");
|
||||
register_vector<vec2>("Vector_vec2");
|
||||
register_vector<std::vector<vec2>>("Vector2_vec2");
|
||||
register_vector<double>("Vector_f64");
|
||||
register_vector<int>("Vector_i32");
|
||||
register_vector<CrossSection>("Vector_crossSection");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<vec4>("Vector_vec4");
|
||||
|
||||
class_<CrossSection>("CrossSection")
|
||||
@@ -138,24 +128,22 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("numContour", &CrossSection::NumContour)
|
||||
.function("_Bounds", &CrossSection::Bounds)
|
||||
.function("_Simplify", &CrossSection::Simplify)
|
||||
.function("tolerance", &CrossSection::GetTolerance)
|
||||
.function("setTolerance", &CrossSection::SetTolerance)
|
||||
.function("_Offset", &cross_js::Offset)
|
||||
.function("_ToPolygons", &CrossSection::ToPolygons)
|
||||
.function("hull",
|
||||
select_overload<CrossSection() const>(&CrossSection::Hull));
|
||||
|
||||
// CrossSection Static Methods
|
||||
function("_EvenOdd", &cross_js::EvenOdd);
|
||||
function("_Square", &CrossSection::Square);
|
||||
function("_Circle", &CrossSection::Circle);
|
||||
function("_crossSectionCompose", &CrossSection::Compose);
|
||||
function("_crossSectionUnionN", &cross_js::UnionN);
|
||||
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
|
||||
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
|
||||
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
|
||||
function("_crossSectionHullPoints",
|
||||
select_overload<CrossSection(const std::vector<vec2>&)>(
|
||||
&CrossSection::Hull));
|
||||
function(
|
||||
"_crossSectionHullPoints",
|
||||
select_overload<CrossSection(std::vector<vec2>)>(&CrossSection::Hull));
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&man_js::FromMeshJS)
|
||||
@@ -165,8 +153,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("_Split", &man_js::Split)
|
||||
.function("_SplitByPlane", &man_js::SplitByPlane)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("minkowskiSum", &Manifold::MinkowskiSum)
|
||||
.function("minkowskiDifference", &Manifold::MinkowskiDifference)
|
||||
.function("_Slice", &Manifold::Slice)
|
||||
.function("_Project", &Manifold::Project)
|
||||
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
|
||||
@@ -174,9 +160,9 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("refineToLength", &Manifold::RefineToLength)
|
||||
.function("refineToTolerance", &Manifold::RefineToTolerance)
|
||||
.function("_SmoothByNormals", &Manifold::SmoothByNormals)
|
||||
.function("smoothByNormals", &Manifold::SmoothByNormals)
|
||||
.function("_SmoothOut", &Manifold::SmoothOut)
|
||||
.function("_Warp", &man_js::Warp)
|
||||
.function("_WarpBatch", &man_js::WarpBatch)
|
||||
.function("_SetProperties", &man_js::SetProperties)
|
||||
.function("transform", &man_js::Transform)
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
@@ -187,7 +173,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &man_js::Status)
|
||||
.function("withContext", &Manifold::WithContext)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.function("numEdge", &Manifold::NumEdge)
|
||||
.function("numTri", &Manifold::NumTri)
|
||||
@@ -196,13 +181,11 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("tolerance", &Manifold::GetTolerance)
|
||||
.function("setTolerance", &Manifold::SetTolerance)
|
||||
.function("_Simplify", &Manifold::Simplify)
|
||||
.function("simplify", &Manifold::Simplify)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("volume", &Manifold::Volume)
|
||||
.function("surfaceArea", &Manifold::SurfaceArea)
|
||||
.function("minGap", &Manifold::MinGap)
|
||||
.function("_RayCast", &man_js::RayCast)
|
||||
.function("_WindingNumber", &Manifold::WindingNumber)
|
||||
.function("calculateCurvature", &Manifold::CalculateCurvature)
|
||||
.function("_CalculateNormals", &Manifold::CalculateNormals)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
@@ -213,13 +196,14 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
function("_Sphere", &Manifold::Sphere);
|
||||
function("_Tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &js::Smooth);
|
||||
function("_Extrude", &Manifold::Extrude);
|
||||
function("_Triangulate", &Triangulate);
|
||||
function("_Revolve", &Manifold::Revolve);
|
||||
function("_LevelSet", &man_js::LevelSet);
|
||||
function("_Merge", &js::Merge);
|
||||
function("_ReserveIDs", &Manifold::ReserveIDs);
|
||||
function("_manifoldCompose", &man_js::UnionN);
|
||||
function("_manifoldCompose", &Manifold::Compose);
|
||||
function("_manifoldUnionN", &man_js::UnionN);
|
||||
function("_manifoldDifferenceN", &man_js::DifferenceN);
|
||||
function("_manifoldIntersectionN", &man_js::IntersectionN);
|
||||
|
||||
@@ -87,23 +87,27 @@ Module.setup = function() {
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function fillRuleToInt(fillRule) {
|
||||
return fillRule == 'EvenOdd' ? 0 :
|
||||
fillRule == 'NonZero' ? 1 :
|
||||
fillRule == 'Negative' ? 3 :
|
||||
/* Positive */ 2;
|
||||
}
|
||||
|
||||
function joinTypeToInt(joinType) {
|
||||
return joinType == 'Round' ? 1 :
|
||||
joinType == 'Miter' ? 2 :
|
||||
joinType == 'Bevel' ? 3 :
|
||||
/* Square */ 0;
|
||||
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
|
||||
}
|
||||
|
||||
// CrossSection methods
|
||||
|
||||
const CrossSectionCtor = Module.CrossSection;
|
||||
|
||||
function cross(polygons) {
|
||||
function cross(polygons, fillRule = 'Positive') {
|
||||
if (polygons instanceof CrossSectionCtor) {
|
||||
return polygons;
|
||||
} else {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
}
|
||||
@@ -160,8 +164,8 @@ Module.setup = function() {
|
||||
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.simplify = function(tolerance = 0) {
|
||||
return this._Simplify(tolerance);
|
||||
Module.CrossSection.prototype.simplify = function(epsilon = 1e-6) {
|
||||
return this._Simplify(epsilon);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.extrude = function(
|
||||
@@ -200,6 +204,11 @@ Module.setup = function() {
|
||||
|
||||
// Manifold methods
|
||||
|
||||
Module.Manifold.prototype.smoothOut = function(
|
||||
minSharpAngle = 60, minSmoothness = 0) {
|
||||
return this._SmoothOut(minSharpAngle, minSmoothness);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'double');
|
||||
@@ -221,36 +230,11 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.warpBatch = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(ptr, count) {
|
||||
const heapF64 = Module.HEAPF64 ?? HEAPF64;
|
||||
if (!heapF64) {
|
||||
throw new Error('WASM heap is not initialized (HEAPF64 unavailable)');
|
||||
}
|
||||
const verts = new Float64Array(heapF64.buffer, ptr, count * 3);
|
||||
|
||||
func(verts, count);
|
||||
}, 'vii');
|
||||
|
||||
const out = this._WarpBatch(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
|
||||
const status = out.status();
|
||||
if (status !== 'NoError') {
|
||||
throw new Module.ManifoldError(status);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.calculateNormals = function(
|
||||
normalIdx = 0, minSharpAngle = 52.5) {
|
||||
normalIdx, minSharpAngle = 60) {
|
||||
return this._CalculateNormals(normalIdx, minSharpAngle);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.smoothByNormals = function(normalIdx = 0) {
|
||||
return this._SmoothByNormals(normalIdx);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.setProperties = function(numProp, func) {
|
||||
const oldNumProp = this.numProp();
|
||||
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
|
||||
@@ -308,42 +292,18 @@ Module.setup = function() {
|
||||
|
||||
Module.Manifold.prototype.slice = function(height = 0.) {
|
||||
const polygonsVec = this._Slice(height);
|
||||
const result = new CrossSectionCtor(polygonsVec);
|
||||
const result = new CrossSectionCtor(polygonsVec, fillRuleToInt('Positive'));
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.project = function() {
|
||||
const polygonsVec = this._Project();
|
||||
const result = new CrossSectionCtor(polygonsVec);
|
||||
const result = new CrossSectionCtor(polygonsVec, fillRuleToInt('Positive'));
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.windingNumber = function(points) {
|
||||
const inVec = new Module.Vector_vec3();
|
||||
for (const p of points) inVec.push_back(vararg2vec3([p]));
|
||||
const outVec = this._WindingNumber(inVec);
|
||||
inVec.delete();
|
||||
const result = fromVec(outVec);
|
||||
outVec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rayCast = function(origin, endpoint) {
|
||||
const vec = this._RayCast(vararg2vec3([origin]), vararg2vec3([endpoint]));
|
||||
const result =
|
||||
fromVec(vec, hit => ({
|
||||
faceID: hit.faceID,
|
||||
distance: hit.distance,
|
||||
position: ['x', 'y', 'z'].map(f => hit.position[f]),
|
||||
normal: ['x', 'y', 'z'].map(f => hit.normal[f]),
|
||||
}));
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
Module.Manifold.prototype.split = function(manifold) {
|
||||
const vec = this._Split(manifold);
|
||||
const result = fromVec(vec);
|
||||
@@ -373,10 +333,6 @@ Module.setup = function() {
|
||||
};
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.simplify = function(tolerance = 0) {
|
||||
return this._Simplify(tolerance);
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
@@ -388,9 +344,7 @@ Module.setup = function() {
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform,
|
||||
runFlags,
|
||||
tolerance = 0
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
@@ -402,8 +356,6 @@ Module.setup = function() {
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
this.runFlags = runFlags;
|
||||
this.tolerance = tolerance;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
@@ -452,16 +404,6 @@ Module.setup = function() {
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
|
||||
backside(run) {
|
||||
return this.runFlags != null && run < this.runFlags.length &&
|
||||
(this.runFlags[run] & 1) !== 0;
|
||||
}
|
||||
|
||||
hasNormals(run) {
|
||||
return this.runFlags != null && run < this.runFlags.length &&
|
||||
(this.runFlags[run] & 2) !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
@@ -502,16 +444,8 @@ Module.setup = function() {
|
||||
break;
|
||||
case 'FaceIDWrongLength':
|
||||
message = 'Face ID vector has wrong length';
|
||||
break;
|
||||
case 'InvalidConstruction':
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
break;
|
||||
case 'ResultTooLarge':
|
||||
message = 'Result exceeds maximum size';
|
||||
break;
|
||||
case 'InvalidTangents':
|
||||
message = 'Invalid halfedge tangents';
|
||||
break;
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -528,22 +462,15 @@ Module.setup = function() {
|
||||
|
||||
// CrossSection Constructors
|
||||
|
||||
Module.CrossSection = function(polygons) {
|
||||
Module.CrossSection = function(polygons, fillRule = 'Positive') {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
};
|
||||
|
||||
Module.CrossSection.ofPolygons = function(polygons) {
|
||||
return new Module.CrossSection(polygons);
|
||||
};
|
||||
|
||||
Module.CrossSection.evenOdd = function(polygons) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = Module._EvenOdd(polygonsVec);
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
|
||||
return new Module.CrossSection(polygons, fillRule);
|
||||
};
|
||||
|
||||
Module.CrossSection.square = function(...args) {
|
||||
@@ -575,6 +502,7 @@ Module.setup = function() {
|
||||
};
|
||||
}
|
||||
|
||||
Module.CrossSection.compose = crossSectionBatchbool('Compose');
|
||||
Module.CrossSection.union = crossSectionBatchbool('UnionN');
|
||||
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
|
||||
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
|
||||
@@ -664,12 +592,20 @@ Module.setup = function() {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
sharp.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.extrude = function(
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0], center = false) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons);
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
|
||||
};
|
||||
|
||||
@@ -677,7 +613,7 @@ Module.setup = function() {
|
||||
polygons, circularSegments = 0, revolveDegrees = 360.0) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons);
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.revolve(circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
@@ -685,6 +621,14 @@ Module.setup = function() {
|
||||
return Module._ReserveIDs(n);
|
||||
};
|
||||
|
||||
Module.Manifold.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._manifoldCompose(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
function manifoldBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
@@ -697,10 +641,6 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
Module.Manifold.union = manifoldBatchbool('Union');
|
||||
// Aliasing compose to union.
|
||||
// Native compose has some issues, and is deprecated.
|
||||
Module.Manifold.compose = Module.Manifold.union;
|
||||
|
||||
Module.Manifold.difference = manifoldBatchbool('Difference');
|
||||
Module.Manifold.intersection = manifoldBatchbool('Intersection');
|
||||
|
||||
@@ -723,31 +663,6 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
// ctx-aware static factories: mirror Manifold.ofMesh / levelSet but
|
||||
// run under this ExecutionContext so progress/cancellation are observed.
|
||||
Module.ExecutionContext.prototype.fromMesh = function(mesh) {
|
||||
return this._FromMesh(mesh);
|
||||
};
|
||||
|
||||
Module.ExecutionContext.prototype.levelSet = function(
|
||||
sdf, bounds, edgeLength, level = 0, tolerance = -1) {
|
||||
const bounds2 = {
|
||||
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
|
||||
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
|
||||
};
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'double');
|
||||
const y = getValue(vec3Ptr + 8, 'double');
|
||||
const z = getValue(vec3Ptr + 16, 'double');
|
||||
const vert = [x, y, z];
|
||||
return sdf(vert);
|
||||
}, 'di');
|
||||
const out =
|
||||
this._LevelSet(wasmFuncPtr, bounds2, edgeLength, level, tolerance);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
function pushVec3(vec, ps) {
|
||||
toVec(vec, ps, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Get Started
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
In your project root folder run:
|
||||
|
||||
```bash
|
||||
npm i manifold-3d
|
||||
```
|
||||
|
||||
To start using Manifold, import it and initialize it:
|
||||
|
||||
```js
|
||||
import Module from 'manifold-3d';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const { Manifold } = wasm;
|
||||
```
|
||||
|
||||
Intro example
|
||||
|
||||
```js
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
const result = box.subtract(ball);
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
In order to visualize Manifold mesh using Three.js library please check out our example [here](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/three.ts)
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Manifold Examples
|
||||
group: none
|
||||
---
|
||||
# Manifold Examples
|
||||
|
||||
## Interoperation with three.js
|
||||
|
||||
{@include ../examples/three/README.md#summary}
|
||||
|
||||
## <model-viewer>
|
||||
|
||||
{@include ../examples/model-viewer/README.md#summary}
|
||||
|
||||
## Manifoldness and EXT_mesh_manifold
|
||||
|
||||
{@include ../examples/make-manifold/README.md#summary}
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: Using Manifold
|
||||
group: none
|
||||
---
|
||||
# Using Manifold
|
||||
|
||||
## Installation
|
||||
|
||||
In your project root folder run:
|
||||
|
||||
```bash
|
||||
npm install --save manifold-3d
|
||||
```
|
||||
|
||||
To start using Manifold, import it and initialize it:
|
||||
|
||||
```js
|
||||
import Module from 'manifold-3d';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
const { Manifold } = wasm;
|
||||
```
|
||||
|
||||
Intro example
|
||||
|
||||
```js
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
const result = box.subtract(ball);
|
||||
```
|
||||
|
||||
## Memory Management
|
||||
|
||||
> [!WARNING]
|
||||
> Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript.
|
||||
|
||||
You must manually `delete()` each object constructed by your scripts (both `Manifold` and `CrossSection`).
|
||||
See [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
See [Examples](./bindings-examples.md).
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: Contributing
|
||||
group: none
|
||||
category: none
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
|
||||
```
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
|
||||
```
|
||||
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
|
||||
|
||||
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
|
||||
deployed version) note that it uses a service worker for faster loading. This
|
||||
means you need to open the page twice to see updates (the first time loads the
|
||||
old version and caches the new one, the second time loads the new version from
|
||||
cache). To see changes on each reload, open Chrome dev tools, go to the
|
||||
Application tab and check "update on reload".
|
||||
|
||||
## Documentation
|
||||
|
||||
This guide and the ManifoldCAD User Guide are built using [TypeDoc](https://typedoc.org).
|
||||
|
||||
### Building Documentation
|
||||
|
||||
Documentation can be built using NPM:
|
||||
|
||||
```bash
|
||||
cd bindings/wasm
|
||||
npm install
|
||||
npm run build
|
||||
npm run docs
|
||||
```
|
||||
|
||||
After this, the User and Developer docs will be in `docs/jsuser` and `docs/jsapi` respectively.
|
||||
|
||||
### Writing for the User Guide
|
||||
|
||||
The ManifoldCAD User Guide is generated from the declaration files present in `types/`. These mostly re-export declarations, allowing the User Guide to inherit from the Developer Guide.
|
||||
|
||||
TypeDoc reflections (comments) flagged as `@internal` will _not_ be visible in the User Guide, but will be present in the Developer Guide. Reflections tagged as `@hidden` will not be visible in either guide.
|
||||
|
||||
### WASM Bindings
|
||||
|
||||
The manifold library itself is documented in three files:
|
||||
|
||||
* `manifold-global-types.d.ts`: Defines lower level types and interfaces like {@link manifold.Vec2 | Vec2}, {@link manifold.Mat3 | Mat3}, &c.
|
||||
* `manifold-encapsulated-types.d.ts`: Declares objects and types that are implemented in C++, and compiled to WASM. Definitions of classes like {@link manifold.Manifold | Manifold} and {@link manifold.CrossSection | CrossSection} can be found here.
|
||||
* `manifold-root.d.ts`: Defines the {@link manifold.ManifoldToplevel | ManifoldToplevel} interface, and re-exports the above two files.
|
||||
|
||||
These three files are consolidated into `manifold.d.ts` using [API Extractor](https://api-extractor.com/). This is automatically run before each build as an npm lifecycle hook. It can be run manually as `npm run prebuild`.
|
||||
|
||||
### Terminology
|
||||
|
||||
These are general guidelines, not hard rules. The aim of this section is to encourage consistent documentation, not to institute brand standards.
|
||||
|
||||
* When referring to the library, manifold should generally be lowercase. Manifold can be capitalized when grammatically correct, for example at the start of a sentence. If unclear, `manifold` can be marked up as code to distinguish it from the manifold property.
|
||||
* The Manifold class should generally be capitalized. Where appropriate, it can be linked directly to the {@link manifold.Manifold | Manifold} class documentation.
|
||||
* ManifoldCAD should be capitalized. [ManifoldCAD.org](https://manifoldcad.org) should be a link.
|
||||
@@ -1,6 +0,0 @@
|
||||
@layer typedoc {
|
||||
:root {
|
||||
--light-color-background-secondary: #c0c000;
|
||||
--dark-color-background-secondary: #606000;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: Embedding ManifoldCAD
|
||||
group: ManifoldCAD
|
||||
---
|
||||
# Embedding ManifoldCAD
|
||||
|
||||
There are three ways to use manifold in your own projects:
|
||||
|
||||
1. Using the ManifoldCAD worker. The worker adds overhead, but it will sandbox scripts. It also makes it possible to include packages at runtime.
|
||||
1. Using ManifoldCAD libraries directly. ManifoldCAD implements importers, exporters, and garbage collection amongst other utilities. These modules are reasonably light and can be used outside of manifoldCAD.
|
||||
1. Using the [Manifold WASM module directly](./bindings.md) without any of the above.
|
||||
|
||||
## Using the ManifoldCAD worker
|
||||
|
||||
The worker is a code evaluation sandbox intended to run as a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). It will bundle a script, include any dependencies, and even fetch those dependencies from a CDN if required. At run time, it will evaluate the script as a [dynamically created function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function).
|
||||
|
||||
See:
|
||||
|
||||
* {@link lib/worker | worker API}
|
||||
* [manifold-cad](https://github.com/elalish/manifold/blob/master/bindings/wasm/bin/manifold-cad) CLI
|
||||
|
||||
### Detecting the worker
|
||||
|
||||
> [!NOTE]
|
||||
> There are slight behaviour differences between scripts running inside the manifoldCAD worker, and those outside.
|
||||
>
|
||||
> See our [import integration tests](https://github.com/elalish/manifold/blob/master/bindings/wasm/test/import.test.ts) for examples.
|
||||
|
||||
When evaluated through the worker, {@link manifoldCAD!isManifoldCAD | isManifoldCAD()} will return true. Otherwise it returns false.
|
||||
|
||||
## Using the ManifoldCAD runtime directly
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
title: Tips and Tricks
|
||||
---
|
||||
## Handling Precision Issues
|
||||
|
||||
> [!WARNING]
|
||||
> Geometry where two manifolds are meant to share a face exactly is called **marginal geometry** and should generally be avoided. Manifold uses symbolic perturbation, which means multiple topologically valid results may be within rounding error of each other — so even small imprecisions can produce unexpected output.
|
||||
|
||||
Two common sources of trouble:
|
||||
|
||||
- **Floating point drift** — JavaScript uses IEEE 754 binary floating point, so `0.1 + 0.2` evaluates to `0.30000000000000004`. Manifold does not compensate for this in its transform pipeline. A bounding box maximum of `0.30000000000000004` instead of `0.3` is enough to prevent a union from closing properly.
|
||||
- **Different triangulations** — even with floating-point identical coordinates, if two coplanar faces are triangulated differently, that can also lead to gaps and slivers.
|
||||
|
||||
Where marginal geometry is unavoidable, the recommended workaround for floating point drift is to **work in larger, integer-friendly units**:
|
||||
|
||||
1. Scale coordinates up by a round factor (e.g., 1000) so your values are whole numbers
|
||||
2. Use `Math.round()` after any arithmetic to eliminate accumulated drift
|
||||
3. Perform all Manifold operations
|
||||
4. Scale the result back down if needed
|
||||
|
||||
```js
|
||||
const scale = 1000;
|
||||
|
||||
// Instead of cube([0.1, 0.1, 0.1]) translated by [0.2, 0.2, 0.2]:
|
||||
const size = Math.round(0.1 * scale); // 100
|
||||
const offset = Math.round(0.2 * scale); // 200
|
||||
|
||||
const { cube } = Manifold;
|
||||
const box = cube([size, size, size]).translate([offset, offset, offset]);
|
||||
```
|
||||
|
||||
See [discussion #1135](https://github.com/elalish/manifold/discussions/1135) for more context.
|
||||
|
||||
## Degrees vs. Radians
|
||||
|
||||
Manifold's rotation API takes **degrees**, not radians. This is a deliberate design choice: Manifold can eliminate floating point error entirely for multiples of 90°, and uses more efficient code paths when it detects such rotations.
|
||||
|
||||
```js
|
||||
// Pass degrees directly
|
||||
box.rotate([90, 0, 0]);
|
||||
box.rotate([0, 45, 0]);
|
||||
```
|
||||
|
||||
If your own code works in radians (e.g. when using `Math.atan2`), convert to degrees before passing to Manifold:
|
||||
|
||||
```js
|
||||
const toDeg = (rad) => rad * (180 / Math.PI);
|
||||
|
||||
const angle = Math.atan2(y, x); // radians
|
||||
box.rotate([0, 0, toDeg(angle)]);
|
||||
```
|
||||
|
||||
For common angles, always prefer the exact degree value (`90`, `45`, `30`, etc.) over a computed approximation to get the precision guarantee. See [issue #1262](https://github.com/elalish/manifold/issues/1262) for a real-world example where a rotation of `-89.999999999999` instead of `-90` caused mesh cracks.
|
||||
@@ -1,12 +0,0 @@
|
||||
@layer typedoc {
|
||||
:root {
|
||||
--light-color-background-secondary: #A288B3;
|
||||
--dark-color-background-secondary: #675772;
|
||||
}
|
||||
/* Hide right-hand navigation bar.
|
||||
* It is redundant for such a small site.
|
||||
*/
|
||||
.tsd-page-navigation {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: Using ManifoldCAD
|
||||
---
|
||||
## Running ManifoldCAD
|
||||
|
||||
### With a Browser
|
||||
|
||||
Just go to [ManifoldCAD.org](https://manifoldcad.org), and check out the examples!
|
||||
|
||||
### On the Command Line
|
||||
|
||||
ManifoldCAD has a [command line interface](https://github.com/elalish/manifold/blob/master/bindings/wasm/bin/manifold-cad). It can be run directly as `./bin/manifold-cad`. It can also be run via `npx`. If Manifold is not already present, use `npx manifold-3d manifold-cad` or `npx manifold-3d`. If Manifold is already installed, `npx manifold-cad` will suffice.
|
||||
|
||||
```
|
||||
Usage: manifold-cad [options] <infile.js> <outfile>
|
||||
```
|
||||
|
||||
The output file can be in either `.glb` or `.3mf` format, determined by extension.
|
||||
|
||||
## Coordinate Systems
|
||||
|
||||
ManifoldCAD uses a right-handed coordinate system where '+Z' is up. Length units are assumed to be millimetres. This is slightly at odds with glTF, which also uses a right handed coordinate system, but specifies '+Y' as up, and length units are in metres.
|
||||
|
||||
ManifoldCAD will scale and rotate automatically when importing or exporting glTF models. This means that an arrow pointing in the '+Z' direction in will point in the '+Y' direction after export. However, that arrow will still point up, and will still be the same size.
|
||||
@@ -1,14 +0,0 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: ['lib/worker.ts'],
|
||||
bundle: true,
|
||||
outfile: 'dist/worker.bundled.js',
|
||||
format: 'esm',
|
||||
target: 'esnext',
|
||||
sourcemap: 'inline',
|
||||
sourcesContent: false,
|
||||
external: [
|
||||
'node:path', 'node:url', 'node:fs', 'node:module', 'module',
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
manifold*
|
||||
@@ -0,0 +1,45 @@
|
||||
# WASM/JS Examples
|
||||
|
||||
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
|
||||
|
||||
## Local development
|
||||
|
||||
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
To develop the manifoldCAD.org editor as well as our other example pages, run
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
See `package.json` for other useful scripts.
|
||||
|
||||
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
|
||||
|
||||
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
|
||||
```
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
|
||||
```
|
||||
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
|
||||
|
||||
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
|
||||
deployed version) note that it uses a service worker for faster loading. This
|
||||
means you need to open the page twice to see updates (the first time loads the
|
||||
old version and caches the new one, the second time loads the new version from
|
||||
cache). To see changes on each reload, open Chrome dev tools, go to the
|
||||
Application tab and check "update on reload".
|
||||
|
||||
### Note for firefox users
|
||||
|
||||
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository.
|
||||
@@ -0,0 +1,34 @@
|
||||
import {beforeAll, expect, suite, test} from 'vitest';
|
||||
|
||||
import Module, {type ManifoldToplevel} from './built/manifold'
|
||||
|
||||
let manifoldModule: ManifoldToplevel;
|
||||
|
||||
beforeAll(async () => {
|
||||
manifoldModule = await Module();
|
||||
manifoldModule.setup();
|
||||
});
|
||||
|
||||
suite('CrossSection Bindings', () => {
|
||||
test('ToPolygons return correct shape', () => {
|
||||
const polygons = manifoldModule.CrossSection.square().toPolygons();
|
||||
expect(polygons).toHaveLength(1);
|
||||
expect(polygons[0]).toHaveLength(4);
|
||||
expect(polygons[0]).toContainEqual([0, 0]);
|
||||
expect(polygons[0]).toContainEqual([0, 1]);
|
||||
expect(polygons[0]).toContainEqual([1, 0]);
|
||||
expect(polygons[0]).toContainEqual([1, 1]);
|
||||
});
|
||||
|
||||
test('project creates a valid polygon', () => {
|
||||
const cs = manifoldModule.Manifold.sphere(1).project();
|
||||
expect(cs.numContour()).toEqual(1);
|
||||
expect(cs.area()).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
test('simplify argument is defaulted', () => {
|
||||
const cs = manifoldModule.CrossSection.circle(1).simplify();
|
||||
expect(cs.numContour()).toEqual(1);
|
||||
expect(cs.area()).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,6 @@ html {
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
@@ -83,7 +82,7 @@ button:hover {
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 8px;
|
||||
margin: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -91,17 +90,15 @@ model-viewer {
|
||||
position: relative;
|
||||
flex: 3;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#console {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
/* draggable pane dividers */
|
||||
max-height: none;
|
||||
/* draggable pane dividers-end */
|
||||
max-height: 25vh;
|
||||
overflow-y: scroll;
|
||||
background-color: #fff;
|
||||
background-color: #eee;
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
font-weight: 100;
|
||||
@@ -110,25 +107,11 @@ model-viewer {
|
||||
}
|
||||
|
||||
.page {
|
||||
/* draggable pane dividers */
|
||||
--left-pane: 50%;
|
||||
--viewer-pane: 75%;
|
||||
/* draggable pane dividers-end */
|
||||
|
||||
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
/* draggable pane dividers */
|
||||
.workbench {
|
||||
min-height: 0;
|
||||
}
|
||||
/* draggable pane dividers-end */
|
||||
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
@@ -137,45 +120,6 @@ model-viewer {
|
||||
position: relative;
|
||||
width: 50vw;
|
||||
}
|
||||
/* draggable pane dividers-start */
|
||||
.pane-left {
|
||||
width: auto;
|
||||
flex: 0 0 var(--left-pane);
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.pane-right {
|
||||
width: auto;
|
||||
flex: 1 1 auto;
|
||||
min-width: 220px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
flex: 0 0 var(--viewer-pane);
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
.splitter {
|
||||
flex: 0 0 8px;
|
||||
background: #d8c5e5;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.splitter:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.splitter-x {
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.splitter-y {
|
||||
cursor: row-resize;
|
||||
}
|
||||
/* draggable pane dividers-end */
|
||||
|
||||
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
@@ -184,13 +128,9 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #A288B3;
|
||||
}
|
||||
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 8px;
|
||||
margin: 18px 8px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
@@ -198,33 +138,8 @@ model-viewer {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-height: 85dvh;
|
||||
overflow: auto;
|
||||
/* Scrollbar styles for Firefox */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #777 transparent;
|
||||
}
|
||||
|
||||
/* Minimal, borderless scrollbar for dropdown (Windows/Chrome/Edge/Safari) */
|
||||
.dropdown::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropdown::-webkit-scrollbar-thumb {
|
||||
background: #777;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropdown::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-scrollable-element > .scrollbar {
|
||||
z-index: 0 !important;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.show {
|
||||
@@ -253,22 +168,6 @@ model-viewer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#fileDropdown .item > button.item {
|
||||
/* Keep room on the right for the trash icon. */
|
||||
width: 100%;
|
||||
padding-right: 42px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#fileDropdown .item > button.item > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.top-edit {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@@ -276,10 +175,6 @@ model-viewer {
|
||||
top: 18px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
-webkit-border-radius: 18px;
|
||||
-moz-border-radius: 18px;
|
||||
-ms-border-radius: 18px;
|
||||
-o-border-radius: 18px;
|
||||
}
|
||||
|
||||
.top-icon {
|
||||
@@ -293,7 +188,7 @@ model-viewer {
|
||||
}
|
||||
|
||||
.squish {
|
||||
margin-right: -8px;
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -324,23 +219,6 @@ model-viewer {
|
||||
background-image: url(/icons/share.png);
|
||||
}
|
||||
|
||||
.docs {
|
||||
background-image: url(/icons/docs.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
-webkit-border-radius: 18px;
|
||||
-moz-border-radius: 18px;
|
||||
-ms-border-radius: 18px;
|
||||
-o-border-radius: 18px;
|
||||
}
|
||||
|
||||
.edges {
|
||||
background-image: url(/icons/edges.png);
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-image: url(/icons/close.png);
|
||||
background-size: 20px;
|
||||
@@ -350,6 +228,7 @@ model-viewer {
|
||||
|
||||
.edit {
|
||||
background-image: url(/icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
@@ -365,11 +244,11 @@ model-viewer {
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.top-icon:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.blue {
|
||||
@@ -389,28 +268,6 @@ model-viewer {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#current {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#current.renaming {
|
||||
/* While typing, show full text (no three dots). */
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
#save {
|
||||
width: 125px;
|
||||
}
|
||||
@@ -426,7 +283,7 @@ model-viewer {
|
||||
|
||||
#play {
|
||||
position: relative;
|
||||
background-color: #0004;
|
||||
background-color: #ccc;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@@ -438,7 +295,7 @@ model-viewer {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 16px;
|
||||
background: #0002;
|
||||
background: #ffffff55;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -448,7 +305,7 @@ model-viewer {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #0004;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -460,29 +317,19 @@ model-viewer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* draggable pane dividers */
|
||||
@media only screen and (max-width: 820px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.splitter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
.pane-left,
|
||||
.pane-right {
|
||||
min-width: 0;
|
||||
}
|
||||
/* draggable pane dividers-end */
|
||||
|
||||
|
||||
#file-margin {
|
||||
width: calc(100% - 32px);
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -0,0 +1,536 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import ManifoldWorker from './worker-wrapper?worker';
|
||||
|
||||
const CODE_START = '<code>';
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register(
|
||||
'/service-worker.js', {scope: './index.html'});
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// Edit UI ------------------------------------------------------------
|
||||
|
||||
const undoButton = document.querySelector('#undo');
|
||||
const redoButton = document.querySelector('#redo');
|
||||
const formatButton = document.querySelector('#format');
|
||||
const shareButton = document.querySelector('#share');
|
||||
|
||||
undoButton.onclick = () => editor.trigger('ignored', 'undo');
|
||||
redoButton.onclick = () => editor.trigger('ignored', 'redo');
|
||||
formatButton.onclick = () =>
|
||||
editor.trigger('ignored', 'editor.action.formatDocument');
|
||||
shareButton.onclick = () => {
|
||||
const url = new URL(window.location.toString());
|
||||
url.hash =
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
currentFileElement.textContent + CODE_START + editor.getValue());
|
||||
navigator.clipboard.writeText(url.toString());
|
||||
console.log('Shareable link copied to clipboard!');
|
||||
console.log('Consider shortening this URL using tinyURL.com');
|
||||
};
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentFileElement = document.querySelector('#current');
|
||||
const fileArrow = document.querySelector('#file .uparrow');
|
||||
const fileDropdown = document.querySelector('#fileDropdown');
|
||||
const saveContainer = document.querySelector('#save');
|
||||
const saveDropdown = document.querySelector('#saveDropdown');
|
||||
const saveArrow = document.querySelector('#save .uparrow');
|
||||
|
||||
const hideDropdown = function() {
|
||||
fileDropdown.classList.remove('show');
|
||||
saveDropdown.classList.remove('show');
|
||||
fileArrow.classList.remove('down');
|
||||
saveArrow.classList.remove('down');
|
||||
};
|
||||
const toggleFileDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
fileDropdown.classList.toggle('show');
|
||||
fileArrow.classList.toggle('down');
|
||||
};
|
||||
const toggleSaveDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
saveDropdown.classList.toggle('show');
|
||||
saveArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleFileDropdown;
|
||||
saveArrow.parentElement.onclick = toggleSaveDropdown;
|
||||
document.body.onclick = hideDropdown;
|
||||
|
||||
const prefix = 'ManifoldCAD';
|
||||
function getScript(name) {
|
||||
return window.localStorage.getItem(prefix + name);
|
||||
}
|
||||
function setScript(name, code) {
|
||||
window.localStorage.setItem(prefix + name, code);
|
||||
}
|
||||
function removeScript(name) {
|
||||
window.localStorage.removeItem(prefix + name);
|
||||
}
|
||||
function nthKey(n) {
|
||||
if (n >= window.localStorage.length) return;
|
||||
const key = window.localStorage.key(n);
|
||||
if (key.startsWith(prefix)) {
|
||||
return key.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
function saveCurrent() {
|
||||
if (editor) {
|
||||
const currentName = currentFileElement.textContent;
|
||||
if (!exampleFunctions.get(currentName)) {
|
||||
setScript(currentName, editor.getValue());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.onpagehide = saveCurrent;
|
||||
window.beforeunload = saveCurrent;
|
||||
|
||||
let switching = false;
|
||||
let isExample = true;
|
||||
function switchTo(scriptName) {
|
||||
if (editor) {
|
||||
switching = true;
|
||||
currentFileElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
isExample = exampleFunctions.get(scriptName) != null;
|
||||
const code = isExample ? exampleFunctions.get(scriptName).substring(1) :
|
||||
getScript(scriptName) ?? '';
|
||||
window.location.hash = '#' + scriptName;
|
||||
editor.setValue(code);
|
||||
}
|
||||
}
|
||||
|
||||
function createDropdownItem(name) {
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('item');
|
||||
const button = document.createElement('button');
|
||||
container.appendChild(button);
|
||||
button.type = 'button';
|
||||
button.classList.add('blue', 'item');
|
||||
const label = document.createElement('span');
|
||||
button.appendChild(label);
|
||||
label.textContent = name;
|
||||
|
||||
button.onclick = function() {
|
||||
saveCurrent();
|
||||
switchTo(label.textContent);
|
||||
};
|
||||
// Stop text input spaces from triggering the button
|
||||
button.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
function addIcon(button) {
|
||||
const icon = document.createElement('button');
|
||||
icon.classList.add('icon');
|
||||
button.parentElement.appendChild(icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
function uniqueName(name) {
|
||||
let num = 1;
|
||||
let newName = name;
|
||||
while (getScript(newName) != null || exampleFunctions.get(newName) != null) {
|
||||
newName = name + ' ' + num++;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.classList.add('edit');
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
const oldName = label.textContent;
|
||||
const code = getScript(oldName);
|
||||
const form = document.createElement('form');
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.classList.add('name');
|
||||
inputElement.value = oldName;
|
||||
label.textContent = '';
|
||||
button.appendChild(form);
|
||||
form.appendChild(inputElement);
|
||||
inputElement.focus();
|
||||
inputElement.setSelectionRange(0, oldName.length);
|
||||
|
||||
function rename() {
|
||||
const input = inputElement.value;
|
||||
inputElement.blur();
|
||||
if (!input) return;
|
||||
const newName = uniqueName(input);
|
||||
label.textContent = newName;
|
||||
if (currentFileElement.textContent == oldName) {
|
||||
currentFileElement.textContent = newName;
|
||||
}
|
||||
removeScript(oldName);
|
||||
setScript(newName, code);
|
||||
}
|
||||
|
||||
form.onsubmit = rename;
|
||||
inputElement.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
inputElement.onblur = function() {
|
||||
button.removeChild(form);
|
||||
label.textContent = oldName;
|
||||
};
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.classList.add('trash');
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
if (button.classList.contains('blue')) {
|
||||
lastClick = performance.now();
|
||||
button.classList.remove('blue');
|
||||
button.classList.add('red');
|
||||
document.body.addEventListener('click', function() {
|
||||
button.classList.add('blue');
|
||||
button.classList.remove('red');
|
||||
}, {once: true});
|
||||
} else if (performance.now() - lastClick > 500) {
|
||||
removeScript(label.textContent);
|
||||
if (currentFileElement.textContent == label.textContent) {
|
||||
switchTo('Intro');
|
||||
}
|
||||
const container = button.parentElement;
|
||||
container.parentElement.removeChild(container);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const newButton = document.querySelector('#new');
|
||||
function newItem(code, scriptName = undefined) {
|
||||
const name = uniqueName(scriptName ?? 'New Script');
|
||||
setScript(name, code);
|
||||
const nextButton = createDropdownItem(name);
|
||||
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
|
||||
addEdit(nextButton);
|
||||
return {button: nextButton, name};
|
||||
};
|
||||
newButton.onclick = function() {
|
||||
newItem('').button.click();
|
||||
};
|
||||
|
||||
const runButton = document.querySelector('#compile');
|
||||
const poster = document.querySelector('#poster');
|
||||
let manifoldInitialized = false;
|
||||
let autoExecute = true;
|
||||
|
||||
function initializeRun() {
|
||||
runButton.disabled = false;
|
||||
if (autoExecute) {
|
||||
runButton.click();
|
||||
} else {
|
||||
poster.textContent = 'Auto-run disabled';
|
||||
}
|
||||
}
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('/manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
resetToCircularDefaults: typeof T.resetToCircularDefaults;
|
||||
setup: () => void;
|
||||
}
|
||||
declare const module: ManifoldToplevel;
|
||||
`;
|
||||
}
|
||||
|
||||
async function getEditorDTS() {
|
||||
const global = await fetch('/editor.d.ts').then(response => response.text());
|
||||
return `${global.replace(/^import.*$/gm, '')}`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getEditorDTS());
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
const w = await monaco.languages.typescript.getTypeScriptWorker();
|
||||
tsWorker = await w(editor.getModel().uri);
|
||||
editor.getModel().updateOptions({tabSize: 2});
|
||||
|
||||
for (const [name] of exampleFunctions) {
|
||||
const button = createDropdownItem(name);
|
||||
fileDropdown.appendChild(button.parentElement);
|
||||
}
|
||||
|
||||
let currentName = currentFileElement.textContent;
|
||||
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = nthKey(i);
|
||||
if (!key) continue;
|
||||
if (key === 'currentName') {
|
||||
currentName = getScript(key);
|
||||
} else if (key === 'safe') {
|
||||
autoExecute = getScript(key) !== 'false';
|
||||
} else {
|
||||
const button = createDropdownItem(key);
|
||||
newButton.insertAdjacentElement('afterend', button.parentElement);
|
||||
addEdit(button);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.hash.length > 0) {
|
||||
const fragment = decodeURIComponent(window.location.hash.substring(1));
|
||||
const codeIdx = fragment.indexOf(CODE_START);
|
||||
if (codeIdx != -1) {
|
||||
autoExecute = true;
|
||||
const name = fragment.substring(0, codeIdx);
|
||||
switchTo(
|
||||
newItem(fragment.substring(codeIdx + CODE_START.length), name).name);
|
||||
} else {
|
||||
if (fragment != currentName) {
|
||||
autoExecute = true;
|
||||
}
|
||||
switchTo(fragment);
|
||||
}
|
||||
} else {
|
||||
switchTo(currentName);
|
||||
}
|
||||
|
||||
if (manifoldInitialized) {
|
||||
initializeRun();
|
||||
}
|
||||
|
||||
editor.onDidChangeModelContent(e => {
|
||||
runButton.disabled = false;
|
||||
if (switching) {
|
||||
switching = false;
|
||||
editor.setScrollTop(0);
|
||||
return;
|
||||
}
|
||||
if (isExample) {
|
||||
const cursor = editor.getPosition();
|
||||
newItem(editor.getValue()).button.click();
|
||||
editor.setPosition(cursor);
|
||||
}
|
||||
});
|
||||
|
||||
window.onresize = () => {
|
||||
editor.layout({});
|
||||
};
|
||||
});
|
||||
|
||||
// Animation ------------------------------------------------------------
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const animationContainer = document.querySelector('#animation');
|
||||
const playButton = document.querySelector('#play');
|
||||
const scrubber = document.querySelector('#scrubber');
|
||||
let paused = false;
|
||||
|
||||
mv.addEventListener('load', () => {
|
||||
const hasAnimation = mv.availableAnimations.length > 0;
|
||||
animationContainer.style.display = hasAnimation ? 'flex' : 'none';
|
||||
if (hasAnimation) {
|
||||
play();
|
||||
}
|
||||
});
|
||||
|
||||
function play() {
|
||||
mv.play();
|
||||
playButton.classList.remove('play');
|
||||
playButton.classList.add('pause');
|
||||
paused = false;
|
||||
scrubber.classList.add('hide');
|
||||
}
|
||||
|
||||
function pause() {
|
||||
mv.pause();
|
||||
playButton.classList.remove('pause');
|
||||
playButton.classList.add('play');
|
||||
paused = true;
|
||||
scrubber.max = mv.duration;
|
||||
scrubber.value = mv.currentTime;
|
||||
scrubber.classList.remove('hide');
|
||||
}
|
||||
|
||||
playButton.onclick = function() {
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
};
|
||||
|
||||
scrubber.oninput = function() {
|
||||
mv.currentTime = scrubber.value;
|
||||
};
|
||||
|
||||
// Execution ------------------------------------------------------------
|
||||
const consoleElement = document.querySelector('#console');
|
||||
const oldLog = console.log;
|
||||
console.log = function(message) {
|
||||
consoleElement.textContent += message + '\r\n';
|
||||
consoleElement.scrollTop = consoleElement.scrollHeight;
|
||||
oldLog(message);
|
||||
};
|
||||
|
||||
function clearConsole() {
|
||||
consoleElement.textContent = '';
|
||||
}
|
||||
|
||||
function enableCancel() {
|
||||
runButton.firstChild.style.visibility = 'hidden';
|
||||
runButton.classList.add('red', 'cancel');
|
||||
}
|
||||
|
||||
function disableCancel() {
|
||||
runButton.firstChild.style.visibility = 'visible';
|
||||
runButton.classList.remove('red', 'cancel');
|
||||
}
|
||||
|
||||
function finishRun() {
|
||||
disableCancel();
|
||||
const log = consoleElement.textContent;
|
||||
// Remove "Running..."
|
||||
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
|
||||
}
|
||||
|
||||
const output = {
|
||||
glbURL: null,
|
||||
threeMFURL: null
|
||||
};
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new ManifoldWorker();
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
initializeRun();
|
||||
}
|
||||
manifoldInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data.log != null) {
|
||||
consoleElement.textContent += e.data.log + '\r\n';
|
||||
consoleElement.scrollTop = consoleElement.scrollHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
|
||||
if (output.threeMFURL != null) {
|
||||
URL.revokeObjectURL(output.threeMFURL);
|
||||
output.threeMFURL = null;
|
||||
}
|
||||
URL.revokeObjectURL(output.glbURL);
|
||||
output.glbURL = e.data.glbURL;
|
||||
output.threeMFURL = e.data.threeMFURL;
|
||||
threemfButton.disabled = output.threeMFURL == null;
|
||||
mv.src = output.glbURL;
|
||||
if (output.glbURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
} else {
|
||||
setScript('safe', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createWorker();
|
||||
|
||||
async function run() {
|
||||
saveCurrent();
|
||||
setScript('safe', 'false');
|
||||
enableCancel();
|
||||
clearConsole();
|
||||
console.log('Running...');
|
||||
const output = await tsWorker.getEmitOutput(editor.getModel().uri.toString());
|
||||
manifoldWorker.postMessage(output.outputFiles[0].text);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
manifoldWorker.terminate();
|
||||
createWorker();
|
||||
finishRun();
|
||||
console.log('Run canceled');
|
||||
}
|
||||
|
||||
runButton.onclick = function() {
|
||||
if (runButton.classList.contains('cancel')) {
|
||||
cancel();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
};
|
||||
|
||||
function clickSave(saveButton, filename, outputName) {
|
||||
const container = saveButton.parentElement;
|
||||
return () => {
|
||||
const oldSave = saveContainer.firstElementChild;
|
||||
if (oldSave !== container) {
|
||||
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
|
||||
saveContainer.insertBefore(container, saveDropdown);
|
||||
container.appendChild(saveArrow.parentElement);
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = output[outputName];
|
||||
link.click();
|
||||
};
|
||||
}
|
||||
|
||||
const glbButton = document.querySelector('#glb');
|
||||
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
|
||||
|
||||
const threemfButton = document.querySelector('#threemf');
|
||||
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
|
||||
@@ -1,26 +0,0 @@
|
||||
## Local development
|
||||
|
||||
First, follow the directions in the root README to get your C++ build environment set up and working for WASM.
|
||||
|
||||
To develop the manifoldCAD.org editor as well as our other example pages, run
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
See `package.json` for other useful scripts.
|
||||
|
||||
## Bundle Analyzer
|
||||
|
||||
To generate a bundle analysis report, run:
|
||||
|
||||
On Windows PowerShell:
|
||||
$env:ANALYZE=1; npm run build
|
||||
|
||||
On Linux/macOS:
|
||||
ANALYZE=1 npm run build
|
||||
|
||||
This will include the analyzer in the build and output a report.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import auger from '../../test/examples/auger.mjs?raw';
|
||||
import gearBearing from '../../test/examples/gear-bearing.ts?raw';
|
||||
import godelEscherBach from '../../test/examples/godel-escher-bach.ts?raw';
|
||||
import gyroidModule from '../../test/examples/gyroid-module.ts?raw';
|
||||
import heart from '../../test/examples/heart.mjs?raw';
|
||||
import importManifold from '../../test/examples/import-manifold.ts?raw';
|
||||
import importModel from '../../test/examples/import-model.ts?raw';
|
||||
import intro from '../../test/examples/intro.mjs?raw';
|
||||
import involuteGearLibrary from '../../test/examples/involute-gear-library.ts?raw';
|
||||
import mengerSponge from '../../test/examples/menger-sponge.mjs?raw';
|
||||
import roundedFrame from '../../test/examples/rounded-frame.mjs?raw';
|
||||
import scallop from '../../test/examples/scallop.mjs?raw';
|
||||
import stretchyBracelet from '../../test/examples/stretchy-bracelet.mjs?raw';
|
||||
import tetrahedronPuzzle from '../../test/examples/tetrahedron-puzzle.mjs?raw';
|
||||
import torusKnot from '../../test/examples/torus-knot.mjs?raw';
|
||||
import voronoi from '../../test/examples/voronoi.mjs?raw';
|
||||
|
||||
const examples = new Map();
|
||||
examples.set('Intro', intro);
|
||||
examples.set('Auger', auger);
|
||||
examples.set('Tetrahedron Puzzle', tetrahedronPuzzle);
|
||||
examples.set('Rounded Frame', roundedFrame);
|
||||
examples.set('Heart', heart);
|
||||
examples.set('Scallop', scallop);
|
||||
examples.set('Torus Knot', torusKnot);
|
||||
examples.set('Menger Sponge', mengerSponge);
|
||||
examples.set('Stretchy Bracelet', stretchyBracelet);
|
||||
examples.set('Gyroid Module', gyroidModule);
|
||||
examples.set('Gear Bearing', gearBearing);
|
||||
examples.set('involute-gear-library', involuteGearLibrary);
|
||||
examples.set('Voronoi', voronoi);
|
||||
examples.set('Import Model', importModel);
|
||||
examples.set('Import Manifold', importManifold);
|
||||
examples.set('Gödel, Escher, Bach', godelEscherBach)
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
self.examples = examples;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "manifold-editor",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"clean": "rm -fr ./dist",
|
||||
"preview": "vite preview",
|
||||
"public": "cp -rv dist/* ../../public/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild-wasm": "^0.27.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@google/model-viewer": "^4.2.0",
|
||||
"manifold-3d": "file:../..",
|
||||
"monaco-editor": "^0.53.0",
|
||||
"monaco-editor-auto-typings": "^0.4.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.9",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-static-copy": "^3.1.5",
|
||||
"vite-bundle-analyzer": "1.3.8"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 330 B |
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -1,10 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting to the ManifoldCAD User Guide</title>
|
||||
<link rel="canonical" href="../docs/jsuser/">
|
||||
<meta http-equiv="refresh" content="0; URL=../docs/jsuser/">
|
||||
</head>
|
||||
<body><a href="../docs/jsuser/">Click here if you are not redirected automatically.</a></body>
|
||||
</html>
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The site is deployed to several paths at once (master/, latest/, vX.Y.Z/),
|
||||
// each registering this worker under its own scope. Key the cache by scope so
|
||||
// deployments do not share or evict each other's entries.
|
||||
const cachePrefix = `manifoldCAD-cache:${self.registration.scope}:`;
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = `${cachePrefix}v5`;
|
||||
|
||||
// Relative to this worker's location, so they resolve within the deployment
|
||||
// this worker is scoped to rather than always hitting the site root.
|
||||
const appShellAssets = [
|
||||
'./',
|
||||
'./index.html',
|
||||
'./manifest.json',
|
||||
'./fonts/orbitron-black-webfont.ttf',
|
||||
'./icons/close.png',
|
||||
'./icons/docs.png',
|
||||
'./icons/manifoldCAD.png',
|
||||
'./icons/manifoldCADonly.png',
|
||||
'./icons/ManifoldIcon.png',
|
||||
'./icons/mengerSponge192.png',
|
||||
'./icons/mengerSponge512.png',
|
||||
'./icons/mengerSponge64.png',
|
||||
'./icons/pause.png',
|
||||
'./icons/pencil.png',
|
||||
'./icons/play.png',
|
||||
'./icons/redo.png',
|
||||
'./icons/share.png',
|
||||
'./icons/star.png',
|
||||
'./icons/trash.png',
|
||||
'./icons/undo.png'
|
||||
];
|
||||
|
||||
const shouldHandleRequest = request => {
|
||||
if (request.method !== 'GET') return false;
|
||||
|
||||
const url = new URL(request.url);
|
||||
if (url.origin !== self.location.origin) return false;
|
||||
|
||||
// Skip transient HTML proxy requests only.
|
||||
// Caching this can cause stale proxy-module lookup issues.
|
||||
if (url.searchParams.has('html-proxy')) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
e.waitUntil((async () => {
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.addAll(appShellAssets);
|
||||
await self.skipWaiting();
|
||||
})());
|
||||
});
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
e.waitUntil(self.skipWaiting());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys()
|
||||
.then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
// Only evict stale caches belonging to this scope, so
|
||||
// other deployments keep theirs.
|
||||
if (key.startsWith(cachePrefix) && key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
})
|
||||
.then(() => self.clients.claim()));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener('fetch', e => {
|
||||
if (!shouldHandleRequest(e.request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.respondWith((async () => {
|
||||
const cachedResponse = await caches.match(e.request);
|
||||
|
||||
try {
|
||||
const response = await fetch(e.request);
|
||||
|
||||
// Only cache successful basic and opaque responses.
|
||||
if (response.ok || response.type === 'opaque') {
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.put(e.request, response.clone());
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
console.debug('Service worker fetch failed:', e.request.url, error);
|
||||
return Response.error();
|
||||
}
|
||||
})());
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
import viteBundleAnalyzer from 'vite-bundle-analyzer';
|
||||
import {viteStaticCopy} from 'vite-plugin-static-copy'
|
||||
|
||||
export default defineConfig({
|
||||
worker: {format: 'es'},
|
||||
// Relative so a build works from any path.
|
||||
base: './',
|
||||
server: {
|
||||
// `npm run dev`
|
||||
headers: {
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
},
|
||||
fs: {allow: [resolve(__dirname, '..')]}
|
||||
},
|
||||
preview: {
|
||||
// `npm run preview`
|
||||
headers: {
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
// If type declaration files are missing, the web editor can't
|
||||
// load them, and type validation won't work for our core modules.
|
||||
{
|
||||
src: './node_modules/manifold-3d/dist/manifoldCAD.d.ts',
|
||||
dest: './', // Targets are relative to 'dist'.
|
||||
},
|
||||
{
|
||||
src: './node_modules/manifold-3d/dist/manifoldCADGlobals.d.ts',
|
||||
dest: './',
|
||||
}
|
||||
],
|
||||
}),
|
||||
process.env.ANALYZE ? viteBundleAnalyzer() : null
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
alias: {
|
||||
path: resolve(
|
||||
__dirname,
|
||||
'./node_modules/rollup-plugin-node-polyfills/polyfills/path.js')
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
sourcemap: 'hidden',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
manifoldCAD: resolve(__dirname, 'index.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023-2025 The Manifold Authors.
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,59 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Convert between in-memory glTF-transform documents and their serialized
|
||||
* formats. This module also includes some utilities for conversion between glTF
|
||||
* meshes and manifold meshes.
|
||||
*
|
||||
* @packageDocumentation
|
||||
* @group ManifoldCAD
|
||||
* @category Input/Output
|
||||
* @groupDescription Import
|
||||
* These properties implement the {@link lib/import-model!Importer | Importer}
|
||||
* interface. Through this interface, manifoldCAD can determine when to use this
|
||||
* module to import a model.
|
||||
* @groupDescription Export
|
||||
* These properties implement the {@link lib/export-model!Exporter | Exporter}
|
||||
* interface. Through this interface, manifoldCAD can determine when to use this
|
||||
* module to export a model.
|
||||
*/
|
||||
|
||||
import * as GLTFTransform from '@gltf-transform/core';
|
||||
import {KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
|
||||
import type {Mesh as ManifoldMesh, MeshOptions} from '../manifold.d.ts';
|
||||
|
||||
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf.ts';
|
||||
import {fetchWithRetry} from './util.ts';
|
||||
|
||||
const binaryFormat = {
|
||||
extension: 'glb',
|
||||
mimetype: 'model/gltf-binary'
|
||||
};
|
||||
|
||||
/**
|
||||
* @group Import
|
||||
* @readonly
|
||||
*/
|
||||
export const importFormats = [binaryFormat];
|
||||
|
||||
/**
|
||||
* @group Export
|
||||
* @readonly
|
||||
*/
|
||||
export const exportFormats = [binaryFormat];
|
||||
import {Accessor, Document, Material, Mesh, Primitive, Texture, WebIO} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf';
|
||||
import {Mesh as ManifoldMesh, MeshOptions} from './public/manifold-encapsulated-types';
|
||||
|
||||
export const attributeDefs = {
|
||||
'POSITION': {type: GLTFTransform.Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: GLTFTransform.Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: GLTFTransform.Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: GLTFTransform.Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: GLTFTransform.Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: GLTFTransform.Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: GLTFTransform.Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: GLTFTransform.Accessor.Type.VEC4, components: 4},
|
||||
'POSITION': {type: Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'SKIP_1': {type: null, components: 1},
|
||||
'SKIP_2': {type: null, components: 2},
|
||||
'SKIP_3': {type: null, components: 3},
|
||||
@@ -74,7 +35,7 @@ export const attributeDefs = {
|
||||
export type Attribute = keyof(typeof attributeDefs);
|
||||
|
||||
export interface Properties {
|
||||
material: GLTFTransform.Material;
|
||||
material: Material;
|
||||
attributes: Attribute[];
|
||||
}
|
||||
|
||||
@@ -82,7 +43,7 @@ export interface Properties {
|
||||
* Call this first to register the manifold extension so that readMesh and
|
||||
* writeMesh will work.
|
||||
*/
|
||||
export function setupIO(io: GLTFTransform.WebIO) {
|
||||
export function setupIO(io: WebIO) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
@@ -106,8 +67,7 @@ export function setupIO(io: GLTFTransform.WebIO) {
|
||||
* attributes are the intersection of the attributes present on the
|
||||
* primitive and those requested in the attributes input.
|
||||
*/
|
||||
export function readMesh(
|
||||
mesh: GLTFTransform.Mesh, attributes: Attribute[] = []):
|
||||
export function readMesh(mesh: Mesh, attributes: Attribute[] = []):
|
||||
{mesh: MeshOptions, runProperties: Properties[]}|null {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
@@ -246,49 +206,36 @@ export function readMesh(
|
||||
* the manifoldMesh vertProperties, thus the first attribute must always be
|
||||
* 'POSITION'. Any properties that should not be output for a given
|
||||
* primitive must use the 'SKIP_*' attributes.
|
||||
* @param EXT_mesh_manifold If false, emit a plain glTF mesh. In this case,
|
||||
* the mesh is not required to be fully manifold when written. Use this
|
||||
* to write explicitly non-manifold meshes, e.g.: CrossSections.
|
||||
* @returns The glTF Mesh to add to the Document.
|
||||
*/
|
||||
export function writeMesh(
|
||||
doc: GLTFTransform.Document, manifoldMesh: ManifoldMesh,
|
||||
id2properties: Map<number, Properties>,
|
||||
EXT_mesh_manifold: boolean = true): GLTFTransform.Mesh {
|
||||
doc: Document, manifoldMesh: ManifoldMesh,
|
||||
id2properties: Map<number, Properties>): Mesh {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
writePrimitiveAttributes(doc, mesh, manifoldMesh, id2properties);
|
||||
if (EXT_mesh_manifold) {
|
||||
writeExtMeshManifoldIndices(doc, mesh, manifoldMesh);
|
||||
} else {
|
||||
writePlainIndices(doc, mesh, manifoldMesh);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the necessary primitives and their attributes needed to represent a
|
||||
* ManifoldMesh object as a glTF mesh, and add those to an existing glTF
|
||||
* transform mesh node.
|
||||
*
|
||||
* This does not create or populate indices. After this call these primitives
|
||||
* will exist and have positions, but will not have visible geometry.
|
||||
*/
|
||||
function writePrimitiveAttributes(
|
||||
doc: GLTFTransform.Document, mesh: GLTFTransform.Mesh,
|
||||
manifoldMesh: ManifoldMesh, id2properties: Map<number, Properties>) {
|
||||
const runIndex = Array<number>();
|
||||
const attributeUnion = Array<Attribute>();
|
||||
const primitive2attributes = new Map<GLTFTransform.Primitive, Attribute[]>();
|
||||
|
||||
// For each run, create a primitive, set material and collate attributes.
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
for (let run = 0; run < (manifoldMesh.runIndex.length - 1); run++) {
|
||||
const primitive2attributes = new Map<Primitive, Attribute[]>();
|
||||
const numRun = manifoldMesh.runIndex.length - 1;
|
||||
let lastID = -1;
|
||||
for (let run = 0; run < numRun; ++run) {
|
||||
const id = manifoldMesh.runOriginalID[run];
|
||||
const primitive = doc.createPrimitive();
|
||||
if (id == lastID) {
|
||||
continue;
|
||||
}
|
||||
lastID = id;
|
||||
runIndex.push(manifoldMesh.runIndex[run]);
|
||||
|
||||
const indices = doc.createAccessor('primitive indices of ID ' + id)
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(1));
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
|
||||
const properties = id2properties.get(id);
|
||||
if (properties) {
|
||||
@@ -321,9 +268,8 @@ function writePrimitiveAttributes(
|
||||
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
runIndex.push(manifoldMesh.runIndex[numRun]);
|
||||
|
||||
// For each primitive, create accessors for each attribute and populate those
|
||||
// attributes.
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
@@ -365,33 +311,14 @@ function writePrimitiveAttributes(
|
||||
}
|
||||
offset += n;
|
||||
});
|
||||
}
|
||||
|
||||
function writeExtMeshManifoldIndices(
|
||||
doc: GLTFTransform.Document, mesh: GLTFTransform.Mesh,
|
||||
manifoldMesh: ManifoldMesh) {
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_mesh_manifold', manifoldPrimitive);
|
||||
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const {runIndex} = manifoldMesh;
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, n) => {
|
||||
// These indices will be populated by `manifold-gtlf` when the
|
||||
// document is written out by glTF transform.
|
||||
const indices = doc.createAccessor('primitive indices of ID ' + runIndex[n])
|
||||
.setBuffer(buffer)
|
||||
.setType(GLTFTransform.Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(1));
|
||||
primitive.setIndices(indices);
|
||||
});
|
||||
|
||||
const indices =
|
||||
doc.createAccessor('manifold indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(GLTFTransform.Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts as Uint32Array<ArrayBuffer>);
|
||||
const indices = doc.createAccessor('manifold indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
|
||||
@@ -414,37 +341,23 @@ function writeExtMeshManifoldIndices(
|
||||
if (ind.length > 0) {
|
||||
const indicesAccessor = doc.createAccessor('merge from')
|
||||
.setBuffer(buffer)
|
||||
.setType(GLTFTransform.Accessor.Type.SCALAR)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor('merge to')
|
||||
.setBuffer(buffer)
|
||||
.setType(GLTFTransform.Accessor.Type.SCALAR)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
}
|
||||
}
|
||||
|
||||
function writePlainIndices(
|
||||
doc: GLTFTransform.Document, mesh: GLTFTransform.Mesh,
|
||||
manifoldMesh: ManifoldMesh) {
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const {runIndex} = manifoldMesh;
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, n) => {
|
||||
const indices = doc.createAccessor('primitive indices of ID ' + runIndex[n])
|
||||
.setBuffer(buffer)
|
||||
.setType(GLTFTransform.Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts.slice(
|
||||
runIndex[n], runIndex[n + 1]));
|
||||
primitive.setIndices(indices);
|
||||
});
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to dispose of a Mesh, useful when replacing an existing Mesh
|
||||
* with one from writeMesh.
|
||||
*/
|
||||
export function disposeMesh(mesh: GLTFTransform.Mesh) {
|
||||
export function disposeMesh(mesh: Mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
@@ -471,15 +384,15 @@ export function disposeMesh(mesh: GLTFTransform.Mesh) {
|
||||
* @param texture The texture to update
|
||||
* @param uri The location of the image to download
|
||||
*/
|
||||
export async function loadTexture(texture: GLTFTransform.Texture, uri: string) {
|
||||
const response = await fetchWithRetry(uri);
|
||||
export async function loadTexture(texture: Texture, uri: string) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
function writeProperties(
|
||||
vertProperties: number[], accessor: GLTFTransform.Accessor, numProp: number,
|
||||
vertProperties: number[], accessor: Accessor, numProp: number,
|
||||
offset: number) {
|
||||
const array = accessor.getArray()!;
|
||||
const size = accessor.getElementSize();
|
||||
@@ -492,8 +405,7 @@ function writeProperties(
|
||||
}
|
||||
|
||||
function readPrimitive(
|
||||
primitive: GLTFTransform.Primitive, numProp: number,
|
||||
attributes: Attribute[]) {
|
||||
primitive: Primitive, numProp: number, attributes: Attribute[]) {
|
||||
const vertProperties: number[] = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
@@ -509,33 +421,4 @@ function readPrimitive(
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
|
||||
let _io: GLTFTransform.PlatformIO|null = null;
|
||||
|
||||
/**
|
||||
* Return an appropriate PlatformIO object.
|
||||
*/
|
||||
const getIO = (): GLTFTransform.PlatformIO => {
|
||||
if (!_io) {
|
||||
_io = new GLTFTransform.WebIO();
|
||||
_io.registerExtensions([EXTManifold, ...KHRONOS_EXTENSIONS]);
|
||||
}
|
||||
|
||||
return _io;
|
||||
};
|
||||
|
||||
/**
|
||||
* @group Export
|
||||
*/
|
||||
export async function toArrayBuffer(doc: GLTFTransform.Document):
|
||||
Promise<ArrayBuffer> {
|
||||
return (await getIO().writeBinary(doc)).buffer as ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group Import
|
||||
*/
|
||||
export async function fromArrayBuffer(buffer: ArrayBuffer) {
|
||||
return await getIO().readBinary(new Uint8Array(buffer));
|
||||
}
|
||||
@@ -14,8 +14,8 @@
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="./icons/ManifoldIcon.png">
|
||||
<link rel="manifest" href="./manifest.json">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -33,25 +33,23 @@
|
||||
self.ModelViewerElement = self.ModelViewerElement || {};
|
||||
self.ModelViewerElement.modelCacheSize = 0;
|
||||
</script>
|
||||
<script type="module" src="/examples.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container header">
|
||||
<p style="flex: 2; margin-left: 16px;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<span class="top-edit blue">
|
||||
<button type="button" id="undo" class="top-icon undo squish" title="Undo"></button>
|
||||
<button type="button" id="redo" class="top-icon redo squish" title="Redo"></button>
|
||||
<button type="button" id="format" class="top-icon star squish" title="Format Code"></button>
|
||||
<button type="button" id="share" class="top-icon share squish" title="Copy Shareable Link"></button>
|
||||
<a target="_blank" href="./docs/jsuser/" class="top-icon docs squish" title="Documentation"></a>
|
||||
<button type="button" id="edgesToggle" class="top-icon edges" title="Toggle Edges" aria-pressed="false"></button>
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<span class="top-edit blue">
|
||||
<button type="button" id="undo" class="top-icon undo squish"></button>
|
||||
<button type="button" id="redo" class="top-icon redo squish"></button>
|
||||
<button type="button" id="format" class="top-icon star squish"></button>
|
||||
<button type="button" id="share" class="top-icon share"></button>
|
||||
</span>
|
||||
<span id="type-indicator" style="font-size: 14px; margin-left: 12px; font-weight: normal; opacity: 0.8;"></span>
|
||||
</p>
|
||||
<div class="margin" id="file-margin">
|
||||
<button type="button" id="file" class="blue item"><b class="uparrow"></b> <span id="current">Intro</span><button
|
||||
id="current-edit" class="icon edit" aria-hidden="true"></button></button>
|
||||
<button type="button" id="file" class="blue item"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<div class="dropdown" id="fileDropdown">
|
||||
<button type="button" class="green item" id="new">New</button>
|
||||
</div>
|
||||
@@ -71,13 +69,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Draggable pane dividers-start -->
|
||||
<div id="workbench" class="container workbench" style="flex: 2;">
|
||||
<div id="editor" class="col pane-left"></div>
|
||||
<div id="split-x" class="splitter splitter-x" aria-hidden="true"></div>
|
||||
<div id="rightPane" class="col container pane-right" style="flex-direction: column;">
|
||||
<model-viewer id="viewer" camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
min-field-of-view="1deg" shadow-intensity="1" tone-mapping="neutral" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="container" style="flex: 2;">
|
||||
<div id="editor" class="col"></div>
|
||||
<div class="col container" style="flex-direction: column;">
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" tone-mapping="neutral" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
@@ -86,13 +82,19 @@
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slider" id="scrubber">
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="split-y" class="splitter splitter-y" aria-hidden="true"></div>
|
||||
<!-- Draggable pane dividers-end -->
|
||||
<div id="console"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module" src="editor-examples.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
|
||||
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -59,9 +59,7 @@
|
||||
<span id="poster" slot="poster">Drop a GLB here</span>
|
||||
</model-viewer>
|
||||
</body>
|
||||
|
||||
<script type="module">
|
||||
import { ModelViewerElement } from '@google/model-viewer';
|
||||
</script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="make-manifold.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -15,14 +15,13 @@
|
||||
import {Document, WebIO} from '@gltf-transform/core';
|
||||
import {KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import {prune} from '@gltf-transform/functions';
|
||||
import Module from 'manifold-3d';
|
||||
import {disposeMesh, Properties, readMesh, writeMesh} from 'manifold-3d/lib/gltf-io';
|
||||
import {EXTManifold} from 'manifold-3d/manifold-gltf';
|
||||
import {SimpleDropzone} from 'simple-dropzone';
|
||||
|
||||
import Module from './built/manifold.js';
|
||||
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
|
||||
|
||||
// Set up gltf-transform
|
||||
const io = new WebIO();
|
||||
io.registerExtensions([EXTManifold]);
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
// Set up Manifold WASM library
|
||||
@@ -97,8 +96,7 @@ async function writeGLB(doc: Document): Promise<void> {
|
||||
}
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob(
|
||||
[glb as Uint8Array<ArrayBuffer>], {type: 'application/octet-stream'});
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
outputGLBurl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
@@ -148,4 +146,4 @@ async function readGLB(url: string): Promise<Document> {
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
## Manifoldness and EXT_mesh_manifold
|
||||
|
||||
<!-- #region summary -->
|
||||
<!-- This region will be included in the examples list at https://manifoldcad.org/docs/jsapi/documents/Manifold_Examples.html -->
|
||||
|
||||
[ [example](https://manifoldcad.org/make-manifold.html) | [source](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/make-manifold/) ]
|
||||
|
||||
This example will test a glTF model for manifoldness. Manifold models will be extended with [EXT_mesh_manifold](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_manifold/README.md), a glTF extension for lossless transmission of manifold meshes.
|
||||
|
||||
Our implementation of `EXT_mesh_manifold` is [manifold-gltf](https://manifoldcad.org/jsdocs/modules/manifold-gltf.html), an extension for [glTF Transform](https://gltf-transform.dev). Although this example uses manifold to test for manifoldness, `manifold-gltf` depends only on glTF Transform.
|
||||
|
||||
<!-- #endregion summary -->
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "manifold-examples-make-manifold",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"public": "cp -rv dist/* ../../public/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^4.2.0",
|
||||
"@gltf-transform/extensions": "^4.2.0",
|
||||
"@gltf-transform/functions": "^4.2.0",
|
||||
"@google/model-viewer": "^4.1.0",
|
||||
"manifold-3d": "file:../..",
|
||||
"simple-dropzone": "0.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.9",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "7.3.1"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./*.ts",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
worker: {format: 'es'},
|
||||
// Relative so a build works from any path.
|
||||
base: './',
|
||||
build: {
|
||||
target: 'esnext',
|
||||
sourcemap: 'hidden',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
three: resolve(__dirname, 'make-manifold.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/make-manifold/[name].js`,
|
||||
chunkFileNames: `assets/make-manifold/[name].js`,
|
||||
assetFileNames: `assets/make-manifold/[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023-2025 The Manifold Authors.
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,53 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* This module implements the
|
||||
* [EXT_mesh_manifold](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_mesh_manifold)
|
||||
* GLTF extension for [glTF Transform](https://gltf-transform.dev/). It does
|
||||
* not depend on manifold. Use it where topological correctness is
|
||||
* required.
|
||||
*
|
||||
* A mesh is typically considered manifold when it topologically represents a
|
||||
* solid object with well-defined volume, sometimes known as watertight. For the
|
||||
* purpose of this extension, a mesh is considered manifold when it is an
|
||||
* oriented 2-manifold triangle mesh, meaning for each halfedge (each of the
|
||||
* three ordered vertex index pairs around a triangle) there is exactly one
|
||||
* other halfedge with swapped vertices, and no other halfedge with the same
|
||||
* vertices.
|
||||
*
|
||||
* > NOTE: This is the same definition as used in the 3D Manufacturing Format
|
||||
* (3MF) for maximum interoperability.
|
||||
*
|
||||
* Manifoldness is critical for reliability of geometric algorithms that operate
|
||||
* on solid objects, and so is important in CAD, FEA (Finite Element Analysis),
|
||||
* CFD (Computational Fluid Dynamics), and more. However, GPU-friendly formats
|
||||
* like glTF with indexed triangles lose manifoldness data because anywhere
|
||||
* vertex properties change at an edge (e.g. change of materials, UV swatch, or
|
||||
* normals at a crease) the entire vertex must be duplicated and referenced
|
||||
* separately. This means the only way to determine linked edges is by merging
|
||||
* vertices with identical positions, but this geometric check cannot losslessly
|
||||
* recreate the topology, as some separate vertices may happen to coincide in
|
||||
* position.
|
||||
*
|
||||
* This extension allows authors to denote that the contained mesh data is in
|
||||
* fact manifold, and adds a small amount of extra data to losslessly recover
|
||||
* this manifold mesh by leveraging sparse accessors.
|
||||
*
|
||||
* It can be imported as `manifold-3d/manifold-gltf`.
|
||||
*
|
||||
* @packageDocumentation
|
||||
* @group glTF Extension
|
||||
* @module manifold-gltf
|
||||
*/
|
||||
|
||||
import type {GLTF, IProperty,} from '@gltf-transform/core';
|
||||
import {Accessor, Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
|
||||
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_mesh_manifold';
|
||||
const MERGE = 'MERGE';
|
||||
|
||||
export interface IManifoldPrimitive extends IProperty {
|
||||
interface IManifoldPrimitive extends IProperty {
|
||||
mergeIndices: Accessor;
|
||||
mergeValues: Accessor;
|
||||
indices: Accessor;
|
||||
@@ -242,22 +201,11 @@ export class EXTManifold extends Extension {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if our primitive indices have been populated.
|
||||
let count = 0;
|
||||
for (const prim of mesh.listPrimitives()) {
|
||||
const indices = prim.getIndices();
|
||||
if (!indices) continue;
|
||||
count += indices.getCount();
|
||||
}
|
||||
|
||||
// If not, fill them in.
|
||||
if (count !== manifoldPrimitive.getIndices().getCount()) {
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors![meshDef.primitives[i].indices!];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors![meshDef.primitives[i].indices!];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
|
||||
meshDef.extensions = meshDef.extensions || {};
|
||||
@@ -13,11 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, WebIO} from '@gltf-transform/core';
|
||||
import {clearNodeTransform, flatten, mergeDocuments, prune} from '@gltf-transform/functions';
|
||||
import type {Manifold as ManifoldType} from 'manifold-3d';
|
||||
import Module from 'manifold-3d';
|
||||
import type {Properties} from 'manifold-3d/lib/gltf-io';
|
||||
import {disposeMesh, readMesh, setupIO, writeMesh} from 'manifold-3d/lib/gltf-io';
|
||||
import {clearNodeTransform, flatten, prune} from '@gltf-transform/functions';
|
||||
|
||||
import Module, {Manifold, Mesh} from './built/manifold';
|
||||
import {disposeMesh, Properties, readMesh, setupIO, writeMesh} from './gltf-io';
|
||||
|
||||
// Set up gltf-transform
|
||||
const io = setupIO(new WebIO());
|
||||
@@ -37,7 +36,7 @@ const id2properties = new Map<number, Properties>();
|
||||
// for reading single-object glTFs, as it simply unions any extra meshes
|
||||
// together rather than returning a scene hierarchy.
|
||||
async function readGLB(url: string) {
|
||||
const manifolds = Array<ManifoldType>();
|
||||
const manifolds = Array<Manifold>();
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
@@ -61,7 +60,7 @@ async function readGLB(url: string) {
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
mergeDocuments(doc, docIn);
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
doc.getRoot().listAccessors().forEach((s) => s.dispose());
|
||||
@@ -110,7 +109,7 @@ const mv = document.querySelector('model-viewer');
|
||||
|
||||
// Use gltf-io and gltf-transform to convert the resulting Manifold to a glTF
|
||||
// and display it with <model-viewer>.
|
||||
async function push2MV(manifold: ManifoldType) {
|
||||
async function push2MV(manifold: Manifold) {
|
||||
disposeMesh(node.getMesh()!);
|
||||
const mesh = writeMesh(doc, manifold.getMesh(), id2properties);
|
||||
node.setMesh(mesh);
|
||||
@@ -118,9 +117,8 @@ async function push2MV(manifold: ManifoldType) {
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob(
|
||||
[glb as Uint8Array<ArrayBuffer>], {type: 'application/octet-stream'});
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
(mv as any).src = objectURL;
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,7 @@
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="neutral"
|
||||
alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
|
||||
<script type="module">
|
||||
import { ModelViewerElement } from '@google/model-viewer';
|
||||
</script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
||||
<script type="module" src="model-viewer-script.ts"></script>
|
||||
|
||||
</html>
|
||||
@@ -1,10 +0,0 @@
|
||||
## <model-viewer>
|
||||
|
||||
<!-- #region summary -->
|
||||
<!-- This region will be included in the examples list at https://manifoldcad.org/docs/jsapi/documents/Manifold_Examples.html -->
|
||||
|
||||
[ [example](https://manifoldcad.org/model-viewer.html) | [source](https://github.com/elalish/manifold/blob/master/bindings/wasm/examples/model-viewer/) ]
|
||||
|
||||
Manifold has full support for arbitrary vertex properties, and also has IDs that make it easy to keep track of materials and what surfaces belong to what input objects or faces. This example is a simple demonstration of combining objects with unique textures. The final result is shown using [\<model-viewer\>](https://modelviewer.dev/).
|
||||
|
||||
<!-- #endregion summary -->
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "manifold-examples-model-viewer",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"public": "cp -rv dist/* ../../public/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^4.2.0",
|
||||
"@gltf-transform/functions": "^4.2.0",
|
||||
"@google/model-viewer": "^4.1.0",
|
||||
"manifold-3d": "file:../.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.9",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "7.3.1"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
worker: {format: 'es'},
|
||||
// Relative so a build works from any path.
|
||||
base: './',
|
||||
build: {
|
||||
target: 'esnext',
|
||||
sourcemap: 'hidden',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
three: resolve(__dirname, 'model-viewer.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/model-viewer/[name].js`,
|
||||
chunkFileNames: `assets/model-viewer/[name].js`,
|
||||
assetFileNames: `assets/model-viewer/[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "manifold-examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:dev": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.8.0",
|
||||
"@gltf-transform/extensions": "^3.8.0",
|
||||
"@gltf-transform/functions": "^3.8.0",
|
||||
"@jscadui/3mf-export": "^0.5.0",
|
||||
"@types/three": "^0.164.0",
|
||||
"fflate": "^0.8.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.164.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/** @type {import("../../manifold-encapsulated-types")["Manifold"]} */
|
||||
const Manifold = globalThis.Manifold;
|
||||
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type
|
||||
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
|
||||
// respectively. Type "module." to see the static API - these functions
|
||||
// can also be used bare. Use console.log() to print output (lower-right).
|
||||
// This editor defines Z as up and units of mm.
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result", or create at least one
|
||||
// GLTFNode - see Menger Sponge and Gyroid Module examples.
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
|
||||
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
|
||||
// keep them better organized in the GLB. This will also allow you to
|
||||
// specify material properties, and even vertex colors via
|
||||
// setProperties(). See Tetrahedron Puzzle example.
|
||||
return result;
|
||||
},
|
||||
|
||||
Auger: function() {
|
||||
const outerRadius = 20;
|
||||
const beadRadius = 2;
|
||||
const height = 40;
|
||||
const twist = 90;
|
||||
|
||||
const {revolve, sphere, union, extrude} = Manifold;
|
||||
const {circle} = CrossSection;
|
||||
setMinCircularEdgeLength(0.1);
|
||||
|
||||
const bead1 =
|
||||
revolve(circle(beadRadius).translate([outerRadius, 0]), 50, 90)
|
||||
.add(sphere(beadRadius).translate([outerRadius, 0, 0]))
|
||||
.translate([0, -outerRadius, 0]);
|
||||
|
||||
const beads = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
beads.push(bead1.rotate(0, 0, 120 * i));
|
||||
}
|
||||
const bead = union(beads);
|
||||
|
||||
const auger = extrude(bead.slice(0), height, 50, twist);
|
||||
|
||||
const result =
|
||||
auger.add(bead).add(bead.translate(0, 0, height).rotate(0, 0, twist));
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This demonstrates how redundant points along a polygon can be
|
||||
// used to make twisted extrusions smoother. It also showcases animated
|
||||
// assemblies. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions = 50; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = Manifold.tetrahedron().intersect(
|
||||
Manifold.tetrahedron().rotate([0, 0, 90]).scale(2.5));
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
const x = pos[2] / 2;
|
||||
color[i] = cyan[i] * x + magenta[i] * (1 - x);
|
||||
}
|
||||
};
|
||||
|
||||
// setProperties(3, fade) creates three channels of vertex properties
|
||||
// according to the above fade function. setMaterial assigns these
|
||||
// channels as colors, and sets the factor to white, since our default is
|
||||
// yellow.
|
||||
const screw = setMaterial(
|
||||
Manifold.extrude(box, 2, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result =
|
||||
tet.intersect(screw.rotate([0, 0, -45]).translate([0, 0, -1]))
|
||||
.scale(scale);
|
||||
|
||||
// Assigned materials are only applied to a GLTFNode. Note that material
|
||||
// definitions cascade, applying recursively to all child surfaces, but
|
||||
// overridden by any materials defined lower down. The default material
|
||||
// properties, as well as animation parameters can be set via
|
||||
// globalDefaults.
|
||||
|
||||
const layFlat = new GLTFNode();
|
||||
layFlat.rotation =
|
||||
[45, -Math.atan(1 / Math.sqrt(2)) * 180 / Math.PI, 120];
|
||||
layFlat.translation = [0, 0, scale * Math.sqrt(3) / 3];
|
||||
|
||||
const fixed = new GLTFNode(layFlat);
|
||||
fixed.manifold = result;
|
||||
fixed.rotation = [0, 0, 180];
|
||||
|
||||
// For 3MF export, only top-level objects are independently arrangeable.
|
||||
const layFlat2 = layFlat.clone();
|
||||
|
||||
const moving = new GLTFNode(layFlat2);
|
||||
moving.manifold = result;
|
||||
// Use functions to create animation, which runs from t=0 to t=1.
|
||||
moving.translation = (t) => {
|
||||
const a = 1 - t;
|
||||
const x = a > 0.5 ? scale * 2 * (0.5 - a) : 0;
|
||||
return [x, x, -2 * x + scale * 4 * (0.5 - Math.abs(0.5 - a))];
|
||||
};
|
||||
moving.rotation = (t) => [0, 0, 270 * 2 * (1 - t)];
|
||||
|
||||
globalDefaults.animationLength = 10; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union, cube} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
// Demonstrate how you can use the .split method to perform
|
||||
// a subtraction and an intersection at once
|
||||
const [inside, outside] = result.split(cube(100, true));
|
||||
|
||||
const outsideNode = new GLTFNode();
|
||||
outsideNode.manifold = outside;
|
||||
|
||||
const insideNode = new GLTFNode();
|
||||
insideNode.manifold = inside;
|
||||
insideNode.material = {baseColorFactor: [0, 1, 1]};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
// It also demonstrates the use of setMorph to animate a warping function.
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const scale = 100 / (box.max[0] - box.min[0]);
|
||||
|
||||
setMorphStart(ball, func);
|
||||
const node = new GLTFNode();
|
||||
node.manifold = ball;
|
||||
node.scale = [scale, scale, scale];
|
||||
|
||||
globalDefaults.animationLength = 5; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
|
||||
const result = heart.scale(scale);
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
|
||||
const colorCurvature = (color, pos, oldProp) => {
|
||||
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
|
||||
const b = a * a * (3 - 2 * a);
|
||||
const red = [1, 0, 0];
|
||||
const blue = [0, 0, 1];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = (1 - b) * blue[i] + b * red[i];
|
||||
}
|
||||
};
|
||||
const result = Manifold.smooth(scallop, sharpenedEdges)
|
||||
.refine(n)
|
||||
.calculateCurvature(-1, 0)
|
||||
.setProperties(3, colorCurvature);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {
|
||||
baseColorFactor: [1, 1, 1],
|
||||
metallic: 0,
|
||||
attributes: ['COLOR_0']
|
||||
};
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
// The number of times the thread passes through the donut hole.
|
||||
const p = 1;
|
||||
// The number of times the thread circles the donut.
|
||||
const q = 3;
|
||||
// Radius of the interior of the imaginary donut.
|
||||
const majorRadius = 25;
|
||||
// Radius of the small cross-section of the imaginary donut.
|
||||
const minorRadius = 10;
|
||||
// Radius of the small cross-section of the actual object.
|
||||
const threadRadius = 3.75;
|
||||
// Number of linear segments making up the threadRadius circle. Default is
|
||||
// getCircularSegments(threadRadius).
|
||||
const circularSegments = -1;
|
||||
// Number of segments along the length of the knot. Default makes roughly
|
||||
// square facets.
|
||||
const linearSegments = -1;
|
||||
|
||||
// These default values recreate Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
const pk = p / kLoops;
|
||||
const qk = q / kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * qk * majorRadius / threadRadius;
|
||||
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = qk * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * pk / qk;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(pk * minorRadius, qk * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
const result = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.rotation = [0, 0, 360 * (k / kLoops) * (qk / pk)];
|
||||
node.translation = [0, 0, result.boundingBox().min[2]];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = Manifold.cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference([
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const posColors = (newProp, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
newProp[i] = (1 - pos[i]) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
const result = mengerSponge(3)
|
||||
.trimByPlane([1, 1, 1], 0)
|
||||
.setProperties(3, posColors)
|
||||
.scale(100);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = Manifold.cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = Manifold.extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
const result =
|
||||
Manifold.intersection(Manifold.extrude(stretch, width), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return Manifold.difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
// number of modules along pyramid edge (use 1 for print orientation)
|
||||
const m = 4;
|
||||
// module size
|
||||
const size = 20;
|
||||
// SDF resolution
|
||||
const n = 20;
|
||||
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return Manifold.levelSet(gyroid, box, period / n, level)
|
||||
.scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
const gyroidModule = rhombicDodecahedron()
|
||||
.intersect(gyroidOffset(-0.4))
|
||||
.subtract(gyroidOffset(0.4));
|
||||
|
||||
if (m > 1) {
|
||||
for (let i = 0; i < m; ++i) {
|
||||
for (let j = i; j < m; ++j) {
|
||||
for (let k = j; k < m; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = gyroidModule;
|
||||
node.translation =
|
||||
[(k + i - j) * size, (k - i) * size, (-j) * size];
|
||||
node.material = {
|
||||
baseColorFactor:
|
||||
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = gyroidModule.rotate([-45, 0, 90]).translate([
|
||||
0, 0, size / Math.sqrt(2)
|
||||
]);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
self.examples = examples;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 952 KiB After Width: | Height: | Size: 952 KiB |