feat:完善仿真环境

This commit is contained in:
li-shihao-code
2026-05-04 11:13:59 +08:00
parent df884252ff
commit 6593ab0e67
@@ -6,6 +6,14 @@ import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
# TF 发布相关导入(用于发布 base_link 和传感器静态 TF
try:
from geometry_msgs.msg import TransformStamped
from tf2_ros import TransformBroadcaster, StaticTransformBroadcaster
TF_AVAILABLE = True
except ImportError:
TF_AVAILABLE = False
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description="Isaac 标定车间构建脚本") parser = argparse.ArgumentParser(description="Isaac 标定车间构建脚本")
@@ -100,6 +108,8 @@ def parse_args():
parser.add_argument("--control-reference-speed-ms", type=float, default=0.3, help="运控遥测参考速度(m/s") parser.add_argument("--control-reference-speed-ms", type=float, default=0.3, help="运控遥测参考速度(m/s")
parser.add_argument("--control-reference-y-m", type=float, default=0.0, help="运控直线参考轨迹的 Y 坐标(米)") parser.add_argument("--control-reference-y-m", type=float, default=0.0, help="运控直线参考轨迹的 Y 坐标(米)")
parser.add_argument("--control-parameter-version", type=str, default="isaac_control_baseline_v1", help="当前控制参数版本") parser.add_argument("--control-parameter-version", type=str, default="isaac_control_baseline_v1", help="当前控制参数版本")
parser.add_argument("--disable-vehicle-tf", action="store_true", help="不发布车辆 TF 变换")
parser.add_argument("--vehicle-tf-publish-hz", type=float, default=50.0, help="车辆 TF 发布频率(Hz")
parser.add_argument("--disable-sensor-telemetry", action="store_true", help="不发布传感器标定遥测") parser.add_argument("--disable-sensor-telemetry", action="store_true", help="不发布传感器标定遥测")
parser.add_argument("--sensor-telemetry-topic", type=str, default="/sensor_calibration/telemetry", help="传感器标定遥测话题") parser.add_argument("--sensor-telemetry-topic", type=str, default="/sensor_calibration/telemetry", help="传感器标定遥测话题")
parser.add_argument("--sensor-telemetry-hz", type=float, default=10.0, help="传感器标定遥测发布频率(Hz") parser.add_argument("--sensor-telemetry-hz", type=float, default=10.0, help="传感器标定遥测发布频率(Hz")
@@ -1144,6 +1154,181 @@ class ExternalTruthTelemetryPublisher:
self.node = None self.node = None
class VehicleTfPublisher:
"""发布车辆 base_link 动态 TF 和传感器静态 TF"""
def __init__(self, args):
self.enabled = TF_AVAILABLE and not getattr(args, 'disable_vehicle_tf', False)
self.tf_publish_hz = getattr(args, 'vehicle_tf_publish_hz', 50.0)
self.publish_period_sec = 0.0 if self.tf_publish_hz <= 0.0 else 1.0 / self.tf_publish_hz
self.last_publish_time = 0.0
self.node = None
self.tf_broadcaster = None
self.static_tf_broadcaster = None
self.static_tfs_published = False
# 传感器相对于 base_link 的安装位置(从 args 读取)
self.sensor_configs = {
'front_camera_link': {
'x': args.vehicle_camera_x,
'y': args.vehicle_camera_y,
'z': args.vehicle_camera_z,
'roll': 0.0,
'pitch': -math.pi / 2, # 相机朝下 -90度
'yaw': 0.0,
'enabled': not args.disable_vehicle_camera,
},
'down_camera_link': {
'x': args.down_camera_x,
'y': args.down_camera_y,
'z': args.down_camera_z,
'roll': 0.0,
'pitch': 0.0, # 朝下相机,垂直向下
'yaw': 0.0,
'enabled': not args.disable_down_camera,
},
'lidar_3d_link': {
'x': args.vehicle_lidar_x,
'y': args.vehicle_lidar_y,
'z': args.vehicle_lidar_z,
'roll': 0.0,
'pitch': 0.0,
'yaw': 0.0,
'enabled': not args.disable_vehicle_lidar,
},
'lidar_2d_link': {
'x': args.vehicle_2d_lidar_x,
'y': args.vehicle_2d_lidar_y,
'z': args.vehicle_2d_lidar_z,
'roll': 0.0,
'pitch': 0.0,
'yaw': 0.0,
'enabled': not args.disable_vehicle_2d_lidar,
},
'imu_link': {
'x': args.vehicle_imu_x,
'y': args.vehicle_imu_y,
'z': args.vehicle_imu_z,
'roll': 0.0,
'pitch': 0.0,
'yaw': 0.0,
'enabled': not args.disable_vehicle_imu,
},
}
if not self.enabled:
print("[*] 车辆 TF 发布已禁用(TF_AVAILABLE={})或参数禁用。".format(TF_AVAILABLE))
return
if rclpy is None:
print("[WARN] rclpy 不可用,车辆 TF 发布已禁用。")
self.enabled = False
return
if not rclpy.ok():
rclpy.init(args=None)
self.node = rclpy.create_node("isaac_vehicle_tf_publisher")
self.tf_broadcaster = TransformBroadcaster(self.node)
self.static_tf_broadcaster = StaticTransformBroadcaster(self.node)
print(f"[*] 车辆 TF 发布已启用: hz={self.tf_publish_hz}")
def _create_transform(self, frame_id, child_frame_id, x, y, z, roll, pitch, yaw, timestamp=None):
"""创建 TransformStamped 消息"""
t = TransformStamped()
if timestamp is None:
timestamp = self.node.get_clock().now().to_msg()
t.header.stamp = timestamp
t.header.frame_id = frame_id
t.child_frame_id = child_frame_id
t.transform.translation.x = x
t.transform.translation.y = y
t.transform.translation.z = z
# 欧拉角转四元数
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)
t.transform.rotation.w = cr * cp * cy + sr * sp * sy
t.transform.rotation.x = sr * cp * cy - cr * sp * sy
t.transform.rotation.y = cr * sp * cy + sr * cp * sy
t.transform.rotation.z = cr * cp * sy - sr * sp * cy
return t
def publish_static_tfs(self):
"""发布传感器相对于 base_link 的静态 TF(只发布一次)"""
if not self.enabled or self.static_tfs_published:
return
transforms = []
timestamp = self.node.get_clock().now().to_msg()
for child_frame, config in self.sensor_configs.items():
if not config['enabled']:
continue
t = self._create_transform(
'base_link', child_frame,
config['x'], config['y'], config['z'],
config['roll'], config['pitch'], config['yaw'],
timestamp
)
transforms.append(t)
print(f"[*] 静态 TF: base_link -> {child_frame} ({config['x']:.3f}, {config['y']:.3f}, {config['z']:.3f})")
if transforms:
self.static_tf_broadcaster.sendTransform(transforms)
self.static_tfs_published = True
print(f"[*] 已发布 {len(transforms)} 个传感器静态 TF")
def publish(self, agv):
"""发布 base_link 相对于 world 的动态 TF"""
if not self.enabled:
return
now = time.time()
if self.publish_period_sec > 0.0 and (now - self.last_publish_time) < self.publish_period_sec:
return
# 首先发布静态 TF(只发布一次)
if not self.static_tfs_published:
self.publish_static_tfs()
# 获取车辆世界位姿
position, quat = agv.get_world_pose()
# 四元数 (w, x, y, z) 转欧拉角(用于调试)
qw, qx, qy, qz = quat
yaw = math.atan2(2.0 * (qw * qz + qx * qy), 1.0 - 2.0 * (qy * qy + qz * qz))
# 发布 world -> base_link 动态 TF
timestamp = self.node.get_clock().now().to_msg()
t = self._create_transform(
'world', 'base_link',
position[0], position[1], position[2],
0.0, 0.0, yaw, # roll/pitch 设为 0,只保留 yaw
timestamp
)
# 使用原始四元数覆盖(更精确)
t.transform.rotation.w = qw
t.transform.rotation.x = qx
t.transform.rotation.y = qy
t.transform.rotation.z = qz
self.tf_broadcaster.sendTransform(t)
self.last_publish_time = now
def shutdown(self):
if self.node is not None:
self.node.destroy_node()
self.node = None
class VehicleImuTopicPublisher: class VehicleImuTopicPublisher:
def __init__(self, args): def __init__(self, args):
self.enabled = not args.disable_vehicle_imu self.enabled = not args.disable_vehicle_imu
@@ -2325,6 +2510,7 @@ class IsaacWorkshopRuntime:
def main(): def main():
runtime = IsaacWorkshopRuntime(ARGS) runtime = IsaacWorkshopRuntime(ARGS)
telemetry_publisher = ExternalTruthTelemetryPublisher(ARGS) telemetry_publisher = ExternalTruthTelemetryPublisher(ARGS)
vehicle_tf_publisher = VehicleTfPublisher(ARGS)
vehicle_imu_publisher = VehicleImuTopicPublisher(ARGS) vehicle_imu_publisher = VehicleImuTopicPublisher(ARGS)
chassis_telemetry_publisher = ChassisCalibrationTelemetryPublisher(ARGS) chassis_telemetry_publisher = ChassisCalibrationTelemetryPublisher(ARGS)
control_telemetry_publisher = ControlCalibrationTelemetryPublisher(ARGS) control_telemetry_publisher = ControlCalibrationTelemetryPublisher(ARGS)
@@ -2352,6 +2538,7 @@ def main():
print(f" - /cmd_vel topic: {runtime.cmd_vel_topic}") print(f" - /cmd_vel topic: {runtime.cmd_vel_topic}")
print(f" - external telemetry topic: {ARGS.external_telemetry_topic}") print(f" - external telemetry topic: {ARGS.external_telemetry_topic}")
print(f" - chassis telemetry topic: {ARGS.chassis_telemetry_topic}") print(f" - chassis telemetry topic: {ARGS.chassis_telemetry_topic}")
print(f" - vehicle TF: enabled={not ARGS.disable_vehicle_tf}, hz={ARGS.vehicle_tf_publish_hz}")
print(f" - control telemetry topic: {ARGS.control_telemetry_topic}") print(f" - control telemetry topic: {ARGS.control_telemetry_topic}")
print(f" - sensor telemetry topic: {ARGS.sensor_telemetry_topic}") print(f" - sensor telemetry topic: {ARGS.sensor_telemetry_topic}")
print(f" - scene manifest: {runtime.scene_manifest_path}") print(f" - scene manifest: {runtime.scene_manifest_path}")
@@ -2399,6 +2586,7 @@ def main():
world.step(render=render_frame) world.step(render=render_frame)
telemetry_publisher.publish(agv) telemetry_publisher.publish(agv)
vehicle_tf_publisher.publish(agv)
vehicle_imu_publisher.publish(agv) vehicle_imu_publisher.publish(agv)
vehicle_laser_scan_publisher.publish(agv) vehicle_laser_scan_publisher.publish(agv)
chassis_telemetry_publisher.publish(agv) chassis_telemetry_publisher.publish(agv)
@@ -2406,6 +2594,7 @@ def main():
sensor_telemetry_publisher.publish() sensor_telemetry_publisher.publish()
finally: finally:
telemetry_publisher.shutdown() telemetry_publisher.shutdown()
vehicle_tf_publisher.shutdown()
vehicle_imu_publisher.shutdown() vehicle_imu_publisher.shutdown()
vehicle_laser_scan_publisher.shutdown() vehicle_laser_scan_publisher.shutdown()
chassis_telemetry_publisher.shutdown() chassis_telemetry_publisher.shutdown()