Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 60 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest]
python-version: ["3.10", "3.11", "3.12"]
# The same Python versions on every platform: the ones dqrobotics
# publishes wheels for (3.10-3.14 on Linux x86_64/aarch64, macOS arm64
# and Windows).
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

# macOS: the oldest macOS the wheel supports (the dqrobotics library that
# delocate bundles into it is built with the same target).
env:
MACOSX_DEPLOYMENT_TARGET: "14.0"

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -46,7 +54,10 @@ jobs:
sudo ln -s /usr/include/eigen3/Eigen /usr/include/Eigen

# ── macOS dependencies ──────────────────────────────────────
# (macOS is not in the matrix yet; kept for when it is added.)
# Eigen from Homebrew, and dqrobotics built from source (there is no
# Homebrew formula) and installed into the runner's temporary directory,
# which the build step passes to CMake. dqrobotics and this package use
# the same Eigen, since Eigen types cross their interface.
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
Expand All @@ -55,9 +66,17 @@ jobs:
git clone --depth 1 https://github.com/dqrobotics/cpp.git dqrobotics/cpp
cmake -S dqrobotics/cpp -B dqrobotics/cpp/build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(brew --prefix)"
-DCMAKE_PREFIX_PATH="$(brew --prefix)" \
-DCMAKE_INSTALL_PREFIX="$RUNNER_TEMP/dqrobotics"
cmake --build dqrobotics/cpp/build -j
cmake --install dqrobotics/cpp/build --prefix /usr/local
cmake --install dqrobotics/cpp/build
echo "CMAKE_ARGS=-DCMAKE_PREFIX_PATH=$(brew --prefix);$RUNNER_TEMP/dqrobotics" >> "$GITHUB_ENV"
# The python.org Pythons of setup-python are universal2 builds, so
# setuptools would tag the wheel universal2; the extension and the
# dqrobotics it links are arm64 only. ARCHFLAGS makes setup.py pass
# the architecture to CMake, and _PYTHON_HOST_PLATFORM tags the wheel.
echo "ARCHFLAGS=-arch arm64" >> "$GITHUB_ENV"
echo "_PYTHON_HOST_PLATFORM=macosx-${MACOSX_DEPLOYMENT_TARGET}-arm64" >> "$GITHUB_ENV"

# ── Windows dependencies ────────────────────────────────────
# dqrobotics/cpp expects the vcpkg toolchain at C:/vcpkg.
Expand Down Expand Up @@ -95,7 +114,7 @@ jobs:
pip install build
python -m build --wheel
env:
CMAKE_ARGS: ${{ runner.os == 'Windows' && '-DCMAKE_PREFIX_PATH=C:/dqrobotics' || '' }}
CMAKE_ARGS: ${{ runner.os == 'Windows' && '-DCMAKE_PREFIX_PATH=C:/dqrobotics' || env.CMAKE_ARGS }}

