Skip to content

Commit ae30e01

Browse files
committed
chore: build dependencies and clean up
- added linting - moved requirements into build dependencies - cleaned up tests - added new dev section to readme
1 parent 687558e commit ae30e01

File tree

7 files changed

+55
-55
lines changed

7 files changed

+55
-55
lines changed

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ jobs:
4242
run: python -m pip install --no-build-isolation '.[dev]'
4343
- name: Code formatting
4444
run: make pyformat
45-
# - name: Code linting
46-
# run: make pylint
45+
- name: Code linting
46+
run: make pylint
4747
- name: Check that stub files are up-to-date
4848
run: make stubgen && git diff --exit-code
4949
- name: Ensure all unittests(pytest) are passing

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,24 @@ print(q)
2929
sudo apt install libeigen3-dev
3030
git clone https://github.com/juelg/frankik.git
3131
cd frankik
32-
pip install requirements.txt
33-
pip install -ve '.[dev]' --no-build-isolation
32+
pip install .
3433
```
34+
### Development installation
35+
```shell
36+
pip install -ve '.[dev]'
37+
```
38+
### Development Tools
39+
```shell
40+
# python code formatting
41+
make pyformat
42+
# python code linting
43+
make pylint
44+
# cpp code linting
45+
make cpplint
46+
# automatic stubfile generation (for changes in bindings)
47+
make stubgen
48+
```
49+
3550

3651
## Installation from PyPI
3752
Coming soon...

pyproject.toml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
[build-system]
2-
requires = []
2+
requires = [
3+
"build",
4+
"wheel",
5+
"scikit-build-core>=0.3.3",
6+
"pybind11",
7+
"cmake",
8+
"ninja",
9+
]
310
build-backend = "scikit_build_core.build"
411

512
[project]
@@ -26,9 +33,7 @@ dev = [
2633
"isort==5.13.2",
2734
"mypy==1.10.1",
2835
"types-requests~=2.31",
29-
# temporarily use fixed version until PR #275 is merged
30-
# pybind11-stubgen==2.5.5
31-
"pybind11-stubgen @ git+https://github.com/juelg/pybind11-stubgen@fix/class-sorting",
36+
"pybind11-stubgen==2.5.5",
3237
"pytest==8.1.1",
3338
"commitizen~=3.28.0",
3439
"clang",

requirements.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/frankik/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ def __init__(self, robot_type: str = RobotType.FR3):
3030
ValueError: If an unsupported robot type is provided.
3131
"""
3232
if robot_type not in (RobotType.PANDA, RobotType.FR3):
33-
raise ValueError(f"Unsupported robot type: {robot_type}. Choose 'panda' or 'fr3'.")
33+
msg = f"Unsupported robot type: {robot_type}. Choose 'panda' or 'fr3'."
34+
raise ValueError(msg)
3435
self.robot_type = robot_type
3536
self.q_min = q_min_fr3 if robot_type == RobotType.FR3 else q_min_panda
3637
self.q_max = q_max_fr3 if robot_type == RobotType.FR3 else q_max_panda
@@ -51,7 +52,7 @@ def pose_inverse(
5152
T_inv = np.eye(4)
5253
T_inv[:3, :3] = R.T
5354
T_inv[:3, 3] = -R.T @ t
54-
return T_inv
55+
return T_inv # type: ignore
5556

5657
def forward(
5758
self,
@@ -67,7 +68,7 @@ def forward(
6768
np.ndarray: A 4x4 homogeneous transformation matrix representing the end-effector pose.
6869
"""
6970
pose = fk(q0)
70-
return pose @ self.pose_inverse(tcp_offset) if tcp_offset is not None else pose
71+
return pose @ self.pose_inverse(tcp_offset) if tcp_offset is not None else pose # type: ignore
7172

7273
def inverse(
7374
self,
@@ -88,13 +89,13 @@ def inverse(
8889
np.ndarray | None: A 7-element array representing the joint angles if a solution is found; otherwise, None.
8990
"""
9091
new_pose = pose @ self.pose_inverse(tcp_offset) if tcp_offset is not None else pose
91-
q = ik(new_pose, q0, q7=q7, is_fr3=(self.robot_type == RobotType.FR3))
92+
q = ik(new_pose, q0, q7=q7, is_fr3=(self.robot_type == RobotType.FR3)) # type: ignore
9293

9394
if not np.isnan(q).any():
9495
return q
9596
if not allow_elbow_flips:
9697
return None
97-
qs = ik_full(new_pose, q0, q7=q7, is_fr3=(self.robot_type == RobotType.FR3))
98+
qs = ik_full(new_pose, q0, q7=q7, is_fr3=(self.robot_type == RobotType.FR3)) # type: ignore
9899
qs_list = list(qs)
99100
# drop nan solutions
100101
qs_list = [q for q in qs_list if not np.isnan(q).any()]
@@ -104,8 +105,7 @@ def inverse(
104105
q0 = self.q_home
105106

106107
# find the closest solution
107-
best_q = min(qs_list, key=lambda q_sol: np.linalg.norm(q_sol - q0))
108-
return best_q
108+
return min(qs_list, key=lambda q_sol: np.linalg.norm(q_sol - q0)) # type: ignore
109109

110110

111111
__all__ = [

src/tests/test_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
def test_example(robot_type):
99
kinematics = FrankaKinematics(robot_type=robot_type)
1010
q_home = np.array([0.0, -np.pi / 4, 0.0, -3 * np.pi / 4, 0.0, np.pi / 2, np.pi / 4])
11-
pose_home = kinematics.forward(q_home)
11+
pose_home = kinematics.forward(q_home) # type: ignore
1212
q = kinematics.inverse(pose_home)
13-
assert np.allclose(q, q_home)
13+
assert np.allclose(q, q_home) # type: ignore

src/tests/test_kinematics.py

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313

1414

1515
@pytest.fixture(autouse=True)
16-
def set_seed():
16+
def _set_seed():
1717
"""Automatically seed the RNG before every test to ensure reproducibility."""
1818
np.random.seed(42)
1919

2020

21-
def generate_random_q(q_min, q_max, n=1):
21+
def generate_random_q(q_min, q_max):
2222
q_min = np.array(q_min)
2323
q_max = np.array(q_max)
2424
range_span = q_max - q_min
2525

2626
# 1. Generate base random values (centered 90% of range)
27-
unit_rand = np.random.rand(n, 7)
27+
unit_rand = np.random.rand(1, 7)
2828
q_rand = q_min + (range_span * 0.05) + unit_rand * (range_span * 0.90)
2929

3030
# 2. Singularity Avoidance
@@ -35,33 +35,18 @@ def generate_random_q(q_min, q_max, n=1):
3535
# Threshold in radians (approx 5 degrees to be safe)
3636
singularity_threshold = 0.08
3737

38-
# Handle single sample case (1D array)
39-
if n == 1:
40-
# Avoid Shoulder Singularity (q[1] ≈ 0)
41-
if abs(q_rand[0, 1]) < singularity_threshold:
42-
# Push it away from 0, preserving the sign (default to positive if 0)
43-
sign = np.sign(q_rand[0, 1]) if q_rand[0, 1] != 0 else 1.0
44-
q_rand[0, 1] = sign * singularity_threshold
38+
# Avoid Shoulder Singularity (q[1] ≈ 0)
39+
if abs(q_rand[0, 1]) < singularity_threshold:
40+
# Push it away from 0, preserving the sign (default to positive if 0)
41+
sign = np.sign(q_rand[0, 1]) if q_rand[0, 1] != 0 else 1.0
42+
q_rand[0, 1] = sign * singularity_threshold
4543

46-
# Avoid Wrist Singularity (q[5] ≈ 0) - though less critical for this specific bug
47-
if abs(q_rand[0, 5]) < singularity_threshold:
48-
sign = np.sign(q_rand[0, 5]) if q_rand[0, 5] != 0 else 1.0
49-
q_rand[0, 5] = sign * singularity_threshold
44+
# Avoid Wrist Singularity (q[5] ≈ 0) - though less critical for this specific bug
45+
if abs(q_rand[0, 5]) < singularity_threshold:
46+
sign = np.sign(q_rand[0, 5]) if q_rand[0, 5] != 0 else 1.0
47+
q_rand[0, 5] = sign * singularity_threshold
5048

51-
return q_rand[0]
52-
53-
# Handle batch case (2D array)
54-
else:
55-
# Vectorized fix for Shoulder (Index 1)
56-
# Find indices where q is too close to 0
57-
shoulder_mask = np.abs(q_rand[:, 1]) < singularity_threshold
58-
# Create a sign vector (replace 0s with 1s)
59-
signs = np.sign(q_rand[:, 1])
60-
signs[signs == 0] = 1.0
61-
# Apply shift
62-
q_rand[shoulder_mask, 1] = signs[shoulder_mask] * singularity_threshold
63-
64-
return q_rand
49+
return q_rand[0]
6550

6651

6752
def get_random_pose_in_isocube():
@@ -177,7 +162,7 @@ def test_ik_full_consistency(robot_type):
177162
continue
178163

179164
valid_sols_count += 1
180-
pose_check = frankik.fk(sol)
165+
pose_check = frankik.fk(sol) # type: ignore
181166

182167
# Every returned solution must result in the target pose
183168
np.testing.assert_allclose(
@@ -193,6 +178,7 @@ def test_ik_full_consistency(robot_type):
193178
def test_limits_respected():
194179
"""Sanity check that our random generator respects limits."""
195180
q_min, q_max = frankik.q_min_panda, frankik.q_max_panda
196-
q = generate_random_q(q_min, q_max, n=100)
197-
assert np.all(q >= q_min)
198-
assert np.all(q <= q_max)
181+
for _ in range(100):
182+
q = generate_random_q(q_min, q_max)
183+
assert np.all(q >= q_min)
184+
assert np.all(q <= q_max)

0 commit comments

Comments
 (0)