Skip to content

Commit af9f527

Browse files
authored
Add scene methods to get joint limit vectors (#162)
1 parent 3fafd4b commit af9f527

File tree

13 files changed

+441
-101
lines changed

13 files changed

+441
-101
lines changed

bindings/src/modules/core.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <nanobind/nanobind.h>
33
#include <nanobind/stl/filesystem.h>
44
#include <nanobind/stl/optional.h>
5+
#include <nanobind/stl/pair.h>
56
#include <nanobind/stl/shared_ptr.h>
67
#include <nanobind/stl/string.h>
78
#include <nanobind/stl/vector.h>
@@ -217,6 +218,14 @@ void init_core_scene(nanobind::module_& m) {
217218
"Set the joint positions for the full robot state.", "positions"_a)
218219
.def("getJointPositionIndices", &Scene::getJointPositionIndices,
219220
"Get the joint position indices for a set of joint names.", "joint_names"_a)
221+
.def("getPositionLimitVectors", unwrap_expected(&Scene::getPositionLimitVectors),
222+
"Get the joint position limit vectors for a specified group.", "group_name"_a = "")
223+
.def("getVelocityLimitVectors", unwrap_expected(&Scene::getVelocityLimitVectors),
224+
"Get the joint velocity limit vectors for a specified group.", "group_name"_a = "")
225+
.def("getAccelerationLimitVectors", unwrap_expected(&Scene::getAccelerationLimitVectors),
226+
"Get the joint acceleration limit vectors for a specified group.", "group_name"_a = "")
227+
.def("getJerkLimitVectors", unwrap_expected(&Scene::getJerkLimitVectors),
228+
"Get the joint jerk limit vectors for a specified group.", "group_name"_a = "")
220229
.def("addBoxGeometry", unwrap_expected(&Scene::addBoxGeometry),
221230
"Adds a box geometry to the scene.", "name"_a, "parent_frame"_a, "box"_a, "tform"_a,
222231
"color"_a)

bindings/src/roboplan/roboplan_ext/core.pyi

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,18 @@ class Scene:
378378
def getJointPositionIndices(self, joint_names: Sequence[str]) -> Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C')]:
379379
"""Get the joint position indices for a set of joint names."""
380380

381+
def getPositionLimitVectors(self, group_name: str = '') -> tuple[Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]]:
382+
"""Get the joint position limit vectors for a specified group."""
383+
384+
def getVelocityLimitVectors(self, group_name: str = '') -> tuple[Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]]:
385+
"""Get the joint velocity limit vectors for a specified group."""
386+
387+
def getAccelerationLimitVectors(self, group_name: str = '') -> tuple[Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]]:
388+
"""Get the joint acceleration limit vectors for a specified group."""
389+
390+
def getJerkLimitVectors(self, group_name: str = '') -> tuple[Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]]:
391+
"""Get the joint jerk limit vectors for a specified group."""
392+
381393
def addBoxGeometry(self, name: str, parent_frame: str, box: Box, tform: Annotated[NDArray[numpy.float64], dict(shape=(4, 4), order='F')], color: Annotated[NDArray[numpy.float64], dict(shape=(4), order='C')]) -> None:
382394
"""Adds a box geometry to the scene."""
383395

@@ -422,7 +434,7 @@ class PathShortcutter:
422434
def getNormalizedPathScaling(self, path: JointPath) -> Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]:
423435
"""Computes length-normalized scaling values along a JointPath."""
424436

425-
def getConfigurationfromNormalizedPathScaling(self, path: JointPath, path_scalings: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], value: float) -> "std::pair<Eigen::Matrix<double, -1, 1, 0, -1, 1>, unsigned long>":
437+
def getConfigurationfromNormalizedPathScaling(self, path: JointPath, path_scalings: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], value: float) -> tuple[Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')], int]:
426438
"""Gets joint configurations from a path with normalized joint scalings."""
427439

428440
def collapseContinuousJointPositions(scene: Scene, group_name: str, q_orig: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]) -> Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C')]:

bindings/test/test_scene.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,70 @@ def test_set_collisions(test_scene: Scene) -> None:
210210
assert str(exc_info.value) == expected_error
211211

212212