# PyPI rejects wheels tagged `linux_x86_64`/`linux_aarch64` (it requires
# a PEP 600 `manylinux` tag). auditwheel relinks/bundles as needed and
Expand All @@ -113,6 +132,41 @@ jobs:
auditwheel repair --plat auto -w dist/ dist/*linux_${arch}.whl
rm -f dist/*linux_${arch}.whl

# macOS: bundle libdqrobotics.dylib into the wheel (as auditwheel does
# on Linux) and retag it for MACOSX_DEPLOYMENT_TARGET.
- name: Repair wheel (macOS, delocate)
if: runner.os == 'macOS'
run: |
pip install delocate
delocate-wheel -v -w dist/repaired dist/*.whl
rm -f dist/*.whl
mv dist/repaired/*.whl dist/
rmdir dist/repaired

# Install the wheel in a clean environment and use it. --no-deps: only
# papers.icra2019 needs marinholab-solvers-qpoases, whose wheels do not
# cover every platform and Python version; dqrobotics comes from its
# pre-releases, the ones with wheels for Python >= 3.10.
- name: Smoke test
shell: bash
run: |
python -m venv smoke
if [ "$RUNNER_OS" = "Windows" ]; then PY=smoke/Scripts/python; else PY=smoke/bin/python; fi
$PY -m pip install --upgrade pip
$PY -m pip install --pre dqrobotics numpy termcolor
$PY -m pip install --no-deps dist/*.whl
cd "$RUNNER_TEMP"
$GITHUB_WORKSPACE/$PY -c "
import numpy as np
from dqrobotics import DQ
from marinholab.sas.core import Clock, SerialManipulatorSimulatorFriendly as SM
arm = SM(offset_before=[DQ([1])] * 3, offset_after=[DQ([1])] * 3,
actuation_types=[SM.ActuationType.RX, SM.ActuationType.RY, SM.ActuationType.RZ])
q = np.array([0.1, -0.2, 0.3])
assert np.shape(arm.raw_pose_jacobian(q, 2)) == (8, 3)
print(arm.raw_fkm(q, 2))
"

- name: Upload wheel artifact
uses: actions/upload-artifact@v6
with:
Expand Down
Empty file added =26.4.0a7
Empty file.
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(MARINHO_LAB_SAS_CORE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)

# The dqrobotics headers include both <Eigen/Dense> and <eigen3/Eigen/Dense>.
# The Eigen3 package provides <prefix>/include/eigen3, which resolves the first;
# the second needs <prefix>/include, which is on the default search path on
# Linux (/usr/include) and Windows (the vcpkg include root) but not on macOS
# (Homebrew's /opt/homebrew/include). Rather than adding all of that prefix
# (which could shadow, e.g., the pybind11 submodule with a Homebrew pybind11),
# expose only Eigen: a directory in the build tree holding an "eigen3" link to
# Eigen's include directory. include_directories applies to sas_cpp as well,
# since it is added below.
find_package(Eigen3 REQUIRED)
if(NOT WIN32)
get_target_property(_eigen3_include_dirs Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES)
list(GET _eigen3_include_dirs 0 _eigen3_include_dir)
set(_eigen3_shim "${CMAKE_BINARY_DIR}/eigen3_include_shim")
file(MAKE_DIRECTORY "${_eigen3_shim}")
if(NOT EXISTS "${_eigen3_shim}/eigen3")
file(CREATE_LINK "${_eigen3_include_dir}" "${_eigen3_shim}/eigen3" SYMBOLIC)
endif()
include_directories(SYSTEM "${_eigen3_shim}")
endif()

add_subdirectory(submodules/sas_cpp)

set(PYBIND11_FINDPYTHON ON)
Expand Down
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ which registers the dual-quaternion types the bindings expose.

## Installation

From PyPI, with wheels for Python 3.10–3.14 on Linux (x86_64, aarch64), macOS
(arm64, 14.0 or later) and Windows (x86_64):

```bash
pip install marinholab-sas-core
```

The wheels match the Python versions that `dqrobotics` publishes wheels for,
in its pre-releases, which the requirement `dqrobotics>=26.4.0a7` selects.

From source:

```bash
Expand All @@ -51,6 +61,19 @@ sudo add-apt-repository -y ppa:dqrobotics-dev/development
sudo apt install libdqrobotics
```

On macOS, Eigen comes from Homebrew and `dqrobotics` is built from source
(there is no Homebrew formula), with the same Eigen:

```bash
brew install eigen cmake ninja
git clone --depth 1 https://github.com/dqrobotics/cpp.git dqrobotics-cpp
cmake -S dqrobotics-cpp -B dqrobotics-cpp/build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(brew --prefix)" -DCMAKE_INSTALL_PREFIX="$HOME/.local/dqrobotics"
cmake --build dqrobotics-cpp/build -j && cmake --install dqrobotics-cpp/build
CMAKE_ARGS="-DCMAKE_PREFIX_PATH=$(brew --prefix);$HOME/.local/dqrobotics" \
pip install . --no-build-isolation
```

## Usage

```python
Expand Down Expand Up @@ -95,8 +118,8 @@ from marinholab.sas.core import SerialManipulatorSimulatorFriendly

# 3 revolute joints about X, Y, Z with zero per-joint offsets.
arm = SerialManipulatorSimulatorFriendly(
offset_before=[DQ(1), DQ(1), DQ(1)],
offset_after=[DQ(1), DQ(1), DQ(1)],
offset_before=[DQ([1]), DQ([1]), DQ([1])],
offset_after=[DQ([1]), DQ([1]), DQ([1])],
actuation_types=[
SerialManipulatorSimulatorFriendly.ActuationType.RX,
SerialManipulatorSimulatorFriendly.ActuationType.RY,
Expand Down
15 changes: 14 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,22 @@ maintainers = [
{name = "Murilo M. Marinho", email = "murilomarinho@ieee.org"}
]
dynamic = ["version"]
classifiers = [
"Programming Language :: Python :: 3",
"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",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
]
dependencies = [
"numpy",
"dqrobotics>=20.4.0",
# Only dqrobotics' pre-releases have wheels for Python >= 3.10; naming a
# pre-release here lets pip choose them (with ">=20.4.0" it does not).
"dqrobotics>=26.4.0a7",
"marinholab-solvers-qpoases",
"termcolor",
]
Expand Down
Loading