From dce2e498393b67ea8af82fa6867932d94a8a530b Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 15:44:20 +0000 Subject: [PATCH 01/80] Transition Hardware package to igvc_hardware --- src/{Hardware => igvc_hardware}/CMakeLists.txt | 2 +- src/{Hardware => igvc_hardware}/config/bot_controllers.yaml | 0 .../include/igvc_hardware}/bot_system.hpp | 0 src/{Hardware => igvc_hardware}/launch/bot.launch.py | 0 src/{Hardware => igvc_hardware}/package.xml | 0 src/{Hardware => igvc_hardware}/rrc_igvc_bot.xml | 0 src/{Hardware => igvc_hardware}/src/bot_system.cpp | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename src/{Hardware => igvc_hardware}/CMakeLists.txt (95%) rename src/{Hardware => igvc_hardware}/config/bot_controllers.yaml (100%) rename src/{Hardware/src/include => igvc_hardware/include/igvc_hardware}/bot_system.hpp (100%) rename src/{Hardware => igvc_hardware}/launch/bot.launch.py (100%) rename src/{Hardware => igvc_hardware}/package.xml (100%) rename src/{Hardware => igvc_hardware}/rrc_igvc_bot.xml (100%) rename src/{Hardware => igvc_hardware}/src/bot_system.cpp (100%) diff --git a/src/Hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt similarity index 95% rename from src/Hardware/CMakeLists.txt rename to src/igvc_hardware/CMakeLists.txt index 25388161..cfc9b3ea 100644 --- a/src/Hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(hardware) +project(igvc_hardware) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) diff --git a/src/Hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml similarity index 100% rename from src/Hardware/config/bot_controllers.yaml rename to src/igvc_hardware/config/bot_controllers.yaml diff --git a/src/Hardware/src/include/bot_system.hpp b/src/igvc_hardware/include/igvc_hardware/bot_system.hpp similarity index 100% rename from src/Hardware/src/include/bot_system.hpp rename to src/igvc_hardware/include/igvc_hardware/bot_system.hpp diff --git a/src/Hardware/launch/bot.launch.py b/src/igvc_hardware/launch/bot.launch.py similarity index 100% rename from src/Hardware/launch/bot.launch.py rename to src/igvc_hardware/launch/bot.launch.py diff --git a/src/Hardware/package.xml b/src/igvc_hardware/package.xml similarity index 100% rename from src/Hardware/package.xml rename to src/igvc_hardware/package.xml diff --git a/src/Hardware/rrc_igvc_bot.xml b/src/igvc_hardware/rrc_igvc_bot.xml similarity index 100% rename from src/Hardware/rrc_igvc_bot.xml rename to src/igvc_hardware/rrc_igvc_bot.xml diff --git a/src/Hardware/src/bot_system.cpp b/src/igvc_hardware/src/bot_system.cpp similarity index 100% rename from src/Hardware/src/bot_system.cpp rename to src/igvc_hardware/src/bot_system.cpp From 766d2329083f757c73aff4ce08a35735694cce05 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 15:48:38 +0000 Subject: [PATCH 02/80] Create nav, gazebo, slam packages --- src/igvc_gazebo/CMakeLists.txt | 26 ++++++++++++++++++++++++++ src/igvc_gazebo/package.xml | 18 ++++++++++++++++++ src/igvc_nav/CMakeLists.txt | 26 ++++++++++++++++++++++++++ src/igvc_nav/package.xml | 18 ++++++++++++++++++ src/igvc_slam/CMakeLists.txt | 26 ++++++++++++++++++++++++++ src/igvc_slam/package.xml | 18 ++++++++++++++++++ 6 files changed, 132 insertions(+) create mode 100644 src/igvc_gazebo/CMakeLists.txt create mode 100644 src/igvc_gazebo/package.xml create mode 100644 src/igvc_nav/CMakeLists.txt create mode 100644 src/igvc_nav/package.xml create mode 100644 src/igvc_slam/CMakeLists.txt create mode 100644 src/igvc_slam/package.xml diff --git a/src/igvc_gazebo/CMakeLists.txt b/src/igvc_gazebo/CMakeLists.txt new file mode 100644 index 00000000..f81f4de3 --- /dev/null +++ b/src/igvc_gazebo/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.8) +project(igvc_gazebo) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/igvc_gazebo/package.xml b/src/igvc_gazebo/package.xml new file mode 100644 index 00000000..9060e469 --- /dev/null +++ b/src/igvc_gazebo/package.xml @@ -0,0 +1,18 @@ + + + + igvc_gazebo + 0.0.0 + Simulation utilities for the 2026 IGVC Robot + Zane Brotherton + MIT + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/igvc_nav/CMakeLists.txt b/src/igvc_nav/CMakeLists.txt new file mode 100644 index 00000000..87b230d4 --- /dev/null +++ b/src/igvc_nav/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.8) +project(igvc_nav) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/igvc_nav/package.xml b/src/igvc_nav/package.xml new file mode 100644 index 00000000..dcb0a075 --- /dev/null +++ b/src/igvc_nav/package.xml @@ -0,0 +1,18 @@ + + + + igvc_nav + 0.0.0 + Nav2 utilization for the 2026 IGVC Robot + Zane Brotherton + MIT + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/igvc_slam/CMakeLists.txt b/src/igvc_slam/CMakeLists.txt new file mode 100644 index 00000000..43dca0aa --- /dev/null +++ b/src/igvc_slam/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.8) +project(igvc_slam) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/igvc_slam/package.xml b/src/igvc_slam/package.xml new file mode 100644 index 00000000..5def1020 --- /dev/null +++ b/src/igvc_slam/package.xml @@ -0,0 +1,18 @@ + + + + igvc_slam + 0.0.0 + SLAM utilization for the 2026 IGVC Robot + Zane Brotherton + MIT + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + From 87d2709fab746dac69bcd044fec2b6141a19bb00 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 15:56:00 +0000 Subject: [PATCH 03/80] Create description package --- src/igvc_description/CMakeLists.txt | 26 ++++++++++++++++++++++++++ src/igvc_description/package.xml | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/igvc_description/CMakeLists.txt create mode 100644 src/igvc_description/package.xml diff --git a/src/igvc_description/CMakeLists.txt b/src/igvc_description/CMakeLists.txt new file mode 100644 index 00000000..889469fe --- /dev/null +++ b/src/igvc_description/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.8) +project(igvc_description) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/igvc_description/package.xml b/src/igvc_description/package.xml new file mode 100644 index 00000000..5e9d5111 --- /dev/null +++ b/src/igvc_description/package.xml @@ -0,0 +1,18 @@ + + + + igvc_description + 0.0.0 + URDF and state publisher for the 2026 IGVC Robot + Zane Brotherton + MIT + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + From 074cc843bca2c6492a6b1099c3c739cb5c605adc Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:07:22 +0000 Subject: [PATCH 04/80] Modify igvc_description CMakeLists --- src/igvc_description/CMakeLists.txt | 52 +++++++++++++++++++++-------- src/igvc_hardware/CMakeLists.txt | 1 - 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/igvc_description/CMakeLists.txt b/src/igvc_description/CMakeLists.txt index 889469fe..bb8abc25 100644 --- a/src/igvc_description/CMakeLists.txt +++ b/src/igvc_description/CMakeLists.txt @@ -1,26 +1,50 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.16) project(igvc_description) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) + add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) + +# Define the library target +add_library( + ${PROJECT_NAME}_library + SHARED + src/bot_system.cpp +) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) + +install( + DIRECTORY meshes + DESTINATION share/${PROJECT_NAME} +) + +install( + DIRECTORY urdf + DESTINATION share/${PROJECT_NAME} +) + +install( + DIRECTORY rviz + DESTINATION share/${PROJECT_NAME} +) if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # comment the line when a copyright and license is added to all source files - set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # comment the line when this package is in a git repo and when - # a copyright and license is added to all source files - set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() endif() ament_package() diff --git a/src/igvc_hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt index cfc9b3ea..b01740e5 100644 --- a/src/igvc_hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -7,7 +7,6 @@ endif() # find dependencies find_package(ament_cmake REQUIRED) - find_package(controller_manager REQUIRED) find_package(hardware_interface REQUIRED) find_package(pluginlib REQUIRED) From 73d4f9cd1259ef39d42794099e08e55b049068d5 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:07:44 +0000 Subject: [PATCH 05/80] Add oak description to decription package --- src/igvc_description/package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/igvc_description/package.xml b/src/igvc_description/package.xml index 5e9d5111..0913f585 100644 --- a/src/igvc_description/package.xml +++ b/src/igvc_description/package.xml @@ -12,6 +12,8 @@ ament_lint_auto ament_lint_common + depthai_descriptions + ament_cmake From 12829b70ccd804ae3bff3ac375c31b4918bb5c75 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:30:58 +0000 Subject: [PATCH 06/80] Remove placeholder uml --- src/igvc_hardware/rrc_igvc_bot.xml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/igvc_hardware/rrc_igvc_bot.xml diff --git a/src/igvc_hardware/rrc_igvc_bot.xml b/src/igvc_hardware/rrc_igvc_bot.xml deleted file mode 100644 index 3cd90c43..00000000 --- a/src/igvc_hardware/rrc_igvc_bot.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - RPI Robotics Club's robot for the 2025 Intelligent Ground Vehicle Competition - - - \ No newline at end of file From eb19fb1bff472a9d8b977e5691214002d95a1673 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:32:50 +0000 Subject: [PATCH 07/80] implement navigation package --- src/igvc_nav/CMakeLists.txt | 42 ++- src/igvc_nav/config/nav2_params.yaml | 450 +++++++++++++++++++++++++ src/igvc_nav/launch/igvc_nav.launch.py | 25 ++ src/igvc_nav/package.xml | 3 + 4 files changed, 506 insertions(+), 14 deletions(-) create mode 100644 src/igvc_nav/config/nav2_params.yaml create mode 100644 src/igvc_nav/launch/igvc_nav.launch.py diff --git a/src/igvc_nav/CMakeLists.txt b/src/igvc_nav/CMakeLists.txt index 87b230d4..a0204b67 100644 --- a/src/igvc_nav/CMakeLists.txt +++ b/src/igvc_nav/CMakeLists.txt @@ -1,26 +1,40 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.16) project(igvc_nav) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) + add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) + +# Define the library target +add_library( + ${PROJECT_NAME}_library + SHARED + src/bot_system.cpp +) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) + +install( + DIRECTORY config + DESTINATION share/${PROJECT_NAME} +) if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # comment the line when a copyright and license is added to all source files - set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # comment the line when this package is in a git repo and when - # a copyright and license is added to all source files - set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() endif() ament_package() diff --git a/src/igvc_nav/config/nav2_params.yaml b/src/igvc_nav/config/nav2_params.yaml new file mode 100644 index 00000000..22e5f6db --- /dev/null +++ b/src/igvc_nav/config/nav2_params.yaml @@ -0,0 +1,450 @@ +amcl: + ros__parameters: + alpha1: 0.2 + alpha2: 0.2 + alpha3: 0.2 + alpha4: 0.2 + alpha5: 0.2 + base_frame_id: "base_footprint" + beam_skip_distance: 0.5 + beam_skip_error_threshold: 0.9 + beam_skip_threshold: 0.3 + do_beamskip: false + global_frame_id: "map" + lambda_short: 0.1 + laser_likelihood_max_dist: 2.0 + laser_max_range: 100.0 + laser_min_range: -1.0 + laser_model_type: "likelihood_field" + max_beams: 60 + max_particles: 2000 + min_particles: 500 + odom_frame_id: "odom" + pf_err: 0.05 + pf_z: 0.99 + recovery_alpha_fast: 0.0 + recovery_alpha_slow: 0.0 + resample_interval: 1 + robot_model_type: "nav2_amcl::DifferentialMotionModel" + save_pose_rate: 0.5 + sigma_hit: 0.2 + tf_broadcast: true + transform_tolerance: 1.0 + update_min_a: 0.2 + update_min_d: 0.25 + z_hit: 0.5 + z_max: 0.05 + z_rand: 0.5 + z_short: 0.05 + scan_topic: scan + +bt_navigator: + ros__parameters: + global_frame: map + robot_base_frame: base_link + odom_topic: /odom + bt_loop_duration: 10 + default_server_timeout: 20 + wait_for_service_timeout: 1000 + action_server_result_timeout: 900.0 + navigators: ["navigate_to_pose", "navigate_through_poses"] + navigate_to_pose: + plugin: "nav2_bt_navigator::NavigateToPoseNavigator" + navigate_through_poses: + plugin: "nav2_bt_navigator::NavigateThroughPosesNavigator" + # 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults: + # nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml + # nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml + # They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2. + + # plugin_lib_names is used to add custom BT plugins to the executor (vector of strings). + # Built-in plugins are added automatically + # plugin_lib_names: [] + + error_code_names: + - compute_path_error_code + - follow_path_error_code + +controller_server: + ros__parameters: + controller_frequency: 20.0 + costmap_update_timeout: 0.30 + min_x_velocity_threshold: 0.5 + min_y_velocity_threshold: 0.0 + min_theta_velocity_threshold: 0.01 + failure_tolerance: 0.3 + progress_checker_plugins: ["progress_checker"] + goal_checker_plugins: ["general_goal_checker"] # "precise_goal_checker" + controller_plugins: ["FollowPath"] + use_realtime_priority: false + + # Progress checker parameters + progress_checker: + plugin: "nav2_controller::SimpleProgressChecker" + required_movement_radius: 0.5 + movement_time_allowance: 10.0 + # Goal checker parameters + #precise_goal_checker: + # plugin: "nav2_controller::SimpleGoalChecker" + # xy_goal_tolerance: 0.25 + # yaw_goal_tolerance: 0.25 + # stateful: True + general_goal_checker: + stateful: True + plugin: "nav2_controller::SimpleGoalChecker" + xy_goal_tolerance: 0.25 + yaw_goal_tolerance: 0.25 + FollowPath: + plugin: "nav2_mppi_controller::MPPIController" + time_steps: 56 + model_dt: 0.05 + batch_size: 2000 + ax_max: 6.0 + ax_min: -6.0 + ay_max: 0.0 + az_max: 3.5 + vx_std: 0.1 + vy_std: 0.0 + wz_std: 0.1 + vx_max: 3.0 + vx_min: -3.0 + vy_max: 0.0 + wz_max: 1.9 + iteration_count: 1 + prune_distance: 1.7 + transform_tolerance: 0.1 + temperature: 0.3 + gamma: 0.015 + motion_model: "DiffDrive" + visualize: false + regenerate_noises: true + TrajectoryVisualizer: + trajectory_step: 5 + time_step: 3 + AckermannConstraints: + min_turning_r: 0.2 + critics: [ + "ConstraintCritic", "CostCritic", "GoalCritic", + "GoalAngleCritic", "PathAlignCritic", "PathFollowCritic", + "PathAngleCritic", "PreferForwardCritic", "VelocityDeadbandCritic"] + ConstraintCritic: + enabled: true + cost_power: 1 + cost_weight: 4.0 + GoalCritic: + enabled: true + cost_power: 1 + cost_weight: 5.0 + threshold_to_consider: 1.4 + GoalAngleCritic: + enabled: true + cost_power: 1 + cost_weight: 3.0 + threshold_to_consider: 0.5 + PreferForwardCritic: + enabled: true + cost_power: 1 + cost_weight: 5.0 + threshold_to_consider: 0.5 + CostCritic: + enabled: true + cost_power: 1 + cost_weight: 3.81 + near_collision_cost: 253 + critical_cost: 300.0 + consider_footprint: false + collision_cost: 1000000.0 + near_goal_distance: 1.0 + trajectory_point_step: 2 + PathAlignCritic: + enabled: true + cost_power: 1 + cost_weight: 14.0 + max_path_occupancy_ratio: 0.05 + trajectory_point_step: 4 + threshold_to_consider: 0.5 + offset_from_furthest: 20 + use_path_orientations: false + PathFollowCritic: + enabled: true + cost_power: 1 + cost_weight: 5.0 + offset_from_furthest: 5 + threshold_to_consider: 1.4 + PathAngleCritic: + enabled: true + cost_power: 1 + cost_weight: 2.0 + offset_from_furthest: 4 + threshold_to_consider: 0.5 + max_angle_to_furthest: 1.0 + mode: 0 + VelocityDeadbandCritic: + enabled: true + cost_weight: 35.0 + cost_power: 1 + deadband_velocities: [0.25, 0.0, 0.25] + # TwirlingCritic: + # enabled: true + # twirling_cost_power: 1 + # twirling_cost_weight: 10.0 + +local_costmap: + local_costmap: + ros__parameters: + update_frequency: 5.0 + publish_frequency: 2.0 + global_frame: odom + robot_base_frame: base_link + rolling_window: true + width: 3 + height: 3 + resolution: 0.05 + robot_radius: 0.22 + plugins: ["voxel_layer", "inflation_layer"] + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 3.0 + inflation_radius: 0.70 + voxel_layer: + plugin: "nav2_costmap_2d::VoxelLayer" + enabled: True + publish_voxel_map: True + origin_z: 0.0 + z_resolution: 0.05 + z_voxels: 16 + max_obstacle_height: 2.0 + mark_threshold: 0 + observation_sources: rtabmap + rtabmap: + topic: /rtabmap/odom_local_scan_map + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "PointCloud2" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + always_send_full_costmap: True + +global_costmap: + global_costmap: + ros__parameters: + update_frequency: 1.0 + publish_frequency: 1.0 + global_frame: map + robot_base_frame: base_link + robot_radius: 0.22 + resolution: 0.05 + track_unknown_space: true + plugins: ["static_layer", "obstacle_layer", "inflation_layer"] + obstacle_layer: + plugin: "nav2_costmap_2d::ObstacleLayer" + enabled: True + observation_sources: scan + scan: + topic: /scan + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "LaserScan" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 3.0 + inflation_radius: 0.7 + always_send_full_costmap: True + +# The yaml_filename does not need to be specified since it going to be set by defaults in launch. +# If you'd rather set it in the yaml, remove the default "map" value in the tb3_simulation_launch.py +# file & provide full path to map below. If CLI map configuration or launch default is provided, that will be used. +# map_server: +# ros__parameters: +# yaml_filename: "" + +map_saver: + ros__parameters: + save_map_timeout: 5.0 + free_thresh_default: 0.25 + occupied_thresh_default: 0.65 + map_subscribe_transient_local: True + +planner_server: + ros__parameters: + expected_planner_frequency: 20.0 + planner_plugins: ["GridBased"] + costmap_update_timeout: 1.0 + GridBased: + plugin: "nav2_navfn_planner::NavfnPlanner" + tolerance: 0.5 + use_astar: false + allow_unknown: true + +smoother_server: + ros__parameters: + smoother_plugins: ["simple_smoother"] + simple_smoother: + plugin: "nav2_smoother::SimpleSmoother" + tolerance: 1.0e-10 + max_its: 1000 + do_refinement: True + +behavior_server: + ros__parameters: + local_costmap_topic: local_costmap/costmap_raw + global_costmap_topic: global_costmap/costmap_raw + local_footprint_topic: local_costmap/published_footprint + global_footprint_topic: global_costmap/published_footprint + cycle_frequency: 10.0 + behavior_plugins: ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"] + spin: + plugin: "nav2_behaviors::Spin" + backup: + plugin: "nav2_behaviors::BackUp" + drive_on_heading: + plugin: "nav2_behaviors::DriveOnHeading" + wait: + plugin: "nav2_behaviors::Wait" + assisted_teleop: + plugin: "nav2_behaviors::AssistedTeleop" + local_frame: odom + global_frame: map + robot_base_frame: base_link + transform_tolerance: 0.1 + simulate_ahead_time: 2.0 + max_rotational_vel: 1.0 + min_rotational_vel: 0.4 + rotational_acc_lim: 3.2 + +waypoint_follower: + ros__parameters: + loop_rate: 20 + stop_on_failure: false + action_server_result_timeout: 900.0 + waypoint_task_executor_plugin: "wait_at_waypoint" + wait_at_waypoint: + plugin: "nav2_waypoint_follower::WaitAtWaypoint" + enabled: True + waypoint_pause_duration: 200 + +velocity_smoother: + ros__parameters: + smoothing_frequency: 20.0 + scale_velocities: False + feedback: "OPEN_LOOP" + max_velocity: [1.0, 0.0, 2.0] + min_velocity: [-1.0, 0.0, -2.0] + max_accel: [3.0, 0.0, 3.2] + max_decel: [-3.0, 0.0, -3.2] + odom_topic: "odom" + odom_duration: 0.1 + deadband_velocity: [0.5, 0.0, 0.5] + velocity_timeout: 1.0 + +collision_monitor: + ros__parameters: + base_frame_id: "base_footprint" + odom_frame_id: "odom" + cmd_vel_in_topic: "cmd_vel_smoothed" + cmd_vel_out_topic: "cmd_vel" + state_topic: "collision_monitor_state" + transform_tolerance: 0.2 + source_timeout: 1.0 + base_shift_correction: True + stop_pub_timeout: 2.0 + # Polygons represent zone around the robot for "stop", "slowdown" and "limit" action types, + # and robot footprint for "approach" action type. + polygons: ["FootprintApproach"] + FootprintApproach: + type: "polygon" + action_type: "approach" + footprint_topic: "/local_costmap/published_footprint" + time_before_collision: 1.2 + simulation_time_step: 0.1 + min_points: 6 + visualize: False + enabled: True + observation_sources: ["scan"] + scan: + type: "scan" + topic: "scan" + min_height: 0.15 + max_height: 2.0 + enabled: True + +docking_server: + ros__parameters: + controller_frequency: 50.0 + initial_perception_timeout: 5.0 + wait_charge_timeout: 5.0 + dock_approach_timeout: 30.0 + undock_linear_tolerance: 0.05 + undock_angular_tolerance: 0.1 + max_retries: 3 + base_frame: "base_link" + fixed_frame: "odom" + dock_backwards: false + dock_prestaging_tolerance: 0.5 + + # Types of docks + dock_plugins: ['simple_charging_dock'] + simple_charging_dock: + plugin: 'opennav_docking::SimpleChargingDock' + docking_threshold: 0.05 + staging_x_offset: -0.7 + use_external_detection_pose: true + use_battery_status: false # true + use_stall_detection: false # true + + external_detection_timeout: 1.0 + external_detection_translation_x: -0.18 + external_detection_translation_y: 0.0 + external_detection_rotation_roll: -1.57 + external_detection_rotation_pitch: -1.57 + external_detection_rotation_yaw: 0.0 + filter_coef: 0.1 + + # Dock instances + # The following example illustrates configuring dock instances. + # docks: ['home_dock'] # Input your docks here + # home_dock: + # type: 'simple_charging_dock' + # frame: map + # pose: [0.0, 0.0, 0.0] + + controller: + k_phi: 3.0 + k_delta: 2.0 + v_linear_min: 0.15 + v_linear_max: 0.15 + use_collision_detection: true + costmap_topic: "local_costmap/costmap_raw" + footprint_topic: "local_costmap/published_footprint" + transform_tolerance: 0.1 + projection_time: 5.0 + simulation_step: 0.1 + dock_collision_threshold: 0.3 + +loopback_simulator: + ros__parameters: + base_frame_id: "base_footprint" + odom_frame_id: "odom" + map_frame_id: "map" + scan_frame_id: "base_scan" # tb4_loopback_simulator.launch.py remaps to 'rplidar_link' + update_duration: 0.02 + scan_range_min: 0.05 + scan_range_max: 30.0 + scan_angle_min: -3.1415 + scan_angle_max: 3.1415 + scan_angle_increment: 0.02617 + scan_use_inf: true diff --git a/src/igvc_nav/launch/igvc_nav.launch.py b/src/igvc_nav/launch/igvc_nav.launch.py new file mode 100644 index 00000000..3658d5e1 --- /dev/null +++ b/src/igvc_nav/launch/igvc_nav.launch.py @@ -0,0 +1,25 @@ +import os + +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + +def generate_launch_description(): + nav2_package = get_package_share_directory('nav2_bringup') + igvc_nav_package = get_package_share_directory('igvc_nav') + + nav2_launch_path = os.path.join(nav2_package, 'launch', 'navigation_launch.py') + config_path = os.path.join(igvc_nav_package, 'config', 'nav2_params.yaml') + + nav2 = IncludeLaunchDescription( + PythonLaunchDescriptionSource(nav2_launch_path), + launch_arguments={ + 'params_file' : config_path + }.items() + ) + + return LaunchDescription([ + nav2 + ]) \ No newline at end of file diff --git a/src/igvc_nav/package.xml b/src/igvc_nav/package.xml index dcb0a075..4b915d5e 100644 --- a/src/igvc_nav/package.xml +++ b/src/igvc_nav/package.xml @@ -12,6 +12,9 @@ ament_lint_auto ament_lint_common + nav2_bringup + navigation2 + ament_cmake From d353d4a5fafee0febbbb5150b143462e34510db3 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:47:11 +0000 Subject: [PATCH 08/80] Implement slam package, TODO tune for igvc --- src/igvc_slam/CMakeLists.txt | 39 +++++---- src/igvc_slam/launch/rtabmap.launch.py | 97 ++++++++++++++++++++++ src/igvc_slam/launch/sim_rtabmap.launch.py | 65 +++++++++++++++ src/igvc_slam/package.xml | 7 ++ 4 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 src/igvc_slam/launch/rtabmap.launch.py create mode 100644 src/igvc_slam/launch/sim_rtabmap.launch.py diff --git a/src/igvc_slam/CMakeLists.txt b/src/igvc_slam/CMakeLists.txt index 43dca0aa..ea11ae89 100644 --- a/src/igvc_slam/CMakeLists.txt +++ b/src/igvc_slam/CMakeLists.txt @@ -1,26 +1,35 @@ -cmake_minimum_required(VERSION 3.8) -project(igvc_slam) +cmake_minimum_required(VERSION 3.16) +project(igvc_description) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) + add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) + +# Define the library target +add_library( + ${PROJECT_NAME}_library + SHARED + src/bot_system.cpp +) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # comment the line when a copyright and license is added to all source files - set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # comment the line when this package is in a git repo and when - # a copyright and license is added to all source files - set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() endif() ament_package() diff --git a/src/igvc_slam/launch/rtabmap.launch.py b/src/igvc_slam/launch/rtabmap.launch.py new file mode 100644 index 00000000..ab23c4e9 --- /dev/null +++ b/src/igvc_slam/launch/rtabmap.launch.py @@ -0,0 +1,97 @@ +import os + +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.actions import Node + +import xacro + +def generate_launch_description(): + rtabmap_package = get_package_share_directory('rtabmap_launch') + realsense_package = get_package_share_directory('realsense2_camera') + + rtabmap_launch_path = os.path.join(rtabmap_package, 'launch', 'rtabmap.launch.py') + realsense_launch_path = os.path.join(realsense_package, 'launch', 'rs_launch.py') + urdf_path = os.path.join(get_package_share_directory('osr_slam'), 'urdf', 'osr.urdf.xacro') + + xacro_doc = xacro.parse(open(urdf_path)) + xacro.process_doc(xacro_doc) + + robot_state_publisher = Node( + package='robot_state_publisher', + executable='robot_state_publisher', + output='screen', + parameters=[{'robot_description': xacro_doc.toxml()}] + ) + + imu_filter = Node( + package='imu_filter_madgwick', + executable='imu_filter_madgwick_node', + output='screen', + remappings=[ + ('/imu/data_raw', '/camera/camera/imu'), + ('imu/data', '/rtabmap/imu') + ], + parameters=[ + {'use_mag' : False}, + {'publish_tf' : True}, + {'fixed_frame' : 'camera_link'} + ] + ) + + rtabmap = IncludeLaunchDescription( + PythonLaunchDescriptionSource(rtabmap_launch_path), + launch_arguments={ + 'args' : '--delete_db_on_start', + 'depth_topic' : '/camera/camera/aligned_depth_to_color/image_raw', + 'rgb_topic' : '/camera/camera/color/image_raw', + 'camera_info_topic' : '/camera/camera/color/camera_info', + 'frame_id' : 'base_footprint', + 'publish_tf_odom' : 'true', + 'odom_topic' : '/odom', + 'odom_frame_id' : 'odom', + 'approx_sync' : 'true', + 'rgbd_sync' : 'true', + 'approx_rgbd_sync' : 'true', + 'subscribe_rgbd' : 'true', + 'visual_odometry' : 'true', + 'qos' : '1', + 'approx_sync_max_interval' : '0.01', + 'sync_queue_size' : '50', + 'topic_queue_size' : '50', + 'wait_for_transform' : '0.4', + 'imu_topic' : '/rtabmap/imu', + 'wait_imu_to_init' : 'true', + 'rtabmap_viz' : 'true', + 'map_topic' : '/map' + }.items() + ) + + realsense = IncludeLaunchDescription( + PythonLaunchDescriptionSource(realsense_launch_path), + launch_arguments={ + 'enable_color' : 'true', + 'enable_depth' : 'true', + 'align_depth.enable' : 'true', + 'pointcloud.enable' : 'false', + 'enable_sync' : 'false', + 'unite_imu_method' : '2', + 'enable_gyro' : 'true', + 'enable_accel' : 'true', + 'color_fps' : '60', + 'depth_fps' : '60', + 'gyro_fps' : '200', + 'accel_fps' : '63', + 'publish_tf' : 'true' + }.items() + ) + + return LaunchDescription([ + robot_state_publisher, + imu_filter, + rtabmap, + realsense + ]) diff --git a/src/igvc_slam/launch/sim_rtabmap.launch.py b/src/igvc_slam/launch/sim_rtabmap.launch.py new file mode 100644 index 00000000..a7ba5430 --- /dev/null +++ b/src/igvc_slam/launch/sim_rtabmap.launch.py @@ -0,0 +1,65 @@ +import os + +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + +from launch_ros.actions import Node + +def generate_launch_description(): + imu_filter = Node( + package='imu_filter_madgwick', + executable='imu_filter_madgwick_node', + output='screen', + remappings=[ + ('/imu/data_raw', '/imu_plugin/out'), + ('imu/data', '/rtabmap/imu') + ], + parameters=[ + {'use_mag' : False}, + {'publish_tf' : False}, + {'fixed_frame' : 'imu_link'} + ] + ) + + rtabmap_package = get_package_share_directory('rtabmap_launch') + + rtabmap_launch_path = os.path.join(rtabmap_package, 'launch', 'rtabmap.launch.py') + + rtabmap = IncludeLaunchDescription( + PythonLaunchDescriptionSource(rtabmap_launch_path), + launch_arguments={ + 'args' : '--delete_db_on_start', + 'depth_topic' : '/camera/camera/depth/image_rect_raw', + 'rgb_topic' : '/camera/camera/color/image_raw', + 'camera_info_topic' : '/camera/camera/depth/camera_info', + 'approx_sync' : 'true', + 'frame_id' : 'base_footprint', + 'log_level' : 'debug', + 'publish_tf_odom' : 'false', + 'odom_topic' : '/odom', + 'odom_frame_id' : 'odom', + 'sync_queue_size' : '10', #TODO verify + 'topic_queue_size' : '10', #TODO verify + 'rgbd_sync' : 'true', + 'approx_rgbd_sync' : 'true', + 'subscribe_depth' : 'false', + 'subscribe_rgbd' : 'true', + 'visual_odometry' : 'true', + 'approx_sync_max_interval' : '0.01', + 'qos' : '1', + 'rviz' : 'true', + 'use_sim_time' : 'true', + 'wait_for_transform' : '0.4', + 'imu_topic' : '/rtabmap/imu', + 'wait_imu_to_init' : 'true', + 'map_topic' : '/map' + }.items() + ) + + return LaunchDescription([ + imu_filter, + rtabmap + ]) \ No newline at end of file diff --git a/src/igvc_slam/package.xml b/src/igvc_slam/package.xml index 5def1020..4b9eec59 100644 --- a/src/igvc_slam/package.xml +++ b/src/igvc_slam/package.xml @@ -12,6 +12,13 @@ ament_lint_auto ament_lint_common + librealsense2 + realsense2_camera + realsense2_camera_msgs + rtabmap_ros + realsense2_description + imu_filter_madgwick + ament_cmake From 21ff274264d17f6003ba3b646539741f014c61d8 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Mon, 2 Feb 2026 17:49:48 +0000 Subject: [PATCH 09/80] Remove redundant CMake library targets --- src/igvc_description/CMakeLists.txt | 7 ------- src/igvc_nav/CMakeLists.txt | 7 ------- src/igvc_slam/CMakeLists.txt | 7 ------- 3 files changed, 21 deletions(-) diff --git a/src/igvc_description/CMakeLists.txt b/src/igvc_description/CMakeLists.txt index bb8abc25..68df5315 100644 --- a/src/igvc_description/CMakeLists.txt +++ b/src/igvc_description/CMakeLists.txt @@ -8,13 +8,6 @@ endif() # find dependencies find_package(ament_cmake REQUIRED) -# Define the library target -add_library( - ${PROJECT_NAME}_library - SHARED - src/bot_system.cpp -) - install( DIRECTORY launch DESTINATION share/${PROJECT_NAME} diff --git a/src/igvc_nav/CMakeLists.txt b/src/igvc_nav/CMakeLists.txt index a0204b67..3ce78873 100644 --- a/src/igvc_nav/CMakeLists.txt +++ b/src/igvc_nav/CMakeLists.txt @@ -8,13 +8,6 @@ endif() # find dependencies find_package(ament_cmake REQUIRED) -# Define the library target -add_library( - ${PROJECT_NAME}_library - SHARED - src/bot_system.cpp -) - install( DIRECTORY launch DESTINATION share/${PROJECT_NAME} diff --git a/src/igvc_slam/CMakeLists.txt b/src/igvc_slam/CMakeLists.txt index ea11ae89..d6559773 100644 --- a/src/igvc_slam/CMakeLists.txt +++ b/src/igvc_slam/CMakeLists.txt @@ -8,13 +8,6 @@ endif() # find dependencies find_package(ament_cmake REQUIRED) -# Define the library target -add_library( - ${PROJECT_NAME}_library - SHARED - src/bot_system.cpp -) - install( DIRECTORY launch DESTINATION share/${PROJECT_NAME} From 0250f08e27cdbac25034d43a27a3b32897cce623 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:22:33 +0000 Subject: [PATCH 10/80] Generate low fidelity sim model URDF --- src/igvc_description/assets/part_1.part | 13 ++ src/igvc_description/assets/part_1.stl | Bin 0 -> 10084 bytes src/igvc_description/assets/part_10.part | 13 ++ src/igvc_description/assets/part_10.stl | Bin 0 -> 273684 bytes src/igvc_description/assets/part_2.part | 13 ++ src/igvc_description/assets/part_2.stl | Bin 0 -> 684 bytes src/igvc_description/assets/part_3.part | 13 ++ src/igvc_description/assets/part_3.stl | Bin 0 -> 684 bytes src/igvc_description/assets/part_4.part | 13 ++ src/igvc_description/assets/part_4.stl | Bin 0 -> 684 bytes src/igvc_description/assets/part_5.part | 13 ++ src/igvc_description/assets/part_5.stl | Bin 0 -> 684 bytes src/igvc_description/assets/part_6.part | 13 ++ src/igvc_description/assets/part_6.stl | Bin 0 -> 46284 bytes src/igvc_description/assets/part_7.part | 13 ++ src/igvc_description/assets/part_7.stl | Bin 0 -> 14484 bytes src/igvc_description/assets/part_8.part | 13 ++ src/igvc_description/assets/part_8.stl | Bin 0 -> 14484 bytes src/igvc_description/assets/part_9.part | 13 ++ src/igvc_description/assets/part_9.stl | Bin 0 -> 273684 bytes src/igvc_description/urdf/otr.urdf.xacro | 205 +++++++++++++++++++++++ 21 files changed, 335 insertions(+) create mode 100644 src/igvc_description/assets/part_1.part create mode 100644 src/igvc_description/assets/part_1.stl create mode 100644 src/igvc_description/assets/part_10.part create mode 100644 src/igvc_description/assets/part_10.stl create mode 100644 src/igvc_description/assets/part_2.part create mode 100644 src/igvc_description/assets/part_2.stl create mode 100644 src/igvc_description/assets/part_3.part create mode 100644 src/igvc_description/assets/part_3.stl create mode 100644 src/igvc_description/assets/part_4.part create mode 100644 src/igvc_description/assets/part_4.stl create mode 100644 src/igvc_description/assets/part_5.part create mode 100644 src/igvc_description/assets/part_5.stl create mode 100644 src/igvc_description/assets/part_6.part create mode 100644 src/igvc_description/assets/part_6.stl create mode 100644 src/igvc_description/assets/part_7.part create mode 100644 src/igvc_description/assets/part_7.stl create mode 100644 src/igvc_description/assets/part_8.part create mode 100644 src/igvc_description/assets/part_8.stl create mode 100644 src/igvc_description/assets/part_9.part create mode 100644 src/igvc_description/assets/part_9.stl create mode 100644 src/igvc_description/urdf/otr.urdf.xacro diff --git a/src/igvc_description/assets/part_1.part b/src/igvc_description/assets/part_1.part new file mode 100644 index 00000000..9dfc6004 --- /dev/null +++ b/src/igvc_description/assets/part_1.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "bd8c1cbb3cbd66454183450f", + "documentMicroversion": "409d4f186d830d8c9f9566b0", + "elementId": "1144a2613502fe7c1b0c9eab", + "fullConfiguration": "default", + "id": "MxrNMet2IdPDL1tpK", + "isStandardContent": false, + "name": "Part 1 <1>", + "partId": "JTD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_1.stl b/src/igvc_description/assets/part_1.stl new file mode 100644 index 0000000000000000000000000000000000000000..ebdcaaefb2e6c95926b2bca74fbeeef277e84e1c GIT binary patch literal 10084 zcmb`My^drx5QX~*NLE53S>AwzbY~_-LP8J|5Rf3A!bFfFmW%`~$-V>KlM)c{9Hc}@ z1k`uzQ@3p284@gY&*}0x_2aVLcV;e^|9AW*ckRJm|N8#h+jqae|M2$3AHRaX_~gUH z*v*ro#izEiP{6V$Frz*8pO?!AKfF%AV8^rFJSiH|mE}xU1=TzJ77(Ju-M9(Ld z_tmDzedSEX-pzQnXABgL7cgw-88lxrbe@(vW#JzD%4MQ!&k3= zzK0d~zF^sj&0O|lWl1qIoE3=FLHxRMujo+7F8?)#`mrh?*pn4UlRH(0f%JZa1w zJx6g?tvzMssOX|jj?Xrk6r8c)9p__VNddtzz#462rW?RnahDW)1!q=OQCY&UID?ZS z0`)rRf%g&_8r!ancxzTvmNA4?k^N}C0i20%1MKcB9(Dy*vnqDwy#{6?lbb69yAIpo zFr;V#3kwB|O`&}>W@?{vABQ5l32)k06N_rAC$L6yX5s5v_U^4<*-a?wJ9=2 zWOB;F?p2uUsJ?29Q$usw#nVNrklottRG$B zuHm=h^B9qP?pJW>N8&jKe%)uXGRDrcRP%Gx&bQF+=&gpt+C`fo+`1ulvw`Vs4t-*;f2qF9l{u9}}(-JFRG^Sk|N zMV+aNzI~QlPQjK{8~sQYC(Eo&L1r=Ze3h(K;b*N*g^r@K zz-AG{7{&2S%?wPQAi>-c#@Jyqj%VtX<$J}RI-hKemOkexvYmK}fuiwZW<7VXP8CS= zi4|7#L-&>OR7b{GS&oOjD)8Q)_M<|bbNmJ)vF$K5niZ903}aJ-t;c}H&KS8TwjH%b zvm)|MmNAS~ar8V^QMVc~j8TBu$JqIt3i=^MWmRlN--XOXCVAG_8Q$%-?~2MYhP5g( zE9GmyWay_Qu-A}Wj=j6GP!MBQeDwEczqtB{uBQoS67Jp%T%Ix(#a&?ulZ8Ej>u8TL zQ-7n&Zn}fSn-$D(o|*Ap0&j{Ix_Gt6$^v_RHU)vCs54bjvxN-%h+)4}>%FuoP{nAE z#E6&w;x%E;<6e1WqP;XK*R3`xPu!gofEW=y6<}vYTxYv0%nC)GiA^CHF=GGdz9N%Q zB*tz}Ta09(Fl$rP7&?Q*V82>XGaTd|ipo`*F=j=rBWG5HYuf5azbMQihVgLUg*tTUMddiOQL( z0B;J#sH}QRoGKKf&Qt}v-F*sTq|;xzMRNw0@8C^=Dn{SC%#NEO&m-|{?^Nhk`0j&Y z)~1jQMV(m{ex93SR2JAQJQ)x7BdeIXl}v7Cg)MO^0`#3|1v|z#Rz1H_=d3CV9BcN< zH!4i96yvSO@XVsf@8DE4vZ}1W`pblS6HI-#ry@Y_pl4QvYm%ZtA2HG|3bWX6#!`&8 zd6%pT?}b?resbNx_FMZTMeDl#yVia;#R$}&8SFQ5zga;CZ6D-O`O11$5z={=;Ma;+ zg{NaotY;O9QGJ1nV(&*A9h_LtDjXwy(LpPsro=(iIvu;DvV5zpisSbv*1Vj%ycukl ze?sK%ILx?H#;{fewjO8cpnA@1?G2w*gW_0$(X5K=sqN41C`wj|ZAY!qBFrNw*vT@+ zzA3`itEUIP!|8d!vZK~$MNwJ8FziX}@z%3q#&sAE#k_ahs6)R;p{T5iy;j{nGpuN5 z^e0O&1G{7MITg){%BuK0M%jJ2^YMfoHA69)Ksl+f*vBwV6-+R5a}U5Jp6nvU+Z1DB zITL%D6#769qB?zN7&GDCY?Z4YC@QOBE3owy<{Q%MZp>_Cm3x8D{fzVTj0Is+K-O+Y zQO~)t?Wi@H6=YxY1 literal 0 HcmV?d00001 diff --git a/src/igvc_description/assets/part_10.part b/src/igvc_description/assets/part_10.part new file mode 100644 index 00000000..9066a78c --- /dev/null +++ b/src/igvc_description/assets/part_10.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "bd8c1cbb3cbd66454183450f", + "documentMicroversion": "409d4f186d830d8c9f9566b0", + "elementId": "1144a2613502fe7c1b0c9eab", + "fullConfiguration": "default", + "id": "MYLQomwBplolm4iQC", + "isStandardContent": false, + "name": "Part 10 <1>", + "partId": "JiD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_10.stl b/src/igvc_description/assets/part_10.stl new file mode 100644 index 0000000000000000000000000000000000000000..d606c2c60829e772a73f2473ee59c883e1298921 GIT binary patch literal 273684 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", + "partId": "JqD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_2.stl b/src/igvc_description/assets/part_2.stl new file mode 100644 index 0000000000000000000000000000000000000000..3fd1b25f32eaa2e1c2d74bb77e79acd92cf65cc7 GIT binary patch literal 684 zcmb`EF%H5o6hr^e5i!8dExG`0SMI=Nva!O>1-Jr2gaI+LagT", + "partId": "JqH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_3.stl b/src/igvc_description/assets/part_3.stl new file mode 100644 index 0000000000000000000000000000000000000000..37a3c518a7c25b02e2ca8e3ce3af3c67231f0f21 GIT binary patch literal 684 zcmb`CJqp4=5QV2mDHeJU8wrAt>P>$eCIuLq?qQ30mSI~~F!sk8fhla#G_G0h9{QTeGq@MuW52M$6tYoukPd<-T{ht=Xp&7`?6sa Hnv(GbHx0+W literal 0 HcmV?d00001 diff --git a/src/igvc_description/assets/part_4.part b/src/igvc_description/assets/part_4.part new file mode 100644 index 00000000..054d4a8e --- /dev/null +++ b/src/igvc_description/assets/part_4.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "bd8c1cbb3cbd66454183450f", + "documentMicroversion": "409d4f186d830d8c9f9566b0", + "elementId": "1144a2613502fe7c1b0c9eab", + "fullConfiguration": "default", + "id": "MKptlXS3nJCJ+I3yJ", + "isStandardContent": false, + "name": "Part 4 <1>", + "partId": "JqL", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_4.stl b/src/igvc_description/assets/part_4.stl new file mode 100644 index 0000000000000000000000000000000000000000..d82e53713ba368dde90ce1a5a1eb0833c8737e32 GIT binary patch literal 684 zcmb`CJqp4=5QV2mDHeJU8wr9wb{;_lD}O2xoHsJ? zmZUVW;jw(*n^}?nex0N)6yx)uDhKld*S%v|FD@g?vAeC7o0(-(g8ub{f~m)PRZl0w zcEdC^q8q#ZY47up-9|Q8`)x*K$~S2mpDd48J#cvj_xe2;5jL3e3??=1+W*ROdm(S- z6u$|!2pNGX&tSX>S>&nxu#kNd^tQbmOkxD4JcIEjWRaWBc>ms!7`_S5YZ^>>2F3G4 Ez91Laz5oCK literal 0 HcmV?d00001 diff --git a/src/igvc_description/assets/part_5.part b/src/igvc_description/assets/part_5.part new file mode 100644 index 00000000..30bfd4a8 --- /dev/null +++ b/src/igvc_description/assets/part_5.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "bd8c1cbb3cbd66454183450f", + "documentMicroversion": "409d4f186d830d8c9f9566b0", + "elementId": "1144a2613502fe7c1b0c9eab", + "fullConfiguration": "default", + "id": "M1H46S4E9tX06o5C3", + "isStandardContent": false, + "name": "Part 5 <1>", + "partId": "JqP", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_5.stl b/src/igvc_description/assets/part_5.stl new file mode 100644 index 0000000000000000000000000000000000000000..de49db9cca3c22eb9f903f62154254903e2fd49b GIT binary patch literal 684 zcmb`DF%H5o3`IS3L=3QVi!Ol3${o0jH&)oW09QaXVL;4m+@oP+AV1|-uF{c6k-n_= zY$qcBJ(EE@b@90OpT%y2^6Y-_{<6(EOi#^u{#^ICrj)KaOD$iwrhUW}sKP)o9St;6 zzboe<(?;%4;4m!3zNHbXI^e>!Gi+bh!H8JHQp{jR4VUfTUttqc$lMl8KLdtZia9gp t*G1k{?f4FEg1%!fhdnUVQp{l32{~_t8F?#Gc6U3kH26YmfMTc4_yTo&?aKfF literal 0 HcmV?d00001 diff --git a/src/igvc_description/assets/part_6.part b/src/igvc_description/assets/part_6.part new file mode 100644 index 00000000..7f440698 --- /dev/null +++ b/src/igvc_description/assets/part_6.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "bd8c1cbb3cbd66454183450f", + "documentMicroversion": "409d4f186d830d8c9f9566b0", + "elementId": "1144a2613502fe7c1b0c9eab", + "fullConfiguration": "default", + "id": "Md7qHm/FEJqjvMCe6", + "isStandardContent": false, + "name": "Part 6 <1>", + "partId": "JWD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_6.stl b/src/igvc_description/assets/part_6.stl new file mode 100644 index 0000000000000000000000000000000000000000..8eb9ba37d8f0dc5469dc3ade39d661c9907d2fb7 GIT binary patch literal 46284 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", + "partId": "JaD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_7.stl b/src/igvc_description/assets/part_7.stl new file mode 100644 index 0000000000000000000000000000000000000000..3e1661ad1ad6ff8d7a24cf4d84160df99d2d08be GIT binary patch literal 14484 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", + "partId": "JbD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_8.stl b/src/igvc_description/assets/part_8.stl new file mode 100644 index 0000000000000000000000000000000000000000..3a2439546e59e3ed345459c93e3854a483083614 GIT binary patch literal 14484 zcmb`OU#J~d6~@OuK`JBd`J+4d(IjEw;F3hZ3&g+LHi(ZDC_nc`BqE@jgN+JkVY-=fL0~YlqR@b-I{`URm?3t5$z(bQR zX6@hl*6f+td+j}ws`~%_cD^jfYpSYgfAih#&@R(>ulocVFvvRqZ@I zz486YHpW`xPt$6vcFXI>>#gPp1Mq8Fjim%4z2VsV)-}92h=-1D9$&uwgYG$e`ZeR} z10T3TR@&;$>FdV-xe({1+MZ7OtDBV)+9R20kkz(rmqus5@*&UUGY|fE^xSj%u8@_s zI=Jgqrb#_4Q3Z9BtqKVbA1yr+zW|+^?>`LRQ-9Z$J3+=%#lJ5WQI` zp*_;g>iqGWN3UJI!87^8-%pHwd`;NJ^=OQz2CEX>G2CIk34^+thCjZd)_qq@@#&MYBi*d*daUuPS?+7Rni?q+o|U$;GhbV}uQH)Mk~Pd| zTDy1Gc!%3{UgMoVoRzk+yJT(Y`zjOKBi*d*IbY*wV9&%FM~UIAw3R(kYfC?anb02T zW@TgIlq08&A5)H~!&zx78-1ork5`${9_eOfqyCh$jE%KZ&OXCgX)7D4r%R9Rnb02T zW(AEmf9t(IG9bhCd1-D|Yl8E6Ft@T1KuJ z{9`vyJT9Jz%YO=cI4f<1XK>e%dWo!*&>rb#g)#9HZy9@Bj2}P!Y}mtDX)BCZ2mUfZ zB%$sVR6={Cn-yl68^1R4tT5I-`lGOiv(i==+n;%6fauLi3GESY+yvtD;0iOryu=K9 zvogvEX|?jlzuy15FD=N*35}(M_K4?Wt?`SxgjdaSAMt8xq-cufAhZ=KfTjB?6V~gw zN3sTFFb|G+hud`?@y;L4N?X}oGFtk+%7opaBlbu)D|^mI91ZN57;%&s&PrRMvRwKZ z%!KwxH!B+x#~e9r{1|gY9nMNyp$cDmyvl_3NH;58{n%JL=Ik?^mA1l)W$Cd!6WSwH z_07kQw@;qgGe&+olW|_!a>JZT`F+p`aBni$Z~gI1U`FrP0ZDiVmL1ntoH z!VAaRQ}-Qm4SEol6@cm$L`4D~C1?ln)UG?)Hj5s#g#d33!yC9mM_< zhud2&opTL(5EnBUDl*k8h>8R}O3)5s^VVD22XDLR8uTD8YdF;_h>8R}O3)7Ci_act zkKA=tjcZO1;$m(`?W=kPQIUX03EDw?eEYuk?(;ibgC4}i{EC`d^$MaQ0gn>2gLry6 zYJdOYZr7j(aao0{UO`kO;8B8h5Z`}scl(Oj$TjFeTvQxby{KM6R3zY0f_4y(pWo5G zefvJwpa*d=>tj`VMjATFv5tfN$~ASx2@C_y`j1GinA zUA^^I*PsV+x!O{_g0S&weMP4P?I3nuIyXCb;;?JbgSe#) zQG#|5w#KQ`8V9S19M@Ja{+0|HJU&doqXg|BY~@vRUMe<7rUIQR}fea<~MxEgaqv%QeVx|dN8*_|6pCKdIf>?pcC*YK|6?a&0T{Y#Km>S zN*Fee7ldCyuCSG$9mFr~-hJ!8PPzs?h>QCbI|!;*5Pm0eO|As(Aks7F8uTD8o;&PJ zs9r(%6E(k=p>z<}+gS4OCoj5&#>M!7y^Mu~j||)+QG#}8q%pBg>%kls;~@5cpfO1J z2*+I&C1{7nKep|wcV5`x8uTD8#&hf~!RGOT@R53cH%jRsj@mkHWa~AYhsMRcgx#ox zgwHtKI0=JX&g>QC(Ns$M~OWy$?dC1?kcYHHV@2XRqfV@Fu^3c@R7?ldbwJBW|oe`s>V z_Q`M_^dK(Q3D`+jy@K#cpL^3v&<^76)0-woZNCp1^teXadsn@J@DJ^0V`|vqe zf_4yTkIXgbL0lUj{e7hB6$HjB&jcPNXa|w@`dot^#I^VRLQExt1QpwRzXyD63vU_`WFTK_zGhVKqa`nt>j~ zwK~DyRI6S=`2H^Eb0ug8k@mv9ujoNst9PcXuXwy5e4m|`YDnUDl^sb0?dyZ@C0spE)^$Nn@0Ni0T!DzgOdTD@xGr5O~|y(8IX+E=To(u$K5W_FrvhSghzk z@U3Oo(K-k_kGviD7jbryxUaM&&C#-c4+Dcb?1>x9y zR!V4(q{d0ZMxSDpvbe8o%qZ4Qy+m(THcI4GnD$6FE1N%x^+2yiW1RhIYMTv;RZ1_B zgt}MI#`$6$6*QYe!)E%T^6u5Jj3O(W&x`dyFF~2P`>I^Alv!aa(-Q$m9}D?&`X5GTwf`nJ(3#x|9l=SzI5r;Xo8FL;OwRI?#B8`3GIcOjhq!m8GNUYRv3Nw zUB*W5t93$qq?;9H8NBs|2F6__qMPL~mayp*=E}mAnndyu=K9&)oXU zW=?6vcmEsDN(t?eZdRyEcs($O^J)%frL9l_EZtX`uwKvK2XwPS4aa*I6(H~Y;jFZk z-BEcrzB5=B-@XpeNW!rFjiBI;<4 zsJ$AVm9|0^&e5lrD6&#Qd!(C{u70rQSRZTSUns", + "partId": "JgD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/igvc_description/assets/part_9.stl b/src/igvc_description/assets/part_9.stl new file mode 100644 index 0000000000000000000000000000000000000000..651062b03f5efe6fe7b513f9c68aa82ae756b686 GIT binary patch literal 273684 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�Amo>%ON0f2u@WxuUzWEzU3gsOnfwFlAXg<09rN8npRQC_w`9k_Y^ za`j0zj=vM!3}2~C2~`2{T>}t5 zA6_S~^TI6CP+k)A-mRA}8h(dq1Y#!;ryWrz?*)S6P$VT(1$3?@hz+Y6<@JA?WE#p# zV(UE`ao7*WNkry3+}!p}Zv8cWRtJz4?a8%oK?4KwJo-0z@212~`0Ljk{O2%?HkC zW*W*%q6L0OvHDw2n?@jRfJU8FZSx)=sG&$osJe*Xv;fik?)LdBFTGkB_hNgS?VIHF z&TnfPf#?fjcM$i3poStTp(-HuJ|H$O?vUSn$1juPUThER(j>2c)(%fFQy_K&anM&C z@_r+JF?B^!Le)ilOV1Nu-|L58p=z;-^X^efzaK)4+v@~k`k&e78(=qL`}Qw7Sm8(68?EKjX>}@ zKLtccQo--DGc?!~sB=S?FJw}Ge*;t} zZNHwIMj)cq*)$YM2~`)1J;S-h4j_+vvF-OIrt!bTQ8-^5haxGV>Y{Vp$<)NvTaK$3 z_hQ@cgG?h3+%2^O(e9T zCE@qirV$A4+qVYM0t7V_NeNXK@ps4|o+@_wrlGtfyiYKVK+sJ*0pbkIq|{I(B~%40 z{Kq$iV=)coCE;C>X#|3<=nD{VMW&%hN~j8m->C-iLgBnjLwQNipN$#zi)jRctJ(k% zTnjl4MN-1MK3sDW-(l4x5%*&I_gUZ4-I+!p_6Kneh|XR^k(5w%vA7BuO+$G}+bmwuGN51K|GIF749>;-}vill_9fW>oO2fkp1 zpA(gr#D#o?fTl^ZOyd*9^ubjaTnnobFsplJkRUuawchuYAuwTh&Ks*BEb25~5S^Ra%UDJZ6CZb*u(FA6iEqH7qL$SF&J+p zCi^{{@{+iy*DD$PplJkx?rujAqx~LEk(5w%vC0pY;Ro${C@+aVr;X3x2Tdao!=X_Z z?_-Yfdr?JFLRCO~gAT;|wRbL8`aQMslK5`wiy8c&?b`#v9ocjcCBLUuBqdY@bnaFV z)$z9LB<~HBmqgu1U&!DGO(PKA50>Ev?V2l+5~>1L`N1;$pm`kSB~fS9xD0-<7>BPL z7On_>una$FXHmIQLRG*jKUjtzG$*9IB=(&!E<5Pq>8*K(2Ex0$GW?*aE0Pkb0#^CK zGW?)uD2QfwqFVTOGRjNBKaZvn2tMb1K-}lw$taQ%ssdK| z!7}_{DelF#pI1yH5S)E3#$589f48JaN~j9x+;R}xVTQZjzavv#5`KO(jX+$9alDFI z^iKbdOp%mO6%c=`2BHCG>J8(HcYVrB!q4-j5eTjZ`-8aDzw1*ZB~%4;jw{X}tT?s( zJ4@vy;nyqE2n649-VUMzh-m$=J3l5=T`c_IuIPQ6fA^|h)bQ)BX#|3Puq}vTAgG~8 zN~pS6BuO+%5CP<65JA8%uJJ07!K+>34RKTIPK z^dDsqtS<4IE0Pkb0v7(`EzHuF`kI&WlJGvtGy=hKd{Vd~(@-QOR0VX7&TCHLylfoG zOTzm>(+Gt3gLHSMp-4)24~P4T#OsBFG!5k?5m);H@l&m%%g;ZvpzD|qxBVN@;KSb8 zUWcyC+Erg+&%9{7rv@>2&f^tZJ+w_Z?!|WdzN@pxkNF`Xz5_7=#C9O!BS{HW0kKa6 z@s_Kd^{&|3G?bS_t?SljW2P@PjX?YW;&l+cKu|-Glu#8Ab32GbM|R0-zTVU{l$XSk zejBreBNv-SApQhVAH>Snnwo|pDWNK$b0iwQ+&`Pwu%T%vFNwRxRLgIDcD2~`1|dlSUUY9q6gx^H0`%1h#f@dK`dKXnLU2# z+EUz$?YG-*o<-UYUIkf*OjXgsO|q%?2_5KF)iD1@X(!VN z#8MDz8#c@vf}n;XDWNK0F<<@pTGM>l$S$U#yd=JPr%`^zalhNHKM;eVasTU0^GzVA zp-4)o3W&ci0kNrK>->3F+ccDy#Q&YtIG=golco`f)_7{`fmj5B8j7TZs*BF;4C3Qa z+vN4VXfAc&68sQKK2zeQ5Q zYok8Mk&ng5f0+GvMcj*Re?2TyAb55^0&yzV!f1qcgfXF7VR1cL;0k`Vsa6)}W!qnW z(+I>s+$F0(JXx)F7F`e1hzV5zof`z=4%~^O@9AP1>O~EIAKTFeg3m`C5XWOJq=q6X zp(NFzpE$GkfuM#WDWU2jzP$tDYFxdUxQcNvw*5ZHGy=ih(m)XJfuM#W zDWU43bDN;?Fz&+5a5u)i*!KH5(+C82eeZxc3j{S3NeNXKo#W2(7CcS=z*80XV%zUa zO(PK8Gn@rt2A*7MD3TJY0v7w-HkcKD!de*jV%zVpO(PK8g|`P$3-bvz6iEqH0r7kE zAS#QUzG)~g3GWk3BM@{GlR@<1>In@+QbJY0V!pbza4e>wyd=CUGL1mc72RC8BGXVL zB~%4;j?Qa%;k-;kc}dWp{k(jIX#`>=h^Ih2h;@qNP$VV1>qC8z#B#dh8A zzook~jX?0I=7NA9Gz~>kLe<5>51x$GAAZm@l$S)Kw-?h7nnoaaJ^F&!9t6jsNJ^-> zSX_^V*u|WKD;W1;d*AU3=?6_C5WGuff;iJ(b45}@Rlq7gSb`rk4do?qMrkhnplJlc z`@s_Ypp8S3lu&iC$`6*{2TenHN$mLBry2a9X$0aMjN=0ALx1(ppdu-uDxh;qKup2z zxQ(Anl$XTUJwMLi2TdaooPGWYVhcZ)D3TJY0#^CK68xZzLwQNuv+n&2e$X@m;r(C< ze$dX9A}OINV3i*%!4KNpuDm2BEu5Oc51K|GydNyV58B+WNJ^**Smg&x@PnqIyd=)9 zJtc!5G>t$s#N2)yoZW?fjZ!2fR0YJhsX%mxdprXxTilE7HMdUA;0H}35WIr#fq1~L zwTh&Ks(|?B8HiWlf;(a@jC-+tLa#{~{Ge$B;tden^jll%>GuqZq=c$~&b>zBmKx=c z{N6`-NsPL0BIc`prV)r*82JSF|2zHON0F3J6%gO81+npjEy{QJJ)H8AsOb1=20v&T zf#7=1_Zav3J)9ybp(-H$76-&4ypO4ll`ZbY_Od-+&fo`4BM{yXmf;8OzEUJ5R0VX7 z#AbM}bRAZe4$lwP}BM@9ant^!2dmKel zLRG+G9pu}`|HH}__hS41M!$eB{(aP%M;i$52g~q-rmjdzs0!$ui0)X~;$D&7WNHLr z1;+6Zj%tkeoQkA`s({5fnq%Z=c<=1bkLmgAVH$zp+3gJCeeazWp+-!o3Ruin*W(Jl z$@AoeE$wWk(5vs&^g|T9dReV=-*{1FA4vAR7Q6XDx=;Hmf;6W zaU>;F1uTd)cm~`1cQVRL!awJx5ePoDi$Q!pzIZ32NJ^->SgarQF*A(z@0OI8gr8ST zBM`MQ`#2Cc`FBf-q=c$~cr6NIDrUGd{W~({CE@2s(+I@t7{?ZvMc4axWQwGOs({XM zrmlmT`f~rSPkBlB^}{p*!PVeBtOi^AcYTVagsOnWp5aBTIOF|0OXVfu*Fn<=L}QHn zVh{&`;5AnyB~%4;jw@$B^ga?)+>33$?wUp*=3pGJgIEZH8j7TZs*6>AumnG78p=z; z?;lJf5Z(`#;0H}Zk(5vsu*wgX;0McbFSh+Y$TR}s{a^`xu*`8Nk`k%{R{6ma{Ge$l zFA2Y&GmSuS&+r27+YTV8p-4)o3RvtJ4#D%s=P>TYw%?bUMj*J~-5bPg5Y$j4B~%5h z@`EM#LDNuP5`KSe8iDYBumnG78j7TZs*8mmoQK)%49s$IFSfn^FpWUaf6N5|H(?rz zq=c$~h5z8Z4##2|%1grg9n%N|{ouJESS91RQY0l*1uWK&mkQ@)8p=z;`$5wP1l`?? z!rhsMA}QfL9O{E4ZY&(6X(%s=xY`$pt_w~rKh<(d*CjXC`5Uo%pkG)`EzSBqdY@#NTp)xbdW)D|YK$ z%QTdi#G_r-XS@GniD?9aM|D1kHXx{>NJ^**Sd63n-8*M@9$LdRl$XRd+ic8sTKBnW z1R{sV?jZgJf*OjXgsOn}J~hWNzDIWINgGRXFSb9rs9JtAzC6xMfjADt$soFbh$AVX zDxh;GfH-3Rq1msytu_tiB{AUn8u=ZQrkO?{4gqmJi25B@n}#AOp(>zrBz|6YU)HL_ zGSg6A691XKS^m(JvS|dO8;IXQv;)C$D3TJYF5(;UAnM=$bT;q&g{GmrBsO1IJ8#_M z71Ia=uSYWwi$G9Ak(5vsu(;++H%!XrufdmYqP%R+d9zO5Zupa?5s34kF$}~g5Y$j4 zB~%4;?g9``teKYe-7v{Cl$S*JE9&O&T=gH*2*l+eMu0dI1T_>%2~`2{P9H>%^A~27 z|9;vul$XR#HR|P^4;gM6fjAq)-5@>#K@CMxLRCQg-ZqF??U!X!m)&O?%1h$FKJ|0_ zva4wX;&u?HffxgV8j7TZs(|=TDu|VV~FNsZeH^@t$A8HzZCyoH|AP8zG zk`k%{;5aQhLwQN`*t2n-?c1vG zSH2#J)fvRqAjX2AhH|BZs*89J2jcu;b@IJhPMI9{Vte+njr0Fk*|~sgIi>&qQ7WZ_ zW0Y||3^_E;M*DqUhr|#PCP_xlIb;mOFos<+8ad|Jpd3na)`1Rtzq?69A`ul8IfV#= zlH>pTz1RBgwVt(izu)ga*JZACz4!gOziX}MdDdF@bHDFy)8jjiVx&NDWmqt%Wx2_= z(@b5Klu&iit-B=7-(p%->=o%%Q6muS)oJL}jBTeCEGgl&Q6JREpTW)+d$H~3%_0Rt zv-=Q;XoS|om{6@S%T}OQ_u~pKIDUm`$cq~OzA}wKa4q>5cgcdDD@;R{lu#8Ae`f;3 zLAVo}VrPrJ*!K6aMG6F;kJ~`J27=>|B_&h^bkD~iJW&&{v&CL)`{&U#0&x>H@YEjh zR8P~8B_&i{bZZG$hCBDW(liv8gkM)oqe%P##J@ps9I~W@s*CtuIC|9|D_k{pw%Chp zzdo8qAWj7_4a5=<)Q}}5R9(z6uGH6HrT*}?F{YuoB>XyW8iC+$&=R{(Nm3 zf#5YKF%;_wHDpN%RRP`gz#drI0qbJy#kS8AOd}A?CN6Z@glWi<5~>1b88epMT*hJ= zic7+0MWzu5j$=;{$cjuumXuHx(B(MCxXjBm6qf|^GyG>7f#CJH3j}vI&Xp`F;j=!} z2T2@+eU?P*#rEkxeaP(2Gy?H^5U+#y*lWm=5~>2?yXPSG!S4SnMjm^y{qN7;Wj<&c zf#B@^1_bg!8;2|@q3WWWt0!=Z*~MRT#U*j?S8p>PG>t&;sT~Gldk~x}SyDn(KzCoQ zi__SRxD#V9wzt{fE#`x!5s00kaU6(8{kL0H?~~{uxwU5@SxBnB;Sf3A8 zkPn)M;*wbZniq5AgQgJ(pAS}$51NK7DWNK0eLh%0K4=<>OJdi*&B&1tnnoaeK3G9M zXd1GlgsOlpAAAW}*H?azQd|d(}&q=c%A_#GsO z1M#-&cHlUBvGVK(xDbow{y5$5C7o!(Vzb zM?Pp8f#3?a5r_kQjw4G-sJiIZ#9#6Dag@*16qm%EH$RzAn)Bjz>}?>pt6@j08;89x zwxoori}m?n9r8iTITbhRRShErg5&rF8q2U3#+H;&70``?Bk%5WXFm?p^Ydnr0-@Qh z^0~7t)QAaH0r9un&?{cSll(gXd6DqbkdQd#WZktr?-zdo8qAox~oA&ANT z9hodCp(Hl1+32p zE64{;Lvcy?^Mh#w!smk(D^Sf3A8kPn)M;*#*^Akzqh&j%~W2Tenklu#A0 zJ|CWrr<_F~(gOHCsXToWG!@gN93 zAF`x`s(|>7B8WY)Dm?CMEQ(9QpW96%5WMEd2P?=2>!=}1N~pT%uE+iEq+b_%vF-B& z(+C9f!I2=4O_+u(DWNK0eLl#H#WWO`gwLZ)BM{7rw#T}jVMpLNWJw8C0rC4m5WTS5 zEW@r7d$G+d4F8!%Ah-|Ky6nz0WJw91!=XOt^Fd~irlGhb;%Z+YPH1sj-FaJ#uKKa> z=XRHfs2TjH{PYfA=7T$bWN(T^%{qY9h=I`D7enK1%;s_7} zLBu^t2~`1ch6B;$&Do`&fBe`q6qm$*zyCgeZNDX^5r|_!tOFwZ>0{H7B_&h^%rf?B z?ZeyV=Zsov8j4Hem;d~nzub9&X$0aAAoc@sI|z{m4^*{%`^hh1H=#zM}nY+EGeNXV3r*X;(#kp%=ce3*EAHDM9cGt&? z2GJVC79gl0OG>B;h`(V9qEfmt-|xDarlGhbCj6^e`SOcvOd}Ay9?L--34$83q=c$~ z*v~K)J~Y1k8W|ZX(%p<5yM-TclhpV(+C7-_qOp< z^TR+;Lza|K70^9{ovxdi_sFj_4aFrf{=aR?541beGy<_1G=2rK7z8zBNeNW}-8eR0 zJU74T$`egPaY-DwS-W!W#Rr*2AbLV$??rR-?LkmOmXuHx5N9|Lhu;2ne)&dwn}*_& zSO>qQ`03=$O(PI}L2L%%d=S)-B_&h^%(9a~958BWUOK$3X(%pAdgx5xWki>l$`Fm|IEyZ4J`+2iSf#B?ZjoBUF`qEM~LhE5ns0x^6 zyn^TB3SKgAwrR+V8vedAjX?0e`Uu2X*bAv4OG>D^=Hfb>};_Y+x~epjX?0JJqW}NAgCcrN~j9x_Kzd6GSvO! zMAJ}Q5`JAVjX-b}T@B)E?1j{jB_&i{%(9LkKEMiBRld?R6qkfwA59|=Tz3x#@g4R; zYRHljssg%o_Zh6z9k3V1UTphy-ZTQiaU6=>U@i!1$dVGOE@l~boN?H3?#0d)d$H~J zE7J(XK_E^Bu{j88$dVGOE+WSPu^zln0~LF*?e|^N2n2WMyRkbD9q@!{$dVGOF1qJ@ zHBJC)VZLH7w*C3RGy=ivQH6QB2L#6B;=s(>yx*wux!^>Idvou@~FS!X9Y%v1tV2NN8M(Tx}cdQ=BVVQo`qOs1K6ZVaw4qBw{bN z7i{nmvpdrW1jm7Vum<^{X~>cisxG>5^u_Lf3HHL+i|t+=-eW#!8iC;Ru?L9$AUFcisxIQ+B?ECFPGk4@ zdslHul+SsK`Jib8q8Est{?N8^y1#d2NeNW}-E)2=PL(_PXHaoTTsw1aj(pHG0>P*D zVG!N?Gbl?+s0!%v!A)^G?&{YP#U;_{xL0!IgQgJ(W-NU`^!ICtEGeNXAl}Jn`%a3i3g_9~TxK*D5ZFZ<^!ZI9z(GX$0Z`Xq*XRJHOY;k`k&e;@_wO@h-C9v;CPtaYt%TuC4&FHwey^ zEGePtqMNG=@K%EFGh#2cr+hatM?Pp8f$;fY1^J+9$dVGO0@mk)7371ap|~V=X*D57 zK4=<&@cCc``JidYk`k%{*5`v2B; zh=o(NP$LjNAFLoBELc)PRY3fklh8mWTsapzTkOTQzaADT5Y*u8UgUFU zSyDn(KsQ%g;0kVloh|la+h2dv2n6q|ukjXjl79yvOG>B;Sf3A8kPlX3FSh-CTpQgz z`2C>I2P?=2E3qXdR9&pk2P?=2O+#@>_~+3y0^#$)3i3hIkR>HlU3AZR7px_%u(QQp zZ2R@fGy=hW@N5t#g5Wr0NeREk@O?Xoi?PBT?cb3pE(yOrnnoaeK3G9MXd1GlgsOmU z-MtTM`@a5NpW>46>%3_MqKsa>0pf4|U7su|p(-HigCI`Fj`Lgp&Qftn`2ET>0^#$) z3i3f4hb$?f>SBF9SV2B$*F$kh_RyR0e0sf@wS%NLza|K6%ha49f%z<>hm%B z*o$p{elU$d_$h|Q3V%wkROd}9JAFLoBG!0o&Le<6ke6WIi&@>d6gg=*>Mj)7_-iYUK2Va|z zB_&h^%rc&kJ7QJ%ldrKTE(w3WHjO~=6uu3J`+SW>mXuI+5&zC8hz#ptC#;LH7u!Dn zFpWTPy=nzw9@a%(4_Q(|RX|t&I0~!u*;uV(FSdOiWg3C-`Cu)pWYmx)B~)Fk&j)Le z51NMJlJNPUX#~ROgUs$sLza~AIULq2pARyFG!4Zi5m);H(d_T%)qU{xkgDO!o+}W4 zdFbSPttMaQhc#JhJCW!tBXPm>?xoNAPp`yYY#;UEH~HcVmLxb z+&&+OUoJndZsU#H=ZAyfIAlo)RRQrEUJ&^vd*@ew`M7B)E{Sh~rhQ`QI-eY#NG7V#%Ye%75#8i)jR6 zJ=`Vt4ZAtt2?RA{NeNW}vupzrx7?jSRvBO#ic4Y={*A-IHw-k5KrBVCt{QfCenH&; z(~u=4R0Yhk#URc-b#y*&Qcu%RToQw>Y*X&Bq@QU7VpH_0cfZm3W*|5YSyDn(K>Xbg z5F32?cs{cI-ln0rB*xy~wmjt1+%y95J$f|`#OENWAxlcA3YcZ9K=kP}F~4Zz_NJk@ zBvuYSC6W=z3vYo;^R-G!&P_x(nAUfA{`9rV)tc=+z1kU(Fb6 z8nUE>s(|=S9EbtuOwV_k-rY15m&6G>uU|gpip@GZF}1^M%Wv%g>rW#MEU;2~`*IHy}VP0Pz}j zw%9AutD;6A*sEXAt2u8CDOgg%Yok6$q6bF))3olT*o$pHZx$&KydI}wc8{OYy%dem zdKeR`0^)DCfmntsc+2x+O+#MP@Ymlo0>QiFC=jP&FQkSnDWU43yRSaMo!AUJTkOTQ ze?BZyAYMhUR<1oYziY|zf+ZzXUCc5*QSIB;=$`ZYu`=w1oh|la+pjC85eTkVJAgQ`y?aw6OG>B;i0?CixB@HOgU|Lf4aFtl z*GJO`#1f3-V639uCik>)$dVF%t;9S1_dxW;O8qK!w%Chpzs{RRAl65(p2u#`vogRm zWJw8C7xA}VKup1o(+WFV?8UacisxG?o)gHKS|AxCU_F~(g=S(9IJoW7d;u;Vfhb$?f z>Y^LRx_Fwpu z9K<{jzhDKThAb(eDxf>J-;dSp&sgPRFSdQ2U>bp7HZcOkKS5AKmXuHxFw2;+40IWb zX(%oUpB0%#Aea^14B}Jl2-J`zB~)E>dDMe0^D+&^CBgix$C9O{5r~%1SODT**x9Hd zOG@~x57&dlUD#(y#9nN7>GTn^JJSe+&j)Le51NK7DWU438^>^*7dZ0Ri|sEDeUJH| zX#|3^`x1!Ney(Im2~`(eK6ndGG1vNQuDB#>|Mm{^LDL8X$8j!*i~Kd0B_&h^bobTw zi~jZ-et&Gu-XG9kPkPZQ(gJ%S$018fs0xU`%L(FRy|x8tN=IR`si?8WwPFCUj9A2f|Xtc&~Vk0AQ{+(4F;P<1iOT7#H{w{}yp zv&CL)U;pOQIr2f%2n5$%z8M?=g4aWqlu&ii?;m*kILha0ic8|U5l`n^o<4m$Mhb+_ z?kWoxx?D|`lu&iCJ|C9bd>@rH9R>};_Y+kTxljX?12;5ZP2@OF^nkR>Hl1+32p zE64{;Lvcy?{mL`~!F_N(_Ncx6J4;zoLRCPw5AF+ZQn44?e&02XK=^#Hf_%`%AxlcA zy6E<(-(a4aVrPrJ*!JfK(+C95SM5L`gES3UQbN^5m#g`cjA{ ze6WIi&@^O82~`1IKDahkh2~ftVlTG+x!p7Z!TV}05I;6`H5OS?Le)jL?q0?9(ASC- zmxRv~Od}A?2mb+Lh_4mNk`k&ex_s~vm$BG56qkh0qf8?Z+zlp!KvraPB}+=E3h2gx ze2|%!X(%oUpAVWwAbdW^?9Mb~NeQ3BVJ-3bATvnQP+SsmwJ#7yZGCawO`G(pI_aC? z1>*Ttee=biuE+wY8iD8qjkk_! zo1X!K3#vZREnfLTUjHl1;lsHLG1ne)AGyr>0ug*OX7tCTa^zv z>qgTEL{|{MfcOLiHDpN%RRQt0cR&mpbaCEpL>JRgToPTUtXpof-Ib;hh*LqVJ@DfE ztb4ndhAb(eDj@zo9Eg)w4$M!!b9>WJToSi5Z&N=0y1u3n2##axRRi-6KyVzgq=c$~ z_)Zasc~1_>4?eoBX(%p!e<{kfXM}A5FYfM9NNj!uaWZ$L3O(PI|&TqN! zj(i{pYRHljssg&_e3zMb<=sBr$}|+0#3}c$SDt@qN7D$z{?J$s;=WI|G7VW$Le<4A zBeCe4;rTtA^s0%y*gmmlz4G8~*Biy&2BHLFCW!4e?Pcn+q=c%A_C>=o%% zQ6muQ)j^x~Dp*p&Yok6$Vi`u>>a(p%u@~EZ-YilexQgxt;@xFim7)<^4`V`AKzBXv zz!h8zJ6jx=ZGZhuBM`i=*2i7)4G3z;k`k&ey8EgZ?!>v+3u7;~{e5ha0x{^OG>D^m}MkR#S`__yUR^OaY^{++#&^nPwjblYJYg&yh(~wl@vMCE?dc(+I?A(6}6{XdCQ> z)Q}}5R9(z65+$tE?XefeUTphy-ZTQiaU6`@pbz#!YRHljsxG>5yn`KQ{e63whT@X& z`;}<~g1g#DAdbaeNDWz1Le)jL51tS2BSFPpY=7_ebJGX}cjwnYG}#~jo&_{yNeNXK z-G1I4qdp3wkGD^=+3(hU7GqtEz1a47f@uVT*+e(4pdhFrOG>B;=rWcUUB+S>ic7+0MWzu5W<}Fo zR%9Boq=c$~E{}T0WnQMCxFndL9e>?o(+I?o(7>))btd*gjzgA|@L3<~gCsV_u1O;H zV*8YX@cY4Wi%cUB%+;>N&iqHOAxlcAy6AGX3U>d#*x6z)wp;$~J?4X^5eRC$0|NP= zjYF1{P!-T=Y=l$Hi~gD`E{Ort-eEpy8iC+_RR*!dUvpVfLe)igUmbMfzzKBnSSxe8BI?8WvkkH4NHA2f|XF#ot2#Haol zlqDtn+Z|kUuGF)SYFqiMUrQ91#JtmA&5;k9Mj*KQ)PgwAuO+gigsOn``CtY4pp8Ru zNsKsVR*rnoGy=i7nvA?)j$adHNeNW}-CQk1wo<`L9ec5TX{t*plJl6 z4>YFX4bCYbs3A*As0xVR8-RFa<*>?w*x6z)w#VKvAxA!F=k`EwUtc#qM?Pp8f#A6W*+iwK&kbZr2~`*C^T7)8K^uqSlK97`<8tJK zrV$9P?PudX-})dp4p~w{RY14b_QBi7W$j(Arnn^jHGW)vb(6a77%33EcMkz^0rtY! zk`k%{x_g(zOYL3Gskl+EY8WXH90z-K1NOq$k`k%{y8DW6I0yUO*^k3=Tt9CXDGd$H}Wzi9-5_Z9DwcK#iJEGePtqPwp?#hp0Qzspcu z68`z9<%|S^cl#U=L;Sl8SyDn(K=*vi#1plNeZ!38iC+*-V#skLjO)imXuI+ z(Or+>SQ(o8cT0*(!mn4R5eUxJ#UP%?UdR#3k`k%{y0v`@R=C^zJ2J&3;n!W$2n6rl znIN9<@5p3H2~`)}z55VW>W{Ir#a?Xtb>1`r!QJ3K5KBOC9I~W@s(^0)I1f9{dH$WH z;*#+DplJlc=YtjGgQg)%N~pS6pAS}$585~smxSMUO(PJTE51cN69mU0OG>D^=;mr3 zMm-6mkG6^v`}3S>1cKLNCWuQwP(zlK@Mk;Jig>cT4^PuFUz<=|68^kv z8iDZnU@h`N(~u=4R9$p))e&dvroP6axFr1f+B5>ebNjX+w)Zs_SyDn(KzDB639H+? zSmk0bwtb%9u7~>`Ax6%8aAy!RLD=>1mXuHxus$DT#$pci zsscKVUtQ*98j4H8=YysZ2%ir!yE6@0Qo`qO7&(bgTn1?xic2D{_66eE<(JicJ-1`k z)+75Ch}M_)&3Bl)A|H9!0^5m1XW1kWs~=oe)#J-PmDr2zRfE3Cr_OmZA*O;j9mG~3 z;+~|0s(|=6SwZw}b3kd~uoFx}aY>xJ&G&h?XXctlAlR$1Ae!8If@#Q-5~>2aaZJQX zY1Ylhnug+%sD9|DeE+p)n?@ieL*q^mPlMn%WJw8C0ke!m{};!W4*73)(@iOA-lU6ZC@zVg?_Rrn%Yvs(BM`jihk!T+1T|zy2~`2JjKr(0 zTI97qcQy^hCGpl3&CBz9JY*Vyr~&a3h$$ebAxlbp@2iVhM&jGW8|S;cissd&iiKBn+oL|?fg=r`*iLJY>TmGo+Wu_4b-icpHl z1$6iB5u0?$-|n}fHuhrsrJ=3MTl}WCX#|2JzZJx~r>-biQbJWgH&>s`=$fCtXtrr6 zE{V|(wkh`>w68@9#0$`P{JF0AOYgX+Qt?)j55$C`%XlDK1N+j6U` zH!_Vt@EPp!-|qPjAUIdDq=c$~?isx6=410!*mYtrwig`OuKdD`_bg8c1lQeHLHq)O z8nUE>s(@KWV*OiB$aidefN3Z$i5AbdD_{T09i|b8r$7t^u>}Zf$dVGO0%jSB_rC0t zcY1JHP3*5Vauw1LD`$I+}(oDWU3OmWg->J6r4(^{R>*fncxhfX33-IuY{8NesxD?3SB6)yGUV^fHVwrk;nx+56bP#)LgI(3CrB_&i{bmx+barF+sRgAsZ_U9ne2!!2NAjX5BhAb(e>Y|&gZ*UhrgS#>IV%wkR zOd}9{244no1_)}%k`k&ex;?5No-rV$99+sA;I4T2i7q=c%A?%ZCD)opF8aKya?E1~Grw38o=S zN~j9xuK5gPEL&i;j=k9SS&?Z3g5$Ug#4#W^4p~w{RY14y?t%Sjn~{A@LvcwkKilFr z3r!;s9LF3GUtyo3hAb)Jvpzf@B-&x0B@uhE-M-I9%yo2lByMToSd{%x6Am8iC;R@id6X{kVjbBR?m&D!weKkiuXc~dw zU2-{yi~L$5OG>B;=&r|!$P4=THBoU%9Q5t19QmMW1j6Towa5qUn#+ z$Nk!_xFl}bV`h$g&@=+U-QZmi5Bs%UmXuHxus$EGARn~rp|~U#PIxXyK4=<&;M$IS zu!4Nh#vw~esJiH`#}&vPPsGj^d$Ik=z-c-1LDLAtIA}Z!;v&D-%90YQ0%jSB1AggT zIe1-nW>8!bKa8x+kq??iAb9V-fV{a42tMbsq=c%A?%q8W+5B+qY_S*Hk8U&->s4>l z2n6S942T!~*+-U?P<7GG)f;##@fdct*o*D|emE&dK4=<&;7PwLh%@{dPL`BVbLRG-}e6WIi&@>d6#3pmb=VxC(bvw>_AbfUL zK|W|2vZREnfc5!c1^Hl2>=pH@h8ltJ`CtY4V8N0Sssh&MgB9e1rs2n7Z9i`oDG)v% ztRNpOSf~*bsukAfgB9e1wXql5{`y;_K=^#Hf_$)+Ba|g2R0VYR)qLEE)BL*(#UHl1$55`&Y>0LgEkJuCE=e((+C8wc}ozRf#5h~NeNW}-8DZJ zE5i={-IC&x@avUD3WS~8K_DM2SW-e&K(}6vzzX-Ve@CXcB>cK-kpjWFIuFD@KyVzg zq=c%AZmy6IR*(-?VlTG+I&YBz!QEhI>;`?-b?^FQNeNW}-TrYucAP8xJ4?kS;rBt) z2n6S9Fo>QYI1X7-LRCOFSI7q|$Omm4ic7-pyQUEcpAS}$51NK7DWU43+t1&_JdN@1 zkQJANKR=j8Ab2kM0K^mi9kMJbq3WVLmmH3(cQmeI?8UY}2bo47xPKf6;v^89D_K%P z)kSx{x*vDp*|-~HFSh-8&NKqS>oJG-EeLAJk`k%{x^w$*JWW^OsfxYW_UBU52n6rl zYd{D6qkfQUzObnkR>Hl1#}wg&+1-UzUL~_P+Srxb<4`LpPFWoip1)d zx|iMoK@C|_LRCPg@py+zOQ-(v3)4_s67!C0QtrOhB#RUXUcntYURoLhf*P`U^xXWUe};?boAOG>B;h<{ra#LFK%P+E29 zJkwBI5{GZqyj;EH0gDs}Ucoum&6BewkY5C z$2%-iAi6>0U=XuGP(zlKP!$mW<|K&8hdf_8aK;4FP+Su4p4qZIf9w#86bO!^ZMWx3 z7l5FKEGeNXpc_Ya=)BT*?>}G~ic4bm*IJeD{OBT!6o`YMF#^Q?AGleRB_&i{#J@4Z zUOl?Bbn1+oOha)=bZFPQeAGS1nMNQujvF6YTG|%`$018fs0!%D@vlFAQL5{3sc9%K ziJi7?Q+|2Eo)#$(yi1<$^+l;i$4d*Alu#8A|27AR&U>#aJvytqX(%pbO`}MZL7WDH8nUE>s*6rz%Ta6R zKR-RxG!&P_#KG;#pOrSUNP##6<9O@Awe!-rp#@7ys0xVR{D9d1;b!@PbyKFsUThB^ z(XPDZ`|FHiZv(-(x)elB?UbppB_&i{bmJg#N8OYzu~(#5#Yll*ueyNfUpu8sY)J`K z0o^#}W8_aiJ+vzJV%yJ~X#|4f_zh4l0>QhzJ?{1#L~Kb3RTtg!aXy}?R@e(; zFSh;jXpsWJ=e#4H+7>T&FIZASRY13Y+=`Xq4eV^O7u$Yau}Fd7di6L~p98UbavZXx zgsOmUy&8cPZZqs`u@~EZeY8k{;41nPh<|{fhAb(e>Y|&ge`2LR343Ae#kODPEm9yj zj*cMS#9l}ZSyDpPMK_LLu}2M@F~KwxmxSN1EK(r2tIY!OI}p^6B_&i{bo<~j@U8_F zd$H~JU5gY5j^pnjT4FDxhAb(e>Y^J*E6meR-LSJoaoP6g2a6O4-dERy*!Hk_1xre( zy6En!3vl&@V=s)o*!Jfjixdbx=bb>jitEmC$dVGOF1lwh!+kprcVq0uwm;8Vq(E@4 z=Hb5W4T2i7q=c%AZmyc(`CGW>D$`J068>Cjk&47RAU+2{4Ovn`)kUXq7f#62u%5(T zZ2NP&MJf`*LA=Zr2pY1agsO{9<9>J2&tosPeV$;E0>SHXpUWn4YRHljsxG?g(GMBR z23W0QFSdPFWEw@H4~PyRs3A*As0!#bX1UDEG!&Nv^RuJwad&8uSOS7O8#QD}37_?$ zKFE=;sF^aAMC`?O=ZimLc4y-V1n;YNK>XEf$dVGO0=nz*`|(3-TKTzBToT>hd5`&^ zX#|438U$i%KUcD(gsOmU94&B)ITkxx?8SEP;}$U=G>t%T9LNW2kPq5i$&wPP0=jYh zWBFFKbNs!lxFml6$$aL6rV$8^qXJ@W?1j7@vZREnfNpKS3a83_uouQ&Y+p0-jU4%) zMG6GRfqbwQ`C!445~>2aaUdV8MLuX6ic8}5L348CgBB?e90&5jTI7QTOG>B;=&lFy z!CK^lrlGhb&K>@8j(pG}1%mrv33&g>2>SL*3f0xFjB4J~Kx? zXpsWJbIAc9hJc`kEGeNXpt~L`k#+qUJ5KDy_9fLba^!;+DG;2iCqNwF_b6FXLe)h# zSHJJ}eC8nYtXIdGMj$wjhd{LPXCGNo zLe)h#4&;Ni$OlbBaY-!ybaIY-&@=+UYyKgK<^BvOOG>B;=&t!Od#$QH)1O5Zm&8Nc zPRfxFnnsa8K3I!e0y?4(o~Qx-os8m=@Xw<~DiS?G zoa*1n$dVGO0y>R%urh3foh|la+pjC85eTkVw}H3|yC=sXOG>B;=+-ObgSE&9O+#@> z_;uGJ1%m7Dg&;o1?nw<N=0AFM?_Sg@pos(@}B z$OmhY585~smxSL3Em9zOU+s)tZ8`{!Lza|K70}&RHSpd8RP4pJ-_I>lAb4MW4B|=< z)Q}}5R0VX`ycs*7%}kPp@(A2bccCE?F=7AX+C=Ew(Ykq;IuDWU43yXKwn zG|lm~3B@Jh&$|{W5WF7UL6q?vavZXxgsOmU91mbsXo=M!_F~(guPss_I1c24wa5nx zmXuHx(2Zj*PWpTMT9M+C@cD;D3IwmmY!Ju!T9GU%p(>yo$FW$~xA8SE#U z#MQn)T>YP`>h}2e>iy3gvxi+l5g%@ZU2VjQeC1(t>~qmsMxtiQlrF~}v3qUo#rADm ze3PI3+3ZG${iaUo@^=t%Pf|ivK&SB<>YUB)*v&K)m&B~=SLOHo^rA&75~qWB_4eHg zmXuI+(P_Mg-&$^ceJ9gUToNz5_hY{Qwa;0kBC#+2{#;iO9EU6^p(>!$n7(*O)#>|e zZyJhAqIJux{LyVS7O6!$ z_TU_kCTp>w>1Hp|~WD zyr4z-i>+_9NJV0Q5SxSGIAlo)RRNvGZx=Kzb-C~Bn%Il&npNwRU#`2xA~htSW2Zh@ zQbJWgr*Xo&ZA$$nFEkCsCDFTgtMU%x2AD>Xn7*VJa%vE@(b^FHjN_j zClF&mP(zlKP!-T=alo;X(%pt@K-bVTj4C~b7t>M5}o+k@M*D?j|VyGF6MfncxJ0r8i6S5Jv8DWU43 z8wZKycdXt&_KNhX7%31O$3hUdk668bY)J{Y~%w0Z&vFcDC4yZT~!)Mv$dVGOE;@~q zurf@`r|JOG>B;=rmr%({vG@s@RKde=fC1MdBq8r-9%&WJw8C7oEoXSQQS#dJ=oF?a$X1 zsYtW{u>}Zf$dVGOE;@~SvAVsCbusp0+vf=usYu)jq6OAPYRHljsxCT>PmtqWf%QH1 zV%uj$7O6R5aXLQNuO*600{2voe9$zC1oFWeSTd2?5PW{@Q%R9$o$ zUm=_S6Lz-Pi|ylE*5cmkVH!mO`Cu*bK^uoGDWNK$(?C90i+s>D6qm$DUrou851K}i zKt5QDe9$yxNeNW}oknN8T^Z)jqKZqR$6k|j)nI3fz1SZ74r-9s-fS90;!hyP z``kd5lu&iiX?%dUc9XCd#$IeMJ@}a%`JibOiJd_F340;$U0G5>)kUXK!rRBK?{T@B z;*!{F{b%xj9rNz?j8r5Z0&&G~m#fK=5~>0^4HDz;aXF{rM!l+`Mv*9k_#9bkY)J{P zjVc*ehFvl8?~w_|UTpj8VH!nZ5r~d>#~Y2XCM8r|bQ*nd1zX@3{;?O^{=PDeBGDQ| zfBz0ZmXuI+(P^BBJMj+xE<SB#6A9Y~#?K3I!<(B?{UN%(ceG>QcB!CK^lHV#=*LRCPgaWPi7>Dbv~ zFSh-#UtsOf@g*@h}%H$zLF&+R0VYV z`3Q`9oPUR`xFr0!#3B`m>p_eFK@C|_Le)j5@g}Z!cU;BTi*0`nvPgm8%7A>Z2KivY zk`n$*ggOq1_iz_Jjk_`SV%wkREK-q}0^&3f9EU6^p(>!$n2M)qZ#-487u)_^YLSY> zGa&FLr(j75RTrH`Gpr+8r4KoFRdkw5)^CDtC-M?(687l?H`P*b-q!JYNDx^c7sL1d4qN$hm^F|4ZHRx4LXh!H)?3F)@)-bNlaE)qI6HUEza%z;ISc6{0 zgl2?()+;uQ(CMCWby{vzqgq7lrL%9N1c3&uu6@1;gHs>!Bae&kh+ z5(FCbDkd}|^egsC88+kk>-@j;D)v(T`G4vKYa|-X zRZM6`8um)n9>!HwW{ny)^4Lo?mNgO$dKD9zk%qley^e8JS6ico^(yvKm28bfgI>ji zW~5=SRB>foRYupSVZDmIROehH(V$l`p&4n|D_{G!)mgReHELL|VlP#c*GM$zRZM6` z3cZRoY!npc=$r?4RSFx8Lxs6HBe?g-` zuVO;q11S)up>GJWC;BGO|Jw`Ziso8r4KoFMT(zQG#L(dKD9z5&9K-rR;}s^?k@j zHL8iGUix-pqXfko^eQGaBj%VL`N$rn`|yThaGRSf??eA9y*Ifu|6WFn{`OG=1#8m+&4EdPa?{Y~Mr0zWAxlb#b{e;y^Lf7EhNqc^EF|vg-mLs(d8ujWbEhGK z8nUE>Xs2<(^)1W){G^v@$U@?Wr&^RhZ2P8Z=yRtbf*P`KL!Ubh5!8?+B}6-oP92UYFPOiXX~;t2^T*niPw0HR zY3OsOA%YsRq=aavanmQg%KffyX&SPSXtU?~<-=PKG7Ww1G(=ECmXr|fG`8LFv~sU= zKDQM|781t}+MxXG{640k&z*({YRHljqMgRsSD#n@Y{he?Aq$D$-L+wP$Y;BohCX*1 zBB&utN{DtElXkeceEZ7brXdT7A%EGZ{LBN*O+%kM4H49kB_%{Vjo~XUEAMxIU(=9< z#FOndF7GsULJcG7bEhF9wxon;r_p`RKg!RJZDty>kho;P#^pD9?`<0T+-ZoQhAb%| z+G$|7DffP4kJ{La?QL${xO~9|&y8ZF0+ES`Eh!%ty~6iJ z@xN+6@;vrp+s}e&6o^bjG>)p25bZSl6*LW5Ncih)z18PNWFn{`OG=1#8vaf+4OvL| zyV5lDxzi9q4Ovn`wA1iUlxfI9!arT6q0gO$2x`cZ5~7`kUl~k877~7yFb#d~G(=EC zmXr|fH2exzj=k9StDR{Sh)e`EWJw9pPQ$O%rXdRnziOL?K6m2~K@C|_LbTKHJC13{ zLc;GprlHTBh6rlNk`khwhTl0&LlzQ#7c~ui?leSDLza{f?KJ!ez%*nb;ZFmmq0gO$ z2x`cZ5~7`kKgpPeEF}D?#x(S~(-1)oSyDo@)9@!U(~yOPKb@I|K6e@-s3A*Ah;|zO zWN8|*knpEW)6nNmLj*NsNeR(T!=I2%LlzSLv}_vs+-ZoQhAb%|+G+TczG=up!si3{ z-9i279N(Jc|3t)=ln`xzaval;g@n(9Ohca=p&X}74Ovn`wA1jJmubjCf^Q0tg_(vv zcN!w7Axlb#b{gklpS9gHic6x$xgRpSGmQd~iJ*ooDIwZvAiJv|AGC4ELgMH+-ZoQhAb%|+G(7C_c8n8y-e)I_M#sj%aIS7MuEsgP(zlK5bZQx$9tu(zZ-5E zvXI#A&_{FRgQlU+-8e*0Lza{f?KJ+0_hUEXJz4C<_LA0*QxOR>GL8HTT;UJ3f~vy|NO`+u@~EZ7EGgYA{s|cN{DtE{tB9g zEF}EZtYsv9UL;~mN{DuZzY|SE783rhG!1=TB&Z=vN{DuZf1*r7783sHG7WuRB&Z=v zN{DuZUl~k877~7yFb#cPB&Z=vN{DuZU*YOvFSh+^XBv$Y)Q}}5L^}B>bvv z8v49QP(zlK5bX%Re7U8v49QP(zlK5bX%RbDD-MB>XOF8v49QP(zlK5bX$m z0x%6(Nchu$Y3TDJK@C|_LbN0NNyao}A>mIorlHS^1T|zy3DJ)5Co9FgWFg_R7t_$^MS>c#q=aZk_{_^RWFg_RFw@ZIMS>c# zq=aZAs#Wcaz1a5Iq-n%Nbse~Bviw%5DwXj!O!fWL`fp~6sBX*)-;=5Tu4;jxgb`Gy zMR7&w`>EzA4eOPvx+GNF6kX7$mPYKQ%4nkm#Y`jaRZM6`=vSO8)wUT|b>K!ds)?pv zsv|(VnQ=QzhbZSJpjg4&Ad^KYNDx^D(;OE6l>6{n9z*Suh=VnXM%C{{eng{ zs)?pv`gTI21jQQkDkd}|^egsC-!EZYeHWuqjcTH)m%ho-C_%9Xy^0CV2>q;A)`&E! z*w*(}8r7&KntJJ5FO3q^Xwa*e(2UTp*eiW+iE(}3OO0w&6HUGR$Zbq|(h3?4dKDA; zc2a>b4SmOpdj2~9FTIMr{9V#GL9qtCiV4j~!(Qq8aO{psz(65OWSKqVypRZu-AK#?|+N*QjB=ioGyu`;Ge=i3Yuj3C&2uUg>+`jH~ZZuTjH# z6?^F$*lQ#j^eQGaBMp0{Z`w1izHh!p4eM3xrEjaRk!aAXn9z(g?3I3Rz_|Ku{u(u` zSFxAA*}q1jL9b#$Gt#hEUc-LjsNXR(s!>grme@!Bae&jZ$|Jyzo6Pgi5F80dbyVfgzovqzKqna#x6?^%+q;Y~kgSm~rpZ||u#e`<0 hVXu_^uqS@4ZCs<8Z0hA#>c$BI4dyB)G$UE|{{S)ba?t<) literal 0 HcmV?d00001 diff --git a/src/igvc_description/urdf/otr.urdf.xacro b/src/igvc_description/urdf/otr.urdf.xacro new file mode 100644 index 00000000..0d08e1a6 --- /dev/null +++ b/src/igvc_description/urdf/otr.urdf.xacro @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7712f8dd514a1c54ad6e7a33b07daa11d9e6908e Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:24:59 +0000 Subject: [PATCH 11/80] Add onshape-to-robot config file --- src/igvc_description/config.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/igvc_description/config.json diff --git a/src/igvc_description/config.json b/src/igvc_description/config.json new file mode 100644 index 00000000..cf9661c4 --- /dev/null +++ b/src/igvc_description/config.json @@ -0,0 +1,4 @@ +{ + "url": "https://cad.onshape.com/documents/bd8c1cbb3cbd66454183450f/w/dfd94c9f83ff0fc6c8761109/e/c765fc0d53504544c054bc0a", + "output_format": "urdf" +} From 9c9a979c587627b5f4176261140a43935102bdcb Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:33:35 +0000 Subject: [PATCH 12/80] Rename otr urdf to model --- src/igvc_description/urdf/{otr.urdf.xacro => model.urdf.xacro} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/igvc_description/urdf/{otr.urdf.xacro => model.urdf.xacro} (98%) diff --git a/src/igvc_description/urdf/otr.urdf.xacro b/src/igvc_description/urdf/model.urdf.xacro similarity index 98% rename from src/igvc_description/urdf/otr.urdf.xacro rename to src/igvc_description/urdf/model.urdf.xacro index 0d08e1a6..3be3d103 100644 --- a/src/igvc_description/urdf/otr.urdf.xacro +++ b/src/igvc_description/urdf/model.urdf.xacro @@ -1,7 +1,7 @@ - + From 80343c07ca984aee0ba4f090eb9c5dbf63df5506 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:33:54 +0000 Subject: [PATCH 13/80] Create robot urdf file --- src/igvc_description/urdf/robot.urdf.xacro | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/igvc_description/urdf/robot.urdf.xacro diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro new file mode 100644 index 00000000..173d46ff --- /dev/null +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 05cd416f5c759efb71c9e1a4eaaea5b3cdca5230 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:34:23 +0000 Subject: [PATCH 14/80] Add rviz launch files --- src/igvc_description/launch/rviz.launch.py | 110 +++++++++++++ src/igvc_description/rviz/rviz_settings.rviz | 164 +++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 src/igvc_description/launch/rviz.launch.py create mode 100644 src/igvc_description/rviz/rviz_settings.rviz diff --git a/src/igvc_description/launch/rviz.launch.py b/src/igvc_description/launch/rviz.launch.py new file mode 100644 index 00000000..647d06b5 --- /dev/null +++ b/src/igvc_description/launch/rviz.launch.py @@ -0,0 +1,110 @@ +import os +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.conditions import IfCondition, UnlessCondition +from launch.substitutions import Command, LaunchConfiguration +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + +def generate_launch_description(): + + # Set the path to this package. + pkg_share = FindPackageShare(package='igvc_description').find('igvc_description') + + # Set the path to the RViz configuration settings + default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz_settings.rviz') + + # Set the path to the URDF file + default_urdf_model_path = os.path.join(pkg_share, 'urdf/robot.urdf.xacro') + + # Launch configuration variables specific to simulation + gui = LaunchConfiguration('gui') + urdf_model = LaunchConfiguration('urdf_model') + rviz_config_file = LaunchConfiguration('rviz_config_file') + use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') + use_rviz = LaunchConfiguration('use_rviz') + use_sim_time = LaunchConfiguration('use_sim_time') + + # Declare the launch arguments + declare_urdf_model_path_cmd = DeclareLaunchArgument( + name='urdf_model', + default_value=default_urdf_model_path, + description='Absolute path to robot urdf file') + + declare_rviz_config_file_cmd = DeclareLaunchArgument( + name='rviz_config_file', + default_value=default_rviz_config_path, + description='Full path to the RVIZ config file to use') + + declare_use_joint_state_publisher_cmd = DeclareLaunchArgument( + name='gui', + default_value='True', + description='Flag to enable joint_state_publisher_gui') + + declare_use_robot_state_pub_cmd = DeclareLaunchArgument( + name='use_robot_state_pub', + default_value='True', + description='Whether to start the robot state publisher') + + declare_use_rviz_cmd = DeclareLaunchArgument( + name='use_rviz', + default_value='true', + description='Whether to start RVIZ') + + declare_use_sim_time_cmd = DeclareLaunchArgument( + name='use_sim_time', + default_value='True', + description='Use simulation (Gazebo) clock if true') + + # Specify the actions + + # Publish the joint state values for the non-fixed joints in the URDF file. + start_joint_state_publisher_cmd = Node( + condition=UnlessCondition(gui), + package='joint_state_publisher', + executable='joint_state_publisher', + name='joint_state_publisher') + + # A GUI to manipulate the joint state values + start_joint_state_publisher_gui_node = Node( + condition=IfCondition(gui), + package='joint_state_publisher_gui', + executable='joint_state_publisher_gui', + name='joint_state_publisher_gui') + + # Subscribe to the joint states of the robot, and publish the 3D pose of each link. + start_robot_state_publisher_cmd = Node( + condition=IfCondition(use_robot_state_pub), + package='robot_state_publisher', + executable='robot_state_publisher', + parameters=[{'use_sim_time': use_sim_time, + 'robot_description': Command(['xacro ', urdf_model])}], + arguments=[default_urdf_model_path]) + + # Launch RViz + start_rviz_cmd = Node( + condition=IfCondition(use_rviz), + package='rviz2', + executable='rviz2', + name='rviz2', + output='screen', + arguments=['-d', rviz_config_file]) + + # Create the launch description and populate + ld = LaunchDescription() + + # Declare the launch options + ld.add_action(declare_urdf_model_path_cmd) + ld.add_action(declare_rviz_config_file_cmd) + ld.add_action(declare_use_joint_state_publisher_cmd) + ld.add_action(declare_use_robot_state_pub_cmd) + ld.add_action(declare_use_rviz_cmd) + ld.add_action(declare_use_sim_time_cmd) + + # Add any actions + ld.add_action(start_joint_state_publisher_cmd) + ld.add_action(start_joint_state_publisher_gui_node) + ld.add_action(start_robot_state_publisher_cmd) + ld.add_action(start_rviz_cmd) + + return ld diff --git a/src/igvc_description/rviz/rviz_settings.rviz b/src/igvc_description/rviz/rviz_settings.rviz new file mode 100644 index 00000000..2db5104b --- /dev/null +++ b/src/igvc_description/rviz/rviz_settings.rviz @@ -0,0 +1,164 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + Splitter Ratio: 0.5 + Tree Height: 617 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Alpha: 1 + Class: rviz_default_plugins/RobotModel + Collision Enabled: false + Description File: "" + Description Source: Topic + Description Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /robot_description + Enabled: true + Links: + All Links Enabled: true + Expand Joint Details: false + Expand Link Details: false + Expand Tree: false + Link Tree Style: Links in Alphabetic Order + base_footprint: + Alpha: 1 + Show Axes: false + Show Trail: false + base_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + box_rotate: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + imu_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Name: RobotModel + TF Prefix: "" + Update Interval: 0 + Value: true + Visual Enabled: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: base_footprint + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 10 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.785398006439209 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.785398006439209 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 846 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd000000040000000000000156000002f4fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002f4000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002f4fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000002f4000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d006501000000000000045000000000000000000000023f000002f400000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1200 + X: 72 + Y: 60 From 5841444abf2533d4e3023dbdaaf404881986d89d Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:46:30 +0000 Subject: [PATCH 15/80] Change hardware package name to igvc_hardware --- src/igvc_hardware/package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/igvc_hardware/package.xml b/src/igvc_hardware/package.xml index 825257a0..91a5977e 100644 --- a/src/igvc_hardware/package.xml +++ b/src/igvc_hardware/package.xml @@ -1,7 +1,7 @@ - hardware + igvc_hardware 0.0.0 The hardware superstructure for the IGVC bot MIT From ab2a8be88786bdc1f1994393e7e3d61cf70db9d5 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:49:08 +0000 Subject: [PATCH 16/80] Correct project name in slam package cmakelist --- src/igvc_slam/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/igvc_slam/CMakeLists.txt b/src/igvc_slam/CMakeLists.txt index d6559773..97eea4ba 100644 --- a/src/igvc_slam/CMakeLists.txt +++ b/src/igvc_slam/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(igvc_description) +project(igvc_slam) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) From d4cf3093e823757ae4258332c8cd3fbc85327a4d Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:52:00 +0000 Subject: [PATCH 17/80] Correct meshes to assets in description cmake --- src/igvc_description/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/igvc_description/CMakeLists.txt b/src/igvc_description/CMakeLists.txt index 68df5315..e863018c 100644 --- a/src/igvc_description/CMakeLists.txt +++ b/src/igvc_description/CMakeLists.txt @@ -14,7 +14,7 @@ install( ) install( - DIRECTORY meshes + DIRECTORY assets DESTINATION share/${PROJECT_NAME} ) From 14535e22e7f720ae1aeef25a189c58ef2ab9351d Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 03:52:40 +0000 Subject: [PATCH 18/80] Remove references to deprecated urdf file --- src/igvc_hardware/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/igvc_hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt index b01740e5..8b1ea367 100644 --- a/src/igvc_hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -44,11 +44,6 @@ install( FILES_MATCHING PATTERN "*.hpp" ) -install( - FILES rrc_igvc_bot.xml - DESTINATION share/${PROJECT_NAME} -) - install( DIRECTORY launch DESTINATION share/${PROJECT_NAME} @@ -61,9 +56,6 @@ install(TARGETS ${PROJECT_NAME}_library RUNTIME DESTINATION bin ) -# Plugin Export -pluginlib_export_plugin_description_file(hardware_interface rrc_igvc_bot.xml) - ament_export_targets(export_${PROJECT_NAME}_library HAS_LIBRARY_TARGET) ament_export_dependencies( controller_manager From 2401e563c67d07b8d739667727e4644cab35c584 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:05:53 +0000 Subject: [PATCH 19/80] add package name to URDF meshes (automate this in the future) --- src/igvc_description/urdf/model.urdf.xacro | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/igvc_description/urdf/model.urdf.xacro b/src/igvc_description/urdf/model.urdf.xacro index 3be3d103..269d91f2 100644 --- a/src/igvc_description/urdf/model.urdf.xacro +++ b/src/igvc_description/urdf/model.urdf.xacro @@ -13,7 +13,7 @@ - + @@ -22,14 +22,14 @@ - + - + @@ -38,14 +38,14 @@ - + - + @@ -54,14 +54,14 @@ - + - + @@ -70,14 +70,14 @@ - + - + @@ -86,14 +86,14 @@ - + - + @@ -102,14 +102,14 @@ - + - + @@ -118,14 +118,14 @@ - + - + @@ -134,7 +134,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -190,7 +190,7 @@ - + From 9d30abc947c1db215e1840822ccb3d708f7f0a17 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:29:09 +0000 Subject: [PATCH 20/80] Remove nonexistent adi rosdep --- src/igvc_hardware/package.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/igvc_hardware/package.xml b/src/igvc_hardware/package.xml index 91a5977e..52ca697e 100644 --- a/src/igvc_hardware/package.xml +++ b/src/igvc_hardware/package.xml @@ -17,7 +17,6 @@ pluginlib rclcpp rclcpp_lifecycle - adi_imu depthai_ros_driver backward_ros From a532c895e9e2c804be5113fc96b9e8e68a37c23d Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:30:22 +0000 Subject: [PATCH 21/80] Remove nonexistent odrive rosdep --- src/igvc_hardware/package.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/igvc_hardware/package.xml b/src/igvc_hardware/package.xml index 52ca697e..ef6c01f5 100644 --- a/src/igvc_hardware/package.xml +++ b/src/igvc_hardware/package.xml @@ -30,7 +30,6 @@ ros2controlcli ros2launch rviz2 - odrive_ros2_control xacro From 9832dd0a41fa01a900abe82ecb2371e954a7cb9b Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:38:41 +0000 Subject: [PATCH 22/80] Add gz and gz control plugin to gazebo dependencies --- src/igvc_gazebo/package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/igvc_gazebo/package.xml b/src/igvc_gazebo/package.xml index 9060e469..8929bd68 100644 --- a/src/igvc_gazebo/package.xml +++ b/src/igvc_gazebo/package.xml @@ -9,6 +9,9 @@ ament_cmake + gz_ros2_control + ros_gz + ament_lint_auto ament_lint_common From 5c2935eb81189a322509e9061b0073b225471cbb Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:40:13 +0000 Subject: [PATCH 23/80] Add directories to gazebo cmake --- src/igvc_gazebo/CMakeLists.txt | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/igvc_gazebo/CMakeLists.txt b/src/igvc_gazebo/CMakeLists.txt index f81f4de3..69502a97 100644 --- a/src/igvc_gazebo/CMakeLists.txt +++ b/src/igvc_gazebo/CMakeLists.txt @@ -1,26 +1,33 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.16) project(igvc_gazebo) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) + add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) + +install( + DIRECTORY worlds + DESTINATION share/${PROJECT_NAME} +) if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # comment the line when a copyright and license is added to all source files - set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # comment the line when this package is in a git repo and when - # a copyright and license is added to all source files - set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() endif() ament_package() From a71e39c5d90774ee839f720357ae1308bb411dfb Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 04:40:36 +0000 Subject: [PATCH 24/80] Implement empty world files --- src/igvc_gazebo/launch/empty_world.launch.py | 80 ++++++++++++++++++ src/igvc_gazebo/worlds/empty_world.sdf | 89 ++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 src/igvc_gazebo/launch/empty_world.launch.py create mode 100644 src/igvc_gazebo/worlds/empty_world.sdf diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py new file mode 100644 index 00000000..2dff1864 --- /dev/null +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -0,0 +1,80 @@ +import os + +from ament_index_python.packages import get_package_share_directory + + +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration + +from launch_ros.actions import Node + +import xacro + +def generate_launch_description(): + pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') + + gz_launch_path = os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py') + + world = LaunchConfiguration('world') + + default_world = os.path.join( + get_package_share_directory('igvc_gazebo'), + 'worlds', + 'empty_world.sdf' + ) + + world_arg = DeclareLaunchArgument( + 'world', + default_value=default_world, + description='World to load' + ) + + gazebo = IncludeLaunchDescription( + PythonLaunchDescriptionSource(gz_launch_path), + launch_arguments={'gz_args': ['-r -v4 ', world], 'on_exit_shutdown': 'true'}.items() + ) + + urdf_path = os.path.join( + get_package_share_directory('igvc_description')) + + xacro_file = os.path.join(urdf_path, + 'urdf', + 'robot.urdf.xacro') + + doc = xacro.parse(open(xacro_file)) + xacro.process_doc(doc) + params = {'robot_description': doc.toxml()} + + node_robot_state_publisher = Node( + package='robot_state_publisher', + executable='robot_state_publisher', + output='screen', + parameters=[params] + ) + + spawn_entity = Node(package='ros_gz_sim', executable='create', + arguments=['-topic', 'robot_description', + '-name', 'rover'], + output='screen' + ) + + bridge_params = os.path.join(get_package_share_directory('igvc_gazebo'), 'config', 'gz_bridge.yaml') + ros_gz_bridge = Node( + package="ros_gz_bridge", + executable="parameter_bridge", + arguments=[ + '--ros-args', + '-p', + f'config_file:={bridge_params}', + ] + ) + + return LaunchDescription([ + world_arg, + gazebo, + node_robot_state_publisher, + spawn_entity, + ros_gz_bridge + ]) diff --git a/src/igvc_gazebo/worlds/empty_world.sdf b/src/igvc_gazebo/worlds/empty_world.sdf new file mode 100644 index 00000000..72a7f56a --- /dev/null +++ b/src/igvc_gazebo/worlds/empty_world.sdf @@ -0,0 +1,89 @@ + + + + 0.001 + 1 + 1000 + + + + + + 0 0 -9.8000000000000007 + 5.5644999999999998e-06 2.2875799999999999e-05 -4.2388400000000002e-05 + + + 0.400000006 0.400000006 0.400000006 1 + 0.699999988 0.699999988 0.699999988 1 + true + + + true + + + + + 0 0 1 + 100 100 + + + + + + + + + + + + + + 0 0 1 + 100 100 + + + + 0.800000012 0.800000012 0.800000012 1 + 0.800000012 0.800000012 0.800000012 1 + 0.800000012 0.800000012 0.800000012 1 + + + 0 0 0 0 0 0 + + 0 0 0 0 0 0 + 1 + + 1 + 0 + 0 + 1 + 0 + 1 + + + false + + 0 0 0 0 0 0 + false + + + 0 0 10 0 0 0 + true + 1 + -0.5 0.10000000000000001 -0.90000000000000002 + 0.800000012 0.800000012 0.800000012 1 + 0.200000003 0.200000003 0.200000003 1 + + 1000 + 0.01 + 0.90000000000000002 + 0.001 + + + 0 + 0 + 0 + + + + From a585e08f10520933151b40b0d1da2570dd27c563 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 05:12:02 +0000 Subject: [PATCH 25/80] Rename robot in launch file --- src/igvc_gazebo/launch/empty_world.launch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index 2dff1864..4dcce882 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -56,11 +56,12 @@ def generate_launch_description(): spawn_entity = Node(package='ros_gz_sim', executable='create', arguments=['-topic', 'robot_description', - '-name', 'rover'], + '-name', 'igvc_robot'], output='screen' ) bridge_params = os.path.join(get_package_share_directory('igvc_gazebo'), 'config', 'gz_bridge.yaml') + ros_gz_bridge = Node( package="ros_gz_bridge", executable="parameter_bridge", From 64c88f48bbed139d1cfd979b9d98b56f4b59ecb2 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 05:12:38 +0000 Subject: [PATCH 26/80] Placeholder sensor simulation and bridge configuration --- src/igvc_description/urdf/control.urdf.xacro | 156 +++++++++++++++++++ src/igvc_description/urdf/robot.urdf.xacro | 15 +- src/igvc_description/urdf/sensors.urdf.xacro | 61 ++++++++ src/igvc_gazebo/config/gz_bridge.yaml | 40 +++++ 4 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 src/igvc_description/urdf/control.urdf.xacro create mode 100644 src/igvc_description/urdf/sensors.urdf.xacro create mode 100644 src/igvc_gazebo/config/gz_bridge.yaml diff --git a/src/igvc_description/urdf/control.urdf.xacro b/src/igvc_description/urdf/control.urdf.xacro new file mode 100644 index 00000000..346c0444 --- /dev/null +++ b/src/igvc_description/urdf/control.urdf.xacro @@ -0,0 +1,156 @@ + + + + + + + gz_ros2_control/GazeboSimSystem + + + + -10 + 10 + + + + + + + + + -10 + 10 + + + + + + + + + -10 + 10 + + + + + + + + + -10 + 10 + + + + + + + + + -10 + 10 + + + + + + + + + -10 + 10 + + + + + + + + + + -5 + 5 + + + -0.1 + 0.1 + + + + + + + + + + -5 + 5 + + + -0.1 + 0.1 + + + + + + + + -5 + 5 + + + -0.1 + 0.1 + + + + + + + + -5 + 5 + + + -0.1 + 0.1 + + + + + + + + + + + $(find osr_gazebo)/config/controller_velocity.yaml + + + + + true + + true + 100 + true + imu_link + imu + + imu + imu_link + 100.0 + 0.0 + 0 0 0 + 0 0 0 + imu_link + false + + 0 0 0 0 0 0 + + + + + \ No newline at end of file diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index 173d46ff..055e9afc 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -1,8 +1,17 @@ - + + + + + + + + + + - - + + \ No newline at end of file diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro new file mode 100644 index 00000000..f65023b4 --- /dev/null +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + 0 0 0 0 0 0 + oak/depth + 30 + 1 + camera_link_optical + + 1.51843645 + + R_FLOAT32 + 1280 + 720 + + + 0.28 + 10 + + + + + + + + + 0 0 0 0 0 0 + oak/color + 30 + 1 + camera_link_optical + + 1.51843645 + + R8G8B8 + 1280 + 720 + + + 0.05 + 100 + + + + + \ No newline at end of file diff --git a/src/igvc_gazebo/config/gz_bridge.yaml b/src/igvc_gazebo/config/gz_bridge.yaml new file mode 100644 index 00000000..69d5baaf --- /dev/null +++ b/src/igvc_gazebo/config/gz_bridge.yaml @@ -0,0 +1,40 @@ +- ros_topic_name: "clock" + gz_topic_name: "clock" + ros_type_name: "rosgraph_msgs/msg/Clock" + gz_type_name: "gz.msgs.Clock" + direction: GZ_TO_ROS + +- ros_topic_name: "imu_plugin/out" + gz_topic_name: "imu" + ros_type_name: "sensor_msgs/msg/Imu" + gz_type_name: "gz.msgs.IMU" + direction: GZ_TO_ROS + qos_profile: SENSOR_DATA + +# D435i Depth Camera + +- ros_topic_name: "camera/camera/color/image_raw" + gz_topic_name: "d435i/color" + ros_type_name: "sensor_msgs/msg/Image" + gz_type_name: "gz.msgs.Image" + direction: "GZ_TO_ROS" + qos_profile: SENSOR_DATA + +- ros_topic_name: "camera/camera/depth/image_rect_raw" + gz_topic_name: "d435i/depth" + ros_type_name: "sensor_msgs/msg/Image" + gz_type_name: "gz.msgs.Image" + direction: GZ_TO_ROS + qos_profile: SENSOR_DATA + +- ros_topic_name: "camera/camera/depth/camera_info" #TODO: verify this matches real ros topic + gz_topic_name: "d435i/camera_info" + ros_type_name: "sensor_msgs/msg/CameraInfo" + gz_type_name: "gz.msgs.CameraInfo" + direction: GZ_TO_ROS + +- ros_topic_name: "camera/camera/depth/color/points" + gz_topic_name: "d435i/depth/points" + ros_type_name: "sensor_msgs/msg/PointCloud2" + gz_type_name: "gz.msgs.PointCloudPacked" + direction: GZ_TO_ROS \ No newline at end of file From d2c3e2901fb7bd45610b12ba65e7960726a254c9 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 15:30:11 +0000 Subject: [PATCH 27/80] Add config directory to gazebo cmake --- src/igvc_gazebo/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/igvc_gazebo/CMakeLists.txt b/src/igvc_gazebo/CMakeLists.txt index 69502a97..571a34bf 100644 --- a/src/igvc_gazebo/CMakeLists.txt +++ b/src/igvc_gazebo/CMakeLists.txt @@ -18,6 +18,11 @@ install( DESTINATION share/${PROJECT_NAME} ) +install( + DIRECTORY config + DESTINATION share/${PROJECT_NAME} +) + if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # the following line skips the linter which checks for copyrights From a5e5afd4351e30a626a7a0091e3ee5d4bfad0d8b Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 16:28:44 +0000 Subject: [PATCH 28/80] Split rviz and publisher into separate launches --- .../launch/publisher.launch.py | 83 +++++++++++++++++++ src/igvc_description/launch/rviz.launch.py | 46 ++++------ 2 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 src/igvc_description/launch/publisher.launch.py diff --git a/src/igvc_description/launch/publisher.launch.py b/src/igvc_description/launch/publisher.launch.py new file mode 100644 index 00000000..7b62d70a --- /dev/null +++ b/src/igvc_description/launch/publisher.launch.py @@ -0,0 +1,83 @@ +import os +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.conditions import IfCondition, UnlessCondition +from launch.substitutions import Command, LaunchConfiguration +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + +def generate_launch_description(): + + # Set the path to this package. + pkg_share = FindPackageShare(package='igvc_description').find('igvc_description') + + # Set the path to the URDF file + default_urdf_model_path = os.path.join(pkg_share, 'urdf/robot.urdf.xacro') + + # Launch configuration variables specific to simulation + gui = LaunchConfiguration('gui') + urdf_model = LaunchConfiguration('urdf_model') + use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') + use_sim_time = LaunchConfiguration('use_sim_time') + + # Declare the launch arguments + declare_urdf_model_path_cmd = DeclareLaunchArgument( + name='urdf_model', + default_value=default_urdf_model_path, + description='Absolute path to robot urdf file') + + declare_use_joint_state_publisher_cmd = DeclareLaunchArgument( + name='gui', + default_value='False', + description='Flag to enable joint_state_publisher_gui') + + declare_use_robot_state_pub_cmd = DeclareLaunchArgument( + name='use_robot_state_pub', + default_value='True', + description='Whether to start the robot state publisher') + + declare_use_sim_time_cmd = DeclareLaunchArgument( + name='use_sim_time', + default_value='False', + description='Use simulation (Gazebo) clock if true') + + # Specify the actions + + # Publish the joint state values for the non-fixed joints in the URDF file. + start_joint_state_publisher_cmd = Node( + condition=UnlessCondition(gui), + package='joint_state_publisher', + executable='joint_state_publisher', + name='joint_state_publisher') + + # A GUI to manipulate the joint state values + start_joint_state_publisher_gui_node = Node( + condition=IfCondition(gui), + package='joint_state_publisher_gui', + executable='joint_state_publisher_gui', + name='joint_state_publisher_gui') + + # Subscribe to the joint states of the robot, and publish the 3D pose of each link. + start_robot_state_publisher_cmd = Node( + condition=IfCondition(use_robot_state_pub), + package='robot_state_publisher', + executable='robot_state_publisher', + parameters=[{'use_sim_time': use_sim_time, + 'robot_description': Command(['xacro ', urdf_model])}], + arguments=[default_urdf_model_path]) + + # Create the launch description and populate + ld = LaunchDescription() + + # Declare the launch options + ld.add_action(declare_urdf_model_path_cmd) + ld.add_action(declare_use_joint_state_publisher_cmd) + ld.add_action(declare_use_robot_state_pub_cmd) + ld.add_action(declare_use_sim_time_cmd) + + # Add any actions + ld.add_action(start_joint_state_publisher_cmd) + ld.add_action(start_joint_state_publisher_gui_node) + ld.add_action(start_robot_state_publisher_cmd) + + return ld diff --git a/src/igvc_description/launch/rviz.launch.py b/src/igvc_description/launch/rviz.launch.py index 647d06b5..98257bfe 100644 --- a/src/igvc_description/launch/rviz.launch.py +++ b/src/igvc_description/launch/rviz.launch.py @@ -1,6 +1,7 @@ import os from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.conditions import IfCondition, UnlessCondition from launch.substitutions import Command, LaunchConfiguration from launch_ros.actions import Node @@ -13,10 +14,13 @@ def generate_launch_description(): # Set the path to the RViz configuration settings default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz_settings.rviz') - + # Set the path to the URDF file default_urdf_model_path = os.path.join(pkg_share, 'urdf/robot.urdf.xacro') + # Set the path to the Publisher launch file + publisher_launch_path = os.path.join(pkg_share, 'launch/publisher.launch.py') + # Launch configuration variables specific to simulation gui = LaunchConfiguration('gui') urdf_model = LaunchConfiguration('urdf_model') @@ -56,30 +60,16 @@ def generate_launch_description(): default_value='True', description='Use simulation (Gazebo) clock if true') - # Specify the actions - - # Publish the joint state values for the non-fixed joints in the URDF file. - start_joint_state_publisher_cmd = Node( - condition=UnlessCondition(gui), - package='joint_state_publisher', - executable='joint_state_publisher', - name='joint_state_publisher') - - # A GUI to manipulate the joint state values - start_joint_state_publisher_gui_node = Node( - condition=IfCondition(gui), - package='joint_state_publisher_gui', - executable='joint_state_publisher_gui', - name='joint_state_publisher_gui') - - # Subscribe to the joint states of the robot, and publish the 3D pose of each link. - start_robot_state_publisher_cmd = Node( - condition=IfCondition(use_robot_state_pub), - package='robot_state_publisher', - executable='robot_state_publisher', - parameters=[{'use_sim_time': use_sim_time, - 'robot_description': Command(['xacro ', urdf_model])}], - arguments=[default_urdf_model_path]) + # Specify the publisher action + start_publisher_cmd = IncludeLaunchDescription( + PythonLaunchDescriptionSource(publisher_launch_path), + launch_arguments={ + 'urdf_model' : urdf_model, + 'gui' : gui, + 'use_robot_state_pub' : use_robot_state_pub, + 'use_sim_time' : use_sim_time, + }.items() + ) # Launch RViz start_rviz_cmd = Node( @@ -102,9 +92,7 @@ def generate_launch_description(): ld.add_action(declare_use_sim_time_cmd) # Add any actions - ld.add_action(start_joint_state_publisher_cmd) - ld.add_action(start_joint_state_publisher_gui_node) - ld.add_action(start_robot_state_publisher_cmd) + ld.add_action(start_publisher_cmd) ld.add_action(start_rviz_cmd) return ld From a3e3e1c92c362d8376882539b41ec76cc851d09b Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 16:43:32 +0000 Subject: [PATCH 29/80] Include publisher launch file in gazebo launch --- src/igvc_gazebo/launch/empty_world.launch.py | 24 +++++--------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index 4dcce882..32f67b69 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -2,11 +2,11 @@ from ament_index_python.packages import get_package_share_directory - from launch import LaunchDescription from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration +from launch_ros.substitutions import FindPackageShare from launch_ros.actions import Node @@ -14,8 +14,10 @@ def generate_launch_description(): pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') + pkg_description = FindPackageShare(package='igvc_description').find('igvc_description') gz_launch_path = os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py') + publisher_launch_path = os.path.join(pkg_description, 'launch/publisher.launch.py') world = LaunchConfiguration('world') @@ -36,22 +38,8 @@ def generate_launch_description(): launch_arguments={'gz_args': ['-r -v4 ', world], 'on_exit_shutdown': 'true'}.items() ) - urdf_path = os.path.join( - get_package_share_directory('igvc_description')) - - xacro_file = os.path.join(urdf_path, - 'urdf', - 'robot.urdf.xacro') - - doc = xacro.parse(open(xacro_file)) - xacro.process_doc(doc) - params = {'robot_description': doc.toxml()} - - node_robot_state_publisher = Node( - package='robot_state_publisher', - executable='robot_state_publisher', - output='screen', - parameters=[params] + start_publisher_cmd = IncludeLaunchDescription( + PythonLaunchDescriptionSource(publisher_launch_path) ) spawn_entity = Node(package='ros_gz_sim', executable='create', @@ -75,7 +63,7 @@ def generate_launch_description(): return LaunchDescription([ world_arg, gazebo, - node_robot_state_publisher, + start_publisher_cmd, spawn_entity, ros_gz_bridge ]) From 5b33e4e1ce0818bd79fd49d764636accaeeebcee Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 16:47:35 +0000 Subject: [PATCH 30/80] Remove unused imports --- src/igvc_description/launch/rviz.launch.py | 4 ++-- src/igvc_gazebo/launch/empty_world.launch.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/igvc_description/launch/rviz.launch.py b/src/igvc_description/launch/rviz.launch.py index 98257bfe..2ca12502 100644 --- a/src/igvc_description/launch/rviz.launch.py +++ b/src/igvc_description/launch/rviz.launch.py @@ -2,8 +2,8 @@ from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.conditions import IfCondition, UnlessCondition -from launch.substitutions import Command, LaunchConfiguration +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index 32f67b69..774862fc 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -10,8 +10,6 @@ from launch_ros.actions import Node -import xacro - def generate_launch_description(): pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') pkg_description = FindPackageShare(package='igvc_description').find('igvc_description') From 8c2153cc66ac7a8b8cb658a9f51471667be1a92d Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 19:27:33 +0000 Subject: [PATCH 31/80] Configure RGBD camera description --- src/igvc_description/urdf/robot.urdf.xacro | 3 +-- src/igvc_description/urdf/sensors.urdf.xacro | 26 ++++++++++++++++---- src/igvc_gazebo/config/gz_bridge.yaml | 10 ++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index 055e9afc..c9a8de9c 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -9,9 +9,8 @@ - - + \ No newline at end of file diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index f65023b4..c1df8a68 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -1,15 +1,31 @@ - + - - - + - + + + + + + + diff --git a/src/igvc_gazebo/config/gz_bridge.yaml b/src/igvc_gazebo/config/gz_bridge.yaml index 69d5baaf..b6f341f6 100644 --- a/src/igvc_gazebo/config/gz_bridge.yaml +++ b/src/igvc_gazebo/config/gz_bridge.yaml @@ -11,30 +11,30 @@ direction: GZ_TO_ROS qos_profile: SENSOR_DATA -# D435i Depth Camera +# Depth Camera - ros_topic_name: "camera/camera/color/image_raw" - gz_topic_name: "d435i/color" + gz_topic_name: "oak_depth/color" ros_type_name: "sensor_msgs/msg/Image" gz_type_name: "gz.msgs.Image" direction: "GZ_TO_ROS" qos_profile: SENSOR_DATA - ros_topic_name: "camera/camera/depth/image_rect_raw" - gz_topic_name: "d435i/depth" + gz_topic_name: "oak_depth/depth" ros_type_name: "sensor_msgs/msg/Image" gz_type_name: "gz.msgs.Image" direction: GZ_TO_ROS qos_profile: SENSOR_DATA - ros_topic_name: "camera/camera/depth/camera_info" #TODO: verify this matches real ros topic - gz_topic_name: "d435i/camera_info" + gz_topic_name: "oak_depth/camera_info" ros_type_name: "sensor_msgs/msg/CameraInfo" gz_type_name: "gz.msgs.CameraInfo" direction: GZ_TO_ROS - ros_topic_name: "camera/camera/depth/color/points" - gz_topic_name: "d435i/depth/points" + gz_topic_name: "oak_depth/depth/points" ros_type_name: "sensor_msgs/msg/PointCloud2" gz_type_name: "gz.msgs.PointCloudPacked" direction: GZ_TO_ROS \ No newline at end of file From f280f38e99b0433edf6d34010d686ac67a8d0d13 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Tue, 3 Feb 2026 19:29:09 +0000 Subject: [PATCH 32/80] Remove madgwick filter from sim launch file --- src/igvc_slam/launch/sim_rtabmap.launch.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/igvc_slam/launch/sim_rtabmap.launch.py b/src/igvc_slam/launch/sim_rtabmap.launch.py index a7ba5430..b42947e0 100644 --- a/src/igvc_slam/launch/sim_rtabmap.launch.py +++ b/src/igvc_slam/launch/sim_rtabmap.launch.py @@ -9,21 +9,6 @@ from launch_ros.actions import Node def generate_launch_description(): - imu_filter = Node( - package='imu_filter_madgwick', - executable='imu_filter_madgwick_node', - output='screen', - remappings=[ - ('/imu/data_raw', '/imu_plugin/out'), - ('imu/data', '/rtabmap/imu') - ], - parameters=[ - {'use_mag' : False}, - {'publish_tf' : False}, - {'fixed_frame' : 'imu_link'} - ] - ) - rtabmap_package = get_package_share_directory('rtabmap_launch') rtabmap_launch_path = os.path.join(rtabmap_package, 'launch', 'rtabmap.launch.py') @@ -60,6 +45,5 @@ def generate_launch_description(): ) return LaunchDescription([ - imu_filter, rtabmap ]) \ No newline at end of file From d91cac8a0d316aa223196b09f40e3b080a64a27b Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 21:30:09 +0000 Subject: [PATCH 33/80] rename launch file to hardware.launch.py --- src/igvc_hardware/launch/bot.launch.py | 12 ------------ src/igvc_hardware/launch/hardware.launch.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 src/igvc_hardware/launch/bot.launch.py create mode 100644 src/igvc_hardware/launch/hardware.launch.py diff --git a/src/igvc_hardware/launch/bot.launch.py b/src/igvc_hardware/launch/bot.launch.py deleted file mode 100644 index a24bd1b1..00000000 --- a/src/igvc_hardware/launch/bot.launch.py +++ /dev/null @@ -1,12 +0,0 @@ -from launch import LaunchDescription - -def generate_launch_description(): - # Declare args - - # Init args - - # Get URDF - - # Get nodes - - return LaunchDescription(declared_arguments + nodes) \ No newline at end of file diff --git a/src/igvc_hardware/launch/hardware.launch.py b/src/igvc_hardware/launch/hardware.launch.py new file mode 100644 index 00000000..0c63fdde --- /dev/null +++ b/src/igvc_hardware/launch/hardware.launch.py @@ -0,0 +1,19 @@ +from launch import LaunchDescription + +def generate_launch_description(): + # Declare args + declared_arguments = [] + + #TODO add use_mock_hardware to declared arguments? + + + # Init args + + # Get URDF + + # Get nodes + + # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is. Do we need to do this? + nodes = [] + + return LaunchDescription(declared_arguments + nodes) \ No newline at end of file From 0e29ca329da623dcba8961638ba58c80e66474e1 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 22:04:05 +0000 Subject: [PATCH 34/80] Update hardware bringups to have the proper ODrive drivetrain --- .../description/bot.ros2_control.urdf.xacro | 37 ++++++++++++++++++ src/igvc_hardware/launch/hardware.launch.py | 38 +++++++++++++++---- 2 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/igvc_hardware/description/bot.ros2_control.urdf.xacro diff --git a/src/igvc_hardware/description/bot.ros2_control.urdf.xacro b/src/igvc_hardware/description/bot.ros2_control.urdf.xacro new file mode 100644 index 00000000..85a84451 --- /dev/null +++ b/src/igvc_hardware/description/bot.ros2_control.urdf.xacro @@ -0,0 +1,37 @@ + + + + + + + + + + odrive_ros2_control_plugin/ODriveHardwareInterface + can0 + + + + + + + mock_components/GenericSystem + true + + + + + + + 0 + + + 1 + + + + + + + + \ No newline at end of file diff --git a/src/igvc_hardware/launch/hardware.launch.py b/src/igvc_hardware/launch/hardware.launch.py index 0c63fdde..416b6246 100644 --- a/src/igvc_hardware/launch/hardware.launch.py +++ b/src/igvc_hardware/launch/hardware.launch.py @@ -1,19 +1,43 @@ from launch import LaunchDescription +from launch.substitutions import Command, FindExecutable, PathJoinSubstitution, LaunchConfiguration +from launch_ros.substitutions import FindPackageShare +from launch_ros.actions import Node def generate_launch_description(): # Declare args declared_arguments = [] - - #TODO add use_mock_hardware to declared arguments? + # Get nodes + robot_controllers = PathJoinSubstitution( + [ + FindPackageShare("hardware"), + "config", + "bot_controllers.yaml", + ] + ) + + control_node = Node( + package="controller_manager", + executable="ros2_control_node", + parameters=[ + # TODO in the ODrive botwheel explorer example, the description contents are also passed in here. + robot_controllers + ], + output="both", + ) - # Init args + robot_controller_spawner = Node( + package="controller_manager", + executable="spawner", + arguments=["drivetrain_controller", "--controller-manager", "/controller_manager"], + ) - # Get URDF - # Get nodes + # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is finished spawning. Implement if we have problems regarding that. - # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is. Do we need to do this? - nodes = [] + nodes = [ + control_node, + robot_controller_spawner + ] return LaunchDescription(declared_arguments + nodes) \ No newline at end of file From c3ad690f2a5c966ab4065deca28b00b3e6bc97fd Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 22:20:20 +0000 Subject: [PATCH 35/80] Add IMU node to hardware launch --- src/igvc_hardware/launch/hardware.launch.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/igvc_hardware/launch/hardware.launch.py b/src/igvc_hardware/launch/hardware.launch.py index 416b6246..766a94ad 100644 --- a/src/igvc_hardware/launch/hardware.launch.py +++ b/src/igvc_hardware/launch/hardware.launch.py @@ -1,8 +1,10 @@ from launch import LaunchDescription -from launch.substitutions import Command, FindExecutable, PathJoinSubstitution, LaunchConfiguration +from launch.substitutions import PathJoinSubstitution from launch_ros.substitutions import FindPackageShare from launch_ros.actions import Node +IMU_NAME = "TODO" # TODO + def generate_launch_description(): # Declare args declared_arguments = [] @@ -29,9 +31,16 @@ def generate_launch_description(): robot_controller_spawner = Node( package="controller_manager", executable="spawner", - arguments=["drivetrain_controller", "--controller-manager", "/controller_manager"], + arguments=["bot_controller", "--controller-manager", "/controller_manager"], + ) + + imu_node = Node( + package="adi_imu", + executable="adi_imu_node", + ros_arguments=["-p", f"imu_device_name:=${IMU_NAME}"] ) + # TODO depthai (camera) # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is finished spawning. Implement if we have problems regarding that. From a6757a8be9463512d7761dc5d616739c751809fe Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 22:28:01 +0000 Subject: [PATCH 36/80] rename hardware xacro --- src/igvc_description/urdf/robot.urdf.xacro | 3 +++ .../{bot.ros2_control.urdf.xacro => bot.ros2_control.xacro} | 0 2 files changed, 3 insertions(+) rename src/igvc_hardware/description/{bot.ros2_control.urdf.xacro => bot.ros2_control.xacro} (100%) diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index c9a8de9c..7925bc32 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -13,4 +13,7 @@ + + + \ No newline at end of file diff --git a/src/igvc_hardware/description/bot.ros2_control.urdf.xacro b/src/igvc_hardware/description/bot.ros2_control.xacro similarity index 100% rename from src/igvc_hardware/description/bot.ros2_control.urdf.xacro rename to src/igvc_hardware/description/bot.ros2_control.xacro From e08b9a4d119650cdc58a5089418c378ff68ed1b1 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 22:29:26 +0000 Subject: [PATCH 37/80] Add hardware xacro to robot xacro --- src/igvc_description/urdf/robot.urdf.xacro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index 7925bc32..5d077872 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -1,5 +1,8 @@ + + + @@ -15,5 +18,6 @@ + \ No newline at end of file From c2bee3e351e03195694f3ff47dc56e6fd92f5195 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 3 Feb 2026 22:53:34 +0000 Subject: [PATCH 38/80] remove joint state broadcaster from controllers yaml --- src/igvc_hardware/config/bot_controllers.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index 923d4589..7fa81a7d 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -2,12 +2,11 @@ controller_manager: ros__parameters: update_rate: 10 # Hz - joint_state_broadcaster: - type: joint_state_broadcaster/JointStateBroadcaster + bot_drive_controller: + type: diff_drive_controller/DiffDriveController bot_drive_controller: ros__parameters: - type: diff_drive_controller/DiffDriveController left_wheel_names: ["left_wheel_joint"] right_wheel_names: ["right_wheel_joint"] From a2bd4aab3cee0f8accea21020b51f911acaf0db6 Mon Sep 17 00:00:00 2001 From: Zane Brotherton <46907447+zbrotherton@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:14:07 -0500 Subject: [PATCH 39/80] Update src/igvc_gazebo/launch/empty_world.launch.py Co-authored-by: Mathew Davis --- src/igvc_gazebo/launch/empty_world.launch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index 774862fc..c7f25237 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -11,8 +11,8 @@ from launch_ros.actions import Node def generate_launch_description(): - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - pkg_description = FindPackageShare(package='igvc_description').find('igvc_description') + package_ros_gz_sim = get_package_share_directory('ros_gz_sim') + package_description = FindPackageShare(package='igvc_description').find('igvc_description') gz_launch_path = os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py') publisher_launch_path = os.path.join(pkg_description, 'launch/publisher.launch.py') From 2e1c12303c6833674580e9d9407ed970bcdccbd9 Mon Sep 17 00:00:00 2001 From: Zane Brotherton <46907447+zbrotherton@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:14:30 -0500 Subject: [PATCH 40/80] Update src/igvc_gazebo/launch/empty_world.launch.py Co-authored-by: Mathew Davis --- src/igvc_gazebo/launch/empty_world.launch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index c7f25237..f508c611 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -14,8 +14,8 @@ def generate_launch_description(): package_ros_gz_sim = get_package_share_directory('ros_gz_sim') package_description = FindPackageShare(package='igvc_description').find('igvc_description') - gz_launch_path = os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py') - publisher_launch_path = os.path.join(pkg_description, 'launch/publisher.launch.py') + gz_launch_path = os.path.join(package_ros_gz_sim, 'launch', 'gz_sim.launch.py') + publisher_launch_path = os.path.join(package_description, 'launch/publisher.launch.py') world = LaunchConfiguration('world') From cb2c2681187fb50e9d76d08b0e8c834b769659ce Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 4 Feb 2026 22:03:33 +0000 Subject: [PATCH 41/80] Fix file URDF locations --- src/igvc_description/urdf/model.urdf.xacro | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/igvc_description/urdf/model.urdf.xacro b/src/igvc_description/urdf/model.urdf.xacro index 269d91f2..77629b76 100644 --- a/src/igvc_description/urdf/model.urdf.xacro +++ b/src/igvc_description/urdf/model.urdf.xacro @@ -13,7 +13,7 @@ - + @@ -22,14 +22,14 @@ - + - + @@ -38,14 +38,14 @@ - + - + @@ -54,14 +54,14 @@ - + - + @@ -70,14 +70,14 @@ - + - + @@ -86,14 +86,14 @@ - + - + @@ -102,14 +102,14 @@ - + - + @@ -118,14 +118,14 @@ - + - + @@ -134,7 +134,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -190,7 +190,7 @@ - + From 801da6f1012073f4d547156aa95b7a8671e7e150 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 4 Feb 2026 22:35:44 +0000 Subject: [PATCH 42/80] Reconfigure robot description to use hardware package for ros2 control --- src/igvc_description/urdf/control.urdf.xacro | 156 ------------------- src/igvc_description/urdf/robot.urdf.xacro | 3 +- src/igvc_hardware/CMakeLists.txt | 5 + 3 files changed, 6 insertions(+), 158 deletions(-) delete mode 100644 src/igvc_description/urdf/control.urdf.xacro diff --git a/src/igvc_description/urdf/control.urdf.xacro b/src/igvc_description/urdf/control.urdf.xacro deleted file mode 100644 index 346c0444..00000000 --- a/src/igvc_description/urdf/control.urdf.xacro +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - gz_ros2_control/GazeboSimSystem - - - - -10 - 10 - - - - - - - - - -10 - 10 - - - - - - - - - -10 - 10 - - - - - - - - - -10 - 10 - - - - - - - - - -10 - 10 - - - - - - - - - -10 - 10 - - - - - - - - - - -5 - 5 - - - -0.1 - 0.1 - - - - - - - - - - -5 - 5 - - - -0.1 - 0.1 - - - - - - - - -5 - 5 - - - -0.1 - 0.1 - - - - - - - - -5 - 5 - - - -0.1 - 0.1 - - - - - - - - - - - $(find osr_gazebo)/config/controller_velocity.yaml - - - - - true - - true - 100 - true - imu_link - imu - - imu - imu_link - 100.0 - 0.0 - 0 0 0 - 0 0 0 - imu_link - false - - 0 0 0 0 0 0 - - - - - \ No newline at end of file diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index 5d077872..a475e718 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -13,10 +13,9 @@ - - + diff --git a/src/igvc_hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt index 8b1ea367..8679bf89 100644 --- a/src/igvc_hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -49,6 +49,11 @@ install( DESTINATION share/${PROJECT_NAME} ) +install( + DIRECTORY description + DESTINATION share/${PROJECT_NAME} +) + install(TARGETS ${PROJECT_NAME}_library EXPORT export_${PROJECT_NAME}_library ARCHIVE DESTINATION lib From 97ccb9d0fe8b9cf7e164ccaf61ec057bffabce7a Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 4 Feb 2026 22:36:00 +0000 Subject: [PATCH 43/80] Remove duplicate OAK and position existing camera --- src/igvc_description/urdf/sensors.urdf.xacro | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index c1df8a68..063b3da5 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -1,6 +1,6 @@ - + - + From b8550263649251c4255e16857a3cc53a7d4fc5a5 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 4 Feb 2026 23:01:18 +0000 Subject: [PATCH 44/80] Change oak name, add igvc_robot to sensor xacro --- src/igvc_description/urdf/sensors.urdf.xacro | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index 063b3da5..15f18611 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -1,9 +1,9 @@ - + + + - - From 7e20b999bcb7601b98249cffca059da11b337fa6 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Wed, 4 Feb 2026 23:18:29 +0000 Subject: [PATCH 45/80] Fix devcontainer symlink install --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 91d2099a..0927af87 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -36,5 +36,5 @@ "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached", "source=/dev/dri,target=/dev/dri,type=bind,consistency=cached" ], - "postCreateCommand": ". .devcontainer/setup.sh && sudo rosdep update --rosdistro=jazzy && sudo rosdep install --from-paths lib src --ignore-src -y --rosdistro=jazzy && sudo chown -R $(whoami) /home/ros2_ws/ && colcon build --packages-select adi_imu odrive_ros2_control hardware" + "postCreateCommand": ". .devcontainer/setup.sh && sudo rosdep update --rosdistro=jazzy && sudo rosdep install --from-paths lib src --ignore-src -y --rosdistro=jazzy && sudo chown -R $(whoami) /home/ros2_ws/ && (rm -r build/ || true) && colcon build --symlink-install" } \ No newline at end of file From e9033f0599d32f59270a0d4a70429be6f8c2fc1f Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Thu, 5 Feb 2026 01:26:58 +0000 Subject: [PATCH 46/80] add friction to ground plane --- src/igvc_gazebo/worlds/empty_world.sdf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/igvc_gazebo/worlds/empty_world.sdf b/src/igvc_gazebo/worlds/empty_world.sdf index 72a7f56a..48e580fc 100644 --- a/src/igvc_gazebo/worlds/empty_world.sdf +++ b/src/igvc_gazebo/worlds/empty_world.sdf @@ -29,7 +29,10 @@ - + + 100 + 50 + From ae24936738c205af770dabbcc882dc89f40cccb5 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Thu, 5 Feb 2026 01:27:37 +0000 Subject: [PATCH 47/80] Change sensor formatting to recommended format --- src/igvc_description/urdf/sensors.urdf.xacro | 33 +++++++++----------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index 15f18611..2457c7f2 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -1,20 +1,17 @@ - - - - + + + + + + + + + + + + + @@ -36,7 +33,7 @@ oak/depth 30 1 - camera_link_optical + 1.51843645 @@ -59,7 +56,7 @@ oak/color 30 1 - camera_link_optical + 1.51843645 From 8f842307002aed3812861c78be4e9ade69edbc7a Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Thu, 5 Feb 2026 03:23:13 +0000 Subject: [PATCH 48/80] Update bridge topic names --- src/igvc_description/urdf/sensors.urdf.xacro | 4 ++-- src/igvc_gazebo/config/gz_bridge.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index 2457c7f2..b195d57d 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -33,7 +33,7 @@ oak/depth 30 1 - + camera_link_optical 1.51843645 @@ -56,7 +56,7 @@ oak/color 30 1 - + camera_link_optical 1.51843645 diff --git a/src/igvc_gazebo/config/gz_bridge.yaml b/src/igvc_gazebo/config/gz_bridge.yaml index b6f341f6..eab5d37d 100644 --- a/src/igvc_gazebo/config/gz_bridge.yaml +++ b/src/igvc_gazebo/config/gz_bridge.yaml @@ -14,27 +14,27 @@ # Depth Camera - ros_topic_name: "camera/camera/color/image_raw" - gz_topic_name: "oak_depth/color" + gz_topic_name: "oak/color" ros_type_name: "sensor_msgs/msg/Image" gz_type_name: "gz.msgs.Image" direction: "GZ_TO_ROS" qos_profile: SENSOR_DATA - ros_topic_name: "camera/camera/depth/image_rect_raw" - gz_topic_name: "oak_depth/depth" + gz_topic_name: "oak/depth" ros_type_name: "sensor_msgs/msg/Image" gz_type_name: "gz.msgs.Image" direction: GZ_TO_ROS qos_profile: SENSOR_DATA - ros_topic_name: "camera/camera/depth/camera_info" #TODO: verify this matches real ros topic - gz_topic_name: "oak_depth/camera_info" + gz_topic_name: "oak/camera_info" ros_type_name: "sensor_msgs/msg/CameraInfo" gz_type_name: "gz.msgs.CameraInfo" direction: GZ_TO_ROS - ros_topic_name: "camera/camera/depth/color/points" - gz_topic_name: "oak_depth/depth/points" + gz_topic_name: "oak/depth/points" ros_type_name: "sensor_msgs/msg/PointCloud2" gz_type_name: "gz.msgs.PointCloudPacked" direction: GZ_TO_ROS \ No newline at end of file From b2580fcfe85607eff93b7a1d3b3ba01940017495 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Thu, 5 Feb 2026 03:23:24 +0000 Subject: [PATCH 49/80] Alter inertial values --- src/igvc_description/urdf/model.urdf.xacro | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/igvc_description/urdf/model.urdf.xacro b/src/igvc_description/urdf/model.urdf.xacro index 77629b76..94c02dd7 100644 --- a/src/igvc_description/urdf/model.urdf.xacro +++ b/src/igvc_description/urdf/model.urdf.xacro @@ -6,8 +6,8 @@ - - + + @@ -142,8 +142,8 @@ - - + + @@ -174,8 +174,8 @@ - - + + From ed17427d467f807bbf6a5a54bf0fce19673e87a2 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Thu, 5 Feb 2026 03:24:00 +0000 Subject: [PATCH 50/80] Change robot spawn location --- src/igvc_description/urdf/robot.urdf.xacro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index a475e718..0aa19268 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -7,7 +7,7 @@ - + From 00f8964763ae502787278d54d5fc8a80b434fae0 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Fri, 6 Feb 2026 20:51:02 +0000 Subject: [PATCH 51/80] Reconfigure OAK simulation URDF --- src/igvc_description/urdf/sensors.urdf.xacro | 30 +++++++------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/igvc_description/urdf/sensors.urdf.xacro b/src/igvc_description/urdf/sensors.urdf.xacro index b195d57d..b910646e 100644 --- a/src/igvc_description/urdf/sensors.urdf.xacro +++ b/src/igvc_description/urdf/sensors.urdf.xacro @@ -3,37 +3,29 @@ - + - - + + - + - + - - + + - - - - - - - - - + 0 0 0 0 0 0 oak/depth 30 1 - camera_link_optical + oak_color_optical_frame 1.51843645 @@ -50,13 +42,13 @@ - + 0 0 0 0 0 0 oak/color 30 1 - camera_link_optical + oak_color_optical_frame 1.51843645 From 99c0e8079e7adae0c9165f32d7dabc18692531e8 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Fri, 6 Feb 2026 20:54:02 +0000 Subject: [PATCH 52/80] Change rtabmap sim launch --- src/igvc_slam/launch/sim_rtabmap.launch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/igvc_slam/launch/sim_rtabmap.launch.py b/src/igvc_slam/launch/sim_rtabmap.launch.py index b42947e0..41b3991f 100644 --- a/src/igvc_slam/launch/sim_rtabmap.launch.py +++ b/src/igvc_slam/launch/sim_rtabmap.launch.py @@ -23,7 +23,7 @@ def generate_launch_description(): 'approx_sync' : 'true', 'frame_id' : 'base_footprint', 'log_level' : 'debug', - 'publish_tf_odom' : 'false', + 'publish_tf_odom' : 'true', 'odom_topic' : '/odom', 'odom_frame_id' : 'odom', 'sync_queue_size' : '10', #TODO verify @@ -39,7 +39,7 @@ def generate_launch_description(): 'use_sim_time' : 'true', 'wait_for_transform' : '0.4', 'imu_topic' : '/rtabmap/imu', - 'wait_imu_to_init' : 'true', + 'wait_imu_to_init' : 'false', 'map_topic' : '/map' }.items() ) From 674bf192dcc302beb1db9ae9657b7104454d39a0 Mon Sep 17 00:00:00 2001 From: Spilsed <93678295+Spilsed@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:35:34 -0500 Subject: [PATCH 53/80] Add igvc_bringup --- src/igvc_bringup/igvc_bringup/__init__.py | 0 src/igvc_bringup/launch/bringup.launch.py | 59 ++++++ src/igvc_bringup/launch/description.launch.py | 14 ++ src/igvc_bringup/launch/gazebo.launch.py | 15 ++ src/igvc_bringup/launch/hardware.launch.py | 15 ++ src/igvc_bringup/launch/nav.launch.py | 15 ++ src/igvc_bringup/launch/slam.launch.py | 15 ++ src/igvc_bringup/package.xml | 21 +++ src/igvc_bringup/resource/igvc_bringup | 0 src/igvc_bringup/setup.cfg | 4 + src/igvc_bringup/setup.py | 33 ++++ src/igvc_bringup/test/test_copyright.py | 25 +++ src/igvc_bringup/test/test_flake8.py | 25 +++ src/igvc_bringup/test/test_pep257.py | 23 +++ src/igvc_description/launch/rviz.launch.py | 168 +++++++++--------- 15 files changed, 350 insertions(+), 82 deletions(-) create mode 100644 src/igvc_bringup/igvc_bringup/__init__.py create mode 100644 src/igvc_bringup/launch/bringup.launch.py create mode 100644 src/igvc_bringup/launch/description.launch.py create mode 100644 src/igvc_bringup/launch/gazebo.launch.py create mode 100644 src/igvc_bringup/launch/hardware.launch.py create mode 100644 src/igvc_bringup/launch/nav.launch.py create mode 100644 src/igvc_bringup/launch/slam.launch.py create mode 100644 src/igvc_bringup/package.xml create mode 100644 src/igvc_bringup/resource/igvc_bringup create mode 100644 src/igvc_bringup/setup.cfg create mode 100644 src/igvc_bringup/setup.py create mode 100644 src/igvc_bringup/test/test_copyright.py create mode 100644 src/igvc_bringup/test/test_flake8.py create mode 100644 src/igvc_bringup/test/test_pep257.py diff --git a/src/igvc_bringup/igvc_bringup/__init__.py b/src/igvc_bringup/igvc_bringup/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py new file mode 100644 index 00000000..bf697ff2 --- /dev/null +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -0,0 +1,59 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription +from launch.conditions import IfCondition, UnlessCondition +from launch.substitutions import LaunchConfiguration +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + use_sim = LaunchConfiguration('use_sim') + + return LaunchDescription([ + DeclareLaunchArgument( + 'use_sim', + default_value='true', + description='Run in Gazebo' + ), + + # Robot description (URDF) + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), + '/launch/description.launch.py'] + ) + ), + + # Simulation + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), '/launch/gazebo.launch.py'] + ), + condition=IfCondition(use_sim), + ), + + # Real hardware + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), + '/launch/hardware.launch.py'] + ), + condition=UnlessCondition(use_sim), + ), + + # SLAM + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), + '/launch/slam.launch.py'] + ) + ), + + # Navigation + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), + '/launch/nav.launch.py'] + ) + ), + ]) diff --git a/src/igvc_bringup/launch/description.launch.py b/src/igvc_bringup/launch/description.launch.py new file mode 100644 index 00000000..74d624d6 --- /dev/null +++ b/src/igvc_bringup/launch/description.launch.py @@ -0,0 +1,14 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_description'), + '/launch/publisher.launch.py'] + ) + ) + ]) diff --git a/src/igvc_bringup/launch/gazebo.launch.py b/src/igvc_bringup/launch/gazebo.launch.py new file mode 100644 index 00000000..4b893888 --- /dev/null +++ b/src/igvc_bringup/launch/gazebo.launch.py @@ -0,0 +1,15 @@ +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_gazebo'), + '/launch/empty_world.launch.py'] + ) + ) + ]) diff --git a/src/igvc_bringup/launch/hardware.launch.py b/src/igvc_bringup/launch/hardware.launch.py new file mode 100644 index 00000000..09a585fc --- /dev/null +++ b/src/igvc_bringup/launch/hardware.launch.py @@ -0,0 +1,15 @@ +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_hardware'), + '/launch/hardware.launch.py'] + ) + ) + ]) diff --git a/src/igvc_bringup/launch/nav.launch.py b/src/igvc_bringup/launch/nav.launch.py new file mode 100644 index 00000000..59fa658b --- /dev/null +++ b/src/igvc_bringup/launch/nav.launch.py @@ -0,0 +1,15 @@ +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_nav'), + '/launch/igvc_nav.launch.py'] + ) + ) + ]) diff --git a/src/igvc_bringup/launch/slam.launch.py b/src/igvc_bringup/launch/slam.launch.py new file mode 100644 index 00000000..9c7653cc --- /dev/null +++ b/src/igvc_bringup/launch/slam.launch.py @@ -0,0 +1,15 @@ +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_slam'), + '/launch/sim_rtabmap.launch.py'] + ) + ) + ]) diff --git a/src/igvc_bringup/package.xml b/src/igvc_bringup/package.xml new file mode 100644 index 00000000..42f2a4d4 --- /dev/null +++ b/src/igvc_bringup/package.xml @@ -0,0 +1,21 @@ + + + + igvc_bringup + 0.0.0 + Bring up package for the IGVC 2026 + Tag Ciccone + MIT + + launch + launch_ros + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/igvc_bringup/resource/igvc_bringup b/src/igvc_bringup/resource/igvc_bringup new file mode 100644 index 00000000..e69de29b diff --git a/src/igvc_bringup/setup.cfg b/src/igvc_bringup/setup.cfg new file mode 100644 index 00000000..57142951 --- /dev/null +++ b/src/igvc_bringup/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/igvc_bringup +[install] +install_scripts=$base/lib/igvc_bringup diff --git a/src/igvc_bringup/setup.py b/src/igvc_bringup/setup.py new file mode 100644 index 00000000..291e8c40 --- /dev/null +++ b/src/igvc_bringup/setup.py @@ -0,0 +1,33 @@ +from setuptools import find_packages, setup +import os +from glob import glob + +package_name = 'igvc_bringup' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), + glob('launch/*.py')), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Tag Ciccone', + maintainer_email='ciccot@rpi.edu', + description='Bring up package for the IGVC 2026', + license='MIT', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/src/igvc_bringup/test/test_copyright.py b/src/igvc_bringup/test/test_copyright.py new file mode 100644 index 00000000..97a39196 --- /dev/null +++ b/src/igvc_bringup/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/igvc_bringup/test/test_flake8.py b/src/igvc_bringup/test/test_flake8.py new file mode 100644 index 00000000..27ee1078 --- /dev/null +++ b/src/igvc_bringup/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/igvc_bringup/test/test_pep257.py b/src/igvc_bringup/test/test_pep257.py new file mode 100644 index 00000000..b234a384 --- /dev/null +++ b/src/igvc_bringup/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/igvc_description/launch/rviz.launch.py b/src/igvc_description/launch/rviz.launch.py index 2ca12502..294b9c05 100644 --- a/src/igvc_description/launch/rviz.launch.py +++ b/src/igvc_description/launch/rviz.launch.py @@ -7,92 +7,96 @@ from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare + def generate_launch_description(): - # Set the path to this package. - pkg_share = FindPackageShare(package='igvc_description').find('igvc_description') - - # Set the path to the RViz configuration settings - default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz_settings.rviz') - - # Set the path to the URDF file - default_urdf_model_path = os.path.join(pkg_share, 'urdf/robot.urdf.xacro') - - # Set the path to the Publisher launch file - publisher_launch_path = os.path.join(pkg_share, 'launch/publisher.launch.py') - - # Launch configuration variables specific to simulation - gui = LaunchConfiguration('gui') - urdf_model = LaunchConfiguration('urdf_model') - rviz_config_file = LaunchConfiguration('rviz_config_file') - use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') - use_rviz = LaunchConfiguration('use_rviz') - use_sim_time = LaunchConfiguration('use_sim_time') - - # Declare the launch arguments - declare_urdf_model_path_cmd = DeclareLaunchArgument( - name='urdf_model', - default_value=default_urdf_model_path, - description='Absolute path to robot urdf file') - - declare_rviz_config_file_cmd = DeclareLaunchArgument( - name='rviz_config_file', - default_value=default_rviz_config_path, - description='Full path to the RVIZ config file to use') - - declare_use_joint_state_publisher_cmd = DeclareLaunchArgument( - name='gui', - default_value='True', - description='Flag to enable joint_state_publisher_gui') - - declare_use_robot_state_pub_cmd = DeclareLaunchArgument( - name='use_robot_state_pub', - default_value='True', - description='Whether to start the robot state publisher') - - declare_use_rviz_cmd = DeclareLaunchArgument( - name='use_rviz', - default_value='true', - description='Whether to start RVIZ') - - declare_use_sim_time_cmd = DeclareLaunchArgument( - name='use_sim_time', - default_value='True', - description='Use simulation (Gazebo) clock if true') - - # Specify the publisher action - start_publisher_cmd = IncludeLaunchDescription( + # Set the path to this package. + pkg_share = FindPackageShare( + package='igvc_description').find('igvc_description') + + # Set the path to the RViz configuration settings + default_rviz_config_path = os.path.join( + pkg_share, 'rviz/rviz_settings.rviz') + + # Set the path to the URDF file + default_urdf_model_path = os.path.join(pkg_share, 'urdf/robot.urdf.xacro') + + # Set the path to the Publisher launch file + publisher_launch_path = os.path.join( + pkg_share, 'launch/publisher.launch.py') + + # Launch configuration variables specific to simulation + gui = LaunchConfiguration('gui') + urdf_model = LaunchConfiguration('urdf_model') + rviz_config_file = LaunchConfiguration('rviz_config_file') + use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') + use_rviz = LaunchConfiguration('use_rviz') + use_sim_time = LaunchConfiguration('use_sim_time') + + # Declare the launch arguments + declare_urdf_model_path_cmd = DeclareLaunchArgument( + name='urdf_model', + default_value=default_urdf_model_path, + description='Absolute path to robot urdf file') + + declare_rviz_config_file_cmd = DeclareLaunchArgument( + name='rviz_config_file', + default_value=default_rviz_config_path, + description='Full path to the RVIZ config file to use') + + declare_use_joint_state_publisher_cmd = DeclareLaunchArgument( + name='gui', + default_value='True', + description='Flag to enable joint_state_publisher_gui') + + declare_use_robot_state_pub_cmd = DeclareLaunchArgument( + name='use_robot_state_pub', + default_value='True', + description='Whether to start the robot state publisher') + + declare_use_rviz_cmd = DeclareLaunchArgument( + name='use_rviz', + default_value='true', + description='Whether to start RVIZ') + + declare_use_sim_time_cmd = DeclareLaunchArgument( + name='use_sim_time', + default_value='True', + description='Use simulation (Gazebo) clock if true') + + # Specify the publisher action + start_publisher_cmd = IncludeLaunchDescription( PythonLaunchDescriptionSource(publisher_launch_path), launch_arguments={ - 'urdf_model' : urdf_model, - 'gui' : gui, - 'use_robot_state_pub' : use_robot_state_pub, - 'use_sim_time' : use_sim_time, + 'urdf_model': urdf_model, + 'gui': gui, + 'use_robot_state_pub': use_robot_state_pub, + 'use_sim_time': use_sim_time, }.items() ) - # Launch RViz - start_rviz_cmd = Node( - condition=IfCondition(use_rviz), - package='rviz2', - executable='rviz2', - name='rviz2', - output='screen', - arguments=['-d', rviz_config_file]) - - # Create the launch description and populate - ld = LaunchDescription() - - # Declare the launch options - ld.add_action(declare_urdf_model_path_cmd) - ld.add_action(declare_rviz_config_file_cmd) - ld.add_action(declare_use_joint_state_publisher_cmd) - ld.add_action(declare_use_robot_state_pub_cmd) - ld.add_action(declare_use_rviz_cmd) - ld.add_action(declare_use_sim_time_cmd) - - # Add any actions - ld.add_action(start_publisher_cmd) - ld.add_action(start_rviz_cmd) - - return ld + # Launch RViz + start_rviz_cmd = Node( + condition=IfCondition(use_rviz), + package='rviz2', + executable='rviz2', + name='rviz2', + output='screen', + arguments=['-d', rviz_config_file]) + + # Create the launch description and populate + ld = LaunchDescription() + + # Declare the launch options + ld.add_action(declare_urdf_model_path_cmd) + ld.add_action(declare_rviz_config_file_cmd) + ld.add_action(declare_use_joint_state_publisher_cmd) + ld.add_action(declare_use_robot_state_pub_cmd) + ld.add_action(declare_use_rviz_cmd) + ld.add_action(declare_use_sim_time_cmd) + + # Add any actions + ld.add_action(start_publisher_cmd) + ld.add_action(start_rviz_cmd) + + return ld From d334418e5111e1f2f7fe67e185660e32215239b7 Mon Sep 17 00:00:00 2001 From: Spilsed <93678295+Spilsed@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:14:41 -0500 Subject: [PATCH 54/80] Add control.launch.py --- src/igvc_bringup/launch/bringup.launch.py | 9 ++++ src/igvc_bringup/launch/description.launch.py | 3 +- src/igvc_hardware/launch/control.launch.py | 46 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/igvc_hardware/launch/control.launch.py diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index bf697ff2..e5c026fc 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -32,6 +32,15 @@ def generate_launch_description(): condition=IfCondition(use_sim), ), + # Control + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_bringup'), + '/launch/hardware.launch.py'] + ), + condition=IfCondition(use_sim), + ), + # Real hardware IncludeLaunchDescription( PythonLaunchDescriptionSource( diff --git a/src/igvc_bringup/launch/description.launch.py b/src/igvc_bringup/launch/description.launch.py index 74d624d6..80276a2c 100644 --- a/src/igvc_bringup/launch/description.launch.py +++ b/src/igvc_bringup/launch/description.launch.py @@ -1,5 +1,6 @@ from launch import LaunchDescription -from launch_ros.actions import Node +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.substitutions import FindPackageShare diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py new file mode 100644 index 00000000..0300a68d --- /dev/null +++ b/src/igvc_hardware/launch/control.launch.py @@ -0,0 +1,46 @@ +from launch import LaunchDescription +from launch.substitutions import PathJoinSubstitution +from launch_ros.substitutions import FindPackageShare +from launch_ros.actions import Node + + +def generate_launch_description(): + # Declare args + declared_arguments = [] + + # Get nodes + robot_controllers = PathJoinSubstitution( + [ + FindPackageShare("hardware"), + "config", + "bot_controllers.yaml", + ] + ) + + control_node = Node( + package="controller_manager", + executable="ros2_control_node", + parameters=[ + # TODO in the ODrive botwheel explorer example, the description contents are also passed in here. + robot_controllers + ], + output="both", + ) + + robot_controller_spawner = Node( + package="controller_manager", + executable="spawner", + arguments=["bot_controller", + "--controller-manager", "/controller_manager"], + ) + + # TODO depthai (camera) + + # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is finished spawning. Implement if we have problems regarding that. + + nodes = [ + control_node, + robot_controller_spawner + ] + + return LaunchDescription(declared_arguments + nodes) From 48e4bdc4686ddf7dd9dca9ad2f98128ad98184d1 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 6 Feb 2026 23:16:13 +0000 Subject: [PATCH 55/80] Remove some extraneous elements and add CMakeLists --- src/igvc_bringup/CMakeLists.txt | 28 +++++++++++++++++++ src/igvc_bringup/igvc_bringup/__init__.py | 0 src/igvc_bringup/package.xml | 15 +++++------ src/igvc_bringup/resource/igvc_bringup | 0 src/igvc_bringup/setup.cfg | 4 --- src/igvc_bringup/setup.py | 33 ----------------------- src/igvc_bringup/test/test_copyright.py | 25 ----------------- src/igvc_bringup/test/test_flake8.py | 25 ----------------- src/igvc_bringup/test/test_pep257.py | 23 ---------------- 9 files changed, 34 insertions(+), 119 deletions(-) create mode 100644 src/igvc_bringup/CMakeLists.txt delete mode 100644 src/igvc_bringup/igvc_bringup/__init__.py delete mode 100644 src/igvc_bringup/resource/igvc_bringup delete mode 100644 src/igvc_bringup/setup.cfg delete mode 100644 src/igvc_bringup/setup.py delete mode 100644 src/igvc_bringup/test/test_copyright.py delete mode 100644 src/igvc_bringup/test/test_flake8.py delete mode 100644 src/igvc_bringup/test/test_pep257.py diff --git a/src/igvc_bringup/CMakeLists.txt b/src/igvc_bringup/CMakeLists.txt new file mode 100644 index 00000000..0de240a0 --- /dev/null +++ b/src/igvc_bringup/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.16) +project(igvc_bringup) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/igvc_bringup/igvc_bringup/__init__.py b/src/igvc_bringup/igvc_bringup/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/igvc_bringup/package.xml b/src/igvc_bringup/package.xml index 42f2a4d4..bcaafeff 100644 --- a/src/igvc_bringup/package.xml +++ b/src/igvc_bringup/package.xml @@ -3,19 +3,16 @@ igvc_bringup 0.0.0 - Bring up package for the IGVC 2026 - Tag Ciccone + Bringups for the 2026 IGVC Robot + Tag Ciccone MIT - launch - launch_ros + ament_cmake - ament_copyright - ament_flake8 - ament_pep257 - python3-pytest + ament_lint_auto + ament_lint_common - ament_python + ament_cmake diff --git a/src/igvc_bringup/resource/igvc_bringup b/src/igvc_bringup/resource/igvc_bringup deleted file mode 100644 index e69de29b..00000000 diff --git a/src/igvc_bringup/setup.cfg b/src/igvc_bringup/setup.cfg deleted file mode 100644 index 57142951..00000000 --- a/src/igvc_bringup/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[develop] -script_dir=$base/lib/igvc_bringup -[install] -install_scripts=$base/lib/igvc_bringup diff --git a/src/igvc_bringup/setup.py b/src/igvc_bringup/setup.py deleted file mode 100644 index 291e8c40..00000000 --- a/src/igvc_bringup/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -from setuptools import find_packages, setup -import os -from glob import glob - -package_name = 'igvc_bringup' - -setup( - name=package_name, - version='0.0.0', - packages=find_packages(exclude=['test']), - data_files=[ - ('share/ament_index/resource_index/packages', - ['resource/' + package_name]), - ('share/' + package_name, ['package.xml']), - (os.path.join('share', package_name, 'launch'), - glob('launch/*.py')), - ], - install_requires=['setuptools'], - zip_safe=True, - maintainer='Tag Ciccone', - maintainer_email='ciccot@rpi.edu', - description='Bring up package for the IGVC 2026', - license='MIT', - extras_require={ - 'test': [ - 'pytest', - ], - }, - entry_points={ - 'console_scripts': [ - ], - }, -) diff --git a/src/igvc_bringup/test/test_copyright.py b/src/igvc_bringup/test/test_copyright.py deleted file mode 100644 index 97a39196..00000000 --- a/src/igvc_bringup/test/test_copyright.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# 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. - -from ament_copyright.main import main -import pytest - - -# Remove the `skip` decorator once the source file(s) have a copyright header -@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') -@pytest.mark.copyright -@pytest.mark.linter -def test_copyright(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found errors' diff --git a/src/igvc_bringup/test/test_flake8.py b/src/igvc_bringup/test/test_flake8.py deleted file mode 100644 index 27ee1078..00000000 --- a/src/igvc_bringup/test/test_flake8.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Open Source Robotics Foundation, Inc. -# -# 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. - -from ament_flake8.main import main_with_errors -import pytest - - -@pytest.mark.flake8 -@pytest.mark.linter -def test_flake8(): - rc, errors = main_with_errors(argv=[]) - assert rc == 0, \ - 'Found %d code style errors / warnings:\n' % len(errors) + \ - '\n'.join(errors) diff --git a/src/igvc_bringup/test/test_pep257.py b/src/igvc_bringup/test/test_pep257.py deleted file mode 100644 index b234a384..00000000 --- a/src/igvc_bringup/test/test_pep257.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# 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. - -from ament_pep257.main import main -import pytest - - -@pytest.mark.linter -@pytest.mark.pep257 -def test_pep257(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found code style errors / warnings' From ef0119704335edb4fe0218519eb4c1d76faf2c62 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 6 Feb 2026 23:29:55 +0000 Subject: [PATCH 56/80] Refactor bringup references --- src/igvc_bringup/launch/bringup.launch.py | 55 ++++++++++++------- src/igvc_bringup/launch/description.launch.py | 15 ----- src/igvc_bringup/launch/hardware.launch.py | 15 ----- src/igvc_bringup/launch/nav.launch.py | 15 ----- src/igvc_bringup/launch/slam.launch.py | 15 ----- .../launch/gazebo.launch.py | 0 6 files changed, 35 insertions(+), 80 deletions(-) delete mode 100644 src/igvc_bringup/launch/description.launch.py delete mode 100644 src/igvc_bringup/launch/hardware.launch.py delete mode 100644 src/igvc_bringup/launch/nav.launch.py delete mode 100644 src/igvc_bringup/launch/slam.launch.py rename src/{igvc_bringup => igvc_gazebo}/launch/gazebo.launch.py (100%) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index e5c026fc..e89bcb65 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -8,61 +8,76 @@ def generate_launch_description(): use_sim = LaunchConfiguration('use_sim') - + return LaunchDescription([ + # Launch Arguments DeclareLaunchArgument( 'use_sim', - default_value='true', - description='Run in Gazebo' + default_value='false', + description='Run in Simulation' + ), + DeclareLaunchArgument( + 'sim_scenario', + default_value='empty_world', + description='The name of the scenario to open in Gazebo' ), - # Robot description (URDF) + # Publishers & URDF IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), - '/launch/description.launch.py'] + [FindPackageShare('igvc_description'), + 'launch', + 'publisher.launch.py'] ) ), # Simulation IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), '/launch/gazebo.launch.py'] + [FindPackageShare('igvc_gazebo'), + 'launch', + 'gazebo.launch.py'] ), condition=IfCondition(use_sim), + launch_arguments={ + 'scenario': LaunchConfiguration('sim_scenario'), + }.items(), ), - # Control + # ROS2_Control IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), - '/launch/hardware.launch.py'] + [FindPackageShare('igvc_hardware'), + 'launch', + 'control.launch.py'] ), - condition=IfCondition(use_sim), ), # Real hardware IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), - '/launch/hardware.launch.py'] + [FindPackageShare('igvc_hardware'), + 'launch', + 'hardware.launch.py'] ), - condition=UnlessCondition(use_sim), + condition = UnlessCondition(use_sim) ), # SLAM IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), - '/launch/slam.launch.py'] - ) + [FindPackageShare('igvc_slam'), + 'launch', + 'rtabmap.launch.py'] + ), ), # Navigation IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_bringup'), - '/launch/nav.launch.py'] - ) + [FindPackageShare('igvc_nav'), + 'launch', + 'igvc_nav.launch.py'] + ), ), ]) diff --git a/src/igvc_bringup/launch/description.launch.py b/src/igvc_bringup/launch/description.launch.py deleted file mode 100644 index 80276a2c..00000000 --- a/src/igvc_bringup/launch/description.launch.py +++ /dev/null @@ -1,15 +0,0 @@ -from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch_ros.substitutions import FindPackageShare - - -def generate_launch_description(): - return LaunchDescription([ - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_description'), - '/launch/publisher.launch.py'] - ) - ) - ]) diff --git a/src/igvc_bringup/launch/hardware.launch.py b/src/igvc_bringup/launch/hardware.launch.py deleted file mode 100644 index 09a585fc..00000000 --- a/src/igvc_bringup/launch/hardware.launch.py +++ /dev/null @@ -1,15 +0,0 @@ -from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch_ros.substitutions import FindPackageShare - - -def generate_launch_description(): - return LaunchDescription([ - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_hardware'), - '/launch/hardware.launch.py'] - ) - ) - ]) diff --git a/src/igvc_bringup/launch/nav.launch.py b/src/igvc_bringup/launch/nav.launch.py deleted file mode 100644 index 59fa658b..00000000 --- a/src/igvc_bringup/launch/nav.launch.py +++ /dev/null @@ -1,15 +0,0 @@ -from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch_ros.substitutions import FindPackageShare - - -def generate_launch_description(): - return LaunchDescription([ - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_nav'), - '/launch/igvc_nav.launch.py'] - ) - ) - ]) diff --git a/src/igvc_bringup/launch/slam.launch.py b/src/igvc_bringup/launch/slam.launch.py deleted file mode 100644 index 9c7653cc..00000000 --- a/src/igvc_bringup/launch/slam.launch.py +++ /dev/null @@ -1,15 +0,0 @@ -from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch_ros.substitutions import FindPackageShare - - -def generate_launch_description(): - return LaunchDescription([ - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_slam'), - '/launch/sim_rtabmap.launch.py'] - ) - ) - ]) diff --git a/src/igvc_bringup/launch/gazebo.launch.py b/src/igvc_gazebo/launch/gazebo.launch.py similarity index 100% rename from src/igvc_bringup/launch/gazebo.launch.py rename to src/igvc_gazebo/launch/gazebo.launch.py From 40d155d88c1b21ae366fda0453e3bbf297caf0ab Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 6 Feb 2026 23:30:51 +0000 Subject: [PATCH 57/80] Small refactor to hardware bringups --- src/igvc_hardware/launch/control.launch.py | 4 --- src/igvc_hardware/launch/hardware.launch.py | 29 ++------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py index 0300a68d..0bec2b30 100644 --- a/src/igvc_hardware/launch/control.launch.py +++ b/src/igvc_hardware/launch/control.launch.py @@ -34,10 +34,6 @@ def generate_launch_description(): "--controller-manager", "/controller_manager"], ) - # TODO depthai (camera) - - # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is finished spawning. Implement if we have problems regarding that. - nodes = [ control_node, robot_controller_spawner diff --git a/src/igvc_hardware/launch/hardware.launch.py b/src/igvc_hardware/launch/hardware.launch.py index 766a94ad..c4c2d954 100644 --- a/src/igvc_hardware/launch/hardware.launch.py +++ b/src/igvc_hardware/launch/hardware.launch.py @@ -9,31 +9,7 @@ def generate_launch_description(): # Declare args declared_arguments = [] - # Get nodes - robot_controllers = PathJoinSubstitution( - [ - FindPackageShare("hardware"), - "config", - "bot_controllers.yaml", - ] - ) - - control_node = Node( - package="controller_manager", - executable="ros2_control_node", - parameters=[ - # TODO in the ODrive botwheel explorer example, the description contents are also passed in here. - robot_controllers - ], - output="both", - ) - - robot_controller_spawner = Node( - package="controller_manager", - executable="spawner", - arguments=["bot_controller", "--controller-manager", "/controller_manager"], - ) - + # Get nodes imu_node = Node( package="adi_imu", executable="adi_imu_node", @@ -45,8 +21,7 @@ def generate_launch_description(): # TODO In the examples, the controller manager is not spawned until the joint state broadcaster is finished spawning. Implement if we have problems regarding that. nodes = [ - control_node, - robot_controller_spawner + imu_node ] return LaunchDescription(declared_arguments + nodes) \ No newline at end of file From fe90cec1346a640f9f05388d6ad1cc0aa52cc324 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 6 Feb 2026 23:48:32 +0000 Subject: [PATCH 58/80] Fix path substitution --- src/igvc_bringup/launch/bringup.launch.py | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index e89bcb65..dd10a337 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -26,8 +26,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_description'), - 'launch', - 'publisher.launch.py'] + '/launch', + '/publisher.launch.py'] ) ), @@ -35,8 +35,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_gazebo'), - 'launch', - 'gazebo.launch.py'] + '/launch', + '/gazebo.launch.py'] ), condition=IfCondition(use_sim), launch_arguments={ @@ -48,8 +48,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_hardware'), - 'launch', - 'control.launch.py'] + '/launch', + '/control.launch.py'] ), ), @@ -57,8 +57,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_hardware'), - 'launch', - 'hardware.launch.py'] + '/launch', + '/hardware.launch.py'] ), condition = UnlessCondition(use_sim) ), @@ -67,8 +67,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_slam'), - 'launch', - 'rtabmap.launch.py'] + '/launch', + '/rtabmap.launch.py'] ), ), @@ -76,8 +76,8 @@ def generate_launch_description(): IncludeLaunchDescription( PythonLaunchDescriptionSource( [FindPackageShare('igvc_nav'), - 'launch', - 'igvc_nav.launch.py'] + '/launch', + '/igvc_nav.launch.py'] ), ), ]) From ec66e4280c483b9c7ae3810c14f50d4563a7a5c5 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 10 Feb 2026 21:49:58 +0000 Subject: [PATCH 59/80] Working selection of sim scenario --- src/igvc_bringup/launch/bringup.launch.py | 77 +++++++++++------------ src/igvc_gazebo/launch/gazebo.launch.py | 17 ++++- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index dd10a337..cc959ed0 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -34,50 +34,49 @@ def generate_launch_description(): # Simulation IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_gazebo'), - '/launch', - '/gazebo.launch.py'] + [FindPackageShare('igvc_gazebo'), + '/launch/', + LaunchConfiguration('sim_scenario'), + '.launch.py' + ] ), condition=IfCondition(use_sim), - launch_arguments={ - 'scenario': LaunchConfiguration('sim_scenario'), - }.items(), ), - # ROS2_Control - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_hardware'), - '/launch', - '/control.launch.py'] - ), - ), + # # ROS2_Control + # IncludeLaunchDescription( + # PythonLaunchDescriptionSource( + # [FindPackageShare('igvc_hardware'), + # '/launch', + # '/control.launch.py'] + # ), + # ), - # Real hardware - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_hardware'), - '/launch', - '/hardware.launch.py'] - ), - condition = UnlessCondition(use_sim) - ), + # # Real hardware + # IncludeLaunchDescription( + # PythonLaunchDescriptionSource( + # [FindPackageShare('igvc_hardware'), + # '/launch', + # '/hardware.launch.py'] + # ), + # condition = UnlessCondition(use_sim) + # ), - # SLAM - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_slam'), - '/launch', - '/rtabmap.launch.py'] - ), - ), + # # SLAM + # IncludeLaunchDescription( + # PythonLaunchDescriptionSource( + # [FindPackageShare('igvc_slam'), + # '/launch', + # '/rtabmap.launch.py'] + # ), + # ), - # Navigation - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [FindPackageShare('igvc_nav'), - '/launch', - '/igvc_nav.launch.py'] - ), - ), + # # Navigation + # IncludeLaunchDescription( + # PythonLaunchDescriptionSource( + # [FindPackageShare('igvc_nav'), + # '/launch', + # '/igvc_nav.launch.py'] + # ), + # ), ]) diff --git a/src/igvc_gazebo/launch/gazebo.launch.py b/src/igvc_gazebo/launch/gazebo.launch.py index 4b893888..077e7e7e 100644 --- a/src/igvc_gazebo/launch/gazebo.launch.py +++ b/src/igvc_gazebo/launch/gazebo.launch.py @@ -2,14 +2,27 @@ from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.substitutions import FindPackageShare +from launch.substitutions import LaunchConfiguration +from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument def generate_launch_description(): + # scenario = + return LaunchDescription([ + DeclareLaunchArgument( + 'scenario', + default_value='empty_world', + description='Simulation Scenario' + ), IncludeLaunchDescription( PythonLaunchDescriptionSource( - [FindPackageShare('igvc_gazebo'), - '/launch/empty_world.launch.py'] + [ + FindPackageShare('igvc_gazebo'), + '/launch/', + LaunchConfiguration('scenario'), + '.launch.py' + ] ) ) ]) From 5176bba2001f36e246c06d9c89e30ac7c9a03676 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 10 Feb 2026 22:59:03 +0000 Subject: [PATCH 60/80] Almost running, controller manager dying on launch --- src/igvc_bringup/launch/bringup.launch.py | 29 ++++++++++++------- .../launch/publisher.launch.py | 19 ++++++++++-- src/igvc_description/urdf/model.urdf.xacro | 4 +-- src/igvc_hardware/config/bot_controllers.yaml | 4 +++ src/igvc_hardware/launch/control.launch.py | 4 +-- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index cc959ed0..e1e6baa5 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -8,7 +8,8 @@ def generate_launch_description(): use_sim = LaunchConfiguration('use_sim') - + use_mock_hardware = LaunchConfiguration('use_mock_hardware') + return LaunchDescription([ # Launch Arguments DeclareLaunchArgument( @@ -21,6 +22,11 @@ def generate_launch_description(): default_value='empty_world', description='The name of the scenario to open in Gazebo' ), + DeclareLaunchArgument( + 'use_mock_hardware', + default_value=use_sim, # You are always mocking in simulation, but can specify if you need to bypass physical descriptors for testing + description='Mocks all hardware' + ), # Publishers & URDF IncludeLaunchDescription( @@ -28,7 +34,10 @@ def generate_launch_description(): [FindPackageShare('igvc_description'), '/launch', '/publisher.launch.py'] - ) + ), + launch_arguments={ + 'use_mock_hardware': use_mock_hardware + }.items() ), # Simulation @@ -43,14 +52,14 @@ def generate_launch_description(): condition=IfCondition(use_sim), ), - # # ROS2_Control - # IncludeLaunchDescription( - # PythonLaunchDescriptionSource( - # [FindPackageShare('igvc_hardware'), - # '/launch', - # '/control.launch.py'] - # ), - # ), + # ROS2_Control + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_hardware'), + '/launch', + '/control.launch.py'] + ), + ), # # Real hardware # IncludeLaunchDescription( diff --git a/src/igvc_description/launch/publisher.launch.py b/src/igvc_description/launch/publisher.launch.py index 7b62d70a..9986899a 100644 --- a/src/igvc_description/launch/publisher.launch.py +++ b/src/igvc_description/launch/publisher.launch.py @@ -63,11 +63,26 @@ def generate_launch_description(): package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'use_sim_time': use_sim_time, - 'robot_description': Command(['xacro ', urdf_model])}], + 'robot_description': Command( + [ + 'xacro ', urdf_model, + ' use_mock_hardware:=', LaunchConfiguration('use_mock_hardware') + ] + ), + }], arguments=[default_urdf_model_path]) # Create the launch description and populate - ld = LaunchDescription() + ld = LaunchDescription( + [ + DeclareLaunchArgument( + 'use_mock_hardware', + default_value='false', + description='Run in Simulation' + ), + + ] + ) # Declare the launch options ld.add_action(declare_urdf_model_path_cmd) diff --git a/src/igvc_description/urdf/model.urdf.xacro b/src/igvc_description/urdf/model.urdf.xacro index 94c02dd7..9dd147ce 100644 --- a/src/igvc_description/urdf/model.urdf.xacro +++ b/src/igvc_description/urdf/model.urdf.xacro @@ -163,7 +163,7 @@ - + @@ -195,7 +195,7 @@ - + diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index 7fa81a7d..0c63778e 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -5,6 +5,10 @@ controller_manager: bot_drive_controller: type: diff_drive_controller/DiffDriveController + joint_state_broadcaster: + type: joint_state_broadcaster/JointStateBroadcaster + + bot_drive_controller: ros__parameters: diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py index 0bec2b30..9b8dacbe 100644 --- a/src/igvc_hardware/launch/control.launch.py +++ b/src/igvc_hardware/launch/control.launch.py @@ -11,7 +11,7 @@ def generate_launch_description(): # Get nodes robot_controllers = PathJoinSubstitution( [ - FindPackageShare("hardware"), + FindPackageShare("igvc_hardware"), "config", "bot_controllers.yaml", ] @@ -30,7 +30,7 @@ def generate_launch_description(): robot_controller_spawner = Node( package="controller_manager", executable="spawner", - arguments=["bot_controller", + arguments=["bot_drive_controller", "--controller-manager", "/controller_manager"], ) From 614e1b48c84cfe633d5d3ad7e77c6f1b4cae26e2 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 10 Feb 2026 23:16:25 +0000 Subject: [PATCH 61/80] oops don't need that --- src/igvc_hardware/config/bot_controllers.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index 0c63778e..a8c993dc 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -4,11 +4,6 @@ controller_manager: bot_drive_controller: type: diff_drive_controller/DiffDriveController - - joint_state_broadcaster: - type: joint_state_broadcaster/JointStateBroadcaster - - bot_drive_controller: ros__parameters: From 905cda50f75cf709524bc662b62c4a9fa2e284a1 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Wed, 11 Feb 2026 00:59:49 +0000 Subject: [PATCH 62/80] comment extraenous stuff --- src/igvc_hardware/CMakeLists.txt | 90 ++++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/igvc_hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt index 8679bf89..5779223c 100644 --- a/src/igvc_hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -6,43 +6,43 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # find dependencies -find_package(ament_cmake REQUIRED) -find_package(controller_manager REQUIRED) -find_package(hardware_interface REQUIRED) -find_package(pluginlib REQUIRED) -find_package(rclcpp REQUIRED) -find_package(rclcpp_lifecycle REQUIRED) +# find_package(ament_cmake REQUIRED) +# find_package(controller_manager REQUIRED) +# find_package(hardware_interface REQUIRED) +# find_package(pluginlib REQUIRED) +# find_package(rclcpp REQUIRED) +# find_package(rclcpp_lifecycle REQUIRED) # Define the library target -add_library( - ${PROJECT_NAME}_library - SHARED - src/bot_system.cpp -) +# add_library( +# ${PROJECT_NAME}_library +# SHARED +# src/bot_system.cpp +# ) -target_compile_features(${PROJECT_NAME}_library PUBLIC cxx_std_17) +# target_compile_features(${PROJECT_NAME}_library PUBLIC cxx_std_17) -target_include_directories( - ${PROJECT_NAME}_library - PUBLIC - $ - $ -) +# target_include_directories( +# ${PROJECT_NAME}_library +# PUBLIC +# $ +# $ +# ) -target_link_libraries( - ${PROJECT_NAME}_library - PUBLIC - hardware_interface::hardware_interface - pluginlib::pluginlib - rclcpp::rclcpp - rclcpp_lifecycle::rclcpp_lifecycle -) +# target_link_libraries( +# ${PROJECT_NAME}_library +# PUBLIC +# hardware_interface::hardware_interface +# pluginlib::pluginlib +# rclcpp::rclcpp +# rclcpp_lifecycle::rclcpp_lifecycle +# ) -install( - DIRECTORY src/include/ - DESTINATION include - FILES_MATCHING PATTERN "*.hpp" -) +# install( +# DIRECTORY src/include/ +# DESTINATION include +# FILES_MATCHING PATTERN "*.hpp" +# ) install( DIRECTORY launch @@ -54,21 +54,21 @@ install( DESTINATION share/${PROJECT_NAME} ) -install(TARGETS ${PROJECT_NAME}_library - EXPORT export_${PROJECT_NAME}_library - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin -) +# install(TARGETS ${PROJECT_NAME}_library +# EXPORT export_${PROJECT_NAME}_library +# ARCHIVE DESTINATION lib +# LIBRARY DESTINATION lib +# RUNTIME DESTINATION bin +# ) -ament_export_targets(export_${PROJECT_NAME}_library HAS_LIBRARY_TARGET) -ament_export_dependencies( - controller_manager - hardware_interface - rclcpp - pluginlib - rclcpp_lifecycle -) +# ament_export_targets(export_${PROJECT_NAME}_library HAS_LIBRARY_TARGET) +# ament_export_dependencies( +# controller_manager +# hardware_interface +# rclcpp +# pluginlib +# rclcpp_lifecycle +# ) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) From 36adb3fc3f336a726e4a2af43129f7747adec464 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Wed, 11 Feb 2026 21:42:17 +0000 Subject: [PATCH 63/80] Pass use_mock_hardware through publisher launch --- .../launch/publisher.launch.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/igvc_description/launch/publisher.launch.py b/src/igvc_description/launch/publisher.launch.py index 241388f2..ce80a524 100644 --- a/src/igvc_description/launch/publisher.launch.py +++ b/src/igvc_description/launch/publisher.launch.py @@ -56,18 +56,34 @@ def generate_launch_description(): package='joint_state_publisher_gui', executable='joint_state_publisher_gui', name='joint_state_publisher_gui') - # Subscribe to the joint states of the robot, and publish the 3D pose of each link. start_robot_state_publisher_cmd = Node( condition=IfCondition(use_robot_state_pub), package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'use_sim_time': use_sim_time, - 'robot_description': Command(['xacro ', urdf_model])}], + 'robot_description': Command( + [ + 'xacro ', urdf_model, + ' use_mock_hardware:=', LaunchConfiguration('use_mock_hardware') + ] + ), + }], arguments=[default_urdf_model_path]) # Create the launch description and populate - launch_description = LaunchDescription() + # Create the launch description and populate + launch_description = LaunchDescription( + [ + DeclareLaunchArgument( + 'use_mock_hardware', + default_value='false', + description='Run in Simulation' + ), + + ] + ) + # Declare the launch options launch_description.add_action(declare_urdf_model_path_cmd) From ca338ad4615e9f7415ee40be3cf9bd98bad35ff4 Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 11 Feb 2026 21:42:21 +0000 Subject: [PATCH 64/80] Add type to bot_drive_controller --- src/igvc_hardware/config/bot_controllers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index a8c993dc..fe0c0fc0 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -6,7 +6,7 @@ controller_manager: type: diff_drive_controller/DiffDriveController bot_drive_controller: ros__parameters: - + type: diff_drive_controller/DiffDriveController left_wheel_names: ["left_wheel_joint"] right_wheel_names: ["right_wheel_joint"] From 948f5e7c1784059cbd97bfcc2e90b32c5d570e3e Mon Sep 17 00:00:00 2001 From: Zane Brotherton Date: Wed, 11 Feb 2026 21:42:30 +0000 Subject: [PATCH 65/80] Add config to cmakelist --- src/igvc_hardware/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/igvc_hardware/CMakeLists.txt b/src/igvc_hardware/CMakeLists.txt index 5779223c..10ab2bc5 100644 --- a/src/igvc_hardware/CMakeLists.txt +++ b/src/igvc_hardware/CMakeLists.txt @@ -54,6 +54,11 @@ install( DESTINATION share/${PROJECT_NAME} ) +install( + DIRECTORY config + DESTINATION share/${PROJECT_NAME} +) + # install(TARGETS ${PROJECT_NAME}_library # EXPORT export_${PROJECT_NAME}_library # ARCHIVE DESTINATION lib From 5febf980ed455289bb26fd4fd9871d3b8dad0db5 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Wed, 11 Feb 2026 21:48:30 +0000 Subject: [PATCH 66/80] add state interfaces to wheel joints --- src/igvc_hardware/description/bot.ros2_control.xacro | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/igvc_hardware/description/bot.ros2_control.xacro b/src/igvc_hardware/description/bot.ros2_control.xacro index 85a84451..3b5bd312 100644 --- a/src/igvc_hardware/description/bot.ros2_control.xacro +++ b/src/igvc_hardware/description/bot.ros2_control.xacro @@ -24,9 +24,15 @@ 0 + + + 1 + + + From 8ca05d0041bc04a32820f8286bde820edb7d3c18 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 13 Feb 2026 22:38:18 +0000 Subject: [PATCH 67/80] Ros2 control actually runs! It was an error in rclcpp --- .devcontainer/setup.sh | 1 + .../launch/publisher.launch.py | 2 +- src/igvc_hardware/config/bot_controllers.yaml | 60 +++++++++---------- src/igvc_hardware/launch/control.launch.py | 3 +- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 5a445bc8..4cc91293 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -2,6 +2,7 @@ mkdir -p lib cd lib apt-get update --fix-missing +apt-get upgrade . /opt/ros/jazzy/setup.sh diff --git a/src/igvc_description/launch/publisher.launch.py b/src/igvc_description/launch/publisher.launch.py index ce80a524..dd9850a9 100644 --- a/src/igvc_description/launch/publisher.launch.py +++ b/src/igvc_description/launch/publisher.launch.py @@ -69,7 +69,7 @@ def generate_launch_description(): ] ), }], - arguments=[default_urdf_model_path]) + ) # Create the launch description and populate # Create the launch description and populate diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index fe0c0fc0..01e67d1f 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -45,33 +45,33 @@ bot_drive_controller: # TODO work w/ mech. & auton to find kinematic limits # TODO - linear.x.has_velocity_limits: true - linear.x.max_velocity: 1.0 - linear.x.min_velocity: -1.0 - - # TODO - linear.x.has_acceleration_limits: true - linear.x.max_acceleration: 1.0 - linear.x.max_deceleration: 1.0 - linear.x.max_acceleration_reverse: 1.0 - linear.x.max_deceleration_reverse: 1.0 - - #TODO - linear.x.has_jerk_limits: false - linear.x.max_jerk: 0.0 - linear.x.min_jerk: 0.0 - - # TODO - angular.z.has_velocity_limits: true - angular.z.max_velocity: 1.0 - angular.z.min_velocity: -1.0 - - # TODO - angular.z.has_acceleration_limits: true - angular.z.max_acceleration: 1.0 - angular.z.min_acceleration: -1.0 - - # TODO - angular.z.has_jerk_limits: false - angular.z.max_jerk: 0.0 - angular.z.min_jerk: 0.0 \ No newline at end of file + # linear.x.has_velocity_limits: true + # linear.x.max_velocity: 1.0 + # linear.x.min_velocity: -1.0 + + # # TODO + # linear.x.has_acceleration_limits: true + # linear.x.max_acceleration: 1.0 + # linear.x.max_deceleration: 1.0 + # linear.x.max_acceleration_reverse: 1.0 + # linear.x.max_deceleration_reverse: 1.0 + + # #TODO + # linear.x.has_jerk_limits: false + # linear.x.max_jerk: 0.0 + # linear.x.min_jerk: 0.0 + + # # TODO + # angular.z.has_velocity_limits: true + # angular.z.max_velocity: 1.0 + # angular.z.min_velocity: -1.0 + + # # TODO + # angular.z.has_acceleration_limits: true + # angular.z.max_acceleration: 1.0 + # angular.z.min_acceleration: -1.0 + + # # TODO + # angular.z.has_jerk_limits: false + # angular.z.max_jerk: 0.0 + # angular.z.min_jerk: 0.0 \ No newline at end of file diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py index 9b8dacbe..1dde2665 100644 --- a/src/igvc_hardware/launch/control.launch.py +++ b/src/igvc_hardware/launch/control.launch.py @@ -3,11 +3,12 @@ from launch_ros.substitutions import FindPackageShare from launch_ros.actions import Node +from launch.substitutions import PathJoinSubstitution def generate_launch_description(): # Declare args declared_arguments = [] - + # Get nodes robot_controllers = PathJoinSubstitution( [ From f041dbaf40ff97a4cb5a71ebae62824bb50cf37f Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 13 Feb 2026 23:02:24 +0000 Subject: [PATCH 68/80] remove publisher node from empty world launch file --- src/igvc_gazebo/launch/empty_world.launch.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index 5b34a6ca..d47892b5 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -15,7 +15,6 @@ def generate_launch_description(): package_description = FindPackageShare(package='igvc_description').find('igvc_description') gz_launch_path = os.path.join(ros_gz_sim_package, 'launch', 'gz_sim.launch.py') - publisher_launch_path = os.path.join(package_description, 'launch/publisher.launch.py') world = LaunchConfiguration('world') @@ -36,10 +35,6 @@ def generate_launch_description(): launch_arguments={'gz_args': ['-r -v4 ', world], 'on_exit_shutdown': 'true'}.items() ) - start_publisher_cmd = IncludeLaunchDescription( - PythonLaunchDescriptionSource(publisher_launch_path) - ) - spawn_entity = Node(package='ros_gz_sim', executable='create', arguments=['-topic', 'robot_description', '-name', 'igvc_robot'], @@ -61,7 +56,6 @@ def generate_launch_description(): return LaunchDescription([ world_arg, gazebo, - start_publisher_cmd, spawn_entity, ros_gz_bridge ]) From 3e7ebaa7362159d6680047f1073b92732256d24a Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 13 Feb 2026 23:02:51 +0000 Subject: [PATCH 69/80] Remove unused launch file --- src/igvc_gazebo/launch/gazebo.launch.py | 28 ------------------------- 1 file changed, 28 deletions(-) delete mode 100644 src/igvc_gazebo/launch/gazebo.launch.py diff --git a/src/igvc_gazebo/launch/gazebo.launch.py b/src/igvc_gazebo/launch/gazebo.launch.py deleted file mode 100644 index 077e7e7e..00000000 --- a/src/igvc_gazebo/launch/gazebo.launch.py +++ /dev/null @@ -1,28 +0,0 @@ -from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch_ros.substitutions import FindPackageShare -from launch.substitutions import LaunchConfiguration -from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument - - -def generate_launch_description(): - # scenario = - - return LaunchDescription([ - DeclareLaunchArgument( - 'scenario', - default_value='empty_world', - description='Simulation Scenario' - ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [ - FindPackageShare('igvc_gazebo'), - '/launch/', - LaunchConfiguration('scenario'), - '.launch.py' - ] - ) - ) - ]) From 2e890fd543a3ce119b671c59537f7d29a7fce03e Mon Sep 17 00:00:00 2001 From: tagciccone Date: Fri, 13 Feb 2026 23:16:19 +0000 Subject: [PATCH 70/80] remove nav2 bringup from launch file --- src/igvc_bringup/launch/bringup.launch.py | 53 ++++++++++++----------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index e1e6baa5..d01f017c 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -27,6 +27,13 @@ def generate_launch_description(): default_value=use_sim, # You are always mocking in simulation, but can specify if you need to bypass physical descriptors for testing description='Mocks all hardware' ), + + DeclareLaunchArgument( + 'auton', + default_value='true', + description='Testing autonomy or not' + ), + # Publishers & URDF IncludeLaunchDescription( @@ -61,31 +68,25 @@ def generate_launch_description(): ), ), - # # Real hardware - # IncludeLaunchDescription( - # PythonLaunchDescriptionSource( - # [FindPackageShare('igvc_hardware'), - # '/launch', - # '/hardware.launch.py'] - # ), - # condition = UnlessCondition(use_sim) - # ), - - # # SLAM - # IncludeLaunchDescription( - # PythonLaunchDescriptionSource( - # [FindPackageShare('igvc_slam'), - # '/launch', - # '/rtabmap.launch.py'] - # ), - # ), + # Real hardware + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_hardware'), + '/launch', + '/hardware.launch.py'] + ), + condition = UnlessCondition(use_mock_hardware) + ), - # # Navigation - # IncludeLaunchDescription( - # PythonLaunchDescriptionSource( - # [FindPackageShare('igvc_nav'), - # '/launch', - # '/igvc_nav.launch.py'] - # ), - # ), + # SLAM + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [FindPackageShare('igvc_slam'), + '/launch', + '/rtabmap.launch.py'] + ), + condition = IfCondition(LaunchConfiguration('auton')) + ), + + # TODO Nav ]) From 5cd6395555a323f88223462d586297ea172a7dac Mon Sep 17 00:00:00 2001 From: tagciccone Date: Thu, 19 Feb 2026 15:28:49 -0500 Subject: [PATCH 71/80] Update launch configuration names for clarity --- src/igvc_bringup/launch/bringup.launch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/igvc_bringup/launch/bringup.launch.py b/src/igvc_bringup/launch/bringup.launch.py index d01f017c..9fcfb48f 100644 --- a/src/igvc_bringup/launch/bringup.launch.py +++ b/src/igvc_bringup/launch/bringup.launch.py @@ -18,7 +18,7 @@ def generate_launch_description(): description='Run in Simulation' ), DeclareLaunchArgument( - 'sim_scenario', + 'sim_world', default_value='empty_world', description='The name of the scenario to open in Gazebo' ), @@ -29,9 +29,9 @@ def generate_launch_description(): ), DeclareLaunchArgument( - 'auton', + 'use_slam', default_value='true', - description='Testing autonomy or not' + description='Launch rtabmap for SLAM' ), @@ -52,7 +52,7 @@ def generate_launch_description(): PythonLaunchDescriptionSource( [FindPackageShare('igvc_gazebo'), '/launch/', - LaunchConfiguration('sim_scenario'), + LaunchConfiguration('sim_world'), '.launch.py' ] ), @@ -85,7 +85,7 @@ def generate_launch_description(): '/launch', '/rtabmap.launch.py'] ), - condition = IfCondition(LaunchConfiguration('auton')) + condition = IfCondition(LaunchConfiguration('use_slam')) ), # TODO Nav From e849f37ca57c32af50237043f1867353e272ef93 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Thu, 19 Feb 2026 15:31:06 -0500 Subject: [PATCH 72/80] Move bot.ros2_control.xacro to igvc_description --- .../control}/bot.ros2_control.xacro | 0 src/igvc_description/urdf/robot.urdf.xacro | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{igvc_hardware/description => igvc_description/control}/bot.ros2_control.xacro (100%) diff --git a/src/igvc_hardware/description/bot.ros2_control.xacro b/src/igvc_description/control/bot.ros2_control.xacro similarity index 100% rename from src/igvc_hardware/description/bot.ros2_control.xacro rename to src/igvc_description/control/bot.ros2_control.xacro diff --git a/src/igvc_description/urdf/robot.urdf.xacro b/src/igvc_description/urdf/robot.urdf.xacro index 0aa19268..ab0aee61 100644 --- a/src/igvc_description/urdf/robot.urdf.xacro +++ b/src/igvc_description/urdf/robot.urdf.xacro @@ -15,7 +15,7 @@ - + From e722e1706e7d80d440474316c6050395afd9b47d Mon Sep 17 00:00:00 2001 From: vincentfborello Date: Fri, 20 Feb 2026 23:41:57 +0000 Subject: [PATCH 73/80] Publisher.launch.py file refactored --- .../launch/publisher.launch.py | 113 +++++++++++++++--- 1 file changed, 94 insertions(+), 19 deletions(-) diff --git a/src/igvc_description/launch/publisher.launch.py b/src/igvc_description/launch/publisher.launch.py index bee0e333..67f755ca 100644 --- a/src/igvc_description/launch/publisher.launch.py +++ b/src/igvc_description/launch/publisher.launch.py @@ -2,61 +2,139 @@ from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.conditions import IfCondition, UnlessCondition -from launch.substitutions import Command, LaunchConfiguration +from launch.substitutions import Command, LaunchConfiguration, PathJoinSubstitution from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare def generate_launch_description(): - + declared_arguments = [] # Set the path to this package. igvc_description_package = FindPackageShare(package='igvc_description').find('igvc_description') + default_urdf = PathJoinSubstitution( + [ + igvc_description_package, + 'urdf', + 'robot.urdf.xacro' + ] + ) + + declared_arguments.append( + DeclareLaunchArgument( + 'urdf_model', + default_value=default_urdf, + description='Absolute path to robot urdf file' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'gui', + default_value='False', + description='Flag to enable joint_state_publisher_gui' + ) + ) + + declared_arguments.append( + DeclareLaunchArgument( + 'use_robot_state_pub', + default_value='True', + description='Whether to start the robot state publisher' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'use_sim_time', + default_value='False', + description='Use simulation (Gazebo) clock if true' + ) + ) + + urdf_model = LaunchConfiguration('urdf_model') + gui = LaunchConfiguration('gui') + use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') + use_sim_time = LaunchConfiguration('use_sim_time') + + + joint_state_publisher_node = Node( + package='joint_state_publisher', + executable='joint_state_publisher', + name='joint_state_publisher', + condition=UnlessCondition(gui) + ) + joint_state_publisher_gui_node = Node( + package='joint_state_publisher_gui', + executable='joint_state_publisher_gui', + name='joint_state_publisher_gui', + condition=IfCondition(gui) + ) + + robot_state_publisher_node = Node( + package='robot_state_publisher', + executable='robot_state_publisher', + name='robot_state_publisher', + parameters=[{'use_sim_time': use_sim_time, + 'robot_description': Command([ + 'xacro ', urdf_model, + 'use_mock_hardware:=', LaunchConfiguration('use_mock_hardware') + ] + ) + }] + ) + foxglove_bridge_node = Node( + package='foxglove_bridge', + executable='foxglove_bridge' + ) + Nodes = [ + joint_state_publisher_node, + joint_state_publisher_gui_node, + robot_state_publisher_node, + foxglove_bridge_node] + + return LaunchDescription(declared_arguments + Nodes) +""" # Set the path to the URDF file default_urdf_model_path = os.path.join(igvc_description_package , 'urdf/robot.urdf.xacro') # Launch configuration variables specific to simulation + + gui = LaunchConfiguration('gui') urdf_model = LaunchConfiguration('urdf_model') use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') use_sim_time = LaunchConfiguration('use_sim_time') - - # Declare the launch arguments + declare_urdf_model_path_cmd = DeclareLaunchArgument( name='urdf_model', default_value=default_urdf_model_path, description='Absolute path to robot urdf file') - + declare_use_joint_state_publisher_cmd = DeclareLaunchArgument( name='gui', default_value='False', description='Flag to enable joint_state_publisher_gui') - + declare_use_robot_state_pub_cmd = DeclareLaunchArgument( name='use_robot_state_pub', default_value='True', description='Whether to start the robot state publisher') - + declare_use_sim_time_cmd = DeclareLaunchArgument( name='use_sim_time', default_value='False', description='Use simulation (Gazebo) clock if true') - - # Specify the actions - - # Publish the joint state values for the non-fixed joints in the URDF file. + start_joint_state_publisher_cmd = Node( condition=UnlessCondition(gui), package='joint_state_publisher', executable='joint_state_publisher', name='joint_state_publisher') - # A GUI to manipulate the joint state values start_joint_state_publisher_gui_node = Node( condition=IfCondition(gui), package='joint_state_publisher_gui', executable='joint_state_publisher_gui', name='joint_state_publisher_gui') - # Subscribe to the joint states of the robot, and publish the 3D pose of each link. + start_robot_state_publisher_cmd = Node( condition=IfCondition(use_robot_state_pub), package='robot_state_publisher', @@ -70,16 +148,12 @@ def generate_launch_description(): ), }], ) - - # Create foxglove bridge + start_foxglove_bridge_cmd = Node( package='foxglove_bridge', executable='foxglove_bridge' ) - - # Create the launch description and populate - # Create the launch description and populate - launch_description = LaunchDescription( + launch_description = LaunchDescription( [ DeclareLaunchArgument( 'use_mock_hardware', @@ -104,3 +178,4 @@ def generate_launch_description(): launch_description.add_action(start_robot_state_publisher_cmd) return launch_description +""" From 3852d6ce815baad74c8f525e47d141b7ce299459 Mon Sep 17 00:00:00 2001 From: vincentfborello Date: Fri, 27 Feb 2026 21:55:26 +0000 Subject: [PATCH 74/80] rviz.launch.py File Refactor --- src/igvc_description/launch/rviz.launch.py | 83 +++++++++++++++++++--- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/igvc_description/launch/rviz.launch.py b/src/igvc_description/launch/rviz.launch.py index 2fd8630e..56ad4744 100644 --- a/src/igvc_description/launch/rviz.launch.py +++ b/src/igvc_description/launch/rviz.launch.py @@ -8,20 +8,81 @@ from launch_ros.substitutions import FindPackageShare def generate_launch_description(): - + declared_arguments = [] # Set the path to this package. igvc_description_package = FindPackageShare(package='igvc_description').find('igvc_description') - - # Set the path to the RViz configuration settings default_rviz_config_path = os.path.join(igvc_description_package, 'rviz/rviz_settings.rviz') - - # Set the path to the URDF file default_urdf_model_path = os.path.join(igvc_description_package, 'urdf/robot.urdf.xacro') - - # Set the path to the Publisher launch file publisher_launch_path = os.path.join(igvc_description_package, 'launch/publisher.launch.py') - # Launch configuration variables specific to simulation + declared_arguments.append( + DeclareLaunchArgument( + 'urdf_model', + default_value=default_urdf_model_path, + description='Absolute path to robot urdf file' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'gui', + default_value='True', + description='Flag to enable joint_state_publisher_gui' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'use_robot_state_pub', + default_value='True', + description='Whether to start the robot state publisher' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'use_sim_time', + default_value='True', + description='Use simulation (Gazebo) clock if true' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'rviz_config_file', + default_value=default_rviz_config_path, + description='Full path to the RVIZ config file to use' + ) + ) + declared_arguments.append( + DeclareLaunchArgument( + 'use_rviz', + default_value='true', + description='Whether to start RVIZ' + ) + ) + urdf_model = LaunchConfiguration('urdf_model') + gui = LaunchConfiguration('gui') + use_robot_state_pub = LaunchConfiguration('use_robot_state_pub') + use_sim_time = LaunchConfiguration('use_sim_time') + rviz_config_file = LaunchConfiguration('rviz_config_file') + use_rviz = LaunchConfiguration('use_rviz') + + + + rviz_node = Node( + package='rviz2', + executable='rviz2', + name='rviz2', + condition=IfCondition(use_rviz), + output='screen', + arguments=['-d', rviz_config_file] + ) + Node = [ + rviz_node + ] + return LaunchDescription(declared_arguments + Node) + + + +""" + # Launch configuration variables specific to simulation gui = LaunchConfiguration('gui') urdf_model = LaunchConfiguration('urdf_model') rviz_config_file = LaunchConfiguration('rviz_config_file') @@ -59,7 +120,7 @@ def generate_launch_description(): name='use_sim_time', default_value='True', description='Use simulation (Gazebo) clock if true') - + # Specify the publisher action start_publisher_cmd = IncludeLaunchDescription( PythonLaunchDescriptionSource(publisher_launch_path), @@ -70,7 +131,6 @@ def generate_launch_description(): 'use_sim_time' : use_sim_time, }.items() ) - # Launch RViz start_rviz_cmd = Node( condition=IfCondition(use_rviz), @@ -80,7 +140,7 @@ def generate_launch_description(): output='screen', arguments=['-d', rviz_config_file]) - # Create the launch description and populate + # Create the launch description and populate launch_description = LaunchDescription() # Declare the launch options @@ -96,3 +156,4 @@ def generate_launch_description(): launch_description.add_action(start_rviz_cmd) return launch_description +""" \ No newline at end of file From 5abd2462c090141facf9141245c11a3399ee43fc Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 27 Feb 2026 22:46:02 +0000 Subject: [PATCH 75/80] empty_world.launch.py File Refactor --- src/igvc_gazebo/launch/empty_world.launch.py | 61 +++++++++++--------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/igvc_gazebo/launch/empty_world.launch.py b/src/igvc_gazebo/launch/empty_world.launch.py index d47892b5..3ff4664d 100644 --- a/src/igvc_gazebo/launch/empty_world.launch.py +++ b/src/igvc_gazebo/launch/empty_world.launch.py @@ -1,47 +1,44 @@ import os - from ament_index_python.packages import get_package_share_directory - from launch import LaunchDescription from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch_ros.substitutions import FindPackageShare - from launch_ros.actions import Node def generate_launch_description(): + declared_arguments = [] + igvc_description_package = FindPackageShare(package='igvc_description').find('igvc_description') ros_gz_sim_package = get_package_share_directory('ros_gz_sim') - package_description = FindPackageShare(package='igvc_description').find('igvc_description') - gz_launch_path = os.path.join(ros_gz_sim_package, 'launch', 'gz_sim.launch.py') - world = LaunchConfiguration('world') - - default_world = os.path.join( - get_package_share_directory('igvc_gazebo'), - 'worlds', - 'empty_world.sdf' - ) - - world_arg = DeclareLaunchArgument( - 'world', - default_value=default_world, - description='World to load' + bridge_params = os.path.join(get_package_share_directory('igvc_gazebo'), 'config', 'gz_bridge.yaml') + default_world = os.path.join(get_package_share_directory('igvc_gazebo'), 'worlds','empty_world.sdf') + + declared_arguments.append( + DeclareLaunchArgument( + 'world', + default_value=default_world, + description='World to load' + ) ) - - gazebo = IncludeLaunchDescription( + declared_arguments.append( + DeclareLaunchArgument( + 'gazebo', PythonLaunchDescriptionSource(gz_launch_path), launch_arguments={'gz_args': ['-r -v4 ', world], 'on_exit_shutdown': 'true'}.items() ) + ) + - spawn_entity = Node(package='ros_gz_sim', executable='create', + spawn_entity = Node( + package='ros_gz_sim', executable='create', arguments=['-topic', 'robot_description', '-name', 'igvc_robot'], output='screen' ) - bridge_params = os.path.join(get_package_share_directory('igvc_gazebo'), 'config', 'gz_bridge.yaml') ros_gz_bridge = Node( package="ros_gz_bridge", @@ -52,10 +49,22 @@ def generate_launch_description(): f'config_file:={bridge_params}', ] ) - - return LaunchDescription([ - world_arg, - gazebo, + Node = [ spawn_entity, ros_gz_bridge - ]) + ] + return LaunchDescription(declared_arguments + Node) + +""" + world_arg = DeclareLaunchArgument( + 'world', + default_value=default_world, + description='World to load' + ) + gazebo = IncludeLaunchDescription( + PythonLaunchDescriptionSource(gz_launch_path), + launch_arguments={'gz_args': ['-r -v4 ', world], 'on_exit_shutdown': 'true'}.items() + ) + +""" + \ No newline at end of file From 1e5c5cb668140bfe172a2760c2434f97a2c16220 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 27 Feb 2026 23:12:01 +0000 Subject: [PATCH 76/80] igvc_nav.launch.py File Refactor --- src/igvc_nav/launch/igvc_nav.launch.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/igvc_nav/launch/igvc_nav.launch.py b/src/igvc_nav/launch/igvc_nav.launch.py index 221c536c..5c79494a 100644 --- a/src/igvc_nav/launch/igvc_nav.launch.py +++ b/src/igvc_nav/launch/igvc_nav.launch.py @@ -1,25 +1,33 @@ import os - from ament_index_python.packages import get_package_share_directory - from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource def generate_launch_description(): + declared_arguments = [] nav2_bringup_package = get_package_share_directory('nav2_bringup') igvc_nav_package = get_package_share_directory('igvc_nav') - nav2_launch_path = os.path.join(nav2_bringup_package , 'launch', 'navigation_launch.py') config_path = os.path.join(igvc_nav_package, 'config', 'nav2_params.yaml') + declared_arguments.append( + IncludeLaunchDescription( + PythonLaunchDescriptionSource(nav2_launch_path), + launch_arguments={ + 'params_file' : config_path + }.items() + ) + ) + Node = [ + ] + + return LaunchDescription(declared_arguments + Node) + """ nav2 = IncludeLaunchDescription( PythonLaunchDescriptionSource(nav2_launch_path), launch_arguments={ 'params_file' : config_path }.items() ) - - return LaunchDescription([ - nav2 - ]) \ No newline at end of file + """ \ No newline at end of file From 6fbaccbf2772855bc0ab1452dc57f39e88fa211f Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 10 Mar 2026 19:57:26 +0000 Subject: [PATCH 77/80] igvc_nav.launch.py File Refactor --- src/igvc_hardware/launch/control.launch.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py index 1dde2665..89059455 100644 --- a/src/igvc_hardware/launch/control.launch.py +++ b/src/igvc_hardware/launch/control.launch.py @@ -2,7 +2,6 @@ from launch.substitutions import PathJoinSubstitution from launch_ros.substitutions import FindPackageShare from launch_ros.actions import Node - from launch.substitutions import PathJoinSubstitution def generate_launch_description(): @@ -10,13 +9,7 @@ def generate_launch_description(): declared_arguments = [] # Get nodes - robot_controllers = PathJoinSubstitution( - [ - FindPackageShare("igvc_hardware"), - "config", - "bot_controllers.yaml", - ] - ) + robot_controllers = PathJoinSubstitution([FindPackageShare("igvc_hardware"),"config","bot_controllers.yaml",]) control_node = Node( package="controller_manager", From 301767d66962f1e84f7d0eb7f9a4fdb47530afdc Mon Sep 17 00:00:00 2001 From: Vincent Borello Date: Fri, 20 Mar 2026 21:37:50 +0000 Subject: [PATCH 78/80] igvc_nav.launch.py Nodes fix/Comment Fix --- src/igvc_nav/launch/igvc_nav.launch.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/igvc_nav/launch/igvc_nav.launch.py b/src/igvc_nav/launch/igvc_nav.launch.py index 5c79494a..600a059b 100644 --- a/src/igvc_nav/launch/igvc_nav.launch.py +++ b/src/igvc_nav/launch/igvc_nav.launch.py @@ -10,7 +10,13 @@ def generate_launch_description(): igvc_nav_package = get_package_share_directory('igvc_nav') nav2_launch_path = os.path.join(nav2_bringup_package , 'launch', 'navigation_launch.py') config_path = os.path.join(igvc_nav_package, 'config', 'nav2_params.yaml') - + + nav2 = IncludeLaunchDescription( + PythonLaunchDescriptionSource(nav2_launch_path), + launch_arguments={ + 'params_file' : config_path + }.items() + ) declared_arguments.append( IncludeLaunchDescription( PythonLaunchDescriptionSource(nav2_launch_path), @@ -19,15 +25,8 @@ def generate_launch_description(): }.items() ) ) - Node = [ + Nodes = [ ] - return LaunchDescription(declared_arguments + Node) - """ - nav2 = IncludeLaunchDescription( - PythonLaunchDescriptionSource(nav2_launch_path), - launch_arguments={ - 'params_file' : config_path - }.items() - ) - """ \ No newline at end of file + return LaunchDescription(declared_arguments + Nodes) + \ No newline at end of file From 317bf0c7071ba371bf5306b32e37368c08bbc794 Mon Sep 17 00:00:00 2001 From: Vincent Borello Date: Tue, 24 Mar 2026 20:58:54 +0000 Subject: [PATCH 79/80] Control.launch.py use_sim fix --- src/igvc_hardware/launch/control.launch.py | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/igvc_hardware/launch/control.launch.py b/src/igvc_hardware/launch/control.launch.py index 89059455..bf718805 100644 --- a/src/igvc_hardware/launch/control.launch.py +++ b/src/igvc_hardware/launch/control.launch.py @@ -1,21 +1,34 @@ from launch import LaunchDescription -from launch.substitutions import PathJoinSubstitution +from launch.actions import DeclareLaunchArgument +from launch.substitutions import PathJoinSubstitution, LaunchConfiguration from launch_ros.substitutions import FindPackageShare from launch_ros.actions import Node -from launch.substitutions import PathJoinSubstitution def generate_launch_description(): # Declare args declared_arguments = [] + + declared_arguments.append( + DeclareLaunchArgument( + name="use_sim", + default_value="false", + description="Whether or not the robot is launching in simulation" + ) + ) + + use_sim = LaunchConfiguration("use_sim") # Get nodes - robot_controllers = PathJoinSubstitution([FindPackageShare("igvc_hardware"),"config","bot_controllers.yaml",]) - + robot_controllers = PathJoinSubstitution([ + FindPackageShare("igvc_hardware"), + "config", + "bot_controllers.yaml", + ]) + control_node = Node( package="controller_manager", executable="ros2_control_node", parameters=[ - # TODO in the ODrive botwheel explorer example, the description contents are also passed in here. robot_controllers ], output="both", @@ -24,8 +37,10 @@ def generate_launch_description(): robot_controller_spawner = Node( package="controller_manager", executable="spawner", - arguments=["bot_drive_controller", - "--controller-manager", "/controller_manager"], + arguments=[ + "bot_drive_controller", + "--controller-manager", "/controller_manager" + ], ) nodes = [ @@ -33,4 +48,4 @@ def generate_launch_description(): robot_controller_spawner ] - return LaunchDescription(declared_arguments + nodes) + return LaunchDescription(declared_arguments + nodes) \ No newline at end of file From 40ad9a1161589196fd549c2264020b64c912059d Mon Sep 17 00:00:00 2001 From: Vincent Borello Date: Tue, 24 Mar 2026 21:00:48 +0000 Subject: [PATCH 80/80] bot_controllers.yaml uncomment --- src/igvc_hardware/config/bot_controllers.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/igvc_hardware/config/bot_controllers.yaml b/src/igvc_hardware/config/bot_controllers.yaml index 5cd1e615..cd4292a6 100644 --- a/src/igvc_hardware/config/bot_controllers.yaml +++ b/src/igvc_hardware/config/bot_controllers.yaml @@ -60,10 +60,10 @@ bot_drive_controller: linear.x.max_acceleration_reverse: -1.0 linear.x.max_deceleration_reverse: 1.0 - # #TODO - # linear.x.has_jerk_limits: false - # linear.x.max_jerk: 0.0 - # linear.x.min_jerk: 0.0 + #TODO + linear.x.has_jerk_limits: false + linear.x.max_jerk: 0.0 + linear.x.min_jerk: 0.0 # TODO angular.z.has_velocity_limits: false