From 78cc63d6909db4cc09acea5afd6bb5f8afa86872 Mon Sep 17 00:00:00 2001 From: Alex Kaszynski Date: Fri, 19 Dec 2025 09:30:50 -0700 Subject: [PATCH 1/5] build api compatible wheels for python 3.11+ and build arm wheels --- .github/workflows/testing-and-deployment.yml | 26 ++++++++--------- pyproject.toml | 21 +++++++++++++- setup.py | 30 ++++++++++++++++++-- tools/audit_wheel.sh | 9 ++++++ 4 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 tools/audit_wheel.sh diff --git a/.github/workflows/testing-and-deployment.yml b/.github/workflows/testing-and-deployment.yml index c943b42..6b78a21 100644 --- a/.github/workflows/testing-and-deployment.yml +++ b/.github/workflows/testing-and-deployment.yml @@ -17,14 +17,14 @@ jobs: # fail fast and early to avoid clogging GH Actions smoke_testing: runs-on: ubuntu-latest - name: Smoke Testing + name: Source distribution testing steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.13' - name: Setup headless display uses: pyvista/setup-headless-display-action@v4 @@ -44,7 +44,7 @@ jobs: cd tests && python -m pytest -v - name: Upload sdist - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: path: ./dist/*.tar.gz name: fast-simplification-sdist @@ -55,12 +55,12 @@ jobs: needs: smoke_testing steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.13' - name: Setup headless display uses: pyvista/setup-headless-display-action@v4 @@ -88,18 +88,18 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-2025, macos-14, macos-13] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest, macos-15-intel] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build wheels - uses: pypa/cibuildwheel@v2.23.3 + uses: pypa/cibuildwheel@v3.3.0 - name: List generated wheels run: ls ./wheelhouse/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: path: ./wheelhouse/*.whl name: fast-simplification-wheel-${{ matrix.os }} @@ -116,7 +116,7 @@ jobs: id-token: write # this permission is mandatory for trusted publishing contents: write # required to create a release steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 - name: Flatten directory structure run: | mkdir -p dist/ diff --git a/pyproject.toml b/pyproject.toml index 1b91361..27be278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,12 +9,31 @@ requires = [ [tool.cibuildwheel] archs = ["auto64"] # 64-bit only -skip = "cp314-* pp* *musllinux*" # Build CPython 3.9 - 3.13 +before-build = "pip install abi3audit" +build = "cp310-* cp311-*" # 3.11+ are abi3 wheels +skip = "*musllinux*" test-command = "pytest {project}/tests" test-requires = "pyvista pytest" +[tool.cibuildwheel.linux] +repair-wheel-command = [ + "auditwheel repair -w {dest_dir} {wheel}", + "bash tools/audit_wheel.sh {wheel}" +] + [tool.cibuildwheel.macos] archs = ["native"] +repair-wheel-command = [ + "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}", + "bash tools/audit_wheel.sh {wheel}" +] + +[tool.cibuildwheel.windows] +before-build = "pip install delvewheel abi3audit" +repair-wheel-command = [ + "delvewheel repair -w {dest_dir} {wheel}", + "bash tools/audit_wheel.sh {wheel}" +] [tool.codespell] ignore-words-list = 'THIRDPARTY' diff --git a/setup.py b/setup.py index c13eefc..f5c3526 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,19 @@ import builtins from io import open as io_open import os +import platform import sys from setuptools import Extension, setup from setuptools.command.build_ext import build_ext as _build_ext +from wheel.bdist_wheel import bdist_wheel filepath = os.path.dirname(__file__) # Define macros for cython macros = [] +ext_kwargs = {} +setup_kwargs = {} if os.name == "nt": # windows extra_compile_args = ["/openmp", "/O2", "/w", "/GS"] elif os.name == "posix": # linux org mac os @@ -28,6 +32,24 @@ macros.append(("IS64BITPLATFORM", None)) +# https://github.com/joerick/python-abi3-package-sample/blob/main/setup.py +class bdist_wheel_abi3(bdist_wheel): # noqa: D101 + def get_tag(self): # noqa: D102 + python, abi, plat = super().get_tag() + + if python.startswith("cp"): + return "cp311", "abi3", plat + + return python, abi, plat + + +if sys.version_info.minor >= 11 and platform.python_implementation() == "CPython": + # Can create an abi3 wheel (typed memoryviews first available in 3.11)! + macros.append(("Py_LIMITED_API", "0x030B0000")) + ext_kwargs["py_limited_api"] = True + setup_kwargs["cmdclass"] = {"bdist_wheel": bdist_wheel_abi3} + + # Get version from version info __version__ = None version_file = os.path.join(filepath, "fast_simplification", "_version.py") @@ -59,6 +81,8 @@ def build_extensions(self): _build_ext.build_extensions(self) +setup_kwargs["cmdclass"]["build_ext"] = build_ext + setup( name="fast_simplification", packages=["fast_simplification"], @@ -73,16 +97,15 @@ def build_extensions(self): "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ], url="https://github.com/pyvista/fast-simplification", python_requires=">=3.9", # Build cython modules - cmdclass={"build_ext": build_ext}, ext_modules=[ Extension( "fast_simplification._simplify", @@ -90,6 +113,7 @@ def build_extensions(self): language="c++", extra_compile_args=extra_compile_args, define_macros=macros, + **ext_kwargs, ), Extension( "fast_simplification._replay", @@ -97,8 +121,10 @@ def build_extensions(self): language="c++", extra_compile_args=extra_compile_args, define_macros=macros, + **ext_kwargs, ), ], keywords="fast-simplification decimation", install_requires=["numpy"], + **setup_kwargs, ) diff --git a/tools/audit_wheel.sh b/tools/audit_wheel.sh new file mode 100644 index 0000000..dcd80be --- /dev/null +++ b/tools/audit_wheel.sh @@ -0,0 +1,9 @@ +#!/bin/bash -eo pipefail +set -x + +PY_MINOR=$(python -c "import sys; print(sys.version_info.minor)") +if [ "$PY_MINOR" -lt 11 ]; then + echo "Not checking abi3audit for Python $PY_MINOR < 3.11" + exit 0 +fi +abi3audit --strict --report --verbose "$1" From ad315cc9353e8051c545238554397afb8168db07 Mon Sep 17 00:00:00 2001 From: Alex Kaszynski Date: Fri, 19 Dec 2025 09:31:30 -0700 Subject: [PATCH 2/5] bump pre-commit --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff7bc18..3c90ac7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.7 + rev: v0.14.10 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -57,6 +57,6 @@ repos: # this validates our github workflow files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.35.0 + rev: 0.36.0 hooks: - id: check-github-workflows From 440684d7441b1807300f18969b209bf0593feb0a Mon Sep 17 00:00:00 2001 From: Alex Kaszynski Date: Fri, 19 Dec 2025 09:39:37 -0700 Subject: [PATCH 3/5] add type hints --- fast_simplification/simplify.py | 76 +++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/fast_simplification/simplify.py b/fast_simplification/simplify.py index 619a9c1..c4e413a 100644 --- a/fast_simplification/simplify.py +++ b/fast_simplification/simplify.py @@ -1,10 +1,19 @@ """Simplification library.""" +from typing import TYPE_CHECKING + import numpy as np +from numpy.typing import NDArray from . import _simplify from .utils import ascontiguous +if TYPE_CHECKING: + try: + from pyvista.core.pointset import PolyData + except ModuleNotFoundError: + pass + def _check_args(target_reduction, target_count, n_faces): """Check arguments.""" @@ -27,14 +36,17 @@ def _check_args(target_reduction, target_count, n_faces): @ascontiguous def simplify( - points, - triangles, - target_reduction=None, - target_count=None, - agg=7, - verbose=False, - return_collapses=False, - lossless=False, + points: NDArray[np.float64], + triangles: NDArray[np.int32], + target_reduction: float | None = None, + target_count: int | None = None, + agg: float = 7.0, + verbose: bool = False, + return_collapses: bool = False, + lossless: bool = False, +) -> ( + tuple[NDArray[np.float64], NDArray[np.int64]] + | tuple[NDArray[np.float64], NDArray[np.int64], NDArray[np.int64]] ): """Simplify a triangular mesh. @@ -54,21 +66,18 @@ def simplify( target_count : int, optional Target number of triangles to reduce mesh to. This may be used in place of ``target_reduction``, but both cannot be set. - agg : int, optional - Controls how aggressively to decimate the mesh. A value of 10 - will result in a fast decimation at the expense of mesh - quality and shape. A value of 0 will attempt to preserve the - original mesh geometry at the expense of time. Setting a low - value may result in being unable to reach the - ``target_reduction`` or ``target_count``. + agg : float, default: 7.0 + Controls how aggressively to decimate the mesh. A value of 10 will + result in a fast decimation at the expense of mesh quality and shape. + A value of 0 will attempt to preserve the original mesh geometry at the + expense of time. Setting a low value may result in being unable to + reach the ``target_reduction`` or ``target_count``. verbose : bool, optional Enable verbose output when simplifying the mesh. return_collapses : bool, optional - If True, return the history of collapses as a - ``(n_collapses, 2)`` array of indices. - ``collapses[i] = [i0, i1]`` means that durint the i-th - collapse, the vertex ``i1`` was collapsed into the vertex - ``i0``. + If True, return the history of collapses as a ``(n_collapses, 2)`` + array of indices. ``collapses[i] = [i0, i1]`` means that durint the + i-th collapse, the vertex ``i1`` was collapsed into the vertex ``i0``. lossless : bool, optional If True, simplify the mesh losslessly. @@ -158,7 +167,13 @@ def simplify( return points, faces -def simplify_mesh(mesh, target_reduction=None, target_count=None, agg=7, verbose=False): +def simplify_mesh( + mesh: "PolyData", + target_reduction: float | None = None, + target_count: int | None = None, + agg: float = 7.0, + verbose: bool = False, +): """Simplify a pyvista mesh. Parameters @@ -166,20 +181,19 @@ def simplify_mesh(mesh, target_reduction=None, target_count=None, agg=7, verbose mesh : pyvista.PolyData PyVista mesh. target_reduction : float - Fraction of the original mesh to remove. If set to ``0.9``, + Fraction of the original mesh to remove. If set to ``0.9``, this function will try to reduce the data set to 10% of its original size and will remove 90% of the input triangles. Use this parameter or ``target_count``. target_count : int, optional - Target number of triangles to reduce mesh to. This may be - used in place of ``target_reduction``, but both cannot be set. - agg : int, optional - Controls how aggressively to decimate the mesh. A value of 10 - will result in a fast decimation at the expense of mesh - quality and shape. A value of 0 will attempt to preserve the - original mesh geometry at the expense of time. Setting a low - value may result in being unable to reach the - ``target_reduction`` or ``target_count``. + Target number of triangles to reduce mesh to. This may be used in + place of ``target_reduction``, but both cannot be set. + agg : float, default: 7.0 + Controls how aggressively to decimate the mesh. A value of ``10.0`` will + result in a fast decimation at the expense of mesh quality and shape. + A value of ``0.0`` will attempt to preserve the original mesh geometry at the + expense of time. Setting a low value may result in being unable to + reach the ``target_reduction`` or ``target_count``. verbose : bool, optional Enable verbose output when simplifying the mesh. From 34dbc2ba1494230e04032dd6752beaec10d51f7d Mon Sep 17 00:00:00 2001 From: Alex Kaszynski Date: Fri, 19 Dec 2025 09:40:59 -0700 Subject: [PATCH 4/5] fix setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f5c3526..5419ef9 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ # Define macros for cython macros = [] ext_kwargs = {} -setup_kwargs = {} +setup_kwargs = {"cmdclass": {}} if os.name == "nt": # windows extra_compile_args = ["/openmp", "/O2", "/w", "/GS"] elif os.name == "posix": # linux org mac os @@ -47,7 +47,7 @@ def get_tag(self): # noqa: D102 # Can create an abi3 wheel (typed memoryviews first available in 3.11)! macros.append(("Py_LIMITED_API", "0x030B0000")) ext_kwargs["py_limited_api"] = True - setup_kwargs["cmdclass"] = {"bdist_wheel": bdist_wheel_abi3} + setup_kwargs["cmdclass"]["bdist_wheel":bdist_wheel_abi3] # Get version from version info From dcb4a1b89d6e7106429e76fa34b16c52e5a32d59 Mon Sep 17 00:00:00 2001 From: Alex Kaszynski Date: Fri, 19 Dec 2025 09:42:55 -0700 Subject: [PATCH 5/5] fix setup.py again... --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5419ef9..d94fec0 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ def get_tag(self): # noqa: D102 # Can create an abi3 wheel (typed memoryviews first available in 3.11)! macros.append(("Py_LIMITED_API", "0x030B0000")) ext_kwargs["py_limited_api"] = True - setup_kwargs["cmdclass"]["bdist_wheel":bdist_wheel_abi3] + setup_kwargs["cmdclass"]["bdist_wheel"] = bdist_wheel_abi3 # Get version from version info