213+
def test_position_limits_vector(test_scene: Scene) -> None:
214+
expected_lower_limits = np.array(
215+
[-3.14159, -3.14159, -3.14159, -3.14159, -3.14159, -3.14159]
216+
)
217+
expected_upper_limits = np.array(
218+
[3.14159, 3.14159, 3.14159, 3.14159, 3.14159, 3.14159]
219+
)
220+
221+
# Default group (all joints)
222+
lower_limits, upper_limits = test_scene.getPositionLimitVectors()
223+
assert np.allclose(lower_limits, expected_lower_limits)
224+
assert np.allclose(upper_limits, expected_upper_limits)
225+
226+
# Specific group (in this case, it's the same as all joints)
227+
lower_limits, upper_limits = test_scene.getPositionLimitVectors("arm")
228+
assert np.allclose(lower_limits, expected_lower_limits)
229+
assert np.allclose(upper_limits, expected_upper_limits)
230+
231+
232+
def test_velocity_limits_vector(test_scene: Scene) -> None:
233+
expected_lower_limits = np.array([-3.15, -3.15, -3.15, -3.2, -3.2, -3.2])
234+
expected_upper_limits = np.array([3.15, 3.15, 3.15, 3.2, 3.2, 3.2])
235+
236+
# Default group (all joints)
237+
lower_limits, upper_limits = test_scene.getVelocityLimitVectors()
238+
assert np.allclose(lower_limits, expected_lower_limits)
239+
assert np.allclose(upper_limits, expected_upper_limits)
240+
241+
# Specific group (in this case, it's the same as all joints)
242+
lower_limits, upper_limits = test_scene.getVelocityLimitVectors("arm")
243+
assert np.allclose(lower_limits, expected_lower_limits)
244+
assert np.allclose(upper_limits, expected_upper_limits)
245+
246+
247+
def test_acceleration_limits_vector(test_scene: Scene) -> None:
248+
expected_lower_limits = np.array([-2.0, -2.0, -2.0, -2.0, -2.0, -2.0])
249+
expected_upper_limits = np.array([2.0, 2.0, 2.0, 2.0, 2.0, 2.0])
250+
251+
# Default group (all joints)
252+
lower_limits, upper_limits = test_scene.getAccelerationLimitVectors()
253+
assert np.allclose(lower_limits, expected_lower_limits)
254+
assert np.allclose(upper_limits, expected_upper_limits)
255+
256+
# Specific group (in this case, it's the same as all joints)
257+
lower_limits, upper_limits = test_scene.getAccelerationLimitVectors("arm")
258+
assert np.allclose(lower_limits, expected_lower_limits)
259+
assert np.allclose(upper_limits, expected_upper_limits)
260+
261+
262+
def test_jerk_limits_vector(test_scene: Scene) -> None:
263+
expected_lower_limits = np.array([-10.0, -10.0, -10.0, -10.0, -10.0, -10.0])
264+
expected_upper_limits = np.array([10.0, 10.0, 10.0, 10.0, 10.0, 10.0])
265+
266+
# Default group (all joints)
267+
lower_limits, upper_limits = test_scene.getJerkLimitVectors()
268+
assert np.allclose(lower_limits, expected_lower_limits)
269+
assert np.allclose(upper_limits, expected_upper_limits)
270+
271+
# Specific group (in this case, it's the same as all joints)
272+
lower_limits, upper_limits = test_scene.getJerkLimitVectors("arm")
273+
assert np.allclose(lower_limits, expected_lower_limits)
274+
assert np.allclose(upper_limits, expected_upper_limits)
275+
276+
213277
def test_mimics() -> None:
214278
# Equivalent to the C++ test, but the updated joint state is returned as a new
215279
# object rather than updated in place.

