diff --git a/src/panda/README.md b/src/panda/README.md
index 8871b5c..a210c9a 100644
--- a/src/panda/README.md
+++ b/src/panda/README.md
@@ -46,7 +46,7 @@ ros2 launch panda panda_pick_and_place_cubes.launch.py
ros2 launch panda panda_pick_and_place_cubes.launch.py cube_count:=5
# Restricted corridor: narrow passage obstacle-avoidance demo
-ros2 launch panda panda_restricted_corridor.launch.py
+ros2 launch panda panda_multi_object_sequence.launch.py
# Restricted pillars: pillar field obstacle-avoidance demo
ros2 launch panda panda_restricted_pillars.launch.py
diff --git a/src/panda/launch/panda_restricted_corridor.launch.py b/src/panda/launch/panda_multi_object_sequence.launch.py
similarity index 54%
rename from src/panda/launch/panda_restricted_corridor.launch.py
rename to src/panda/launch/panda_multi_object_sequence.launch.py
index 8d6687b..94cbd3f 100644
--- a/src/panda/launch/panda_restricted_corridor.launch.py
+++ b/src/panda/launch/panda_multi_object_sequence.launch.py
@@ -16,9 +16,19 @@ def generate_launch_description() -> LaunchDescription:
),
launch_arguments={
'world_file': PathJoinSubstitution(
- [FindPackageShare('panda'), 'worlds', 'restricted_corridor_env.sdf']
+ [FindPackageShare('panda'), 'worlds', 'multi_object_sequential_env.sdf']
),
'bridge_external_camera': 'false',
+ # External RGB-D camera positioned in front of the robot and table,
+ # elevated and angled down so it sees the full workspace: all three
+ # target objects, the corridor walls/barriers, and the Panda arm.
+ 'spawn_external_rgbd_camera': 'true',
+ 'external_camera_x': '1.10',
+ 'external_camera_y': '1.80',
+ 'external_camera_z': '2.10',
+ 'external_camera_roll': '0.0',
+ 'external_camera_pitch': '0.70',
+ 'external_camera_yaw': '3.14159',
}.items(),
)
]
diff --git a/src/panda/launch/panda_vla_pipeline.launch.py b/src/panda/launch/panda_vla_pipeline.launch.py
new file mode 100644
index 0000000..d16584d
--- /dev/null
+++ b/src/panda/launch/panda_vla_pipeline.launch.py
@@ -0,0 +1,61 @@
+from launch import LaunchDescription
+from launch.actions import IncludeLaunchDescription
+from launch.launch_description_sources import PythonLaunchDescriptionSource
+from launch.substitutions import PathJoinSubstitution
+from launch_ros.substitutions import FindPackageShare
+
+
+def generate_launch_description() -> LaunchDescription:
+ """VLA sort-and-place environment.
+
+ Table layout (top-down, robot at x=0 y=1.70 facing +x):
+
+ obj_sphere (red ball) – (0.35, 1.72)
+ obj_puck (orange disc) – (0.45, 1.65)
+ obj_brick (green block) – (0.30, 1.90)
+ obj_cylinder(blue can) – (0.42, 1.88)
+
+ basket_red – (0.55, 1.62) ← place red objects here
+ basket_blue – (0.55, 1.80) ← place blue objects here
+ basket_green – (0.55, 1.98) ← place green objects here
+
+ External RGB-D camera: x=1.10, y=1.80, z=2.10, pitch=0.70, yaw=π
+ → positioned directly in front of the arm+table, elevated and angled
+ down so it captures all objects, all baskets, and the Panda end-effector.
+
+ Wrist-eye camera: built into Panda URDF → /wrist_eye/image_raw
+ /wrist_eye/depth/image_raw
+ """
+ return LaunchDescription(
+ [
+ IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ PathJoinSubstitution(
+ [FindPackageShare('panda'), 'launch', 'panda_pick_and_place.launch.py']
+ )
+ ),
+ launch_arguments={
+ 'world_file': PathJoinSubstitution(
+ [FindPackageShare('panda'), 'worlds', 'vla_pipeline_env.sdf']
+ ),
+ # Cube spawner disabled – objects are embedded in the world SDF.
+ 'cube_count': '0',
+ # Wrist camera is always bridged by panda_pick_and_place.
+ # External in-world camera (rgbd_camera topic) is not used here.
+ 'bridge_external_camera': 'false',
+ # Spawn a dedicated front-view RGB-D camera at run-time so
+ # its pose can be tuned without editing the world SDF.
+ 'spawn_external_rgbd_camera': 'true',
+ # Camera sits in front of the robot and table, looking back
+ # at roughly 40° downward (pitch=0.70 rad) to see the full
+ # table surface including objects and baskets.
+ 'external_camera_x': '1.10',
+ 'external_camera_y': '1.80',
+ 'external_camera_z': '2.10',
+ 'external_camera_roll': '0.0',
+ 'external_camera_pitch': '0.70',
+ 'external_camera_yaw': '3.14159',
+ }.items(),
+ )
+ ]
+ )
diff --git a/src/panda/worlds/multi_object_sequential_env.sdf b/src/panda/worlds/multi_object_sequential_env.sdf
new file mode 100644
index 0000000..9db2a01
--- /dev/null
+++ b/src/panda/worlds/multi_object_sequential_env.sdf
@@ -0,0 +1,303 @@
+
+
+
+
+ 0.001
+ 1.0
+ 1000
+
+
+
+
+
+ ogre2
+
+
+
+
+ 0.45 0.45 0.45 1
+ 0.70 0.72 0.74 1
+ true
+
+
+ 0 0 -9.8
+
+
+ model://ground_plane
+
+
+
+ model://sun
+
+
+
+ model://table
+ 0.270759 1.801050 0 0 0 0
+
+
+
+
+
+
+ true
+ 0.44 1.60 1.085 0 0 0
+
+
+
+ 0.48 0.03 0.14
+
+
+
+
+ 0.48 0.03 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+ true
+ 0.44 2.00 1.085 0 0 0
+
+
+
+ 0.48 0.03 0.14
+
+
+
+
+ 0.48 0.03 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+
+ true
+
+ 0.37 1.665 1.085 0 0 0
+
+
+
+ 0.03 0.13 0.14
+
+
+
+
+ 0.03 0.13 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+ true
+
+ 0.37 1.935 1.085 0 0 0
+
+
+
+ 0.03 0.13 0.14
+
+
+
+
+ 0.03 0.13 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+
+ true
+ 0.52 1.665 1.085 0 0 0
+
+
+
+ 0.03 0.13 0.14
+
+
+
+
+ 0.03 0.13 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+ true
+ 0.52 1.935 1.085 0 0 0
+
+
+
+ 0.03 0.13 0.14
+
+
+
+
+ 0.03 0.13 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+
+ true
+ 0.68 1.800 1.085 0 0 0
+
+
+
+ 0.03 0.40 0.14
+
+
+
+
+ 0.03 0.40 0.14
+
+
+ 1 1 0 1
+ 1 1 0 1
+
+
+
+
+
+
+
+
+
+
+ false
+ 0.44 1.80 1.040 0 0 0
+
+
+ 0.12
+
+ 0.000050.00.0
+ 0.000050.0
+ 0.00004
+
+
+
+
+ 0.0250.05
+
+
+
+
+ 0.0250.05
+
+
+ 1 0 0 1
+ 1 0 0 1
+ 0.05 0.05 0.05 1
+
+
+
+
+
+
+
+ false
+ 0.60 1.80 1.040 0 0 0
+
+
+ 0.12
+
+ 0.000030.00.0
+ 0.000080.0
+ 0.00008
+
+
+
+
+ 0.08 0.04 0.04
+
+
+
+
+ 0.08 0.04 0.04
+
+
+ 0 0 1 1
+ 0 0 1 1
+ 0.05 0.05 0.05 1
+
+
+
+
+
+
diff --git a/src/panda/worlds/vla_pipeline_env.sdf b/src/panda/worlds/vla_pipeline_env.sdf
new file mode 100644
index 0000000..e235e14
--- /dev/null
+++ b/src/panda/worlds/vla_pipeline_env.sdf
@@ -0,0 +1,304 @@
+
+
+
+
+ 0.001
+ 1.0
+ 1000
+
+
+
+
+
+ ogre2
+
+
+
+
+ 0.50 0.50 0.50 1
+ 0.68 0.72 0.76 1
+ true
+
+
+ 0 0 -9.8
+
+
+ model://ground_plane
+
+
+
+ model://sun
+
+
+
+
+
+
+ model://table
+ 0.270759 1.801050 0 0 0 0
+
+
+
+
+
+
+ false
+ 0.350 1.720 1.040 0 0 0
+
+
+ 0.08
+
+ 0.0000200.00.0
+ 0.0000200.0
+ 0.000020
+
+
+
+
+ 0.025
+
+
+
+
+ 0.025
+
+
+ 0.90 0.15 0.15 1
+ 0.90 0.15 0.15 1
+ 0.20 0.05 0.05 1
+
+
+
+
+
+
+
+ false
+ 0.420 1.880 1.040 0 0 0
+
+
+ 0.10
+
+ 0.0000180.00.0
+ 0.0000180.0
+ 0.000012
+
+
+
+
+ 0.0220.055
+
+
+
+
+ 0.0220.055
+
+
+ 0.20 0.40 0.92 1
+ 0.20 0.40 0.92 1
+ 0.05 0.08 0.20 1
+
+
+
+
+
+
+
+ false
+
+ 0.300 1.900 1.042 0 0 0
+
+
+ 0.10
+
+ 0.0000120.00.0
+ 0.0000300.0
+ 0.000030
+
+
+
+
+ 0.034 0.080 0.034
+
+
+
+
+ 0.034 0.080 0.034
+
+
+ 0.15 0.78 0.25 1
+ 0.15 0.78 0.25 1
+ 0.05 0.15 0.05 1
+
+
+
+
+
+
+
+ false
+
+ 0.450 1.650 1.038 0 0 0
+
+
+ 0.08
+
+ 0.0000150.00.0
+ 0.0000150.0
+ 0.000022
+
+
+
+
+ 0.0300.026
+
+
+
+
+ 0.0300.026
+
+
+ 0.95 0.55 0.10 1
+ 0.95 0.55 0.10 1
+ 0.15 0.08 0.02 1
+
+
+
+
+
+
+
+
+
+ true
+ 0.550 1.620 1.015 0 0 0
+
+
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0.85 0.15 0.15 10.85 0.15 0.15 1
+
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.85 0.15 0.15 10.85 0.15 0.15 1
+
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.85 0.15 0.15 10.85 0.15 0.15 1
+
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.85 0.15 0.15 10.85 0.15 0.15 1
+
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.85 0.15 0.15 10.85 0.15 0.15 1
+
+
+
+
+
+ true
+ 0.550 1.800 1.015 0 0 0
+
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0.20 0.40 0.92 10.20 0.40 0.92 1
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.20 0.40 0.92 10.20 0.40 0.92 1
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.20 0.40 0.92 10.20 0.40 0.92 1
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.20 0.40 0.92 10.20 0.40 0.92 1
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.20 0.40 0.92 10.20 0.40 0.92 1
+
+
+
+
+
+ true
+ 0.550 1.980 1.015 0 0 0
+
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0 0 0 0 0 0
+ 0.160 0.160 0.010
+ 0.15 0.78 0.25 10.15 0.78 0.25 1
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 -0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.15 0.78 0.25 10.15 0.78 0.25 1
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0 0.075 0.035 0 0 0
+ 0.160 0.010 0.060
+ 0.15 0.78 0.25 10.15 0.78 0.25 1
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ -0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.15 0.78 0.25 10.15 0.78 0.25 1
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.075 0 0.035 0 0 0
+ 0.010 0.160 0.060
+ 0.15 0.78 0.25 10.15 0.78 0.25 1
+
+
+
+
+
diff --git a/src/pick_place/CMakeLists.txt b/src/pick_place/CMakeLists.txt
index 523a180..7d72f07 100644
--- a/src/pick_place/CMakeLists.txt
+++ b/src/pick_place/CMakeLists.txt
@@ -57,6 +57,13 @@ add_executable(pick_place_executor_node
)
ament_target_dependencies(pick_place_executor_node ${dependencies})
+# Build pick_place_multi_executor_node executable
+add_executable(pick_place_multi_executor_node
+ src/pick_place_multi_executor.cpp
+ src/pick_place_multi_executor_main.cpp
+)
+ament_target_dependencies(pick_place_multi_executor_node ${dependencies})
+
# Build object_detector_pcl_node executable
add_executable(object_detector_pcl_node
src/object_detector_pcl.cpp
@@ -70,6 +77,7 @@ install(TARGETS
object_detector_node
object_detector_pcl_node
pick_place_executor_node
+ pick_place_multi_executor_node
DESTINATION lib/${PROJECT_NAME}
)
diff --git a/src/pick_place/README.md b/src/pick_place/README.md
index 5978eba..5d23843 100644
--- a/src/pick_place/README.md
+++ b/src/pick_place/README.md
@@ -18,4 +18,21 @@ ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 panda_wrist_eye_link Pan
ros2 run pick_place pick_place_executor_node --ros-args --params-file ros2_ws/src/dev_robotic_arm_engineering/src/pick_place/config/params.yaml
+```
+
+### For multiple objects
+
+```bash
+# env
+ros2 launch panda panda_multi_object_sequence.launch.py
+
+# finding objects and publishing their poses
+ros2 launch cube_segmentation segmentation.launch.py \
+ use_sim_time:=true \
+ publish_header_alias_tf:=true \
+ publish_optical_tf:=true
+
+# pick and place executor
+ros2 launch pick_place pick_place_multi_object.launch.py
+
```
diff --git a/src/pick_place/config/params.yaml b/src/pick_place/config/params.yaml
index 43f10b4..b724ed2 100644
--- a/src/pick_place/config/params.yaml
+++ b/src/pick_place/config/params.yaml
@@ -101,3 +101,58 @@
detection_stability_count: 5 # frames before accepting detection
detection_stability_dist: 0.02 # metres
+
+ # Multi-object static-scene executor parameters
+ pick_place_multi_executor:
+ object_pose_topic: "/segmentation/object_poses"
+ arm_group_name: panda_arm
+ end_effector_link: panda_hand_tcp
+ hand_group_name: hand
+ home_named_target: ready
+ max_objects: 3
+
+ execute_enabled: true
+ execute_pick_only: false
+ continue_on_failure: true
+
+ approach_offset_z: 0.1
+ post_grasp_lift_z: 0.18 # metres; extra lift after grasp before transfer to place
+ cartesian_step: 0.01
+ min_cartesian_fraction: 0.9
+ enforce_min_z: true
+ min_allowed_z: 0.0
+
+ use_current_ee_orientation_for_pick: false
+ use_fixed_pick_orientation: true
+ pick_pose:
+ qx: 1.0
+ qy: 0.0
+ qz: 0.0
+ qw: 0.0
+
+ # Place targets consumed by index in PoseArray order.
+ place_poses_x: [-0.059, -0.249, -0.335]
+ place_poses_y: [0.424, 0.424, 0.094]
+ place_poses_z: [0.20, 0.20, 0.20]
+ place_poses_qx: [1.0, 1.0, 1.0]
+ place_poses_qy: [0.0, 0.0, 0.0]
+ place_poses_qz: [0.0, 0.0, 0.0]
+ place_poses_qw: [0.0, 0.0, 0.0]
+
+ # Static obstacle import from multi_object_sequential_env.sdf.
+ # Default offsets convert world coordinates to panda_link0 for the
+ # default spawn pose in panda_pick_and_place.launch.py.
+ publish_static_env_obstacles: true
+ static_env_sdf_path: "src/dev_robotic_arm_engineering/src/panda/worlds/multi_object_sequential_env.sdf"
+ static_env_offset_x: 0.0
+ static_env_offset_y: -1.70
+ static_env_offset_z: -1.0
+ static_env_offset_roll: 0.0
+ static_env_offset_pitch: 0.0
+ static_env_offset_yaw: 0.0
+
+ table_proxy_enabled: true
+ table_proxy_size_x: 1.50
+ table_proxy_size_y: 0.80
+ table_proxy_size_z: 0.08
+ table_proxy_center_z_offset: 1.0
diff --git a/src/pick_place/include/pick_place/pick_place_multi_executor.hpp b/src/pick_place/include/pick_place/pick_place_multi_executor.hpp
new file mode 100644
index 0000000..38c9486
--- /dev/null
+++ b/src/pick_place/include/pick_place/pick_place_multi_executor.hpp
@@ -0,0 +1,107 @@
+#ifndef PICK_PLACE__PICK_PLACE_MULTI_EXECUTOR_HPP_
+#define PICK_PLACE__PICK_PLACE_MULTI_EXECUTOR_HPP_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace pick_place {
+
+ class PickPlaceMultiExecutor : public rclcpp::Node {
+ public:
+ explicit PickPlaceMultiExecutor(rclcpp::NodeOptions const& options = rclcpp::NodeOptions());
+
+ private:
+ enum class State { IDLE, WAITING_FOR_SNAPSHOT, RUNNING, COMPLETED };
+
+ void objectPosesCallback(const geometry_msgs::msg::PoseArray::SharedPtr msg);
+ void rerunServiceCallback(const std::shared_ptr request,
+ std::shared_ptr response);
+ bool executeSequence();
+ bool runPlanningSequenceForObject(geometry_msgs::msg::PoseStamped const& object_pose_base,
+ geometry_msgs::msg::PoseStamped const& place_pose_base,
+ size_t object_index);
+ bool loadStaticObstaclesFromSdf();
+ bool transformPoseToBase(geometry_msgs::msg::PoseStamped const& input,
+ geometry_msgs::msg::PoseStamped& output) const;
+
+ geometry_msgs::msg::PoseStamped computePreGraspPose(geometry_msgs::msg::PoseStamped const& object_pose_base) const;
+ bool planToPoseTarget(geometry_msgs::msg::PoseStamped const& target_pose,
+ std::string const& stage_name,
+ bool execute_after_plan);
+ bool planCartesianMove(geometry_msgs::msg::Pose const& target_pose,
+ std::string const& stage_name,
+ bool execute_after_plan);
+ bool planToNamedTarget(std::string const& target_name, std::string const& stage_name, bool execute_after_plan);
+ bool planGripperAction(std::string const& action_name, bool execute_action);
+ bool executeArmPlan(moveit::planning_interface::MoveGroupInterface::Plan const& plan,
+ std::string const& stage_name);
+ bool executeHandNamedTarget(std::string const& target_name, std::string const& stage_name);
+ void setState(State new_state);
+ char const* stateToString(State state) const;
+
+ rclcpp::Subscription::SharedPtr object_poses_sub_;
+ rclcpp::Service::SharedPtr rerun_service_;
+
+ std::unique_ptr tf_buffer_;
+ std::unique_ptr tf_listener_;
+
+ std::shared_ptr arm_move_group_;
+ std::shared_ptr hand_move_group_;
+ moveit::planning_interface::PlanningSceneInterface planning_scene_interface_;
+
+ std::vector object_poses_snapshot_;
+ std::vector place_poses_;
+
+ std::string object_pose_topic_;
+ std::string base_frame_;
+ std::string arm_group_name_;
+ std::string end_effector_link_;
+ std::string hand_group_name_;
+ std::string home_named_target_;
+
+ int64_t max_objects_{3};
+ double approach_offset_z_{0.1};
+ double post_grasp_lift_z_{0.1};
+ double cartesian_step_{0.01};
+ double min_cartesian_fraction_{0.9};
+ double min_allowed_z_{0.0};
+ bool enforce_min_z_{true};
+ bool use_current_ee_orientation_for_pick_{false};
+ bool use_fixed_pick_orientation_{true};
+ geometry_msgs::msg::Quaternion fixed_pick_orientation_;
+ bool execute_enabled_{false};
+ bool execute_pick_only_{false};
+ bool continue_on_failure_{true};
+
+ bool publish_static_env_obstacles_{true};
+ std::string static_env_sdf_path_;
+ double static_env_offset_x_{0.0};
+ double static_env_offset_y_{-1.70};
+ double static_env_offset_z_{-1.0};
+ double static_env_offset_roll_{0.0};
+ double static_env_offset_pitch_{0.0};
+ double static_env_offset_yaw_{0.0};
+ bool table_proxy_enabled_{true};
+ double table_proxy_size_x_{1.50};
+ double table_proxy_size_y_{0.80};
+ double table_proxy_size_z_{0.08};
+ double table_proxy_center_z_offset_{1.0};
+
+ bool snapshot_locked_{false};
+ bool sequence_completed_once_{false};
+ bool planning_busy_{false};
+ State state_{State::IDLE};
+ };
+
+} // namespace pick_place
+
+#endif // PICK_PLACE__PICK_PLACE_MULTI_EXECUTOR_HPP_
diff --git a/src/pick_place/launch/pick_place_multi_object.launch.py b/src/pick_place/launch/pick_place_multi_object.launch.py
new file mode 100644
index 0000000..29e85d7
--- /dev/null
+++ b/src/pick_place/launch/pick_place_multi_object.launch.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+from launch import LaunchDescription
+from launch.actions import DeclareLaunchArgument
+from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
+from launch_ros.actions import Node
+from launch_ros.substitutions import FindPackageShare
+
+
+def generate_launch_description() -> LaunchDescription:
+ params_file_arg = DeclareLaunchArgument(
+ "params_file",
+ default_value=PathJoinSubstitution(
+ [FindPackageShare("pick_place"), "config", "params.yaml"]
+ ),
+ description="Path to pick_place params yaml",
+ )
+
+ multi_executor_node = Node(
+ package="pick_place",
+ executable="pick_place_multi_executor_node",
+ name="pick_place_multi_executor",
+ output="screen",
+ parameters=[LaunchConfiguration("params_file")],
+ )
+
+ return LaunchDescription([params_file_arg, multi_executor_node])
diff --git a/src/pick_place/src/pick_place_multi_executor.cpp b/src/pick_place/src/pick_place_multi_executor.cpp
new file mode 100644
index 0000000..e4ba8f8
--- /dev/null
+++ b/src/pick_place/src/pick_place_multi_executor.cpp
@@ -0,0 +1,914 @@
+#include "pick_place/pick_place_multi_executor.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include