From cc3ee83ea75a40d872683b1acb1bf7fe682a3d5d Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Fri, 24 Apr 2026 15:59:11 -0400 Subject: [PATCH 1/9] Add pip installation support and Conan bootstrap - Implemented a CMake option to auto-run Conan install when building as a pip package, ensuring C++ dependencies are fetched. - Updated README to include instructions for installing via pip. - Added a minimal Conan recipe for pip installations to avoid requiring internal Ultimaker packages. - Enhanced conanfile.py to support system Python option. - Added installation rules for the pyUvula module to facilitate packaging for pip. --- CMakeLists.txt | 63 +++++++++++++++++++++++++++++++++++++++++- README.md | 10 +++++++ conanfile.py | 6 ++-- conanfile_pip.py | 28 +++++++++++++++++++ pyUvula/CMakeLists.txt | 8 ++++++ pyproject.toml | 61 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 conanfile_pip.py create mode 100644 pyproject.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index c24ee28..0566290 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,68 @@ cmake_minimum_required(VERSION 3.23) + +# --- Bootstrap Conan dependencies when building as a pip package --- +# When invoked via `pip install` (scikit-build-core sets UVULA_PIP_BUILD=ON), +# auto-run `conan install` to fetch the C++ dependencies and wire up the +# toolchain before project() is called. +option(UVULA_PIP_BUILD "Auto-run 'conan install' to fetch deps for a pip build" OFF) +if (UVULA_PIP_BUILD AND NOT CMAKE_TOOLCHAIN_FILE AND NOT _UVULA_CONAN_BOOTSTRAPPED) + find_program(_UVULA_CONAN_EXE conan REQUIRED) + set(_UVULA_CONAN_OUTPUT "${CMAKE_BINARY_DIR}/conan_deps") + execute_process( + COMMAND "${_UVULA_CONAN_EXE}" profile detect --exist-ok + RESULT_VARIABLE _UVULA_CONAN_PROFILE_RC + ) + message(STATUS "UVULA_PIP_BUILD: running 'conan install' into ${_UVULA_CONAN_OUTPUT}") + execute_process( + COMMAND "${_UVULA_CONAN_EXE}" install "${CMAKE_CURRENT_LIST_DIR}/conanfile_pip.py" + --build=missing + --output-folder=${_UVULA_CONAN_OUTPUT} + -s build_type=Release + RESULT_VARIABLE _UVULA_CONAN_RC + ) + if (NOT _UVULA_CONAN_RC EQUAL 0) + message(FATAL_ERROR "conan install failed (exit ${_UVULA_CONAN_RC}). " + "Ensure Conan 2.x is installed and any private remotes " + "needed for Ultimaker packages are configured.") + endif() + file(GLOB_RECURSE _UVULA_TOOLCHAIN_FILES "${_UVULA_CONAN_OUTPUT}/*conan_toolchain.cmake") + if (NOT _UVULA_TOOLCHAIN_FILES) + message(FATAL_ERROR "conan install succeeded but no conan_toolchain.cmake was generated under ${_UVULA_CONAN_OUTPUT}") + endif() + list(GET _UVULA_TOOLCHAIN_FILES 0 _UVULA_TOOLCHAIN) + set(CMAKE_TOOLCHAIN_FILE "${_UVULA_TOOLCHAIN}" CACHE FILEPATH "Conan-generated toolchain" FORCE) + set(_UVULA_CONAN_BOOTSTRAPPED TRUE CACHE INTERNAL "Conan deps already fetched") + message(STATUS "UVULA_PIP_BUILD: using toolchain ${CMAKE_TOOLCHAIN_FILE}") +endif() + project(uvula) -find_package(standardprojectsettings REQUIRED) +if (UVULA_PIP_BUILD) + # The C++ sources use std::span and other C++20 features. The default + # conan profile typically selects gnu17, so force C++20 explicitly here. + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + # libuvula is STATIC but gets linked into the pyUvula shared module. + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + # Inline minimal replacements for the Ultimaker-internal + # `standardprojectsettings` helpers so a pip install doesn't need the + # private conan remote. Sanitizers and extensive warnings are no-ops here + # since a redistributable wheel shouldn't ship with them enabled anyway. + find_package(Threads) + function(use_threads target) + if (TARGET Threads::Threads) + target_link_libraries(${target} PUBLIC Threads::Threads) + endif() + endfunction() + function(enable_sanitizers target) + endfunction() + function(set_project_warnings target) + endfunction() +else() + find_package(standardprojectsettings REQUIRED) +endif() find_package(spdlog REQUIRED) find_package(range-v3 REQUIRED) find_package(clipper REQUIRED) diff --git a/README.md b/README.md index 7976365..8dedfb4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,16 @@ cmake --build --preset conan-release The python bindings are built by default, but can be ignored by adding `-o with_python_bindings=False` when doing the setup with `conan`. +### Installing via pip + +The Python binding can also be installed directly from source with pip (no prior Conan setup required — the build backend installs Conan transparently and fetches the C++ dependencies from conancenter): + +```bash +pip install git+https://github.com/Ultimaker/libUvula.git +``` + +A C++20 compiler and CMake ≥ 3.23 must be available on the machine running the install. + Once built, just make sure you have the library in the path and call the `unwrap` function: ```python diff --git a/conanfile.py b/conanfile.py index 5254a39..137bb91 100644 --- a/conanfile.py +++ b/conanfile.py @@ -30,6 +30,7 @@ class UvulaConan(ConanFile): "fPIC": [True, False], "enable_extensive_warnings": [True, False], "with_python_bindings": [True, False], + "with_system_python": [True, False], "with_js_bindings": [True, False], "with_cli": [True, False], } @@ -38,6 +39,7 @@ class UvulaConan(ConanFile): "fPIC": True, "enable_extensive_warnings": False, "with_python_bindings": True, + "with_system_python": False, "with_js_bindings": False, "with_cli": False, } @@ -79,7 +81,7 @@ def config_options(self): self.options.with_js_bindings = True def configure(self): - if self.options.get_safe("with_python_bindings", False): + if self.options.get_safe("with_python_bindings", False) and not self.options.get_safe("with_system_python", False): self.options["cpython"].shared = True def layout(self): @@ -103,7 +105,7 @@ def requirements(self): self.requires("spdlog/1.15.1") self.requires("range-v3/0.12.0") self.requires("clipper/6.4.2@ultimaker/stable") - if self.options.get_safe("with_python_bindings", False): + if self.options.get_safe("with_python_bindings", False) and not self.options.get_safe("with_system_python", False): self.requires("cpython/3.12.2") self.requires("pybind11/2.11.1") if self.options.get_safe("with_cli", False): diff --git a/conanfile_pip.py b/conanfile_pip.py new file mode 100644 index 0000000..d398469 --- /dev/null +++ b/conanfile_pip.py @@ -0,0 +1,28 @@ +# Minimal Conan recipe used exclusively by the `pip install` path (see +# pyproject.toml / CMakeLists.txt UVULA_PIP_BUILD bootstrap). It mirrors the +# subset of `conanfile.py` needed to build the Python binding, but without the +# Emscripten-only `npmpackage` python_requires — which would otherwise force +# every conan operation to resolve an Ultimaker-internal package even when +# building the Python wheel. + +from conan import ConanFile +from conan.tools.cmake import cmake_layout + + +class UvulaPipBuildConan(ConanFile): + name = "uvula-pip-build" + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires("spdlog/1.15.1") + self.requires("range-v3/0.12.0") + # The main conanfile pins Ultimaker's `clipper/6.4.2@ultimaker/stable` + # fork, which is hosted on a private remote. Fall back to the upstream + # `clipper/6.4.2` on conancenter so `pip install` works without that + # remote. If the build breaks on fork-specific API, configure the + # Ultimaker remote and pin the @ultimaker/stable revision here instead. + self.requires("clipper/6.4.2") + + def layout(self): + cmake_layout(self) diff --git a/pyUvula/CMakeLists.txt b/pyUvula/CMakeLists.txt index e9bb37f..feeb03d 100644 --- a/pyUvula/CMakeLists.txt +++ b/pyUvula/CMakeLists.txt @@ -12,3 +12,11 @@ target_compile_definitions(pyUvula PRIVATE PYUVULA_VERSION="${PYUVULA_VERSION}") if (NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) pybind11_strip(pyUvula) endif () + +# Install rule for pip / scikit-build-core wheel packaging. +# Dropping the module at the wheel root makes `import pyUvula` work. +install(TARGETS pyUvula + LIBRARY DESTINATION . + RUNTIME DESTINATION . + COMPONENT pyUvula +) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..395dab8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = [ + "scikit-build-core>=0.10", + "pybind11>=2.11", + "conan>=2.7", +] +build-backend = "scikit_build_core.build" + +[project] +name = "pyUvula" +version = "1.0.1" +description = "UV-unwrapping library: normal-based face segmentation with xatlas chart packing." +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "UltiMaker" }] +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 4 - Beta", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Topic :: Multimedia :: Graphics :: 3D Modeling", +] + +[project.urls] +Homepage = "https://github.com/Ultimaker/libUvula" +Source = "https://github.com/Ultimaker/libUvula" + +[tool.scikit-build] +minimum-version = "build-system.requires" +cmake.version = ">=3.23" +cmake.build-type = "Release" +build.targets = ["pyUvula"] +sdist.include = [ + "CMakeLists.txt", + "conanfile.py", + "conanfile_pip.py", + "conandata.yml", + "include/**", + "src/**", + "pyUvula/**", + "LICENSE", + "README.md", +] +sdist.exclude = [ + "UvulaJS", + "cli", + "demo.png", + ".github", +] + +[tool.scikit-build.cmake.define] +UVULA_PIP_BUILD = "ON" +UVULA_VERSION = "1.0.1" +PYUVULA_VERSION = "1.0.1" +WITH_PYTHON_BINDINGS = "ON" +WITH_JS_BINDINGS = "OFF" +WITH_CLI = "OFF" +EXTENSIVE_WARNINGS = "OFF" From 398c9c9977eeec3eeeb348bacda94143030ebcdb Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 11:41:24 -0400 Subject: [PATCH 2/9] Add C++20 standard support for Conan dependencies Updated the CMake configuration to specify the C++20 standard when bootstrapping Conan dependencies for pip builds. This ensures compatibility with modern C++ features and improves the overall build process. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0566290..ef3f02f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.23) - +o # --- Bootstrap Conan dependencies when building as a pip package --- # When invoked via `pip install` (scikit-build-core sets UVULA_PIP_BUILD=ON), # auto-run `conan install` to fetch the C++ dependencies and wire up the @@ -18,6 +18,7 @@ if (UVULA_PIP_BUILD AND NOT CMAKE_TOOLCHAIN_FILE AND NOT _UVULA_CONAN_BOOTSTRAPP --build=missing --output-folder=${_UVULA_CONAN_OUTPUT} -s build_type=Release + -s compiler.cppstd=20 RESULT_VARIABLE _UVULA_CONAN_RC ) if (NOT _UVULA_CONAN_RC EQUAL 0) From 967fed7c1133e2d2595138adfa7ead2116118bd7 Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 11:44:24 -0400 Subject: [PATCH 3/9] Fix formatting issue in CMakeLists.txt Removed an extraneous character at the beginning of the file. This change ensures proper parsing of the CMake configuration. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef3f02f..d89ad06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.23) -o + # --- Bootstrap Conan dependencies when building as a pip package --- # When invoked via `pip install` (scikit-build-core sets UVULA_PIP_BUILD=ON), # auto-run `conan install` to fetch the C++ dependencies and wire up the From c2cb541f2b8248ea2124033c70a8a66544414a13 Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 13:36:07 -0400 Subject: [PATCH 4/9] Fix CMake condition syntax for build type check Updated the CMakeLists.txt to properly quote the CMAKE_BUILD_TYPE variable in the conditional check. This ensures correct evaluation of the build type when determining whether to strip the pyUvula module for non-debug builds. --- pyUvula/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyUvula/CMakeLists.txt b/pyUvula/CMakeLists.txt index feeb03d..d59f480 100644 --- a/pyUvula/CMakeLists.txt +++ b/pyUvula/CMakeLists.txt @@ -9,7 +9,7 @@ if (NOT MSVC) endif() target_link_libraries(pyUvula PUBLIC libuvula ${NEEDED_DEPS}) target_compile_definitions(pyUvula PRIVATE PYUVULA_VERSION="${PYUVULA_VERSION}") -if (NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) +if (NOT MSVC AND NOT "${CMAKE_BUILD_TYPE}" MATCHES "Debug|RelWithDebInfo") pybind11_strip(pyUvula) endif () From 9d0e00495d81e9507c1a2f931ff7092198178347 Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 13:52:37 -0400 Subject: [PATCH 5/9] Update Python package requirements in CMake and pyproject - Change find_package from Python to Python3 to ensure compatibility with newer Python versions. - Specify pybind11 version range to avoid potential breaking changes in future releases. --- pyUvula/CMakeLists.txt | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyUvula/CMakeLists.txt b/pyUvula/CMakeLists.txt index d59f480..b085c74 100644 --- a/pyUvula/CMakeLists.txt +++ b/pyUvula/CMakeLists.txt @@ -1,5 +1,5 @@ set(ENV{LD_LIBRARY_PATH} "${CMAKE_LIBRARY_PATH}:${LD_LIBRARY_PATH}") # Needed to ensure that CMake finds the Conan CPython library -find_package(Python COMPONENTS Interpreter Development) +find_package(Python3 COMPONENTS Interpreter Development.Module) find_package(pybind11 REQUIRED) pybind11_add_module(pyUvula pyUvula.cpp) diff --git a/pyproject.toml b/pyproject.toml index 395dab8..922b938 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ "scikit-build-core>=0.10", - "pybind11>=2.11", + "pybind11>=2.11,<3.0", "conan>=2.7", ] build-backend = "scikit_build_core.build" From 7e86c58c574c5552019873f73166cc89f9075455 Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 14:03:54 -0400 Subject: [PATCH 6/9] Add ninja to build-system requirements This change includes 'ninja' in the list of required packages for the build system. This ensures that the necessary build tools are available for the project, improving the build process. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 922b938..204cbc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ requires = [ "scikit-build-core>=0.10", "pybind11>=2.11,<3.0", "conan>=2.7", + "ninja", ] build-backend = "scikit_build_core.build" From d924ca5785c714690faa55f87871c40b21055d90 Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 14:14:36 -0400 Subject: [PATCH 7/9] Remove ninja from build-system requirements The ninja dependency has been removed from the build-system requirements in pyproject.toml. The CMake generator is now set directly to "Ninja" in the configuration. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 204cbc9..ffd6122 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,6 @@ requires = [ "scikit-build-core>=0.10", "pybind11>=2.11,<3.0", "conan>=2.7", - "ninja", ] build-backend = "scikit_build_core.build" @@ -33,6 +32,7 @@ Source = "https://github.com/Ultimaker/libUvula" minimum-version = "build-system.requires" cmake.version = ">=3.23" cmake.build-type = "Release" +cmake.generator = "Ninja" build.targets = ["pyUvula"] sdist.include = [ "CMakeLists.txt", From 77ad780fb31b11e393723271109a04a38067926a Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 14:18:01 -0400 Subject: [PATCH 8/9] Update CMake generator configuration in pyproject.toml Changed the CMake generator specification from a string to an argument in the args list. This aligns with the expected format for scikit-build and ensures proper configuration during the build process. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ffd6122..65e9dd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ Source = "https://github.com/Ultimaker/libUvula" minimum-version = "build-system.requires" cmake.version = ">=3.23" cmake.build-type = "Release" -cmake.generator = "Ninja" +cmake.args = ["-GNinja"] build.targets = ["pyUvula"] sdist.include = [ "CMakeLists.txt", From 68215da8b24df2601669557f55115bed9e855b5d Mon Sep 17 00:00:00 2001 From: brettmichaelgreen Date: Sat, 25 Apr 2026 14:24:15 -0400 Subject: [PATCH 9/9] Add Ninja generator option for Conan toolchain This change adds the Ninja generator option to the Conan toolchain configuration when building as a pip package. This ensures that the build system uses Ninja for improved build performance and efficiency. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d89ad06..f11afe0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ if (UVULA_PIP_BUILD AND NOT CMAKE_TOOLCHAIN_FILE AND NOT _UVULA_CONAN_BOOTSTRAPP --output-folder=${_UVULA_CONAN_OUTPUT} -s build_type=Release -s compiler.cppstd=20 + -c tools.cmake.cmaketoolchain:generator=Ninja RESULT_VARIABLE _UVULA_CONAN_RC ) if (NOT _UVULA_CONAN_RC EQUAL 0)