From 7770b76a206ec16d3fd2dbb434454b61337e9d62 Mon Sep 17 00:00:00 2001 From: artem-ogre Date: Wed, 22 Jul 2026 23:31:58 +0200 Subject: [PATCH 1/2] Update CDT to 1.4.5, bump pybind11 to v3.0.4, add CI - CDT 1.4.0 -> 1.4.5 and pybind11 2.12.0.dev1 -> v3.0.4. The bindings themselves needed no source changes. - Re-baseline the golden OFF checksums. Only the internal super-triangle scaffold changed (CDT #173): the mesh over the real input vertices is identical and every case still passes verify_topology. Upstream CDT re-baselined its own 31 expected files over the same range. - Set the version to 1.4.5 to track CDT, and wire it through to PythonCDT.__version__, which always reported "dev" because VERSION_INFO was never defined and MACRO_STRINGIFY was missing. - Pass Python_EXECUTABLE alongside PYTHON_EXECUTABLE: pybind11 >= 3 defaults to CMake's FindPython and would otherwise build against an unrelated interpreter's headers. - Point testpaths and the cibuildwheel test-command at the actual test file; they referenced a tests/ dir that does not exist, which made a bare `pytest` run fail. - Add GitHub Actions CI that builds the wheel and runs the tests on ubuntu/macos/windows against Python 3.10 and 3.13. --- .github/workflows/ci.yml | 57 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 4 +++ CDT | 2 +- CMakeLists.txt | 10 +++++-- cdt_bindings.cpp | 3 +++ cdt_bindings_test.py | 15 +++++++---- pybind11 | 2 +- pyproject.toml | 4 +-- setup.py | 17 +++++++----- 9 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9071af3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +# Cancel superseded runs on the same branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + name: ${{ matrix.os }} / Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.13"] + + defaults: + run: + shell: bash + + steps: + - name: Check out with submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install build and test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build + python -m pip install -r requirements.txt + + - name: Build the wheel + run: python -m build --wheel --outdir dist + + - name: Install the built wheel + run: python -m pip install --no-index --find-links dist PythonCDT + + - name: Run tests + run: pytest ./cdt_bindings_test.py + + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: wheel-${{ matrix.os }}-py${{ matrix.python-version }} + path: dist/*.whl diff --git a/.gitignore b/.gitignore index 4ef79c3..e27938d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ .cache/* +.pytest_cache/ +__pycache__/ .vscode/* .idea/* build/* +dist/* +*.egg-info/ cmake-build-*/* python/.venv/* .venv/* diff --git a/CDT b/CDT index 58f34da..2068d01 160000 --- a/CDT +++ b/CDT @@ -1 +1 @@ -Subproject commit 58f34da24b438bd17629450fcec189ccb181dc9f +Subproject commit 2068d015b9db3c92481e869b0c1f669b96a1d70a diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a51f53..9daaea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,8 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) project( PythonCDT - VERSION 0.1.0 - DESCRIPTION "Software surface data rasterizer library" + VERSION 1.4.5 + DESCRIPTION "Python bindings for CDT: constrained Delaunay triangulation" LANGUAGES CXX ) @@ -19,5 +19,11 @@ target_include_directories( PythonCDT PRIVATE $ ) target_link_libraries(PythonCDT PRIVATE CDT::CDT) +# Exposed to Python as PythonCDT.__version__. setup.py passes the wheel +# version; fall back to the project version for plain CMake builds. +if(NOT DEFINED VERSION_INFO) + set(VERSION_INFO ${PROJECT_VERSION}) +endif() +target_compile_definitions(PythonCDT PRIVATE VERSION_INFO=${VERSION_INFO}) # Use rasterizer as pre-compiled header for faster test-only re-compiles #target_precompile_headers(PythonCDT PRIVATE include/rasterizer.h) diff --git a/cdt_bindings.cpp b/cdt_bindings.cpp index 8250ac1..692dc4b 100644 --- a/cdt_bindings.cpp +++ b/cdt_bindings.cpp @@ -15,6 +15,9 @@ #include #include +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + namespace py = pybind11; using coord_t = double; diff --git a/cdt_bindings_test.py b/cdt_bindings_test.py index 172c1b0..c98d4e9 100644 --- a/cdt_bindings_test.py +++ b/cdt_bindings_test.py @@ -20,6 +20,11 @@ def test_constants() -> None: assert cdt.NO_VERTEX == np.iinfo(np.uintc).max, "NO_VERTEX constant has wrong value" +def test_version() -> None: + """Test that the version is passed in from the build system""" + assert cdt.__version__ != "dev", "Version was not passed in from the build system" + + def test_V2d() -> None: """Test 2D vector""" p = cdt.V2d(42, 42) @@ -140,7 +145,7 @@ def test_triangulate_input_file() -> None: with tempfile.TemporaryDirectory() as tmp_dir: off_file = f"{tmp_dir}/cdt.off" save_triangulation_as_off(t, off_file) - assert md5_checksum(off_file) == '5fb163a9f27ec6bdd05b7d5f2b23416c', "Wrong OFF file contents" + assert md5_checksum(off_file) == 'db59c00d9dad866781cd96779e5262b7', "Wrong OFF file contents" def test_conform_to_edges() -> None: @@ -149,7 +154,7 @@ def test_conform_to_edges() -> None: t.insert_vertices(vv) t.conform_to_edges(ee) t.erase_outer_triangles_and_holes() - assert triangulation_md5_checksum(t) == 'df2503c614e2f98656038948b355b27e', "Wrong OFF file contents" + assert triangulation_md5_checksum(t) == 'b64cae39c91a55dd4e23a146eb7df0d3', "Wrong OFF file contents" @pytest.mark.parametrize("vv", [[cdt.V2d(-1, 0), cdt.V2d(0, 0.5), cdt.V2d(1, 0), cdt.V2d(0, -0.5)], @@ -161,7 +166,7 @@ def test_insert_vertices(vv) -> None: assert len(t.vertices) == 7, "Wrong vertex count in triangulation" assert len(t.triangles) == 9, "Wrong triangle count in triangulation" assert len(t.fixed_edges) == 0, "Wrong fixed edge count in triangulation" - assert triangulation_md5_checksum(t) == 'c424c4f2691dc3b9aabd39dcf2e17c53', "Wrong OFF file contents" + assert triangulation_md5_checksum(t) == 'db9176f4429942862a7a73155fb55322', "Wrong OFF file contents" @pytest.mark.parametrize("ee", [[cdt.Edge(0, 1), cdt.Edge(2, 3), cdt.Edge(3, 4), cdt.Edge(5, 6)], @@ -175,7 +180,7 @@ def test_insert_conform_edges(ee) -> None: assert len(t.vertices) == 10, "Wrong vertex count in triangulation" assert len(t.triangles) == 15, "Wrong triangle count in triangulation" assert len(t.fixed_edges) == 4, "Wrong fixed edge count in triangulation" - assert triangulation_md5_checksum(t) == '8424ba2c8f8ebabe1bea4141464a347b', "Wrong OFF file contents" + assert triangulation_md5_checksum(t) == '639c7a1492b2adb8f25464ec81ff6a00', "Wrong OFF file contents" # conform to edges t = cdt.Triangulation(cdt.VertexInsertionOrder.AS_PROVIDED, cdt.IntersectingConstraintEdges.NOT_ALLOWED, 0.0) @@ -184,4 +189,4 @@ def test_insert_conform_edges(ee) -> None: assert len(t.vertices) == 12, "Wrong vertex count in triangulation" assert len(t.triangles) == 19, "Wrong triangle count in triangulation" assert len(t.fixed_edges) == 6, "Wrong fixed edge count in triangulation" - assert triangulation_md5_checksum(t) == '9cb9dbaca4943ff0e3aab6c1d31f5a35', "Wrong OFF file contents" + assert triangulation_md5_checksum(t) == '9c87b435e247c1658ec0f04af3340dc7', "Wrong OFF file contents" diff --git a/pybind11 b/pybind11 index 8b48ff8..d03662f 160000 --- a/pybind11 +++ b/pybind11 @@ -1 +1 @@ -Subproject commit 8b48ff878c168b51fe5ef7b8c728815b9e1a9857 +Subproject commit d03662f0984f652b60e7ddce53d3868002275197 diff --git a/pyproject.toml b/pyproject.toml index 7b3b581..135e709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,10 +15,10 @@ minversion = "6.0" addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] xfail_strict = true filterwarnings = ["error"] -testpaths = ["tests"] +testpaths = ["cdt_bindings_test.py"] [tool.cibuildwheel] -test-command = "pytest {project}/tests" +test-command = "pytest {project}/cdt_bindings_test.py" test-extras = ["test"] test-skip = ["*universal2:arm64"] # Setuptools bug causes collision between pypy and cpython artifacts diff --git a/setup.py b/setup.py index cbe1c3b..8bea506 100644 --- a/setup.py +++ b/setup.py @@ -41,12 +41,17 @@ def build_extension(self, ext): # Can be set with Conda-Build, for example. cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON - # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code - # from Python. + # Both spellings are passed on purpose: pybind11 < 3 uses its own + # PYTHON_EXECUTABLE, while pybind11 >= 3 defaults to CMake's + # FindPython, which reads Python_EXECUTABLE. Passing only the former + # lets CMake pick an unrelated interpreter and build against the + # wrong Python headers. + # VERSION_INFO is passed into the C++ code and surfaces as + # PythonCDT.__version__. cmake_args = [ f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DPython_EXECUTABLE={sys.executable}", f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm ] build_args = [] @@ -56,9 +61,7 @@ def build_extension(self, ext): cmake_args += [ item for item in os.environ["CMAKE_ARGS"].split(" ") if item] - # In this example, we pass in the version to C++. You might not need to. - cmake_args += [ - f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] + cmake_args += [f"-DVERSION_INFO={self.distribution.get_version()}"] if self.compiler.compiler_type != "msvc": # Using Ninja-build since it a) is available as a wheel and b) @@ -127,7 +130,7 @@ def build_extension(self, ext): # logic and declaration, and simpler if you include description/version in a file. setup( name="PythonCDT", - version="0.0.1", + version="1.4.5", author="Leica Geosystems", author_email="", description="Test", From c4649c876e767b5f5752b62a2f86adfef5015f62 Mon Sep 17 00:00:00 2001 From: artem-ogre Date: Wed, 22 Jul 2026 23:37:56 +0200 Subject: [PATCH 2/2] Fill in real package metadata in setup.py Replaces the placeholder metadata: description was "Test", the long description was empty, and the author was a leftover from the project's origin. Adds the project URLs, license, classifiers and keywords, and uses README.md as the long description. The README is read with an explicit utf-8 encoding because it contains non-ASCII characters that would fail to decode under the default locale on Windows. Also raises python_requires from 3.6 to 3.8, which is the floor for the pybind11 3.x now used to build the extension. --- setup.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 8bea506..e9a5c06 100644 --- a/setup.py +++ b/setup.py @@ -126,18 +126,46 @@ def build_extension(self, ext): ) +# Explicit encoding: the README contains non-ASCII characters, which would +# fail to decode under the default locale on Windows. +with open( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"), + encoding="utf-8", +) as f: + long_description = f.read() + # The information here can also be placed in setup.cfg - better separation of # logic and declaration, and simpler if you include description/version in a file. setup( name="PythonCDT", version="1.4.5", - author="Leica Geosystems", - author_email="", - description="Test", - long_description="", + author="Artem Amirkhanov", + description=( + "Python bindings for CDT: constrained Delaunay triangulation" + ), + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/artem-ogre/PythonCDT", + project_urls={ + "Source": "https://github.com/artem-ogre/PythonCDT", + "Issues": "https://github.com/artem-ogre/PythonCDT/issues", + "CDT": "https://github.com/artem-ogre/CDT", + }, + license="MPL-2.0", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Multimedia :: Graphics :: 3D Modeling", + ], + keywords="delaunay triangulation constrained cdt computational-geometry", ext_modules=[CMakeExtension("PythonCDT")], cmdclass={"build_ext": CMakeBuild}, zip_safe=False, extras_require={"test": ["pytest>=6.0"]}, - python_requires=">=3.6", + # pybind11 3.x supports Python 3.8 and newer + python_requires=">=3.8", )