diff --git a/src/picknik_ur_gazebo_scan_and_plan_config/config/config.yaml b/src/picknik_ur_gazebo_scan_and_plan_config/config/config.yaml
index 62ce3d59..21b9ac22 100644
--- a/src/picknik_ur_gazebo_scan_and_plan_config/config/config.yaml
+++ b/src/picknik_ur_gazebo_scan_and_plan_config/config/config.yaml
@@ -5,10 +5,11 @@
# Name of the package to specialize
based_on_package: "picknik_ur_gazebo_config"
-# Optional parameters that can be read in your launch files for specific functionality
-optional_feature_params:
- gazebo_world_package_name: "picknik_ur_gazebo_scan_and_plan_config"
- gazebo_world_path: "description/simulation_worlds/scan_and_plan_world.sdf"
+hardware:
+ # The following launch file is started when hardware.simulated is True
+ simulated_hardware_launch_file:
+ package: "picknik_ur_gazebo_scan_and_plan_config"
+ path: "launch/sim/hardware_sim.launch.py"
objectives:
# Override with a new set of waypoints based on the Gazebo world.
diff --git a/src/picknik_ur_gazebo_scan_and_plan_config/launch/sim/hardware_sim.launch.py b/src/picknik_ur_gazebo_scan_and_plan_config/launch/sim/hardware_sim.launch.py
new file mode 100644
index 00000000..92ff5f9d
--- /dev/null
+++ b/src/picknik_ur_gazebo_scan_and_plan_config/launch/sim/hardware_sim.launch.py
@@ -0,0 +1,261 @@
+# Copyright 2023 PickNik Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of the PickNik Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+import os
+import re
+import shlex
+
+from launch import LaunchDescription
+from launch.actions import IncludeLaunchDescription, OpaqueFunction
+from launch_ros.actions import Node
+
+from moveit_studio_utils_py.launch_common import get_launch_file, get_ros_path
+from moveit_studio_utils_py.system_config import get_config_folder, SystemConfigParser
+
+
+def path_pattern_change_for_gazebo(urdf_string):
+ """
+ Replaces strings in a URDF file such as
+ package://package_name/path/to/file
+ to the actual full path of the file.
+ """
+ data = urdf_string
+ package_expressions = re.findall("(package://([^//]*))", data)
+ for expr in set(package_expressions):
+ data = data.replace(expr[0], get_ros_path(expr[1]))
+ return data
+
+
+def generate_simulation_description(context, *args, **settings):
+ world_path = "description/simulation_worlds/scan_and_plan_world.sdf"
+ use_gui = False
+ is_verbose = False
+
+ # Create a Gazebo world file that swaps out package:// paths with absolute path.
+ original_world_file = get_ros_path(
+ "picknik_ur_gazebo_scan_and_plan_config",
+ world_path,
+ )
+ modified_world_file = os.path.join(
+ get_config_folder(), "auto_created", "gazebo_world.sdf"
+ )
+ with open(original_world_file, "r") as file:
+ world_sdf = path_pattern_change_for_gazebo(file.read())
+ with open(modified_world_file, "w") as file:
+ file.write(world_sdf)
+
+ # Launch Gazebo.
+ print(f"Starting Gazebo with world at {world_path}")
+ print(f"GUI: {use_gui}, Verbose: {is_verbose}")
+
+ sim_args = "-r --render-engine ogre"
+ if is_verbose:
+ sim_args += " -v 4"
+ if not use_gui:
+ sim_args += " -s --headless-rendering"
+
+ gazebo = IncludeLaunchDescription(
+ get_launch_file("ros_gz_sim", "launch/gz_sim.launch.py"),
+ launch_arguments=[("gz_args", [f"{sim_args} {modified_world_file}"])],
+ )
+ return [gazebo]
+
+
+def generate_launch_description():
+ system_config_parser = SystemConfigParser()
+
+ # The path to the auto_created urdf files
+ robot_urdf = system_config_parser.get_processed_urdf()
+ robot_urdf_ignition = path_pattern_change_for_gazebo(robot_urdf)
+
+ # Launch Gazebo
+ gazebo = OpaqueFunction(function=generate_simulation_description)
+
+ init_pose_args = shlex.split("-x 0.0 -y 0.0 -z 1.03 -R 0.0 -P 0.0 -Y 0.0")
+ spawn_robot = Node(
+ package="ros_gz_sim",
+ executable="create",
+ output="both",
+ arguments=[
+ "-string",
+ robot_urdf_ignition,
+ "-name",
+ "robot",
+ "-allow_renaming",
+ "true",
+ ]
+ + init_pose_args,
+ )
+
+ ########################
+ # Camera Topic Bridges #
+ ########################
+ # For the scene camera, enable RGB image topics only.
+ scene_image_rgb_ignition_bridge = Node(
+ package="ros_gz_image",
+ executable="image_bridge",
+ name="scene_image_rgb_ignition_bridge",
+ arguments=[
+ "/scene_camera/image",
+ ],
+ remappings=[
+ ("/scene_camera/image", "/scene_camera/color/image_raw"),
+ ],
+ output="both",
+ )
+ scene_image_depth_ignition_bridge = Node(
+ package="ros_gz_image",
+ executable="image_bridge",
+ name="scene_image_depth_ignition_bridge",
+ arguments=[
+ "/scene_camera/depth_image",
+ ],
+ remappings=[
+ (
+ "/scene_camera/depth_image",
+ "/scene_camera/depth/image_rect_raw",
+ ),
+ ],
+ output="both",
+ )
+
+ scene_camera_info_ignition_bridge = Node(
+ package="ros_gz_bridge",
+ executable="parameter_bridge",
+ name="scene_camera_info_ignition_bridge",
+ arguments=[
+ "/scene_camera/camera_info@sensor_msgs/msg/CameraInfo[ignition.msgs.CameraInfo",
+ ],
+ remappings=[
+ ("/scene_camera/camera_info", "/scene_camera/color/camera_info"),
+ ],
+ output="both",
+ )
+
+ # For the wrist mounted camera, enable RGB and depth topics.
+ wrist_image_rgb_ignition_bridge = Node(
+ package="ros_gz_image",
+ executable="image_bridge",
+ name="wrist_image_rgb_ignition_bridge",
+ arguments=[
+ "/wrist_mounted_camera/image",
+ ],
+ remappings=[
+ ("/wrist_mounted_camera/image", "/wrist_mounted_camera/color/image_raw"),
+ ],
+ output="both",
+ )
+ wrist_image_depth_ignition_bridge = Node(
+ package="ros_gz_image",
+ executable="image_bridge",
+ name="wrist_image_depth_ignition_bridge",
+ arguments=[
+ "/wrist_mounted_camera/depth_image",
+ ],
+ remappings=[
+ (
+ "/wrist_mounted_camera/depth_image",
+ "/wrist_mounted_camera/depth/image_rect_raw",
+ ),
+ ],
+ output="both",
+ )
+ wrist_camera_pointcloud_ignition_bridge = Node(
+ package="ros_gz_bridge",
+ executable="parameter_bridge",
+ name="wrist_camera_pointcloud_ignition_bridge",
+ arguments=[
+ "/wrist_mounted_camera/points@sensor_msgs/msg/PointCloud2[ignition.msgs.PointCloudPacked",
+ ],
+ remappings=[
+ (
+ "/wrist_mounted_camera/points",
+ "/wrist_mounted_camera/depth/color/points",
+ ),
+ ],
+ output="both",
+ )
+ wrist_camera_info_ignition_bridge = Node(
+ package="ros_gz_bridge",
+ executable="parameter_bridge",
+ name="wrist_camera_info_ignition_bridge",
+ arguments=[
+ "/wrist_mounted_camera/camera_info@sensor_msgs/msg/CameraInfo[ignition.msgs.CameraInfo",
+ ],
+ remappings=[
+ (
+ "/wrist_mounted_camera/camera_info",
+ "/wrist_mounted_camera/color/camera_info",
+ ),
+ ],
+ output="both",
+ )
+
+ #######################
+ # Force Torque Sensor #
+ #######################
+ fts_bridge = Node(
+ package="ros_gz_bridge",
+ executable="parameter_bridge",
+ name="fts_bridge",
+ arguments=[
+ "/tcp_fts_sensor/ft_data@geometry_msgs/msg/WrenchStamped[ignition.msgs.Wrench",
+ ],
+ remappings=[
+ (
+ "/tcp_fts_sensor/ft_data",
+ "/force_torque_sensor_broadcaster/wrench",
+ ),
+ ],
+ output="both",
+ )
+
+ clock_bridge = Node(
+ package="ros_gz_bridge",
+ executable="parameter_bridge",
+ name="clock_bridge",
+ arguments=["/clock@rosgraph_msgs/msg/Clock[ignition.msgs.Clock"],
+ output="both",
+ )
+
+ return LaunchDescription(
+ [
+ scene_image_rgb_ignition_bridge,
+ scene_image_depth_ignition_bridge,
+ scene_camera_info_ignition_bridge,
+ wrist_image_rgb_ignition_bridge,
+ wrist_camera_info_ignition_bridge,
+ wrist_image_depth_ignition_bridge,
+ wrist_camera_pointcloud_ignition_bridge,
+ clock_bridge,
+ fts_bridge,
+ gazebo,
+ spawn_robot,
+ ]
+ )
diff --git a/src/picknik_ur_gazebo_scan_and_plan_config/objectives/estimate_object_pose.xml b/src/picknik_ur_gazebo_scan_and_plan_config/objectives/estimate_object_pose.xml
index 4ff8caf9..3b3a36fc 100644
--- a/src/picknik_ur_gazebo_scan_and_plan_config/objectives/estimate_object_pose.xml
+++ b/src/picknik_ur_gazebo_scan_and_plan_config/objectives/estimate_object_pose.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/picknik_ur_gazebo_scan_and_plan_config/objectives/object_inspection.xml b/src/picknik_ur_gazebo_scan_and_plan_config/objectives/object_inspection.xml
index edad26be..f84ebf96 100644
--- a/src/picknik_ur_gazebo_scan_and_plan_config/objectives/object_inspection.xml
+++ b/src/picknik_ur_gazebo_scan_and_plan_config/objectives/object_inspection.xml
@@ -1,6 +1,6 @@
-
+
@@ -10,7 +10,7 @@
-
+
@@ -18,8 +18,17 @@
-
+
+
+
+
+
+
+
+
+
+