# ndicapi -- the C API for Northern Digital (NDI) Polaris and Aurora trackers, # maintained by the Plus Toolkit project. This is the tracker driver underneath # PlusLib, and therefore underneath NaviNIBS and 3D Slicer/SlicerIGT when they # talk to a Polaris; nothing in Fedora provides it. # Chain: ndicapi -> PlusLib -> PlusApp (PlusServer) -> NaviNIBS / SlicerIGT. # # Snapshot rather than the 1.6.0 tag (2019): CMakeLists still says 1.6.0 but # HEAD carries seven years of fixes, the last of which (2026-09-08, "release GIL # while calling ndiCommand") matters for the Python module, which is how # NaviNIBS-side tooling reaches the hardware. %global commit 615ee27d06ddbc167745411946628bc5a5e246d5 %global shortcommit %(c=%{commit}; echo ${c:0:8}) %global snapdate 20260908 Name: ndicapi Version: 1.6.0^%{snapdate}git%{shortcommit} Release: 1%{?dist} Summary: C API for NDI Polaris and Aurora tracking devices # LICENSE, MIT, Plus Toolkit 2017. The original code is David Gobbi's (Atamai, # 2000-2005) under a BSD-style notice retained in the source headers. License: MIT URL: https://github.com/PlusToolkit/ndicapi Source0: %{url}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz # Install with GNUInstallDirs (lib64), give the shared library a soname, # put the headers in include/ndicapi, and install a CMake package config that # points at the installed tree rather than the build tree. Upstream's install # rules are lib/ + flat include/ with a config that only works from the build # directory. See docs/upstream/ndicapi-install-layout.md. Patch0: ndicapi-install-layout.patch BuildRequires: cmake >= 3.22 BuildRequires: gcc-c++ BuildRequires: ninja-build BuildRequires: python3-devel %description ndicapi is a C library for communicating with Northern Digital Inc. optical (Polaris) and electromagnetic (Aurora) tracking systems over serial or TCP. It implements the NDI Combined API command set -- device probing, port and tool handle management, tracking start/stop, and parsing of TX/BX transform replies into position and quaternion data -- plus helpers to convert those transforms to matrices and Euler angles. It is the tracker back end of the Plus Toolkit, and through it of NaviNIBS and 3D Slicer's SlicerIGT. %package devel Summary: Development files for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} %description devel Headers and CMake package configuration for building against %{name}. %package -n python3-%{name} Summary: Python bindings for %{name} (the ndicapy module) Requires: %{name}%{?_isa} = %{version}-%{release} %description -n python3-%{name} The ndicapy extension module: Python access to the ndicapi C API for NDI Polaris and Aurora trackers. This is a thin binding over the C library -- every function keeps its C name (ndiOpen, ndiCommand, ndiGetTXTransform, ...). %prep %autosetup -p1 -n %{name}-%{commit} # Upstream's setup.py.in is distutils (gone in Python 3.12) and recompiles the # whole library into the extension while also linking the shared one. Replace # it with a setuptools build of just the module against the library we build. cat > pyproject.toml <<'EOF' [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" EOF cat > setup.py <<'EOF' from setuptools import setup, Extension import os # %%generate_buildrequires runs this before %%build sets the env, so default. builddir = os.environ.get("NDICAPI_BUILD_DIR", ".") ndicapy = Extension( "ndicapy", sources=["ndicapimodule.cxx"], include_dirs=[".", builddir], libraries=["ndicapi"], library_dirs=[builddir + "/lib"], ) setup( name="ndicapi", version=os.environ.get("NDICAPI_PY_VERSION", "1.6.0"), description="Python interface to NDI Polaris and Aurora tracking devices", ext_modules=[ndicapy], ) EOF %generate_buildrequires %pyproject_buildrequires %build %cmake -G Ninja \ -DBUILD_SHARED_LIBS:BOOL=ON \ -DBUILD_PYTHON:BOOL=OFF \ -Dndicapi_BUILD_APPLICATIONS:BOOL=OFF \ -DCMAKE_SKIP_INSTALL_RPATH:BOOL=ON %cmake_build # The extension needs the generated ndicapiExport.h from the build dir and # links the just-built library. Use the tag version for the Python # distribution; the caret snapshot syntax is not PEP 440. export NDICAPI_BUILD_DIR="$PWD/%{__cmake_builddir}" export NDICAPI_PY_VERSION=1.6.0 %pyproject_wheel %install %cmake_install export NDICAPI_BUILD_DIR="$PWD/%{__cmake_builddir}" export NDICAPI_PY_VERSION=1.6.0 %pyproject_install %pyproject_save_files ndicapy %check # No hardware in a build root, so exercise what does not need a device: # the library resolves every symbol, and the Python module imports and its # pure-computation entry points behave. ndiDeviceName(0) names the first # candidate serial port; ndiHexToUnsignedLong is the reply parser. ldd -r %{buildroot}%{_libdir}/libndicapi.so.1 | grep '^undefined symbol' \ && { echo "libndicapi has undefined symbols"; exit 1; } || : PYTHONPATH=%{buildroot}%{python3_sitearch} \ LD_LIBRARY_PATH=%{buildroot}%{_libdir} \ %{python3} - <<'EOF' import ndicapy assert ndicapy.ndiHexToUnsignedLong("FF", 2) == 255 assert ndicapy.ndiSignedToLong("-0012", 5) == -12 name = ndicapy.ndiDeviceName(0) assert isinstance(name, str) and name.startswith("/dev/tty"), name # An unreachable device must fail cleanly, not crash. assert ndicapy.ndiProbe("/nonexistent") != ndicapy.NDI_OKAY print("ndicapy ok:", ndicapy.ndiDeviceName(0)) EOF # The installed CMake config has to work from the install tree. cat > %{_builddir}/ndicapi-consumer.cmake <<'EOF' cmake_minimum_required(VERSION 3.22) project(consumer CXX) find_package(ndicapi REQUIRED) message(STATUS "ndicapi found: ${ndicapi_LIBRARIES} ${ndicapi_INCLUDE_DIRS}") get_target_property(_loc ndicapi IMPORTED_LOCATION_NOCONFIG) if(NOT _loc) get_target_property(_loc ndicapi IMPORTED_LOCATION_RELEASE) endif() if(NOT _loc) get_target_property(_loc ndicapi IMPORTED_LOCATION) endif() message(STATUS "ndicapi library: ${_loc}") EOF mkdir -p %{_builddir}/consumer && cd %{_builddir}/consumer cp %{_builddir}/ndicapi-consumer.cmake CMakeLists.txt cmake -DCMAKE_PREFIX_PATH=%{buildroot}%{_prefix} \ -Dndicapi_DIR=%{buildroot}%{_libdir}/cmake/ndicapi . | grep 'ndicapi found' %files %license LICENSE %doc README.md %{_libdir}/libndicapi.so.1* %files devel %{_includedir}/ndicapi/ %{_libdir}/libndicapi.so %{_libdir}/cmake/ndicapi/ %files -n python3-%{name} -f %{pyproject_files} %changelog * Sat Sep 26 2026 Morgan Hough - 1.6.0^20260908git615ee27d-1 - Initial package: the NDI Polaris/Aurora driver under Plus, NaviNIBS and SlicerIGT. Snapshot for the GIL-release fix the Python module needs.