58 return std::atan2(pose.linear()(1, 0), pose.linear()(0, 0));
129 if (not std::filesystem::exists(configFile))
131 throw std::runtime_error(
"No platform dynamics config at " + configFile.string());
134 std::ifstream stream{configFile};
135 if (not stream.good())
137 throw std::runtime_error(
"Cannot read platform dynamics config at " +
138 configFile.string());
141 const nlohmann::json config = nlohmann::json::parse(stream);
148 const bool mecanum = config.value(
"model", std::string{}) ==
"mecanum_torque";
150 const nlohmann::json& drive =
151 mecanum ? config.at(
"mecanumTorque") : config.at(
"omniWheelTorque");
155 const nlohmann::json& geometry = config.at(
"mecanum");
158 params.
mecanum.
gauge = geometry.at(
"gauge").get<
float>();
164 const nlohmann::json& geometry = config.at(
"omniWheel");
167 constexpr float degToRad =
static_cast<float>(
M_PI) / 180.F;
173 geometry.at(
"angularPositionFirstWheelDegrees").get<
float>() * degToRad;
175 geometry.at(
"relativeAngleDegrees").get<
float>() * degToRad;
178 const std::vector<bool> invert =
179 geometry.value(
"invertWheel", std::vector<bool>{
false,
false,
false});
182 Eigen::Vector3i{invert[0] ? 1 : 0, invert[1] ? 1 : 0, invert[2] ? 1 : 0};
185 const nlohmann::json& robot = drive.at(
"robot");
188 robot.at(
"file").get<std::string>())
190 params.
nodeSet = robot.at(
"nodeSet").get<std::string>();
191 params.
configuration = robot.at(
"configuration").get<std::string>();
194 params.
gearRatio = drive.at(
"gearRatio").get<
float>();
200 const float inputSpeedRpm = std::min(drive.at(
"motorNominalSpeedRpm").get<
float>(),
201 drive.at(
"gearboxMaxInputSpeedRpm").get<
float>());
203 inputSpeedRpm / 60.F / params.
gearRatio * 2.F *
static_cast<float>(
M_PI);
208 const nlohmann::json& ramp = config.at(
"commandRateLimit");
244#if NAVIGATION_TOPPRA_ENABLED
247 if (not reparametrizer_)
257 const py::gil_scoped_acquire gil;
258 reparametrizer_ = py::object{};
265#if NAVIGATION_TOPPRA_ENABLED
273 return reparametrizer_;
276 const py::module_ module =
277 py::module_::import(
"armarx_navigation.reparametrization");
279 reparametrizer_ =
module.attr("Reparametrizer")(
280 py::arg("robot") = module.attr("RobotSpec")(
281 py::arg("file") = drive.robotFile,
282 py::arg("node_set") = drive.nodeSet,
283 py::arg("configuration") = drive.configuration),
284 py::arg("kinematics") = kinematics(module),
285 py::arg("limits") = module.attr("DriveLimits")(
286 py::arg("max_velocity") = config.maxVel.linear,
287 py::arg("max_angular_velocity") = config.maxVel.angular,
288 py::arg("motor_max_torque") = drive.motorMaxTorque,
289 py::arg("gear_ratio") = drive.gearRatio,
290 py::arg("gearbox_efficiency") = drive.gearboxEfficiency,
291 py::arg("torque_fraction") = drive.torqueFraction,
292 py::arg("max_wheel_velocity") = drive.maxWheelVelocity,
293 py::arg("max_acceleration") = drive.maxAcceleration,
294 py::arg("max_deceleration") = drive.maxDeceleration,
295 py::arg("max_angular_acceleration") = drive.maxAngularAcceleration));
297 return reparametrizer_;
311 kinematics(
const py::module_& module)
const
316 return module.attr("MecanumKinematics")(
317 py::arg("gauge") = drive.mecanum.gauge,
318 py::arg("wheelbase") = drive.mecanum.wheelbase,
319 py::arg("wheel_radius") = drive.mecanum.wheelRadius);
322 return module.attr("OmniWheelKinematics")(
323 py::arg("body_radius") = drive.omniWheel.bodyRadius,
324 py::arg("wheel_radius") = drive.omniWheel.wheelRadius,
325 py::arg("delta") = drive.omniWheel.delta,
326 py::arg("relative_angle") = drive.omniWheel.relativeAngle,
327 py::arg("gear_ratio") = drive.omniWheel.gearRatio,
329 std::vector<bool>{drive.omniWheel.invertWheel.x() != 0,
330 drive.omniWheel.invertWheel.y() != 0,
331 drive.omniWheel.invertWheel.z() != 0});
334 throw std::runtime_error(
"Unknown platform drive type.");
342 py::object reparametrizer_;
363 impl_(
std::make_unique<
Impl>(config, drive))
365#if NAVIGATION_TOPPRA_ENABLED
371 const auto started = std::chrono::steady_clock::now();
376 const py::gil_scoped_acquire gil;
380 impl_->reparametrizer();
382 catch (
const py::error_already_set& error)
384 throw std::runtime_error(
385 std::string{
"TOPP-RA reparametrization could not be initialised: "} +
391 << std::chrono::duration<double, std::milli>(
392 std::chrono::steady_clock::now() - started)
394 <<
" ms (interpreter, imports and robot model).";
402 [[maybe_unused]]
float startVelocity)
const
404#if not NAVIGATION_TOPPRA_ENABLED
405 throw std::runtime_error(
"TOPP-RA reparametrization not available because " +
408 const std::vector<core::GlobalTrajectoryPoint>& input =
trajectory.points();
412 Eigen::MatrixXd waypoints(
static_cast<Eigen::Index
>(input.size()), 4);
413 for (Eigen::Index i = 0; i < waypoints.rows(); i++)
418 waypoints(i, 0) = position.x();
419 waypoints(i, 1) = position.y();
428 const auto timed = [](
const char* label,
auto&& work)
430 const auto started = std::chrono::steady_clock::now();
432 const double ms = std::chrono::duration<double, std::milli>(
433 std::chrono::steady_clock::now() - started)
436 std::cout <<
" [reparametrization] " << std::left << std::setw(32) << label
437 << std::right << std::setw(8) << std::fixed << std::setprecision(1) << ms
438 <<
" ms" << std::endl;
445 const py::gil_scoped_acquire gil;
447 Eigen::MatrixXd samples;
448 double duration = 0.0;
452 timed(
"solve + sample (total)",
455 const py::object result =
456 impl_->reparametrizer()(waypoints, impl_->drive.numSamples);
458 samples = result.attr(
"samples").cast<Eigen::MatrixXd>();
459 duration = result.attr(
"duration").cast<
double>();
462 catch (
const py::error_already_set& error)
464 throw std::runtime_error(std::string{
"TOPP-RA reparametrization failed: "} +
470 if (samples.rows() < 2)
472 throw std::runtime_error(
"TOPP-RA returned fewer than 2 waypoints.");
476 impl_->lastSamples = samples;
477 impl_->lastDuration = duration;
479 std::vector<core::GlobalTrajectoryPoint> points;
480 points.reserve(
static_cast<std::size_t
>(samples.rows()));
482 for (Eigen::Index i = 0; i < samples.rows(); i++)
485 pose.translation() <<
static_cast<float>(samples(i,
X)),
486 static_cast<float>(samples(i,
Y)), 0.F;
487 pose.linear() = Eigen::AngleAxisf(
static_cast<float>(samples(i,
YAW)),
488 Eigen::Vector3f::UnitZ())
499 .waypoint = {.pose = pose},
500 .velocity = std::max(
static_cast<float>(samples(i,
VELOCITY)),
501 impl_->config.boundaryVelocity)});
504 ARMARX_INFO <<
"TOPP-RA: " << points.size() <<
" waypoints, " << duration <<
" s.";