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 if (drive.contains(
"maxWheelVelocity"))
216 const nlohmann::json& ramp = config.at(
"commandRateLimit");
253#if NAVIGATION_TOPPRA_ENABLED
256 if (not reparametrizer_)
266 const py::gil_scoped_acquire gil;
267 reparametrizer_ = py::object{};
274#if NAVIGATION_TOPPRA_ENABLED
282 return reparametrizer_;
285 const py::module_ module =
286 py::module_::import(
"armarx_navigation.reparametrization");
288 reparametrizer_ =
module.attr("Reparametrizer")(
289 py::arg("robot") = module.attr("RobotSpec")(
290 py::arg("file") = drive.robotFile,
291 py::arg("node_set") = drive.nodeSet,
292 py::arg("configuration") = drive.configuration),
293 py::arg("kinematics") = kinematics(module),
294 py::arg("limits") = module.attr("DriveLimits")(
295 py::arg("max_velocity") = config.maxVel.linear,
296 py::arg("max_angular_velocity") = config.maxVel.angular,
297 py::arg("motor_max_torque") = drive.motorMaxTorque,
298 py::arg("gear_ratio") = drive.gearRatio,
299 py::arg("gearbox_efficiency") = drive.gearboxEfficiency,
300 py::arg("torque_fraction") = drive.torqueFraction,
301 py::arg("max_wheel_velocity") = drive.maxWheelVelocity,
302 py::arg("max_acceleration") = drive.maxAcceleration,
303 py::arg("max_deceleration") = drive.maxDeceleration,
304 py::arg("max_angular_acceleration") = drive.maxAngularAcceleration,
305 py::arg("limit_angular_acceleration") = drive.limitAngularAcceleration));
307 return reparametrizer_;
321 kinematics(
const py::module_& module)
const
326 return module.attr("MecanumKinematics")(
327 py::arg("gauge") = drive.mecanum.gauge,
328 py::arg("wheelbase") = drive.mecanum.wheelbase,
329 py::arg("wheel_radius") = drive.mecanum.wheelRadius);
332 return module.attr("OmniWheelKinematics")(
333 py::arg("body_radius") = drive.omniWheel.bodyRadius,
334 py::arg("wheel_radius") = drive.omniWheel.wheelRadius,
335 py::arg("delta") = drive.omniWheel.delta,
336 py::arg("relative_angle") = drive.omniWheel.relativeAngle,
337 py::arg("gear_ratio") = drive.omniWheel.gearRatio,
339 std::vector<bool>{drive.omniWheel.invertWheel.x() != 0,
340 drive.omniWheel.invertWheel.y() != 0,
341 drive.omniWheel.invertWheel.z() != 0});
344 throw std::runtime_error(
"Unknown platform drive type.");
352 py::object reparametrizer_;
373 impl_(
std::make_unique<
Impl>(config, drive))
375#if NAVIGATION_TOPPRA_ENABLED
381 const auto started = std::chrono::steady_clock::now();
386 const py::gil_scoped_acquire gil;
390 impl_->reparametrizer();
392 catch (
const py::error_already_set& error)
394 throw std::runtime_error(
395 std::string{
"TOPP-RA reparametrization could not be initialised: "} +
401 << std::chrono::duration<double, std::milli>(
402 std::chrono::steady_clock::now() - started)
404 <<
" ms (interpreter, imports and robot model).";
412 [[maybe_unused]]
float startVelocity)
const
414#if not NAVIGATION_TOPPRA_ENABLED
415 throw std::runtime_error(
"TOPP-RA reparametrization not available because " +
418 const std::vector<core::GlobalTrajectoryPoint>& input =
trajectory.points();
422 Eigen::MatrixXd waypoints(
static_cast<Eigen::Index
>(input.size()), 4);
423 for (Eigen::Index i = 0; i < waypoints.rows(); i++)
428 waypoints(i, 0) = position.x();
429 waypoints(i, 1) = position.y();
438 const auto timed = [](
const char* label,
auto&& work)
440 const auto started = std::chrono::steady_clock::now();
442 const double ms = std::chrono::duration<double, std::milli>(
443 std::chrono::steady_clock::now() - started)
446 std::cout <<
" [reparametrization] " << std::left << std::setw(32) << label
447 << std::right << std::setw(8) << std::fixed << std::setprecision(1) << ms
448 <<
" ms" << std::endl;
455 const py::gil_scoped_acquire gil;
457 Eigen::MatrixXd samples;
458 double duration = 0.0;
462 timed(
"solve + sample (total)",
465 const py::object result =
466 impl_->reparametrizer()(waypoints, impl_->drive.numSamples);
468 samples = result.attr(
"samples").cast<Eigen::MatrixXd>();
469 duration = result.attr(
"duration").cast<
double>();
472 catch (
const py::error_already_set& error)
474 throw std::runtime_error(std::string{
"TOPP-RA reparametrization failed: "} +
480 if (samples.rows() < 2)
482 throw std::runtime_error(
"TOPP-RA returned fewer than 2 waypoints.");
486 impl_->lastSamples = samples;
487 impl_->lastDuration = duration;
489 std::vector<core::GlobalTrajectoryPoint> points;
490 points.reserve(
static_cast<std::size_t
>(samples.rows()));
514 const double boundaryFloor = impl_->config.boundaryVelocity;
515 const double goalFloor =
516 std::min<double>(impl_->config.goalVelocity, impl_->config.boundaryVelocity);
518 Eigen::Index leadingEnd = 0;
519 while (leadingEnd < samples.rows() and samples(leadingEnd,
VELOCITY) < boundaryFloor)
524 for (Eigen::Index i = 0; i < samples.rows(); i++)
527 pose.translation() <<
static_cast<float>(samples(i,
X)),
528 static_cast<float>(samples(i,
Y)), 0.F;
529 pose.linear() = Eigen::AngleAxisf(
static_cast<float>(samples(i,
YAW)),
530 Eigen::Vector3f::UnitZ())
534 const double floorVelocity = i <= leadingEnd ? boundaryFloor : goalFloor;
537 .waypoint = {.pose = pose},
538 .velocity = std::max(
static_cast<float>(samples(i,
VELOCITY)),
539 static_cast<float>(floorVelocity))});
542 ARMARX_INFO <<
"TOPP-RA: " << points.size() <<
" waypoints, " << duration <<
" s.";