diff --git a/CMakeLists.txt b/CMakeLists.txt index b4c3cc9..6d8c16b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,9 @@ SET(CMAKE_AUTOUIC ON) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(Qt5 REQUIRED COMPONENTS Widgets) -find_package(simulation_interfaces 1 REQUIRED) +# Both the 1.x and 2.x message definitions are supported (see src/resource_compat.h). +# The minimum version is 1.1.0, which introduced the world services. +find_package(simulation_interfaces REQUIRED) find_package(rclcpp_action REQUIRED) find_package(tf2 REQUIRED) find_package(pluginlib REQUIRED) diff --git a/README.md b/README.md index 7d6cd4c..be05ab6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ It utilizes https://github.com/ros-simulation/simulation_interfaces to change th # Prerequisites +`simulation_interfaces` 1.1.0 or newer is required; both the 1.x and the 2.x message definitions are supported. + ```shell sudo apt install ros-$ROS_DISTRO-simulation-interfaces libqt5-dev ``` diff --git a/src/resource_compat.h b/src/resource_compat.h new file mode 100644 index 0000000..952258d --- /dev/null +++ b/src/resource_compat.h @@ -0,0 +1,87 @@ +/* Copyright 2025, Robotec.ai sp. z o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +//! Compatibility helpers for the resource fields of simulation_interfaces. +//! +//! simulation_interfaces 2.0 replaced the flat `uri` and `resource_string` fields of +//! SpawnEntity::Request, LoadWorld::Request and Spawnable with a single nested +//! simulation_interfaces/msg/Resource member, named `entity_resource` or `world_resource` +//! depending on the message. The accessors below resolve the field at compile time, so the +//! panel builds against both the 1.x and the 2.x message definitions. + +namespace ResourceCompat +{ + template + struct HasEntityResource : std::false_type + { + }; + + template + struct HasEntityResource().entity_resource)>> : std::true_type + { + }; + + template + struct HasWorldResource : std::false_type + { + }; + + template + struct HasWorldResource().world_resource)>> : std::true_type + { + }; +} // namespace ResourceCompat + +//! Returns the `uri` field of a message, whether it is nested in a Resource member or not. +//! Constness of the message is preserved in the returned reference. +template +auto& ResourceUri(T& message) +{ + if constexpr (ResourceCompat::HasEntityResource::value) + { + return message.entity_resource.uri; + } + else if constexpr (ResourceCompat::HasWorldResource::value) + { + return message.world_resource.uri; + } + else + { + return message.uri; + } +} + +//! Returns the `resource_string` field of a message, whether it is nested in a Resource member or not. +//! Constness of the message is preserved in the returned reference. +template +auto& ResourceString(T& message) +{ + if constexpr (ResourceCompat::HasEntityResource::value) + { + return message.entity_resource.resource_string; + } + else if constexpr (ResourceCompat::HasWorldResource::value) + { + return message.world_resource.resource_string; + } + else + { + return message.resource_string; + } +} diff --git a/src/simulation_widget.cpp b/src/simulation_widget.cpp index 4d69041..bd5a3f7 100644 --- a/src/simulation_widget.cpp +++ b/src/simulation_widget.cpp @@ -22,6 +22,7 @@ #include #include #include "quaternion_utils.h" +#include "resource_compat.h" #include "service.h" #include "string_to_keys.h" #include "ui_sim_widget.h" @@ -252,11 +253,11 @@ namespace q_simulation_interfaces auto selectedWorld = ui_->availableWorldsCombo->currentText(); if (useUriForWorlds_) { - request.uri = selectedWorld.toStdString(); + ResourceUri(request) = selectedWorld.toStdString(); } else { - request.resource_string = selectedWorld.toStdString(); + ResourceString(request) = selectedWorld.toStdString(); } auto cb = [this](auto response) @@ -729,7 +730,7 @@ namespace q_simulation_interfaces simulation_interfaces::srv::SpawnEntity::Request request; request.name = ui_->lineEditName->text().toStdString(); - request.uri = ui_->ComboSpawables->currentText().toStdString(); + ResourceUri(request) = ui_->ComboSpawables->currentText().toStdString(); request.entity_namespace = ui_->lineEditNamespace->text().toStdString(); request.allow_renaming = ui_->checkBoxAllowRename->isChecked(); request.initial_pose.header.frame_id = ui_->spawnFrameLineEdit->text().toStdString(); @@ -780,11 +781,11 @@ namespace q_simulation_interfaces auto spawnables = response->spawnables; std::sort(spawnables.begin(), spawnables.end(), - [](const auto& a, const auto& b) { return a.uri < b.uri; }); + [](const auto& a, const auto& b) { return ResourceUri(a) < ResourceUri(b); }); for (const auto& spawnable : spawnables) { - ui_->ComboSpawables->addItem(QString::fromStdString(spawnable.uri)); + ui_->ComboSpawables->addItem(QString::fromStdString(ResourceUri(spawnable))); } if (ui_->ComboSpawables->findText(selectedSpawnable) != -1)