roboplan/include/roboplan/core/scene.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,34 @@ class Scene {
164164
/// @return The corresponding joint position indices.
165165
Eigen::VectorXi getJointPositionIndices(const std::vector<std::string>& joint_names) const;
166166

167+
/// @brief Get the joint position limit vectors for a specified group.
168+
/// @param group_name The name of the group. Defaults to the complete robot model.
169+
/// @return A pair of vectors for the lower and upper joint position limits, if successful,
170+
/// or a string describing any errors.
171+
tl::expected<EigenVectorPair, std::string>
172+
getPositionLimitVectors(const std::string& group_name = "") const;
173+
174+
/// @brief Get the joint velocity limit vectors for a specified group.
175+
/// @param group_name The name of the group. Defaults to the complete robot model.
176+
/// @return A pair of vectors for the lower and upper joint velocity limits, if successful,
177+
/// or a string describing any errors.
178+
tl::expected<EigenVectorPair, std::string>
179+
getVelocityLimitVectors(const std::string& group_name = "") const;
180+
181+
/// @brief Get the joint acceleration limit vectors for a specified group.
182+
/// @param group_name The name of the group. Defaults to the complete robot model.
183+
/// @return A pair of vectors for the lower and upper joint acceleration limits, if successful,
184+
/// or a string describing any errors.
185+
tl::expected<EigenVectorPair, std::string>
186+
getAccelerationLimitVectors(const std::string& group_name = "") const;
187+
188+
/// @brief Get the joint jerk limit vectors for a specified group.
189+
/// @param group_name The name of the group. Defaults to the complete robot model.
190+
/// @return A pair of vectors for the lower and upper joint jerk limits, if successful,
191+
/// or a string describing any errors.
192+
tl::expected<EigenVectorPair, std::string>
193+
getJerkLimitVectors(const std::string& group_name = "") const;
194+
167195
/// @brief Adds a box geometry to the scene.
168196
/// @param name The name of the object to add.
169197
/// @param parent_frame The name of the parent frame to add the object to.

roboplan/include/roboplan/core/types.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
#include <iostream>
44
#include <optional>
55
#include <string>
6+
#include <utility>
67
#include <vector>
78

89
#include <Eigen/Dense>
910

1011
namespace roboplan {
1112

13+
using EigenVectorPair = std::pair<Eigen::VectorXd, Eigen::VectorXd>;
14+
1215
/// @brief Represents a robot joint configuration.
1316
/// @details Creating and validating these structures are handled by separate
1417
/// utility functions.

roboplan/src/core/scene.cpp

Lines changed: 190 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,30 @@ Scene::Scene(const std::string& name, const std::string& urdf, const std::string
8585
joint.shortname() + "' but this is not in the RoboPlan joint map.");
8686
}
8787
auto info = JointInfo(kPinocchioJointTypeMap.at(joint.shortname()));
88-
for (int idx = 0; idx < joint.nq(); ++idx) {
89-
info.limits.min_position[idx] = mimic_model.lowerPositionLimit(q_idx);
90-
info.limits.max_position[idx] = mimic_model.upperPositionLimit(q_idx);
91-
++q_idx;
88+
switch (info.type) {
89+
case (JointType::PRISMATIC):
90+
case (JointType::REVOLUTE):
91+
info.limits.min_position[0] = mimic_model.lowerPositionLimit(q_idx);
92+
info.limits.max_position[0] = mimic_model.upperPositionLimit(q_idx);
93+
break;
94+
case (JointType::PLANAR):
95+
// Only the position limits need to be incorporated, as orientation is unlimited.
96+
for (size_t dof = 0; dof < 2; ++dof) {
97+
info.limits.min_position[dof] = mimic_model.lowerPositionLimit(q_idx + dof);
98+
info.limits.max_position[dof] = mimic_model.upperPositionLimit(q_idx + dof);
99+
}
100+
break;
101+
case (JointType::FLOATING):
102+
// Only the position limits need to be incorporated, as orientation is unlimited.
103+
for (size_t dof = 0; dof < 3; ++dof) {
104+
info.limits.min_position[dof] = mimic_model.lowerPositionLimit(q_idx + dof);
105+
info.limits.max_position[dof] = mimic_model.upperPositionLimit(q_idx + dof);
106+
}
107+
break;
108+
default: // Includes continuous joints, where no operation is needed.
109+
break;
92110
}
111+
q_idx += info.num_position_dofs;
93112

94113
std::optional<YAML::Node> maybe_acc_limits;
95114
std::optional<YAML::Node> maybe_jerk_limits;
@@ -400,6 +419,173 @@ Eigen::VectorXi Scene::getJointPositionIndices(const std::vector<std::string>& j
400419
return Eigen::VectorXi::Map(q_indices.data(), q_indices.size());
401420
}
402421

422+
tl::expected<EigenVectorPair, std::string>
423+
Scene::getPositionLimitVectors(const std::string& group_name) const {
424+
const auto maybe_joint_group_info = getJointGroupInfo(group_name);
425+
if (!maybe_joint_group_info) {
426+
return tl::make_unexpected("Failed to get position limit vectors: " +
427+
maybe_joint_group_info.error());
428+
}
429+
const auto& joint_group_info = maybe_joint_group_info.value();
430+
431+
// Initialize all limits as infinity and only set the joint DOFs that are finite.
432+
Eigen::VectorXd lower_limits = Eigen::VectorXd::Constant(
433+
joint_group_info.nq_collapsed, -std::numeric_limits<double>::infinity());
434+
Eigen::VectorXd upper_limits = Eigen::VectorXd::Constant(joint_group_info.nq_collapsed,
435+
std::numeric_limits<double>::infinity());
436+
size_t q_idx = 0;
437+
for (size_t j_idx = 0; j_idx < joint_group_info.joint_names.size(); ++j_idx) {
438+
const auto& joint_name = joint_group_info.joint_names.at(j_idx);
439+
const auto maybe_joint_info = getJointInfo(joint_name);
440+
if (!maybe_joint_info) {
441+
return tl::make_unexpected("Failed to get position limit vectors: " +
442+
maybe_joint_info.error());
443+
}
444+
const auto& joint_info = maybe_joint_info.value();
445+
446+
switch (joint_info.type) {
447+
case JointType::FLOATING:
448+
// Position limits can be finite, orientation stays unlimited.
449+
for (int dof = 0; dof < 3; ++dof) {
450+
if (joint_info.limits.min_position.size() > dof) {
451+
lower_limits(q_idx + dof) = joint_info.limits.min_position(dof);
452+
}
453+
if (joint_info.limits.max_position.size() > dof) {
454+
upper_limits(q_idx + dof) = joint_info.limits.max_position(dof);
455+
}
456+
}
457+
q_idx += 6;
458+
break;
459+
case JointType::PLANAR:
460+
// Position limits can be finite, orientation stays unlimited.
461+
for (int dof = 0; dof < 2; ++dof) {
462+
if (joint_info.limits.min_position.size() > dof) {
463+
lower_limits(q_idx + dof) = joint_info.limits.min_position(dof);
464+
}
465+
if (joint_info.limits.max_position.size() > dof) {
466+
upper_limits(q_idx + dof) = joint_info.limits.max_position(dof);
467+
}
468+
}
469+
q_idx += 3;
470+
break;
471+
case JointType::CONTINUOUS:
472+
// Already has infinite limits, no action needed.
473+
++q_idx;
474+
break;
475+
default: // Prismatic or revolute.
476+
if (joint_info.limits.min_position.size() > 0) {
477+
lower_limits(q_idx) = joint_info.limits.min_position(0);
478+
}
479+
if (joint_info.limits.max_position.size() > 0) {
480+
upper_limits(q_idx) = joint_info.limits.max_position(0);
481+
}
482+
++q_idx;
483+
}
484+
}
485+
return std::make_pair(lower_limits, upper_limits);
486+
}
487+
488+
tl::expected<EigenVectorPair, std::string>
489+
Scene::getVelocityLimitVectors(const std::string& group_name) const {
490+
const auto maybe_joint_group_info = getJointGroupInfo(group_name);
491+
if (!maybe_joint_group_info) {
492+
return tl::make_unexpected("Failed to get velocity limit vectors: " +
493+
maybe_joint_group_info.error());
494+
}
495+
const auto& joint_group_info = maybe_joint_group_info.value();
496+
497+
// Initialize all limits as infinity and only set the joint DOFs that are finite.
498+
Eigen::VectorXd lower_limits = Eigen::VectorXd::Constant(
499+
joint_group_info.v_indices.size(), -std::numeric_limits<double>::infinity());
500+
Eigen::VectorXd upper_limits = Eigen::VectorXd::Constant(joint_group_info.v_indices.size(),
501+
std::numeric_limits<double>::infinity());
502+
size_t v_idx = 0;
503+
for (size_t j_idx = 0; j_idx < joint_group_info.joint_names.size(); ++j_idx) {
504+
const auto& joint_name = joint_group_info.joint_names.at(j_idx);
505+
const auto maybe_joint_info = getJointInfo(joint_name);
506+
if (!maybe_joint_info) {
507+
return tl::make_unexpected("Failed to get velocity limit vectors: " +
508+
maybe_joint_info.error());
509+
}
510+
const auto& joint_info = maybe_joint_info.value();
511+
512+
for (size_t dof = 0; dof < joint_info.num_velocity_dofs; ++dof) {
513+
const auto& max_vel = joint_info.limits.max_velocity(dof);
514+
lower_limits(v_idx + dof) = -max_vel;
515+
upper_limits(v_idx + dof) = max_vel;
516+
}
517+
v_idx += joint_info.num_velocity_dofs;
518+
}
519+
return std::make_pair(lower_limits, upper_limits);
520+
}
521+
522+
tl::expected<EigenVectorPair, std::string>
523+
Scene::getAccelerationLimitVectors(const std::string& group_name) const {
524+
const auto maybe_joint_group_info = getJointGroupInfo(group_name);
525+
if (!maybe_joint_group_info) {
526+
return tl::make_unexpected("Failed to get acceleration limit vectors: " +
527+
maybe_joint_group_info.error());
528+
}
529+
const auto& joint_group_info = maybe_joint_group_info.value();
530+
531+
// Initialize all limits as infinity and only set the joint DOFs that are finite.
532+
Eigen::VectorXd lower_limits = Eigen::VectorXd::Constant(
533+
joint_group_info.v_indices.size(), -std::numeric_limits<double>::infinity());
534+
Eigen::VectorXd upper_limits = Eigen::VectorXd::Constant(joint_group_info.v_indices.size(),
535+
std::numeric_limits<double>::infinity());
536+
size_t v_idx = 0;
537+
for (size_t j_idx = 0; j_idx < joint_group_info.joint_names.size(); ++j_idx) {
538+
const auto& joint_name = joint_group_info.joint_names.at(j_idx);
539+
const auto maybe_joint_info = getJointInfo(joint_name);
540+
if (!maybe_joint_info) {
541+
return tl::make_unexpected("Failed to get acceleration limit vectors: " +
542+
maybe_joint_info.error());
543+
}
544+
const auto& joint_info = maybe_joint_info.value();
545+
546+
for (size_t dof = 0; dof < joint_info.num_velocity_dofs; ++dof) {
547+
const auto& max_accel = joint_info.limits.max_acceleration(dof);
548+
lower_limits(v_idx + dof) = -max_accel;
549+
upper_limits(v_idx + dof) = max_accel;
550+
}
551+
v_idx += joint_info.num_velocity_dofs;
552+
}
553+
return std::make_pair(lower_limits, upper_limits);
554+
}
555+
556+
tl::expected<EigenVectorPair, std::string>
557+
Scene::getJerkLimitVectors(const std::string& group_name) const {
558+
const auto maybe_joint_group_info = getJointGroupInfo(group_name);
559+
if (!maybe_joint_group_info) {
560+
return tl::make_unexpected("Failed to get jerk limit vectors: " +
561+
maybe_joint_group_info.error());
562+
}
563+
const auto& joint_group_info = maybe_joint_group_info.value();
564+
565+
// Initialize all limits as infinity and only set the joint DOFs that are finite.
566+
Eigen::VectorXd lower_limits = Eigen::VectorXd::Constant(
567+
joint_group_info.v_indices.size(), -std::numeric_limits<double>::infinity());
568+
Eigen::VectorXd upper_limits = Eigen::VectorXd::Constant(joint_group_info.v_indices.size(),
569+
std::numeric_limits<double>::infinity());
570+
size_t v_idx = 0;
571+
for (size_t j_idx = 0; j_idx < joint_group_info.joint_names.size(); ++j_idx) {
572+
const auto& joint_name = joint_group_info.joint_names.at(j_idx);
573+
const auto maybe_joint_info = getJointInfo(joint_name);
574+
if (!maybe_joint_info) {
575+
return tl::make_unexpected("Failed to get jerk limit vectors: " + maybe_joint_info.error());
576+
}
577+
const auto& joint_info = maybe_joint_info.value();
578+
579+
for (size_t dof = 0; dof < joint_info.num_velocity_dofs; ++dof) {
580+
const auto& max_jerk = joint_info.limits.max_jerk(dof);
581+
lower_limits(v_idx + dof) = -max_jerk;
582+
upper_limits(v_idx + dof) = max_jerk;
583+
}
584+
v_idx += joint_info.num_velocity_dofs;
585+
}
586+
return std::make_pair(lower_limits, upper_limits);
587+
}
588+
403589
tl::expected<void, std::string> Scene::addBoxGeometry(const std::string& name,
404590
const std::string& parent_frame,
405591
const Box& box, const Eigen::Matrix4d& tform,

0 commit comments

Comments
 (0)