Done
This commit is contained in:
@@ -8,6 +8,7 @@ install(PROGRAMS
|
|||||||
scripts/move_to_point.py
|
scripts/move_to_point.py
|
||||||
scripts/arm_point_controller.py
|
scripts/arm_point_controller.py
|
||||||
scripts/mission.py
|
scripts/mission.py
|
||||||
|
scripts/detection.py
|
||||||
DESTINATION lib/${PROJECT_NAME}
|
DESTINATION lib/${PROJECT_NAME}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import time
|
|||||||
|
|
||||||
# ROS 2 Message, Action, and Service Imports
|
# ROS 2 Message, Action, and Service Imports
|
||||||
from moveit_msgs.action import MoveGroup, ExecuteTrajectory
|
from moveit_msgs.action import MoveGroup, ExecuteTrajectory
|
||||||
from moveit_msgs.msg import Constraints, RobotState
|
from moveit_msgs.msg import Constraints, RobotState, PositionConstraint, OrientationConstraint, BoundingVolume
|
||||||
from moveit_msgs.srv import GetCartesianPath, GetPositionFK
|
from moveit_msgs.srv import GetCartesianPath, GetPositionFK
|
||||||
|
from shape_msgs.msg import SolidPrimitive
|
||||||
from geometry_msgs.msg import PoseStamped, Pose
|
from geometry_msgs.msg import PoseStamped, Pose
|
||||||
from sensor_msgs.msg import JointState
|
from sensor_msgs.msg import JointState
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ class CameraArmController(Node):
|
|||||||
self.get_logger().info("Connecting to Forward Kinematics service...")
|
self.get_logger().info("Connecting to Forward Kinematics service...")
|
||||||
self._fk_client.wait_for_service()
|
self._fk_client.wait_for_service()
|
||||||
|
|
||||||
# 5. Subscriber to listen to current joint states (Required to compute current position)
|
# 5. Subscriber to listen to current joint states
|
||||||
self.current_joint_state = None
|
self.current_joint_state = None
|
||||||
self._joint_sub = self.create_subscription(
|
self._joint_sub = self.create_subscription(
|
||||||
JointState,
|
JointState,
|
||||||
@@ -52,13 +53,24 @@ class CameraArmController(Node):
|
|||||||
self.current_joint_state = msg
|
self.current_joint_state = msg
|
||||||
|
|
||||||
def get_horizontal_camera_quaternion(self):
|
def get_horizontal_camera_quaternion(self):
|
||||||
"""Calculates a 90-degree pitch offset to keep the camera level with the horizon"""
|
"""Keeps camera level with horizon but yaws it 90 degrees sideways"""
|
||||||
pitch_angle = math.radians(-90.0)
|
pitch = math.radians(-90.0) # Keep horizon level
|
||||||
|
yaw = math.radians(90.0) # Rotate 90 degrees sideways (use -90.0 for the opposite side)
|
||||||
|
roll = 0.0
|
||||||
|
|
||||||
|
# Standard Euler to Quaternion conversion equations
|
||||||
|
cy = math.cos(yaw * 0.5)
|
||||||
|
sy = math.sin(yaw * 0.5)
|
||||||
|
cp = math.cos(pitch * 0.5)
|
||||||
|
sp = math.sin(pitch * 0.5)
|
||||||
|
cr = math.cos(roll * 0.5)
|
||||||
|
sr = math.sin(roll * 0.5)
|
||||||
|
|
||||||
q = Pose().orientation
|
q = Pose().orientation
|
||||||
q.x = 0.0
|
q.w = cr * cp * cy + sr * sp * sy
|
||||||
q.y = math.sin(pitch_angle / 2.0)
|
q.x = sr * cp * cy - cr * sp * sy
|
||||||
q.z = 0.0
|
q.y = cr * sp * cy + sr * cp * sy
|
||||||
q.w = math.cos(pitch_angle / 2.0)
|
q.z = cr * cp * sy - sr * sp * cy
|
||||||
return q
|
return q
|
||||||
|
|
||||||
def get_current_end_effector_pose(self):
|
def get_current_end_effector_pose(self):
|
||||||
@@ -101,31 +113,21 @@ class CameraArmController(Node):
|
|||||||
target_pose.pose.position.x = float(x)
|
target_pose.pose.position.x = float(x)
|
||||||
target_pose.pose.position.y = float(y)
|
target_pose.pose.position.y = float(y)
|
||||||
target_pose.pose.position.z = float(z)
|
target_pose.pose.position.z = float(z)
|
||||||
|
|
||||||
# Enforces the horizontal alignment strictly at the final destination pose
|
|
||||||
target_pose.pose.orientation = self.get_horizontal_camera_quaternion()
|
target_pose.pose.orientation = self.get_horizontal_camera_quaternion()
|
||||||
|
|
||||||
# Add target pose to the path request constraints
|
# Add target pose to the path request constraints
|
||||||
goal_constraints = Constraints()
|
goal_constraints = Constraints()
|
||||||
goal_msg.request.goal_constraints.append(goal_constraints)
|
goal_msg.request.goal_constraints.append(goal_constraints)
|
||||||
|
|
||||||
# MoveIt handles position and end-point orientation without path constraints
|
|
||||||
# By avoiding constraints during movement, the planner should avoid RRTConnect failures.
|
|
||||||
goal_msg.request.workspace_parameters.header.frame_id = "base_link"
|
goal_msg.request.workspace_parameters.header.frame_id = "base_link"
|
||||||
|
|
||||||
# Pack the target pose directly into the goal message requests
|
|
||||||
from moveit_msgs.msg import PositionConstraint, OrientationConstraint
|
|
||||||
|
|
||||||
# Create a simple end-state constraint matching our target pose
|
|
||||||
# Position Constraint
|
# Position Constraint
|
||||||
pos_con = PositionConstraint()
|
pos_con = PositionConstraint()
|
||||||
pos_con.header.frame_id = "base_link"
|
pos_con.header.frame_id = "base_link"
|
||||||
pos_con.link_name = "tool_link"
|
pos_con.link_name = "tool_link"
|
||||||
from moveit_msgs.msg import BoundingVolume
|
|
||||||
from shape_msgs.msg import SolidPrimitive
|
|
||||||
box = SolidPrimitive()
|
box = SolidPrimitive()
|
||||||
box.type = SolidPrimitive.BOX
|
box.type = SolidPrimitive.BOX
|
||||||
box.dimensions = [0.01, 0.01, 0.01] # Tight convergence tolerance
|
box.dimensions = [0.01, 0.01, 0.01]
|
||||||
volume = BoundingVolume()
|
volume = BoundingVolume()
|
||||||
volume.primitives.append(box)
|
volume.primitives.append(box)
|
||||||
|
|
||||||
@@ -141,25 +143,40 @@ class CameraArmController(Node):
|
|||||||
ori_con.header.frame_id = "base_link"
|
ori_con.header.frame_id = "base_link"
|
||||||
ori_con.link_name = "tool_link"
|
ori_con.link_name = "tool_link"
|
||||||
ori_con.orientation = target_pose.pose.orientation
|
ori_con.orientation = target_pose.pose.orientation
|
||||||
ori_con.absolute_x_axis_tolerance = 0.05
|
ori_con.absolute_x_axis_tolerance = 0.005
|
||||||
ori_con.absolute_y_axis_tolerance = 0.05
|
ori_con.absolute_y_axis_tolerance = 0.005
|
||||||
ori_con.absolute_z_axis_tolerance = 0.05
|
ori_con.absolute_z_axis_tolerance = 0.005
|
||||||
ori_con.weight = 1.0
|
ori_con.weight = 1.0
|
||||||
|
|
||||||
goal_constraints.position_constraints.append(pos_con)
|
goal_constraints.position_constraints.append(pos_con)
|
||||||
goal_constraints.orientation_constraints.append(ori_con)
|
goal_constraints.orientation_constraints.append(ori_con)
|
||||||
|
|
||||||
send_goal_future = self._action_client.send_goal_async(goal_msg)
|
send_goal_future = self._action_client.send_goal_async(goal_msg)
|
||||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
|
||||||
|
# 2. CREATE A TEMPORARY EXECUTOR FOR THIS BLOCKED MOVE
|
||||||
|
from rclpy.executors import SingleThreadedExecutor
|
||||||
|
temp_executor = SingleThreadedExecutor()
|
||||||
|
temp_executor.add_node(self) # Temporarily borrow this node context
|
||||||
|
|
||||||
|
# 3. Spin our isolated executor until MoveIt decides to accept or reject the goal
|
||||||
|
temp_executor.spin_until_future_complete(send_goal_future)
|
||||||
|
|
||||||
goal_handle = send_goal_future.result()
|
goal_handle = send_goal_future.result()
|
||||||
if not goal_handle.accepted:
|
if not goal_handle.accepted:
|
||||||
self.get_logger().error("Point-to-Point goal rejected by MoveIt!")
|
self.get_logger().error("Point-to-Point goal rejected by MoveIt!")
|
||||||
|
temp_executor.remove_node(self) # Clean up before returning
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.get_logger().info("Goal accepted! Executing path...")
|
self.get_logger().info("Goal accepted! Executing path...")
|
||||||
|
|
||||||
|
# 4. Request the execution outcome and spin our local executor again until the arm physically lands
|
||||||
get_result_future = goal_handle.get_result_async()
|
get_result_future = goal_handle.get_result_async()
|
||||||
rclpy.spin_until_future_complete(self, get_result_future)
|
temp_executor.spin_until_future_complete(get_result_future)
|
||||||
|
|
||||||
|
# 5. MANDATORY CLEANUP: Release the node back to your main script's executor loop
|
||||||
|
temp_executor.remove_node(self)
|
||||||
|
|
||||||
|
self.get_logger().info("Ruch ramienia zakończony sukcesem!")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def execute_straight_camera_sweep(self, distance=0.2):
|
def execute_straight_camera_sweep(self, distance=0.2):
|
||||||
@@ -216,19 +233,52 @@ class CameraArmController(Node):
|
|||||||
self.get_logger().error("Trajectory execution rejected.")
|
self.get_logger().error("Trajectory execution rejected.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def home_arm(self):
|
||||||
|
self.send_pt_to_pt_goal(0.35, 0.1, 1.35)
|
||||||
|
|
||||||
|
|
||||||
|
def harvest(self):
|
||||||
|
inspection_x = 0.35 # 45cm forward past the shoulder joint
|
||||||
|
inspection_y = -0.4 # Centered
|
||||||
|
inspection_z = 1.35
|
||||||
|
at_target = self.send_pt_to_pt_goal(inspection_x, inspection_y, inspection_z)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
rclpy.init(args=args)
|
rclpy.init(args=args)
|
||||||
node = CameraArmController()
|
node = CameraArmController()
|
||||||
|
|
||||||
# Coordinates are now evaluated relative to the arm's base link.
|
# --- DEFINE ROUTINE CONFIGURATIONS ---
|
||||||
# Reaches 40cm forward, 30cm down toward crops. It will rotate freely during
|
# Targets are relative to the arm's base link origin (inverted setup)
|
||||||
# transit and level out perfectly flat once it locks into the target destination.
|
inspection_x = 0.35 # 45cm forward past the shoulder joint
|
||||||
success = node.send_pt_to_pt_goal(.2, 0.3, 0.3)
|
inspection_y = -0.4 # Centered
|
||||||
|
inspection_z = 1.35 # 35cm down toward the field plants
|
||||||
|
|
||||||
if success:
|
home_x = 0.35 # Tucked back close to the vehicle frame
|
||||||
time.sleep(2.0)
|
home_y = 0.10
|
||||||
# Execute the forward crawl sweep
|
home_z = 1.35 # Retracted up high out of the crop height line
|
||||||
node.execute_straight_camera_sweep(distance=0.2)
|
|
||||||
|
# 1. Move to Predefined Inspection Target Point
|
||||||
|
node.get_logger().info("Starting inspection routine. Advancing to inspection coordinate...")
|
||||||
|
at_target = node.send_pt_to_pt_goal(inspection_x, inspection_y, inspection_z)
|
||||||
|
|
||||||
|
if at_target:
|
||||||
|
# 2. Arrived at target: Wait for sensors to settle/take pictures
|
||||||
|
wait_seconds = 3.0
|
||||||
|
node.get_logger().info(f"Arrived at inspection point. Holding position for {wait_seconds} seconds for camera sweep...")
|
||||||
|
time.sleep(wait_seconds)
|
||||||
|
|
||||||
|
# 3. Return to base point (Safe Home Configuration)
|
||||||
|
node.get_logger().info("Inspection complete. Returning to safe home position...")
|
||||||
|
returned_home = node.send_pt_to_pt_goal(home_x, home_y, home_z)
|
||||||
|
|
||||||
|
if returned_home:
|
||||||
|
node.get_logger().info("Arm securely home. Routine complete!")
|
||||||
|
else:
|
||||||
|
node.get_logger().error("Failed to return to home base point!")
|
||||||
|
else:
|
||||||
|
node.get_logger().error("Failed to reach initial inspection target!")
|
||||||
|
|
||||||
node.destroy_node()
|
node.destroy_node()
|
||||||
rclpy.shutdown()
|
rclpy.shutdown()
|
||||||
|
|||||||
Binary file not shown.
Executable
+118
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from sensor_msgs.msg import Image
|
||||||
|
# IMPORTUJEMY PROFIL QoS DLA SENSORÓW
|
||||||
|
from rclpy.qos import qos_profile_sensor_data
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
from ultralytics import YOLO
|
||||||
|
|
||||||
|
class YoloGazeboDetector(Node):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('yolo_gazebo_detector')
|
||||||
|
|
||||||
|
self.get_logger().info("Ładowanie modelu YOLO...")
|
||||||
|
self.model = YOLO('best.pt')
|
||||||
|
self.model.to('cpu')
|
||||||
|
|
||||||
|
self.window_name = "YOLOv8 - Gazebo Camera"
|
||||||
|
cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
|
||||||
|
cv2.resizeWindow(self.window_name, 640, 480)
|
||||||
|
|
||||||
|
self.frame_count = 0
|
||||||
|
self.camera_topic = '/arm_camera/image_raw'
|
||||||
|
|
||||||
|
# ZMIANA: Zastępujemy liczbę queue_size gotowym profilem qos_profile_sensor_data
|
||||||
|
self.subscription = self.create_subscription(
|
||||||
|
Image,
|
||||||
|
self.camera_topic,
|
||||||
|
self.camera_callback,
|
||||||
|
qos_profile_sensor_data
|
||||||
|
)
|
||||||
|
|
||||||
|
self.window_timer = self.create_timer(0.03, self.refresh_blank_window)
|
||||||
|
self.latest_annotated_frame = None
|
||||||
|
|
||||||
|
self.get_logger().info(f"Node i Okno GUI gotowe (Zaktualizowano QoS). Słucham na: {self.camera_topic}")
|
||||||
|
|
||||||
|
def refresh_blank_window(self):
|
||||||
|
if self.latest_annotated_frame is None:
|
||||||
|
loading_frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
||||||
|
cv2.putText(loading_frame, "Oczekiwanie na obrazy z Gazebo...", (80, 240),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||||
|
cv2.imshow(self.window_name, loading_frame)
|
||||||
|
else:
|
||||||
|
cv2.imshow(self.window_name, self.latest_annotated_frame)
|
||||||
|
cv2.waitKey(1)
|
||||||
|
|
||||||
|
def camera_callback(self, msg):
|
||||||
|
self.frame_count += 1
|
||||||
|
|
||||||
|
# Wymuszony log dla każdej klatki, żeby potwierdzić wejście do funkcji
|
||||||
|
self.get_logger().info(f"--- ODEBRANO WIADOMOŚĆ (Klatka {self.frame_count}) ---")
|
||||||
|
self.get_logger().info(f"Wymiary z nagłówka: Width={msg.width}, Height={msg.height}, Encoding={msg.encoding}")
|
||||||
|
self.get_logger().info(f"Rozmiar surowej tablicy danych (msg.data): {len(msg.data)} bajtów")
|
||||||
|
|
||||||
|
if len(msg.data) == 0:
|
||||||
|
self.get_logger().error("⚠️ Krytyczny błąd: Tablica msg.data jest całkowicie pusta!")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Konwersja do formatu numpy
|
||||||
|
flat_img_array = np.frombuffer(msg.data, dtype=np.uint8)
|
||||||
|
|
||||||
|
# Dynamiczne obliczanie oczekiwanego rozmiaru na podstawie kanałów
|
||||||
|
channels = 3
|
||||||
|
if 'rgba' in msg.encoding or 'bgra' in msg.encoding:
|
||||||
|
channels = 4
|
||||||
|
elif 'mono' in msg.encoding:
|
||||||
|
channels = 1
|
||||||
|
|
||||||
|
expected_elements = msg.height * msg.width * channels
|
||||||
|
if flat_img_array.size != expected_elements:
|
||||||
|
self.get_logger().error(f"⚠️ Niezgodność rozmiarów! Otrzymano {flat_img_array.size} elementów, a oczekiwano {expected_elements}")
|
||||||
|
# Próba awaryjnego dopasowania
|
||||||
|
cv_image = flat_img_array.reshape((msg.height, msg.step // channels, channels))[:, :msg.width, :]
|
||||||
|
else:
|
||||||
|
cv_image = flat_img_array.reshape((msg.height, msg.width, channels))
|
||||||
|
|
||||||
|
# Obsługa konwersji przestrzeni barw
|
||||||
|
if 'rgb' in msg.encoding.lower():
|
||||||
|
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_RGB2BGR)
|
||||||
|
elif channels == 4:
|
||||||
|
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGRA2BGR)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.get_logger().error(f"💥 Błąd krytyczny podczas przetwarzania macierzy: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Jeśli doszliśmy tutaj, obraz jest poprawny. Przekazujemy do YOLO
|
||||||
|
try:
|
||||||
|
results = self.model(cv_image, verbose=False, imgsz=320)
|
||||||
|
annotated_frame = cv_image.copy()
|
||||||
|
for r in results:
|
||||||
|
annotated_frame = r.plot()
|
||||||
|
|
||||||
|
# Sukces! Podmieniamy klatkę dla okna wyświetlacza
|
||||||
|
self.latest_annotated_frame = annotated_frame
|
||||||
|
except Exception as e:
|
||||||
|
self.get_logger().error(f"Błąd podczas wnioskowania YOLO: {e}")
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
rclpy.init(args=args)
|
||||||
|
node = YoloGazeboDetector()
|
||||||
|
|
||||||
|
try:
|
||||||
|
rclpy.spin(node)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
node.get_logger().info("Zamykanie node'a...")
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
node.destroy_node()
|
||||||
|
if rclpy.ok():
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -9,6 +9,7 @@ import math
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from arm_point_controller import CameraArmController
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pygame
|
import pygame
|
||||||
@@ -34,6 +35,9 @@ class HarvesterStateMachine(Node):
|
|||||||
self.lidar_left_sub = self.create_subscription(LaserScan, '/lidar_left', self.lidar_left_callback, qos_profile)
|
self.lidar_left_sub = self.create_subscription(LaserScan, '/lidar_left', self.lidar_left_callback, qos_profile)
|
||||||
self.lidar_right_sub = self.create_subscription(LaserScan, '/lidar_right', self.lidar_right_callback, qos_profile)
|
self.lidar_right_sub = self.create_subscription(LaserScan, '/lidar_right', self.lidar_right_callback, qos_profile)
|
||||||
|
|
||||||
|
self.arm = CameraArmController()
|
||||||
|
self.arm.home_arm()
|
||||||
|
|
||||||
self.STATE_INITIALIZING = "INITIALIZING"
|
self.STATE_INITIALIZING = "INITIALIZING"
|
||||||
self.STATE_GPS_DRIVE = "GPS_DRIVE"
|
self.STATE_GPS_DRIVE = "GPS_DRIVE"
|
||||||
self.STATE_LIDAR_DRIVE = "LIDAR_DRIVE"
|
self.STATE_LIDAR_DRIVE = "LIDAR_DRIVE"
|
||||||
@@ -261,7 +265,13 @@ class HarvesterStateMachine(Node):
|
|||||||
|
|
||||||
def execute_harvesting(self):
|
def execute_harvesting(self):
|
||||||
self.stop_robot()
|
self.stop_robot()
|
||||||
time.sleep(2.0)
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.arm.harvest()
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.arm.home_arm()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
self.harvesting_done_in_this_row = True
|
self.harvesting_done_in_this_row = True
|
||||||
self.current_state = self.STATE_LIDAR_DRIVE
|
self.current_state = self.STATE_LIDAR_DRIVE
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
@@ -66,7 +66,7 @@
|
|||||||
<child link="link_2"/>
|
<child link="link_2"/>
|
||||||
<origin rpy="0 0 0" xyz="0 0 0.48"/>
|
<origin rpy="0 0 0" xyz="0 0 0.48"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit effort="300.0" lower="-2.0" upper="2.0" velocity="2.0"/>
|
<limit effort="300.0" lower="-3.141592653589793" upper="3.141592653589793" velocity="2.0"/>
|
||||||
<dynamics damping="1.0" friction="1.0"/>
|
<dynamics damping="1.0" friction="1.0"/>
|
||||||
</joint>
|
</joint>
|
||||||
<link name="link_2">
|
<link name="link_2">
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<child link="link_3"/>
|
<child link="link_3"/>
|
||||||
<origin rpy="0 0 0" xyz="0 0 0.6400000000000001"/>
|
<origin rpy="0 0 0" xyz="0 0 0.6400000000000001"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit effort="200.0" lower="-2.5" upper="2.5" velocity="2.5"/>
|
<limit effort="200.0" lower="-3.141592653589793" upper="3.141592653589793" velocity="2.5"/>
|
||||||
<dynamics damping="1.0" friction="1.0"/>
|
<dynamics damping="1.0" friction="1.0"/>
|
||||||
</joint>
|
</joint>
|
||||||
<link name="link_3">
|
<link name="link_3">
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
<child link="link_5"/>
|
<child link="link_5"/>
|
||||||
<origin rpy="0 0 0" xyz="0 0 0.32000000000000006"/>
|
<origin rpy="0 0 0" xyz="0 0 0.32000000000000006"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit effort="100.0" lower="-2.0" upper="2.0" velocity="3.0"/>
|
<limit effort="100.0" lower="-3.141592653589793" upper="3.141592653589793" velocity="3.0"/>
|
||||||
<dynamics damping="0.5" friction="0.5"/>
|
<dynamics damping="0.5" friction="0.5"/>
|
||||||
</joint>
|
</joint>
|
||||||
<link name="link_5">
|
<link name="link_5">
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<child link="link_2"/>
|
<child link="link_2"/>
|
||||||
<origin xyz="0 0 ${0.3 * LENGTH_SCALE}" rpy="0 0 0"/>
|
<origin xyz="0 0 ${0.3 * LENGTH_SCALE}" rpy="0 0 0"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit lower="-2.0" upper="2.0" effort="300.0" velocity="2.0"/>
|
<limit lower="-${PI}" upper="${PI}" effort="300.0" velocity="2.0"/>
|
||||||
<dynamics damping="1.0" friction="1.0"/>
|
<dynamics damping="1.0" friction="1.0"/>
|
||||||
</joint>
|
</joint>
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<child link="link_3"/>
|
<child link="link_3"/>
|
||||||
<origin xyz="0 0 ${0.4 * LENGTH_SCALE}" rpy="0 0 0"/>
|
<origin xyz="0 0 ${0.4 * LENGTH_SCALE}" rpy="0 0 0"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit lower="-2.5" upper="2.5" effort="200.0" velocity="2.5"/>
|
<limit lower="-${PI}" upper="${PI}" effort="200.0" velocity="2.5"/>
|
||||||
<dynamics damping="1.0" friction="1.0"/>
|
<dynamics damping="1.0" friction="1.0"/>
|
||||||
</joint>
|
</joint>
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<child link="link_5"/>
|
<child link="link_5"/>
|
||||||
<origin xyz="0 0 ${0.2 * LENGTH_SCALE}" rpy="0 0 0"/>
|
<origin xyz="0 0 ${0.2 * LENGTH_SCALE}" rpy="0 0 0"/>
|
||||||
<axis xyz="0 1 0"/>
|
<axis xyz="0 1 0"/>
|
||||||
<limit lower="-2.0" upper="2.0" effort="100.0" velocity="3.0"/>
|
<limit lower="-${PI}" upper="${PI}" effort="100.0" velocity="3.0"/>
|
||||||
<dynamics damping="0.5" friction="0.5"/>
|
<dynamics damping="0.5" friction="0.5"/>
|
||||||
</joint>
|
</joint>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user