Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
8c75f2f
Joints - Add a first implementation of joint spline
MegMll Oct 8, 2025
5d84f40
Unittest - Add TU for spline joint
MegMll Oct 8, 2025
534afde
Bindings - add bindings for spline joint
MegMll Oct 8, 2025
928cee7
Spatial - Weird fix to make spline joint work for now (to remove)
MegMll Oct 8, 2025
53f14a2
Unittest - update joint unittests to include spline joints
MegMll Oct 10, 2025
2c047ed
SplineJoint - apply pre-commit
MegMll Oct 10, 2025
d45b129
Spline - use the true cumulative form of the spline
MegMll Oct 10, 2025
c775bc7
Spline - Add isEqual Method
MegMll Oct 13, 2025
e3bb968
Spline - Add TU with finite difference
MegMll Oct 13, 2025
f842642
Unittest - fix tests for spline joint
MegMll Oct 13, 2025
dde3ca7
Spline joint - change if/else to be casadi compatible
MegMll Oct 14, 2025
46962a8
Joint Spline - Remove useless comments
MegMll Oct 15, 2025
110e9cb
Joint Spline - Fix indexing
MegMll Oct 15, 2025
6a0edea
Joint Spline - Eigen No Malloc checked and passed
MegMll Oct 15, 2025
8aa58f2
Spline joints - fix casadi joints tests
MegMll Oct 15, 2025
53d4ba1
Joint Spline - Add a build method
MegMll Oct 16, 2025
d3726d0
Unittest - Fix joint spline's unittests
MegMll Oct 16, 2025
4612a28
Model-graph - Add spline joint to model graph
MegMll Oct 16, 2025
d8d9c86
JointSpline - make some correction to the joint
MegMll Oct 20, 2025
4a6d905
TU - apply new jointSpline constructor
MegMll Oct 20, 2025
365e615
ModelGraph - use new JointSpline method
MegMll Oct 20, 2025
88387be
Serialization - Add attributes from spline model and data
MegMll Oct 21, 2025
434e654
Unittest - use std vector instead of se3
MegMll Oct 21, 2025
6704dd2
JointSpline - Diverse Changes
MegMll Oct 21, 2025
25a7696
Casadi - Add a separated file for findSpan to be able to do specializ…
MegMll Oct 21, 2025
f00b562
Bindings - modify joint spline binding
MegMll Oct 21, 2025
0c32dfb
JointSpline - Remove findSpan
MegMll Oct 21, 2025
b85d202
Unittest - remove buildJoint function
MegMll Oct 21, 2025
2875940
ModelGraph - finish adding jointSpline to model graph + unittest
MegMll Oct 21, 2025
c6d110a
cppadcg: Fix cppadcg cast test with spline joint
jorisv Oct 22, 2025
76b9368
multiprecision: Fix multiprecision cast test with Spline joint
jorisv Oct 22, 2025
e567234
multiprecision: Add missing header
jorisv Oct 22, 2025
259343a
lint: Run linter
jorisv Oct 22, 2025
f6287fb
JointSpline - Removed comments
MegMll Oct 22, 2025
7e5c230
JointSpline - move bspline functions in public to be able to test it
MegMll Oct 22, 2025
c6e7970
Unittest - add more test for joint spline
MegMll Oct 22, 2025
689565c
Bindings - Add another constructor for joint spline
MegMll Oct 23, 2025
9b0108e
Unittest - put full knee trajectory
MegMll Oct 23, 2025
31f831f
Unittest - try a fix for windows and serialization
MegMll Oct 23, 2025
a37889d
Unittest - fix getTrajectory function
MegMll Oct 23, 2025
30dd38e
Bindings - add modelgraph bindings constant
MegMll Oct 27, 2025
3108bff
JointSpline - Fix indexing
MegMll Oct 27, 2025
f49a734
JointSpline - Add a doc
MegMll Oct 27, 2025
e0c96bb
JointSpline - Fix bias computation
MegMll Oct 27, 2025
f6234b1
Unittest - add a finite difference test for S and bias c for JointSpline
MegMll Oct 27, 2025
a741434
JointSpline - Add comments and complete isEqual function
MegMll Oct 28, 2025
98d4af5
Unittest - Try to add tolerance to see if it fixes macos-intel problem
MegMll Oct 28, 2025
d0f6a3f
Unittest - fix compilation error
MegMll Oct 28, 2025
9b3443e
JointSpline - add assert for configuration to be between 0 and 1
MegMll Oct 30, 2025
c93d6a4
Bindings - Add graph spline joints bindings
MegMll Oct 30, 2025
5d3342d
Algo/Splines - remove comments
MegMll Oct 30, 2025
5cc06ef
Unittest - add find span test for q = 0
MegMll Oct 30, 2025
57f553b
Changelog - update
MegMll Oct 30, 2025
395d570
JointSpline - remove SE3 and use Transformation_t
MegMll Oct 30, 2025
7f56fc1
Unittest - remove comments
MegMll Oct 30, 2025
c9f97ff
JointSpline - Use transformation_t and motion_t
MegMll Oct 30, 2025
ecc4600
Examples - Add a small examples on what the spline joint can do
MegMll Oct 30, 2025
64b8634
Unittest - Specialize for joint spline
MegMll Oct 30, 2025
145f33d
Examples - add spline joint examples
MegMll Oct 30, 2025
a07d7a9
Unittest - Fix joint generic test for joint spline
MegMll Nov 3, 2025
f20c196
Examples - Fix some linting errors
MegMll Nov 3, 2025
d6798ea
JointSpline - Disable assert
MegMll Nov 4, 2025
557533b
core: Apply small changes
jorisv Nov 25, 2025
6949b41
Algo/splines - add doc
MegMll Nov 26, 2025
9c9b14b
Unittest - Avoid code duplication for randomConfig vector generation
MegMll Nov 26, 2025
c1168e4
Unittest - more complex and complete tests
MegMll Nov 26, 2025
12c4b61
Unittest/cppad - use degree 3 spline joint
MegMll Nov 27, 2025
e009789
unittest - change name of struct to be more generic
MegMll Nov 27, 2025
8741199
unittest - avoid code duplication
MegMll Nov 27, 2025
c110d73
unittest - avoid code duplication
MegMll Nov 27, 2025
6b44312
Unittest - fix casadi/joints test
MegMll Dec 9, 2025
9216634
Unittest - add forgotten helical unaligned joint
MegMll Dec 10, 2025
0932ef5
Unittest - Fix casadi test for universal joints
MegMll Dec 11, 2025
678005b
Mode-graph - use new function for joint spline
MegMll Dec 11, 2025
2b36568
Linting
MegMll Dec 12, 2025
3d6bdd6
unittest - fix new joint
MegMll Dec 12, 2025
18cda3f
JointSpline - Fix bias computation
MegMll Dec 12, 2025
5b6839e
Unittest - Fix finite-difference test
MegMll Dec 12, 2025
078eab8
unittest - use config vector of joints
MegMll Dec 12, 2025
852c605
unittest - remove useless lines
MegMll Dec 12, 2025
a522d82
Unittest - Add a utils file to centralize init of joints with parameters
MegMll Dec 15, 2025
004af4a
Run pre-commit
MegMll Dec 15, 2025
c38e0fc
unittest/utils - Fix mimic init
MegMll Dec 15, 2025
00bc40a
unittest/serialization - use new utils
MegMll Dec 15, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add color support for robot meshes in Viser ([#2793](https://github.com/stack-of-tasks/pinocchio/pull/2793))
- Add Realtime Sanitizer (RTSan) uniittest and CI to track dynamic allocations in main API ([#2809](https://github.com/stack-of-tasks/pinocchio/pull/2809))
- Add Ellipsoid Joint (3-DOF surface constraint), get ready for biomechanics ([#2797](https://github.com/stack-of-tasks/pinocchio/pull/2797))
- Add a new spline joint to default joint collection ([#2784](https://github.com/stack-of-tasks/pinocchio/pull/2784))

### Changed
- Python version update ([#2802](https://github.com/stack-of-tasks/pinocchio/pull/2802)):
Expand Down
18 changes: 18 additions & 0 deletions bindings/python/parsers/graph/expose-edges.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ namespace pinocchio

const int JointMimic::nq;
const int JointMimic::nv;

const int JointSpline::nq;
const int JointSpline::nv;
} // namespace graph

namespace python
Expand Down Expand Up @@ -158,6 +161,21 @@ namespace pinocchio
.def_readonly("nq", &JointUniversal::nq, "Number of configuration variables.")
.def_readonly("nv", &JointUniversal::nv, "Number of tangent variables.");

bp::class_<JointSpline>(
"JointSpline", "Represents a spline-based joint.",
bp::init<>(bp::args("self"), "Default constructor."))
.def(bp::init<int>(bp::args("self", "degree"), "Constructor with degree."))
.def(bp::init<const SE3 &, int>(
bp::args("self", "ctrlFrame", "degree"),
"Constructor with a single control frame and degree."))
.def_readwrite("ctrlFrames", &JointSpline::ctrlFrames, "Control frames of the spline.")
.def_readwrite("degree", &JointSpline::degree, "Degree of the spline.")
.def_readonly("nq", &JointSpline::nq, "Number of configuration variables.")
.def_readonly("nv", &JointSpline::nv, "Number of tangent variables.")
.def(
"addCtrlFrame", &JointSpline::addCtrlFrame, bp::args("self", "ctrlFrame"),
"Add a control frame to the spline.");

bp::class_<JointComposite>(
"JointComposite", "Represents a composite joint.",
bp::init<>(bp::args("self"), "Default constructor."))
Expand Down
94 changes: 94 additions & 0 deletions examples/spline-joint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import time

import hppfcl
import numpy as np
import pinocchio as pin
from pinocchio.visualize import MeshcatVisualizer


def generate_random_se3_trajectory(num_steps, radius, num_revolutions, height):
"""
Generates a random trajectory in SE(3) with rotating orientation.

Args:
num_keyframes: The number of random keyframes to generate.
num_steps_per_segment: The number of intermediate steps between each keyframe.

Returns:
A list of pinocchio.SE3 objects representing the trajectory.
"""
trajectory = []
for i in range(num_steps):
# 1. Parameter to track progress along the helix (from 0.0 to 1.0)
alpha = float(i) / num_steps

# 2. Define the translational part (the helical path)
# The angle determines the position on the XY plane
angle = alpha * num_revolutions * 2 * np.pi

# Calculate the x, y, z coordinates for the helix
translation = np.array(
[radius * np.cos(angle), radius * np.sin(angle), alpha * height]
)

# 3. Define the rotational part (a new random orientation at each step)
# pin.SE3.Random().rotation generates a random 3x3 rotation matrix
random_rotation = pin.SE3.Random().rotation

# 4. Combine the translation and random rotation into a single SE(3) pose
pose = pin.SE3(random_rotation, translation)
trajectory.append(pose)

return trajectory


# --- Visualization Setup ---

# Generate the random trajectory
# Parameters for the helical trajectory
num_steps = 30
radius = 1.0
num_revolutions = 3
height = 2

trajectory = generate_random_se3_trajectory(num_steps, radius, num_revolutions, height)

# Create a Pinocchio model with a single free-flyer joint
model = pin.Model()
joint_id = model.addJoint(
0, pin.JointModelSpline(trajectory, 3), pin.SE3.Identity(), "free_flyer"
)

# Attach a simple visual geometry (a box) to the joint
visual_model = pin.GeometryModel()
box_shape = hppfcl.Box(0.1, 0.2, 0.3)
# The placement of the geometry with respect to the joint frame
geom_placement = pin.SE3.Identity()
geom_obj = pin.GeometryObject("box", joint_id, geom_placement, box_shape)
# Assign a color to the geometry
geom_obj.meshColor = np.array([1.0, 0.5, 0.5, 1.0]) # RGBA
visual_model.addGeometryObject(geom_obj)

# --- Main Execution ---

# Initialize the MeshCat visualizer.
try:
viz = MeshcatVisualizer(model, visual_model, visual_model)
viz.initViewer(open=True)
viz.loadViewerModel()
except ImportError as e:
print("Error while initializing the viewer.")
print(e)


time.sleep(0.1)

q = pin.neutral(model)

q_vector = np.arange(0, 1, 0.05)
for q in q_vector:
# Display the new configuration.
viz.display(np.array([q]))

# Delay for visualization
time.sleep(0.05)
58 changes: 58 additions & 0 deletions include/pinocchio/algorithm/splines.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Copyright (c) 2025 INRIA
//

#ifndef __pinocchio_algorithm_splines_hpp__
#define __pinocchio_algorithm_splines_hpp__

namespace pinocchio
{
/// @brief Helper structure defining a range of indices.
/// @details This struct identifies the subset of control frames in a spline that are active
/// (i.e., have non-zero basis functions) for a specific spline parameter value.
/// Using this local support property allows for efficient computation of the joint
/// transformation, S, and bias c.
struct SpanIndexes
{
size_t start_idx;
size_t end_idx;
};

/// @brief Algorithm to locate the span for a given B-spline parameter, q.
/// @details This struct implements a binary search (FindSpan) to determine which knot span
/// a given parameter value falls into. In B-spline curves, a parameter value $u$ implies that
/// only $(p+1)$ control points affect the curve at that location (where $p$ is the degree).
template<typename Scalar, int Options>
struct FindSpan
{
template<typename ConfigVector, typename KnotsVector>
static SpanIndexes run(
const Eigen::MatrixBase<ConfigVector> & q,
const int degree,
const int nbCtrlFrames,
const Eigen::MatrixBase<KnotsVector> & knots)
{
// Edge case: if q is at or beyond the end of the spline parameterization
if (q[0] >= 1.0)
return {
static_cast<size_t>(nbCtrlFrames - (degree + 1)), static_cast<size_t>(nbCtrlFrames)};

int low = degree;
int high = nbCtrlFrames;
int mid;

while (low < high)
{
mid = low + (high - low) / 2;
if (q[0] < knots[mid])
high = mid;
else
low = mid + 1;
}

return {static_cast<size_t>(low - (degree + 1)), static_cast<size_t>(low)};
}
};
} // namespace pinocchio

#endif // __pinocchio_algorithm_splines_hpp__
1 change: 1 addition & 0 deletions include/pinocchio/autodiff/casadi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,5 +443,6 @@ namespace Eigen
#include "pinocchio/autodiff/casadi/math/matrix.hpp"
#include "pinocchio/autodiff/casadi/math/quaternion.hpp"
#include "pinocchio/autodiff/casadi/math/triangular-matrix.hpp"
#include "pinocchio/autodiff/casadi/algorithm/splines.hpp"

#endif // #ifndef __pinocchio_autodiff_casadi_hpp__
33 changes: 33 additions & 0 deletions include/pinocchio/autodiff/casadi/algorithm/splines.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright (c) 2025 INRIA
//

#ifndef __pinocchio_autodiff_casadi_splines_hpp__
#define __pinocchio_autodiff_casadi_splines_hpp__

#include "pinocchio/algorithm/splines.hpp"

namespace pinocchio
{
// Fwd Declare
struct SpanIndexes;

template<typename Scalar, int Options>
struct FindSpan;

template<int Options>
struct FindSpan<::casadi::SX, Options>
{
template<typename ConfigVector, typename KnotsVector>
static SpanIndexes run(
const Eigen::MatrixBase<ConfigVector> & /*q*/,
const int /*degree*/,
const int nbCtrlFrames,
const Eigen::MatrixBase<KnotsVector> & /*knots*/)
{
return {0, static_cast<size_t>(nbCtrlFrames)};
}
};
} // namespace pinocchio

#endif // __pinocchio_autodiff_casadi_splines_hpp__
3 changes: 3 additions & 0 deletions include/pinocchio/bindings/python/context/generic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ namespace pinocchio
typedef JointModelUniversalTpl<Scalar> JointModelUniversal;
typedef JointDataUniversalTpl<Scalar> JointDataUniversal;

typedef JointModelSplineTpl<Scalar> JointModelSpline;
typedef JointDataSplineTpl<Scalar> JointDataSpline;

typedef JointModelTranslationTpl<Scalar> JointModelTranslation;
typedef JointDataTranslationTpl<Scalar> JointDataTranslation;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ namespace pinocchio
.add_property("pjMi", &JointDataComposite::pjMi)
.add_property("StU", &JointDataComposite::StU);
}

} // namespace python
} // namespace pinocchio

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,38 @@ namespace pinocchio
"Second rotation axis of the JointModelUniversal.");
}

// Specialization for JointModelSpline
template<>
bp::class_<context::JointModelSpline> &
expose_joint_model<context::JointModelSpline>(bp::class_<context::JointModelSpline> & cl)
{
return cl
.def(bp::init<>(
bp::args("self"),
"Init an empty joint Spline. Default degree of spline basis function is 3."))
.def(bp::init<int>(
bp::args("self", "degree"),
"Init an empty joint Spline, with the degree of the future basis functions"))
.def(bp::init<const ::pinocchio::container::aligned_vector<context::SE3> &, int>(
bp::args("self", "controlFrames", "degree"),
"Init an empty joint Spline, with a list of controlFrames and the degree of the future "
"basis functions"))
.def(
"setControlFrames", &context::JointModelSpline::setControlFrames, bp::arg("frames"),
"Add a vector of frames to the joint. It cannot be changed afterward and no frames can "
"be added afterwards either")
.def(
"makeKnots", &context::JointModelSpline::makeKnots,
"generate the knots vector for the spline. Call after all the control frame have been "
"added")
.def(
"computeRelativeMotion", &context::JointModelSpline::computeRelativeMotions,
"compute the relative motion between the control frames. Call after all the control "
"frames have been added")
.def_readwrite(
"degree", &context::JointModelSpline::degree, "Degree of the spline basis functions");
}

// specialization for JointModelComposite

struct JointModelCompositeAddJointVisitor
Expand Down
1 change: 1 addition & 0 deletions include/pinocchio/math/multiprecision.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "pinocchio/math/fwd.hpp"

#include <boost/mpl/bool_fwd.hpp>
#include <boost/multiprecision/number.hpp>
#include <boost/random.hpp>
#include <Eigen/Dense>
Expand Down
8 changes: 8 additions & 0 deletions include/pinocchio/multibody/joint/fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ namespace pinocchio
struct JointDataTranslationTpl;
typedef JointDataTranslationTpl<context::Scalar> JointDataTranslation;

template<typename Scalar, int Options = context::Options>
struct JointModelSplineTpl;
typedef JointModelSplineTpl<context::Scalar> JointModelSpline;

template<typename Scalar, int Options = context::Options>
struct JointDataSplineTpl;
typedef JointDataSplineTpl<context::Scalar> JointDataSpline;

template<typename Scalar, int Options = context::Options>
struct JointCollectionDefaultTpl;
typedef JointCollectionDefaultTpl<context::Scalar> JointCollectionDefault;
Expand Down
8 changes: 8 additions & 0 deletions include/pinocchio/multibody/joint/joint-collection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ namespace pinocchio
// Joint Universal
typedef JointModelUniversalTpl<Scalar, Options> JointModelUniversal;

// Joint Spline
typedef JointModelSplineTpl<Scalar, Options> JointModelSpline;

typedef boost::variant<
// JointModelVoid,
JointModelRX,
Expand All @@ -110,6 +113,7 @@ namespace pinocchio
JointModelHz,
JointModelHelicalUnaligned,
JointModelUniversal,
JointModelSpline,
boost::recursive_wrapper<JointModelComposite>,
boost::recursive_wrapper<JointModelMimic>>
JointModelVariant;
Expand Down Expand Up @@ -176,6 +180,9 @@ namespace pinocchio
// Joint Universal
typedef JointDataUniversalTpl<Scalar, Options> JointDataUniversal;

// Joint Spline
typedef JointDataSplineTpl<Scalar, Options> JointDataSpline;

typedef boost::variant<
// JointDataVoid
JointDataRX,
Expand All @@ -201,6 +208,7 @@ namespace pinocchio
JointDataHz,
JointDataHelicalUnaligned,
JointDataUniversal,
JointDataSpline,
boost::recursive_wrapper<JointDataComposite>,
boost::recursive_wrapper<JointDataMimic>>
JointDataVariant;
Expand Down
Loading
Loading