From 9a4517586d858ca2273be94d805a4ab26313bdaf Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:51:53 +0800 Subject: [PATCH 1/9] Add files via upload --- sim/__init__.cpython-311.pyc | Bin 0 -> 13475 bytes sim/__init__.py | 385 +++++++++++++++++++++++++++++++++++ sim/editor_tools.py | 303 +++++++++++++++++++++++++++ sim/sensor_tools.py | 327 +++++++++++++++++++++++++++++ sim/vehicle_tools.py | 347 +++++++++++++++++++++++++++++++ sim/weather_tools.py | 317 ++++++++++++++++++++++++++++ 6 files changed, 1679 insertions(+) create mode 100644 sim/__init__.cpython-311.pyc create mode 100644 sim/__init__.py create mode 100644 sim/editor_tools.py create mode 100644 sim/sensor_tools.py create mode 100644 sim/vehicle_tools.py create mode 100644 sim/weather_tools.py diff --git a/sim/__init__.cpython-311.pyc b/sim/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c5a568926ee4b09394173b74a108cf20ec6560a GIT binary patch literal 13475 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 Jr>W5W{{xiI2%i7| literal 0 HcmV?d00001 diff --git a/sim/__init__.py b/sim/__init__.py new file mode 100644 index 0000000..e96d69b --- /dev/null +++ b/sim/__init__.py @@ -0,0 +1,385 @@ +""" +天气控制工具模块 + +提供天气参数设置、预设天气、时间控制等功能。 +""" + +import logging +from typing import Dict, Any +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.weather") + +# 天气预设 +WEATHER_PRESETS = { + "clear": { + "cloudiness": 0.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 0.0, + "sun_altitude_angle": 70.0, + "fog_density": 0.0, + "wetness": 0.0 + }, + "cloudy": { + "cloudiness": 80.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 20.0, + "sun_altitude_angle": 45.0, + "fog_density": 0.0, + "wetness": 0.0 + }, + "rainy": { + "cloudiness": 100.0, + "precipitation": 80.0, + "precipitation_deposits": 50.0, + "wind_intensity": 30.0, + "sun_altitude_angle": 30.0, + "fog_density": 10.0, + "wetness": 80.0 + }, + "foggy": { + "cloudiness": 50.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 10.0, + "sun_altitude_angle": 30.0, + "fog_density": 80.0, + "wetness": 20.0 + }, + "stormy": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 10.0, + "fog_density": 30.0, + "wetness": 100.0 + } +} + + +def register_weather_tools(mcp: FastMCP) -> None: + """注册天气控制工具""" + + @mcp.tool() + def get_weather(ctx: Context) -> Dict[str, Any]: + """ + 获取当前天气参数 + + Returns: + 天气参数字典 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + weather = conn.world.get_weather() + + result = { + "success": True, + "cloudiness": weather.cloudiness, + "precipitation": weather.precipitation, + "precipitation_deposits": weather.precipitation_deposits, + "wind_intensity": weather.wind_intensity, + "sun_azimuth_angle": weather.sun_azimuth_angle, + "sun_altitude_angle": weather.sun_altitude_angle, + "fog_density": weather.fog_density, + "fog_distance": weather.fog_distance, + "wetness": weather.wetness, + "fog_falloff": weather.fog_falloff, + "scattering_intensity": weather.scattering_intensity, + "mie_scattering_scale": weather.mie_scattering_scale, + "rayleigh_scattering_scale": weather.rayleigh_scattering_scale + } + + logger.info("获取天气参数成功") + return result + + except Exception as e: + logger.error(f"获取天气参数失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_weather( + ctx: Context, + cloudiness: float = None, + precipitation: float = None, + precipitation_deposits: float = None, + wind_intensity: float = None, + sun_azimuth_angle: float = None, + sun_altitude_angle: float = None, + fog_density: float = None, + fog_distance: float = None, + wetness: float = None + ) -> Dict[str, Any]: + """ + 设置天气参数 + + Args: + cloudiness: 云量 (0-100) + precipitation: 降水量 (0-100) + precipitation_deposits: 降水沉积 (0-100) + wind_intensity: 风力强度 (0-100) + sun_azimuth_angle: 太阳方位角 (0-360) + sun_altitude_angle: 太阳高度角 (-90 到 90) + fog_density: 雾密度 (0-100) + fog_distance: 雾距离 + wetness: 湿度 (0-100) + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + # 获取当前天气 + weather = conn.world.get_weather() + + # 更新指定参数(带范围限制) + if cloudiness is not None: + weather.cloudiness = max(0.0, min(100.0, cloudiness)) + if precipitation is not None: + weather.precipitation = max(0.0, min(100.0, precipitation)) + if precipitation_deposits is not None: + weather.precipitation_deposits = max(0.0, min(100.0, precipitation_deposits)) + if wind_intensity is not None: + weather.wind_intensity = max(0.0, min(100.0, wind_intensity)) + if sun_azimuth_angle is not None: + weather.sun_azimuth_angle = sun_azimuth_angle % 360.0 + if sun_altitude_angle is not None: + weather.sun_altitude_angle = max(-90.0, min(90.0, sun_altitude_angle)) + if fog_density is not None: + weather.fog_density = max(0.0, min(100.0, fog_density)) + if fog_distance is not None: + weather.fog_distance = max(0.0, fog_distance) + if wetness is not None: + weather.wetness = max(0.0, min(100.0, wetness)) + + # 应用天气 + conn.world.set_weather(weather) + + logger.info("设置天气参数成功") + return { + "success": True, + "cloudiness": weather.cloudiness, + "precipitation": weather.precipitation, + "sun_altitude_angle": weather.sun_altitude_angle, + "fog_density": weather.fog_density, + "wetness": weather.wetness + } + + except Exception as e: + logger.error(f"设置天气参数失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_weather_preset(ctx: Context, preset: str) -> Dict[str, Any]: + """ + 应用天气预设 + + Args: + preset: 预设名称,可选值: clear, cloudy, rainy, foggy, stormy + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + preset = preset.lower() + if preset not in WEATHER_PRESETS: + return { + "success": False, + "error": f"未知预设: {preset},可选值: {list(WEATHER_PRESETS.keys())}" + } + + params = WEATHER_PRESETS[preset] + + # 创建天气对象 + weather = carla.WeatherParameters( + cloudiness=params["cloudiness"], + precipitation=params["precipitation"], + precipitation_deposits=params["precipitation_deposits"], + wind_intensity=params["wind_intensity"], + sun_altitude_angle=params["sun_altitude_angle"], + fog_density=params["fog_density"], + wetness=params["wetness"] + ) + + conn.world.set_weather(weather) + + logger.info(f"应用天气预设成功: {preset}") + return { + "success": True, + "preset": preset, + "parameters": params + } + + except Exception as e: + logger.error(f"应用天气预设失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_time_of_day(ctx: Context, hour: int, minute: int = 0) -> Dict[str, Any]: + """ + 设置一天中的时间(通过调整太阳位置) + + Args: + hour: 小时 (0-23) + minute: 分钟 (0-59) + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + # 限制范围 + hour = max(0, min(23, hour)) + minute = max(0, min(59, minute)) + + # 计算太阳高度角 + # 简化模型:6:00 日出 (0°), 12:00 正午 (90°), 18:00 日落 (0°) + time_decimal = hour + minute / 60.0 + + if 6 <= time_decimal <= 18: + # 白天 + sun_altitude = 90.0 * (1 - abs(time_decimal - 12) / 6) + else: + # 夜晚 + sun_altitude = -45.0 + + # 计算太阳方位角 + sun_azimuth = (time_decimal - 6) * 15 # 每小时15度 + + weather = conn.world.get_weather() + weather.sun_altitude_angle = sun_altitude + weather.sun_azimuth_angle = sun_azimuth % 360 + conn.world.set_weather(weather) + + logger.info(f"设置时间成功: {hour:02d}:{minute:02d}") + return { + "success": True, + "time": f"{hour:02d}:{minute:02d}", + "sun_altitude_angle": sun_altitude, + "sun_azimuth_angle": sun_azimuth % 360 + } + + except Exception as e: + logger.error(f"设置时间失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_extreme_weather(ctx: Context, weather_type: str) -> Dict[str, Any]: + """ + 设置极端天气(用于压力测试) + + Args: + weather_type: 极端天气类型,可选值: + - heavy_rain: 暴雨 + - dense_fog: 浓雾 + - blizzard: 暴风雪 + - sandstorm: 沙尘暴 + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + extreme_presets = { + "heavy_rain": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 80.0, + "sun_altitude_angle": 5.0, + "fog_density": 20.0, + "wetness": 100.0 + }, + "dense_fog": { + "cloudiness": 100.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 5.0, + "sun_altitude_angle": 20.0, + "fog_density": 100.0, + "wetness": 50.0 + }, + "blizzard": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 10.0, + "fog_density": 50.0, + "wetness": 0.0 + }, + "sandstorm": { + "cloudiness": 80.0, + "precipitation": 0.0, + "precipitation_deposits": 50.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 30.0, + "fog_density": 70.0, + "wetness": 0.0 + } + } + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + weather_type = weather_type.lower() + if weather_type not in extreme_presets: + return { + "success": False, + "error": f"未知极端天气类型: {weather_type},可选值: {list(extreme_presets.keys())}" + } + + params = extreme_presets[weather_type] + + weather = carla.WeatherParameters( + cloudiness=params["cloudiness"], + precipitation=params["precipitation"], + precipitation_deposits=params["precipitation_deposits"], + wind_intensity=params["wind_intensity"], + sun_altitude_angle=params["sun_altitude_angle"], + fog_density=params["fog_density"], + wetness=params["wetness"] + ) + + conn.world.set_weather(weather) + + logger.info(f"设置极端天气成功: {weather_type}") + return { + "success": True, + "weather_type": weather_type, + "parameters": params + } + + except Exception as e: + logger.error(f"设置极端天气失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("天气控制工具注册完成") diff --git a/sim/editor_tools.py b/sim/editor_tools.py new file mode 100644 index 0000000..a285c20 --- /dev/null +++ b/sim/editor_tools.py @@ -0,0 +1,303 @@ +""" +无人机控制工具模块 + +提供无人机生成、飞行控制等功能。 +注意:HUTB/CARLA 原生不支持无人机,此模块提供基于 Walker 的模拟实现。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.air") + + +def register_air_tools(mcp: FastMCP) -> None: + """注册无人机控制工具""" + + @mcp.tool() + def spawn_drone( + ctx: Context, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 生成无人机(模拟实现) + + 注意:CARLA 原生不支持无人机,此功能使用特殊 Actor 模拟。 + + Args: + location: 生成位置 [x, y, z] + rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 使用静态道具模拟无人机 + # 在实际项目中,可以使用自定义的无人机模型 + blueprint = conn.blueprint_library.find("static.prop.box01") + + if not blueprint: + # 尝试其他可用的静态道具 + props = list(conn.blueprint_library.filter("static.prop.*")) + if props: + blueprint = props[0] + else: + return {"success": False, "error": "未找到可用的无人机蓝图"} + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成无人机 + drone = conn.world.spawn_actor(blueprint, transform) + + if drone: + conn.track_actor(drone.id) + logger.info(f"生成无人机成功: {drone.id}") + return { + "success": True, + "drone_id": drone.id, + "location": location, + "note": "无人机为模拟实现,使用静态道具代替" + } + else: + return {"success": False, "error": "生成无人机失败"} + + except Exception as e: + logger.error(f"生成无人机失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_drone_state(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 获取无人机状态 + + Args: + drone_id: 无人机 ID + + Returns: + 无人机状态信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + transform = drone.get_transform() + velocity = drone.get_velocity() + + result = { + "success": True, + "drone_id": drone_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "altitude": transform.location.z + } + + logger.info(f"获取无人机 {drone_id} 状态成功") + return result + + except Exception as e: + logger.error(f"获取无人机状态失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_drone_destination( + ctx: Context, + drone_id: int, + destination: List[float] + ) -> Dict[str, Any]: + """ + 设置无人机目标位置 + + Args: + drone_id: 无人机 ID + destination: 目标位置 [x, y, z] + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 直接设置位置(简化实现) + # 在实际项目中,应该实现平滑的飞行路径 + current_transform = drone.get_transform() + current_transform.location = carla.Location( + x=destination[0], + y=destination[1], + z=destination[2] + ) + drone.set_transform(current_transform) + + logger.info(f"设置无人机 {drone_id} 目标位置: {destination}") + return { + "success": True, + "drone_id": drone_id, + "destination": destination, + "note": "位置已直接设置,实际项目中应实现平滑飞行" + } + + except Exception as e: + logger.error(f"设置无人机目标位置失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def drone_hover(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 无人机悬停 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 静态道具默认就是悬停状态 + transform = drone.get_transform() + + logger.info(f"无人机 {drone_id} 悬停中") + return { + "success": True, + "drone_id": drone_id, + "status": "hovering", + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + } + } + + except Exception as e: + logger.error(f"无人机悬停失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def drone_land(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 无人机降落 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 将无人机降落到地面 + transform = drone.get_transform() + transform.location.z = 0.5 # 降落到接近地面的高度 + drone.set_transform(transform) + + logger.info(f"无人机 {drone_id} 降落成功") + return { + "success": True, + "drone_id": drone_id, + "status": "landed", + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + } + } + + except Exception as e: + logger.error(f"无人机降落失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_drone(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 销毁无人机 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + drone.destroy() + conn.untrack_actor(drone_id) + + logger.info(f"销毁无人机成功: {drone_id}") + return {"success": True, "drone_id": drone_id} + + except Exception as e: + logger.error(f"销毁无人机失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("无人机控制工具注册完成") diff --git a/sim/sensor_tools.py b/sim/sensor_tools.py new file mode 100644 index 0000000..f28ed68 --- /dev/null +++ b/sim/sensor_tools.py @@ -0,0 +1,327 @@ +""" +编辑器控制工具模块 + +提供场景编辑、Actor 管理、地图加载等功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.editor") + + +def register_editor_tools(mcp: FastMCP) -> None: + """注册编辑器控制工具""" + + @mcp.tool() + def get_actors_in_level(ctx: Context) -> List[Dict[str, Any]]: + """ + 获取当前场景中所有 Actor 的列表 + + Returns: + Actor 列表,每个 Actor 包含 id、type_id、name 等信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return [] + + actors = conn.world.get_actors() + result = [] + + for actor in actors: + result.append({ + "id": actor.id, + "type_id": actor.type_id, + "name": actor.attributes.get("role_name", ""), + "location": { + "x": actor.get_location().x, + "y": actor.get_location().y, + "z": actor.get_location().z + } + }) + + logger.info(f"获取到 {len(result)} 个 Actor") + return result + + except Exception as e: + logger.error(f"获取 Actor 列表失败: {e}") + return [] + + @mcp.tool() + def get_current_map(ctx: Context) -> str: + """ + 获取当前加载的地图名称 + + Returns: + 地图名称字符串 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return "" + + map_name = conn.map.name + logger.info(f"当前地图: {map_name}") + return map_name + + except Exception as e: + logger.error(f"获取地图名称失败: {e}") + return "" + + @mcp.tool() + def load_map(ctx: Context, map_name: str) -> Dict[str, Any]: + """ + 加载指定地图 + + Args: + map_name: 地图名称,如 "Town10" 或 "Town01" + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + logger.info(f"正在加载地图: {map_name}") + + # 清理当前资源 + conn.cleanup_all() + + # 加载新地图 + conn.client.load_world(map_name) + + # 更新世界和地图引用 + conn.world = conn.client.get_world() + conn.map = conn.world.get_map() + conn.blueprint_library = conn.world.get_blueprint_library() + + logger.info(f"地图 {map_name} 加载成功") + return {"success": True, "map": map_name} + + except Exception as e: + logger.error(f"加载地图失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def spawn_actor( + ctx: Context, + actor_type: str, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 在场景中生成 Actor + + Args: + actor_type: Actor 类型,如 "static.prop.bench" + location: 位置 [x, y, z] + rotation: 旋转 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果,包含 Actor ID + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 查找蓝图 + blueprint = conn.blueprint_library.find(actor_type) + if not blueprint: + return {"success": False, "error": f"未找到蓝图: {actor_type}"} + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成 Actor + actor = conn.world.spawn_actor(blueprint, transform) + + if actor: + conn.track_actor(actor.id) + logger.info(f"生成 Actor 成功: {actor.id} ({actor_type})") + return { + "success": True, + "actor_id": actor.id, + "type": actor_type, + "location": location + } + else: + return {"success": False, "error": "生成 Actor 失败"} + + except Exception as e: + logger.error(f"生成 Actor 失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def delete_actor(ctx: Context, actor_id: int) -> Dict[str, Any]: + """ + 删除指定 Actor + + Args: + actor_id: Actor ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + actor.destroy() + conn.untrack_actor(actor_id) + + logger.info(f"删除 Actor 成功: {actor_id}") + return {"success": True, "actor_id": actor_id} + + except Exception as e: + logger.error(f"删除 Actor 失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_actor_transform( + ctx: Context, + actor_id: int, + location: List[float] = None, + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 设置 Actor 的位置和旋转 + + Args: + actor_id: Actor ID + location: 新位置 [x, y, z] + rotation: 新旋转 [pitch, yaw, roll] + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + current_transform = actor.get_transform() + + # 更新位置 + if location: + current_transform.location = carla.Location( + x=location[0], y=location[1], z=location[2] + ) + + # 更新旋转 + if rotation: + current_transform.rotation = carla.Rotation( + pitch=rotation[0], yaw=rotation[1], roll=rotation[2] + ) + + actor.set_transform(current_transform) + + logger.info(f"设置 Actor {actor_id} 变换成功") + return { + "success": True, + "actor_id": actor_id, + "location": [ + current_transform.location.x, + current_transform.location.y, + current_transform.location.z + ], + "rotation": [ + current_transform.rotation.pitch, + current_transform.rotation.yaw, + current_transform.rotation.roll + ] + } + + except Exception as e: + logger.error(f"设置 Actor 变换失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_actor_properties(ctx: Context, actor_id: int) -> Dict[str, Any]: + """ + 获取 Actor 的属性信息 + + Args: + actor_id: Actor ID + + Returns: + Actor 属性字典 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + transform = actor.get_transform() + velocity = actor.get_velocity() + + result = { + "success": True, + "id": actor.id, + "type_id": actor.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "attributes": dict(actor.attributes) + } + + logger.info(f"获取 Actor {actor_id} 属性成功") + return result + + except Exception as e: + logger.error(f"获取 Actor 属性失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("编辑器控制工具注册完成") diff --git a/sim/vehicle_tools.py b/sim/vehicle_tools.py new file mode 100644 index 0000000..6c7646f --- /dev/null +++ b/sim/vehicle_tools.py @@ -0,0 +1,347 @@ +""" +传感器管理工具模块 + +提供相机、激光雷达、雷达等传感器的创建和管理功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.sensor") + + +def register_sensor_tools(mcp: FastMCP) -> None: + """注册传感器管理工具""" + + @mcp.tool() + def attach_camera( + ctx: Context, + vehicle_id: int, + camera_type: str = "rgb", + location: List[float] = None, + rotation: List[float] = None, + image_size_x: int = 800, + image_size_y: int = 600, + fov: float = 90.0 + ) -> Dict[str, Any]: + """ + 为车辆附加相机传感器 + + Args: + vehicle_id: 车辆 ID + camera_type: 相机类型,可选值: rgb, depth, semantic_segmentation + location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + image_size_x: 图像宽度 + image_size_y: 图像高度 + fov: 视场角 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [0, 0, 2] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 确定相机蓝图 + camera_blueprints = { + "rgb": "sensor.camera.rgb", + "depth": "sensor.camera.depth", + "semantic_segmentation": "sensor.camera.semantic_segmentation" + } + + if camera_type not in camera_blueprints: + return { + "success": False, + "error": f"未知相机类型: {camera_type},可选值: {list(camera_blueprints.keys())}" + } + + # 获取蓝图 + blueprint = conn.blueprint_library.find(camera_blueprints[camera_type]) + + # 设置属性 + blueprint.set_attribute("image_size_x", str(image_size_x)) + blueprint.set_attribute("image_size_y", str(image_size_y)) + blueprint.set_attribute("fov", str(fov)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加相机成功: {sensor.id} ({camera_type})") + return { + "success": True, + "sensor_id": sensor.id, + "type": camera_type, + "parent_id": vehicle_id, + "image_size": [image_size_x, image_size_y], + "fov": fov + } + else: + return {"success": False, "error": "附加相机失败"} + + except Exception as e: + logger.error(f"附加相机失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def attach_lidar( + ctx: Context, + vehicle_id: int, + location: List[float] = None, + rotation: List[float] = None, + channels: int = 32, + range: float = 100.0, + points_per_second: int = 56000, + rotation_frequency: float = 10.0, + upper_fov: float = 10.0, + lower_fov: float = -30.0 + ) -> Dict[str, Any]: + """ + 为车辆附加激光雷达传感器 + + Args: + vehicle_id: 车辆 ID + location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2.5] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + channels: 激光通道数 + range: 探测范围(米) + points_per_second: 每秒点数 + rotation_frequency: 旋转频率(Hz) + upper_fov: 上视场角 + lower_fov: 下视场角 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [0, 0, 2.5] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 获取蓝图 + blueprint = conn.blueprint_library.find("sensor.lidar.ray_cast") + + # 设置属性 + blueprint.set_attribute("channels", str(channels)) + blueprint.set_attribute("range", str(range)) + blueprint.set_attribute("points_per_second", str(points_per_second)) + blueprint.set_attribute("rotation_frequency", str(rotation_frequency)) + blueprint.set_attribute("upper_fov", str(upper_fov)) + blueprint.set_attribute("lower_fov", str(lower_fov)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加激光雷达成功: {sensor.id}") + return { + "success": True, + "sensor_id": sensor.id, + "type": "lidar", + "parent_id": vehicle_id, + "channels": channels, + "range": range + } + else: + return {"success": False, "error": "附加激光雷达失败"} + + except Exception as e: + logger.error(f"附加激光雷达失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def attach_radar( + ctx: Context, + vehicle_id: int, + location: List[float] = None, + rotation: List[float] = None, + horizontal_fov: float = 30.0, + vertical_fov: float = 10.0, + range: float = 100.0, + points_per_second: int = 1500 + ) -> Dict[str, Any]: + """ + 为车辆附加雷达传感器 + + Args: + vehicle_id: 车辆 ID + location: 相对于车辆的位置 [x, y, z],默认 [2, 0, 1] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + horizontal_fov: 水平视场角 + vertical_fov: 垂直视场角 + range: 探测范围(米) + points_per_second: 每秒点数 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [2, 0, 1] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 获取蓝图 + blueprint = conn.blueprint_library.find("sensor.other.radar") + + # 设置属性 + blueprint.set_attribute("horizontal_fov", str(horizontal_fov)) + blueprint.set_attribute("vertical_fov", str(vertical_fov)) + blueprint.set_attribute("range", str(range)) + blueprint.set_attribute("points_per_second", str(points_per_second)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加雷达成功: {sensor.id}") + return { + "success": True, + "sensor_id": sensor.id, + "type": "radar", + "parent_id": vehicle_id, + "horizontal_fov": horizontal_fov, + "vertical_fov": vertical_fov, + "range": range + } + else: + return {"success": False, "error": "附加雷达失败"} + + except Exception as e: + logger.error(f"附加雷达失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_sensor(ctx: Context, sensor_id: int) -> Dict[str, Any]: + """ + 销毁传感器 + + Args: + sensor_id: 传感器 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + sensor = conn.world.get_actor(sensor_id) + if not sensor: + return {"success": False, "error": f"未找到传感器: {sensor_id}"} + + sensor.destroy() + conn.untrack_sensor(sensor_id) + + logger.info(f"销毁传感器成功: {sensor_id}") + return {"success": True, "sensor_id": sensor_id} + + except Exception as e: + logger.error(f"销毁传感器失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def list_vehicle_sensors(ctx: Context, vehicle_id: int) -> List[Dict[str, Any]]: + """ + 列出车辆上的所有传感器 + + Args: + vehicle_id: 车辆 ID + + Returns: + 传感器列表 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return [] + + # 获取所有传感器类型的 Actor + sensors = conn.world.get_actors().filter("sensor.*") + + result = [] + for sensor in sensors: + # 检查是否附加到指定车辆 + parent = sensor.parent + if parent and parent.id == vehicle_id: + transform = sensor.get_transform() + result.append({ + "id": sensor.id, + "type_id": sensor.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "attributes": dict(sensor.attributes) + }) + + logger.info(f"车辆 {vehicle_id} 有 {len(result)} 个传感器") + return result + + except Exception as e: + logger.error(f"列出传感器失败: {e}") + return [] + + logger.info("传感器管理工具注册完成") diff --git a/sim/weather_tools.py b/sim/weather_tools.py new file mode 100644 index 0000000..242336f --- /dev/null +++ b/sim/weather_tools.py @@ -0,0 +1,317 @@ +""" +车辆控制工具模块 + +提供车辆生成、控制、状态获取等功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.vehicle") + + +def register_vehicle_tools(mcp: FastMCP) -> None: + """注册车辆控制工具""" + + @mcp.tool() + def get_vehicle_blueprints(ctx: Context) -> List[str]: + """ + 获取所有可用的车辆蓝图列表 + + Returns: + 车辆蓝图 ID 列表 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return [] + + blueprints = conn.blueprint_library.filter("vehicle.*") + result = [bp.id for bp in blueprints] + + logger.info(f"获取到 {len(result)} 个车辆蓝图") + return result + + except Exception as e: + logger.error(f"获取车辆蓝图失败: {e}") + return [] + + @mcp.tool() + def spawn_vehicle( + ctx: Context, + blueprint_id: str, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 生成车辆 + + Args: + blueprint_id: 车辆蓝图 ID,如 "vehicle.tesla.model3" + location: 生成位置 [x, y, z] + rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果,包含车辆 ID + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 查找蓝图 + blueprint = conn.blueprint_library.find(blueprint_id) + if not blueprint: + return {"success": False, "error": f"未找到车辆蓝图: {blueprint_id}"} + + # 设置随机颜色(如果支持) + if blueprint.has_attribute("color"): + color = blueprint.get_attribute("color").recommended_values + if color: + blueprint.set_attribute("color", color[0]) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成车辆 + vehicle = conn.world.spawn_actor(blueprint, transform) + + if vehicle: + conn.track_vehicle(vehicle.id) + logger.info(f"生成车辆成功: {vehicle.id} ({blueprint_id})") + return { + "success": True, + "vehicle_id": vehicle.id, + "blueprint": blueprint_id, + "location": location + } + else: + return {"success": False, "error": "生成车辆失败,可能位置有碰撞"} + + except Exception as e: + logger.error(f"生成车辆失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_vehicle(ctx: Context, vehicle_id: int) -> Dict[str, Any]: + """ + 销毁车辆 + + Args: + vehicle_id: 车辆 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + vehicle.destroy() + conn.untrack_vehicle(vehicle_id) + + logger.info(f"销毁车辆成功: {vehicle_id}") + return {"success": True, "vehicle_id": vehicle_id} + + except Exception as e: + logger.error(f"销毁车辆失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_vehicle_autopilot( + ctx: Context, + vehicle_id: int, + enabled: bool + ) -> Dict[str, Any]: + """ + 设置车辆自动驾驶模式 + + Args: + vehicle_id: 车辆 ID + enabled: 是否启用自动驾驶 + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + vehicle.set_autopilot(enabled) + + logger.info(f"设置车辆 {vehicle_id} 自动驾驶: {enabled}") + return { + "success": True, + "vehicle_id": vehicle_id, + "autopilot": enabled + } + + except Exception as e: + logger.error(f"设置自动驾驶失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def apply_vehicle_control( + ctx: Context, + vehicle_id: int, + throttle: float = 0.0, + steer: float = 0.0, + brake: float = 0.0, + hand_brake: bool = False, + reverse: bool = False + ) -> Dict[str, Any]: + """ + 应用车辆控制 + + Args: + vehicle_id: 车辆 ID + throttle: 油门 (0.0-1.0) + steer: 转向 (-1.0 到 1.0,负值左转,正值右转) + brake: 刹车 (0.0-1.0) + hand_brake: 手刹 + reverse: 倒车 + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 限制参数范围 + throttle = max(0.0, min(1.0, throttle)) + steer = max(-1.0, min(1.0, steer)) + brake = max(0.0, min(1.0, brake)) + + # 创建控制对象 + control = carla.VehicleControl( + throttle=throttle, + steer=steer, + brake=brake, + hand_brake=hand_brake, + reverse=reverse + ) + + vehicle.apply_control(control) + + logger.info(f"应用车辆 {vehicle_id} 控制: throttle={throttle}, steer={steer}, brake={brake}") + return { + "success": True, + "vehicle_id": vehicle_id, + "control": { + "throttle": throttle, + "steer": steer, + "brake": brake, + "hand_brake": hand_brake, + "reverse": reverse + } + } + + except Exception as e: + logger.error(f"应用车辆控制失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_vehicle_state(ctx: Context, vehicle_id: int) -> Dict[str, Any]: + """ + 获取车辆状态 + + Args: + vehicle_id: 车辆 ID + + Returns: + 车辆状态信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + transform = vehicle.get_transform() + velocity = vehicle.get_velocity() + acceleration = vehicle.get_acceleration() + angular_velocity = vehicle.get_angular_velocity() + + # 计算速度大小 (km/h) + speed = 3.6 * (velocity.x**2 + velocity.y**2 + velocity.z**2)**0.5 + + result = { + "success": True, + "vehicle_id": vehicle_id, + "type_id": vehicle.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "speed_kmh": speed, + "acceleration": { + "x": acceleration.x, + "y": acceleration.y, + "z": acceleration.z + }, + "angular_velocity": { + "x": angular_velocity.x, + "y": angular_velocity.y, + "z": angular_velocity.z + } + } + + logger.info(f"获取车辆 {vehicle_id} 状态成功") + return result + + except Exception as e: + logger.error(f"获取车辆状态失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("车辆控制工具注册完成") From f6356b346ad6738a772b29f9118d88cbe876825d Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:53:47 +0800 Subject: [PATCH 2/9] Delete sim/__init__.cpython-311.pyc --- sim/__init__.cpython-311.pyc | Bin 13475 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 sim/__init__.cpython-311.pyc diff --git a/sim/__init__.cpython-311.pyc b/sim/__init__.cpython-311.pyc deleted file mode 100644 index 9c5a568926ee4b09394173b74a108cf20ec6560a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13475 zcmdU0Yj6`sww}>w^c>j&$(H=W!UlrCmNDij0$DJxghw8bU1FfTLL-ccY&j!|hgVKP zmTVM40!!E>50fZ+ftZj4-y4#U4J3ptsr=Y`OLD2MRa2EJs)CI9wMu2FO6Au*XGR*$ zNFqS?QFUA6(>*=irytXO&Ua3a-!_>H1cc8I?CAcxlOVpun_N(56W{NI#BqWpItZ3z zRerLAgqZTHdZ-SnN8O?Bp*!dvO^1fW=W4&UN7te2(Rb)cLPhw$7(6$OUphVh+3&_)dOIEcDE;xN^s^t24ZW2< zaok`SJACB&XRnLTZybAbEPCYIef!0NkhpR1qp^Ma$6xw5efWUyV|S1<4OhIsYb?qGb?uMv+V((i$hRld zU%4Y3+Sb|Q?Q?|!0e{f7%eSN3>-X^#Wbqo#7YcK|UTIL!h86xO`1@W1Q+S++5HYwZ z4yPgOtdz}?A(eDPu&PjQ`XMu<$v>%*k}zr10kx9L0C{(<=?T_W&>}5wh1Dq9*GY{M z`Sw}uU6fIUq}Q(;M~KPu+^))V-EM%TKo^Wkb$6pG7?D0ymTv=Q^j>?SVGTv|OZ^ji zVhox8p?>LDrl?=)yX%*p$o*LRPtI0(NcPTIv*K=o;%=g1G#16(z0bLswHCd7`tHW| zljFJfo@m&LqFL62Cg$H_16#5nk031gjsQpQbltf421hoCdS~qUePe^qrw^aIactOq%~JK{(1aQp!0`Y zW*RqT@NYo&5g9X~pB2}&$ghmLOkbI@Og^$CVQEZS8l%fo7Tc(0%7|r3!ZIysnU*0` zrs{9ZmB*Q*OuTMR!aO%=o*OsMP1&lB=&x1PC97IStLBYV%^UtrqN+Vv)gFB)CA_`Z zPIzsJiq>RBYjkCrnRs~3!8LLFvQry|D&N|Cax)~wE5WoUnf5r-p0ZR$*Zq(#w?mWq z$~^f7>aIPwHeR)G*aHNv1mjLJ?l|L4St_Cr=6^px$k0$3;@}h*b9CXd3gQdZ(uojV zsW7zbs4Ml8+9~SFViM9{QZ&%@JR`lB&Tf|1;mXCG$?Ld4NJx3*QdbXrh3&TwHlj?< zt3i2CfT&div>@*b!Ja0fiQ(2Q3QAcUq*LOU(pU43!qS-0+A|}ao zIWy2O3dBC3jc9{9;HHm|C@Zr(wYSj)24!t-K|~uXE$ItN?v%jCfG(orYM~y^QBhzG z5gkkA!Q*KVMrTnV34tUcbZI3G`Nmu+c^py6xxt+=&l~Q@{N20EX_)!5((D6A8KBMc zj8F=Wl!7Cr;Am2ERE}jp4{ytq;~SF!+gu(&M6?loK`leXz^#Qfqi<2~YDRjJgV7%h>Y(l7{6v*6rYFZ@?emZlalehT_RR zJh_)A`yojSW8^6qBd^9WhHH`oZv1I@?C2XsP3Ay>vywy9AgXNI>*4$!t_CwE0pZo# zyL(yQw8InZ^n^lO_qK4z$D2@B=Wi!+K0rl1zFyYHcJA^3+6nTeprVL=Bj@Q2ZVzxh zyl$cwtuMSHo(hII zCnLg+>6nX?!{OuzL*f=;su77Bi3~Fpk zBvBN{Z60*NA_F8cr5K5%bk-Y1xX z%cqT$PmAqNl(!_yTQUS?dLU(=JZf(lv9}Cs6ZQp3yw)6{Qx?D~vWX-Vd!i4k23wypMFaG98 zS;D*~Xa*?u6NuG`r$v7XmrsQDeOJ zk>UC0-4MZbxmJvUCd`i}&5y>-k6x>9Of@)9&PYvePC2G$^t63qh7iewiJ3BDiZkBU z8Mpl!FY~W+plv>aHjzl6t!FVCE#5IdNuAs(#9Q!Qs|3yWZUux6H$fDZFw( zZaxCJ7Faij8dkW8b50;%R4uQ8=;~ZGQnz8{V(KfkYvls!tJx%`7tlaCG;q8q&vF3~ zlYrVTE3I=?);d=yQ5fJY>aMJF8ZjSBi)d}9gfL25twjh;9Z>^9qgYKAp^1FdrSYAz zhk%!;(nzkb6~G5F1QL_bT5f7EX0>7ul-d{2fG$F^dK_JmoxTw4bU9zN%8CMDzLIu2 zONCH@WJ9C?h2VS&THlzptly$iIYqS|q`ai5 zL>09hn&I_=HWGA(mi2))2<+v{!@WxNQ9^TYtDttyV8xvy>I*?tJcze~M$kY?)CQt1 zD9<*LQ%wCWchUw06`ll!!I`ifK_65W>w}DX!p~6ymDMN~XN*+N7;+~n=OruWMOURP z)hP8J7p4A&%l+}T4S3l$;$_}=jj~)+-4nsE#jE5QQ+bri8@pGf1s))WyatC3@nQ*-hS?# z^ueK- zt!DWc9bq(09P-xPj*VcBpj9AhXQ@w{y<3d`iV0n>uz3d8cWGdZXK}7;`AJ+ zQUF^NlUfmA3u5w7>;oooGm^}VI5R^Mlxv3!ar;`hF3%U^1hX#5tcx@2uGQ41CQVP( zP0480*0KyC5>wd^w-|umc@Qb2BqUkF{dbQ@60IT-o(2@21`wWZpjQlrb`y2Q>OzO0 ziiGr+IvVH=C@X}Oye_l@Z1xbGBGcgMMxQV^fS>g7h8><>wo^=NIo~cH7xa1ME;EEg zf}|Y+C@OOmY9&{pPH+`!3nZ>SqQ7TXq5huTgWzrw+=Bq|^6tS9Zoq6{^&y!n3ZR@y z>9A7X4X6}#jRkcnWohNz0Hvr~*sfYxT6qWhNmm-wMpO|hqK?oJ4LAV705~hTO$Uq- zLun^vp#uuQv&@|pGv?+-9I@Tg;m{Xln)(g%x&@#DRvCJS1&NWfv z;E7{42pWe`v@3fnxl#byEfJ=q#-T&#v&n`CqwIyurN%tX2re~}8hi|LO~!*jSDd+$H+_V5RAt7IeWQa9B61V(b3rt;ev`t<$Me`;Gz=#WU&V=hzE!$D3d^2&$0#CA`2c@g-&+$R$2^(%i2gliP&k2_zjzHUklq zr~Gv}!srZkh+o^d9l04_+^=gA=T%4vQ?pp9Q?+nn1@?nq(5P~j*-w13=V;DynL-u( zOM=4;tr1bHQazqM@a;`^FJJac@k^X`kuh)b^!54o;(;5sb9;SjlIh#q4ZP65B`D&p z*okRCg1C8bDM>*zU@F8O6o3cwIywB(beBrZLg?Lcw>#R@T-BrK*H)uT3yiZfIlnp*HgA<-HTI*0_JgZ*C$ z9nkJN)UdpaIM)c|3(GP$M1LtWte8Ok#qL_cP**i1q^~kGP=)6SJ*ff+;J^rzfq?(F zo~HpC3_>&_i$Vd6(Lgyl9Focxl+!}Fl3uBTa`5#boU2L37fbd-Ey$5PPhw6#v;(=4 zs?O)KI;_zTFknD07<}rT_;!R4q4Otp`8E`}Qag&=d^=z~!V}3Swo?L6id@OGROF(c zNC>PExX4B+AD?1W*qT0~mGxDdp9#oU_Dd%#r<0Y_Da!#Q(GM6xQ*yl_h#0QjqL70; zCBEr_4^O~Xh)_ElvL}Rco-%_tR|?=v`E5=JsZeviR3Si<3}|!qgiwfOaArdIHK;8q zX!%DW;@79rBPsE7;EYtcBmxlOli1$%&)*u`e@-f2(r+jrfUf`kD+cBlS>=E2fsNcO z7z;NCh*K{!fwiH%eLnbjA%En<>vs9zRI)p?m)8gTd~hE1WX}$s@xYOu-^YpHH<&%W zU12!n0>e}+Dq7#d$GBpL9YT&n?6>HoEc&uWe3-YfZ8AtufChx)viyk?BYx?U?WS4u zUdsAs6>Y}?<|DbSw=;WAR?6GC8Y}vc;JkA?fPmqL zbI47_jA=k_b3Qgf9Fg$Jac2-d9`W_J-pRx-s`3N!pDmt~mGq0w`*$ITYxeq4d-I6B zIbm-}+FRiG%r$(p&*wVlcmGFG|!e zPS!7uGqr#oD(tT;7<9*;dUf&8f_L4+Pn}tus9A7f!A1Awr#^w?>aW`p)$0@1^#atW zb)avcreW}bYgG-&s+q~E=2TGv90zrP1CTJpr2CR}Ey=pMshTOtn&xCpYpQl?veuQX zZBrDhZWycv{E{WIM+Rb20fhjG zuDtaNk}z4s&zzPb&{-;831(T6Sr%uODL!y2byoI|dpK|-L>pGLfrkUg7v;+_y4q$~ zIgh%!*tN2i`f4T#>91O8pkX}N7B{-$%YEUy*Yx{`V8c7f^Qb58-1oGg`dUgf3=ew860`mj-mE={=Pqs5d?-ZMSqWQZJ*cw)wSMbC6Z&7_pA8m}5h%3hZ# zUq)h8q{75tLTC*WI5qZ+R!Hgyb45lkBn_A}3Q2~jsLGgxWEpl%V0X(b@7n}?Dz+d} zse8hM)hflHG4I4~vEYa8Vti@&Rm)ZHW&6eTayBKH-2!H`@wqk6z()kL66)5Zx;37= zxGGp!P7|-~>|_Jp&Q4y7ABA@JcJUfexDbm=UDyD)-Z>2rej@(&NzliEKhU)u{yzn7 zIb6=dZV?XY)(W4NidzELFNYXQXiQk^KkDv>kBq{8-x6*wJOHDT2%Z3vp-7UzckgYD?;?;~>QKbCUWw`!pF%Kuyk$X)^@IL(9eZ Jr>W5W{{xiI2%i7| From 5bb7ab759c9af55249ffde212056b5f816e23b09 Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:53:57 +0800 Subject: [PATCH 3/9] Delete sim/__init__.py --- sim/__init__.py | 385 ------------------------------------------------ 1 file changed, 385 deletions(-) delete mode 100644 sim/__init__.py diff --git a/sim/__init__.py b/sim/__init__.py deleted file mode 100644 index e96d69b..0000000 --- a/sim/__init__.py +++ /dev/null @@ -1,385 +0,0 @@ -""" -天气控制工具模块 - -提供天气参数设置、预设天气、时间控制等功能。 -""" - -import logging -from typing import Dict, Any -from mcp.server.fastmcp import FastMCP, Context - -logger = logging.getLogger("hutb_mcp.tools.weather") - -# 天气预设 -WEATHER_PRESETS = { - "clear": { - "cloudiness": 0.0, - "precipitation": 0.0, - "precipitation_deposits": 0.0, - "wind_intensity": 0.0, - "sun_altitude_angle": 70.0, - "fog_density": 0.0, - "wetness": 0.0 - }, - "cloudy": { - "cloudiness": 80.0, - "precipitation": 0.0, - "precipitation_deposits": 0.0, - "wind_intensity": 20.0, - "sun_altitude_angle": 45.0, - "fog_density": 0.0, - "wetness": 0.0 - }, - "rainy": { - "cloudiness": 100.0, - "precipitation": 80.0, - "precipitation_deposits": 50.0, - "wind_intensity": 30.0, - "sun_altitude_angle": 30.0, - "fog_density": 10.0, - "wetness": 80.0 - }, - "foggy": { - "cloudiness": 50.0, - "precipitation": 0.0, - "precipitation_deposits": 0.0, - "wind_intensity": 10.0, - "sun_altitude_angle": 30.0, - "fog_density": 80.0, - "wetness": 20.0 - }, - "stormy": { - "cloudiness": 100.0, - "precipitation": 100.0, - "precipitation_deposits": 100.0, - "wind_intensity": 100.0, - "sun_altitude_angle": 10.0, - "fog_density": 30.0, - "wetness": 100.0 - } -} - - -def register_weather_tools(mcp: FastMCP) -> None: - """注册天气控制工具""" - - @mcp.tool() - def get_weather(ctx: Context) -> Dict[str, Any]: - """ - 获取当前天气参数 - - Returns: - 天气参数字典 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - weather = conn.world.get_weather() - - result = { - "success": True, - "cloudiness": weather.cloudiness, - "precipitation": weather.precipitation, - "precipitation_deposits": weather.precipitation_deposits, - "wind_intensity": weather.wind_intensity, - "sun_azimuth_angle": weather.sun_azimuth_angle, - "sun_altitude_angle": weather.sun_altitude_angle, - "fog_density": weather.fog_density, - "fog_distance": weather.fog_distance, - "wetness": weather.wetness, - "fog_falloff": weather.fog_falloff, - "scattering_intensity": weather.scattering_intensity, - "mie_scattering_scale": weather.mie_scattering_scale, - "rayleigh_scattering_scale": weather.rayleigh_scattering_scale - } - - logger.info("获取天气参数成功") - return result - - except Exception as e: - logger.error(f"获取天气参数失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_weather( - ctx: Context, - cloudiness: float = None, - precipitation: float = None, - precipitation_deposits: float = None, - wind_intensity: float = None, - sun_azimuth_angle: float = None, - sun_altitude_angle: float = None, - fog_density: float = None, - fog_distance: float = None, - wetness: float = None - ) -> Dict[str, Any]: - """ - 设置天气参数 - - Args: - cloudiness: 云量 (0-100) - precipitation: 降水量 (0-100) - precipitation_deposits: 降水沉积 (0-100) - wind_intensity: 风力强度 (0-100) - sun_azimuth_angle: 太阳方位角 (0-360) - sun_altitude_angle: 太阳高度角 (-90 到 90) - fog_density: 雾密度 (0-100) - fog_distance: 雾距离 - wetness: 湿度 (0-100) - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - # 获取当前天气 - weather = conn.world.get_weather() - - # 更新指定参数(带范围限制) - if cloudiness is not None: - weather.cloudiness = max(0.0, min(100.0, cloudiness)) - if precipitation is not None: - weather.precipitation = max(0.0, min(100.0, precipitation)) - if precipitation_deposits is not None: - weather.precipitation_deposits = max(0.0, min(100.0, precipitation_deposits)) - if wind_intensity is not None: - weather.wind_intensity = max(0.0, min(100.0, wind_intensity)) - if sun_azimuth_angle is not None: - weather.sun_azimuth_angle = sun_azimuth_angle % 360.0 - if sun_altitude_angle is not None: - weather.sun_altitude_angle = max(-90.0, min(90.0, sun_altitude_angle)) - if fog_density is not None: - weather.fog_density = max(0.0, min(100.0, fog_density)) - if fog_distance is not None: - weather.fog_distance = max(0.0, fog_distance) - if wetness is not None: - weather.wetness = max(0.0, min(100.0, wetness)) - - # 应用天气 - conn.world.set_weather(weather) - - logger.info("设置天气参数成功") - return { - "success": True, - "cloudiness": weather.cloudiness, - "precipitation": weather.precipitation, - "sun_altitude_angle": weather.sun_altitude_angle, - "fog_density": weather.fog_density, - "wetness": weather.wetness - } - - except Exception as e: - logger.error(f"设置天气参数失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_weather_preset(ctx: Context, preset: str) -> Dict[str, Any]: - """ - 应用天气预设 - - Args: - preset: 预设名称,可选值: clear, cloudy, rainy, foggy, stormy - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - preset = preset.lower() - if preset not in WEATHER_PRESETS: - return { - "success": False, - "error": f"未知预设: {preset},可选值: {list(WEATHER_PRESETS.keys())}" - } - - params = WEATHER_PRESETS[preset] - - # 创建天气对象 - weather = carla.WeatherParameters( - cloudiness=params["cloudiness"], - precipitation=params["precipitation"], - precipitation_deposits=params["precipitation_deposits"], - wind_intensity=params["wind_intensity"], - sun_altitude_angle=params["sun_altitude_angle"], - fog_density=params["fog_density"], - wetness=params["wetness"] - ) - - conn.world.set_weather(weather) - - logger.info(f"应用天气预设成功: {preset}") - return { - "success": True, - "preset": preset, - "parameters": params - } - - except Exception as e: - logger.error(f"应用天气预设失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_time_of_day(ctx: Context, hour: int, minute: int = 0) -> Dict[str, Any]: - """ - 设置一天中的时间(通过调整太阳位置) - - Args: - hour: 小时 (0-23) - minute: 分钟 (0-59) - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - # 限制范围 - hour = max(0, min(23, hour)) - minute = max(0, min(59, minute)) - - # 计算太阳高度角 - # 简化模型:6:00 日出 (0°), 12:00 正午 (90°), 18:00 日落 (0°) - time_decimal = hour + minute / 60.0 - - if 6 <= time_decimal <= 18: - # 白天 - sun_altitude = 90.0 * (1 - abs(time_decimal - 12) / 6) - else: - # 夜晚 - sun_altitude = -45.0 - - # 计算太阳方位角 - sun_azimuth = (time_decimal - 6) * 15 # 每小时15度 - - weather = conn.world.get_weather() - weather.sun_altitude_angle = sun_altitude - weather.sun_azimuth_angle = sun_azimuth % 360 - conn.world.set_weather(weather) - - logger.info(f"设置时间成功: {hour:02d}:{minute:02d}") - return { - "success": True, - "time": f"{hour:02d}:{minute:02d}", - "sun_altitude_angle": sun_altitude, - "sun_azimuth_angle": sun_azimuth % 360 - } - - except Exception as e: - logger.error(f"设置时间失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_extreme_weather(ctx: Context, weather_type: str) -> Dict[str, Any]: - """ - 设置极端天气(用于压力测试) - - Args: - weather_type: 极端天气类型,可选值: - - heavy_rain: 暴雨 - - dense_fog: 浓雾 - - blizzard: 暴风雪 - - sandstorm: 沙尘暴 - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - extreme_presets = { - "heavy_rain": { - "cloudiness": 100.0, - "precipitation": 100.0, - "precipitation_deposits": 100.0, - "wind_intensity": 80.0, - "sun_altitude_angle": 5.0, - "fog_density": 20.0, - "wetness": 100.0 - }, - "dense_fog": { - "cloudiness": 100.0, - "precipitation": 0.0, - "precipitation_deposits": 0.0, - "wind_intensity": 5.0, - "sun_altitude_angle": 20.0, - "fog_density": 100.0, - "wetness": 50.0 - }, - "blizzard": { - "cloudiness": 100.0, - "precipitation": 100.0, - "precipitation_deposits": 100.0, - "wind_intensity": 100.0, - "sun_altitude_angle": 10.0, - "fog_density": 50.0, - "wetness": 0.0 - }, - "sandstorm": { - "cloudiness": 80.0, - "precipitation": 0.0, - "precipitation_deposits": 50.0, - "wind_intensity": 100.0, - "sun_altitude_angle": 30.0, - "fog_density": 70.0, - "wetness": 0.0 - } - } - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - weather_type = weather_type.lower() - if weather_type not in extreme_presets: - return { - "success": False, - "error": f"未知极端天气类型: {weather_type},可选值: {list(extreme_presets.keys())}" - } - - params = extreme_presets[weather_type] - - weather = carla.WeatherParameters( - cloudiness=params["cloudiness"], - precipitation=params["precipitation"], - precipitation_deposits=params["precipitation_deposits"], - wind_intensity=params["wind_intensity"], - sun_altitude_angle=params["sun_altitude_angle"], - fog_density=params["fog_density"], - wetness=params["wetness"] - ) - - conn.world.set_weather(weather) - - logger.info(f"设置极端天气成功: {weather_type}") - return { - "success": True, - "weather_type": weather_type, - "parameters": params - } - - except Exception as e: - logger.error(f"设置极端天气失败: {e}") - return {"success": False, "error": str(e)} - - logger.info("天气控制工具注册完成") From 4e12e48ba763dc922f87de201dd0a0fcd0d6cc0a Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:54:22 +0800 Subject: [PATCH 4/9] Delete sim/editor_tools.py --- sim/editor_tools.py | 303 -------------------------------------------- 1 file changed, 303 deletions(-) delete mode 100644 sim/editor_tools.py diff --git a/sim/editor_tools.py b/sim/editor_tools.py deleted file mode 100644 index a285c20..0000000 --- a/sim/editor_tools.py +++ /dev/null @@ -1,303 +0,0 @@ -""" -无人机控制工具模块 - -提供无人机生成、飞行控制等功能。 -注意:HUTB/CARLA 原生不支持无人机,此模块提供基于 Walker 的模拟实现。 -""" - -import logging -from typing import Dict, List, Any, Optional -from mcp.server.fastmcp import FastMCP, Context - -logger = logging.getLogger("hutb_mcp.tools.air") - - -def register_air_tools(mcp: FastMCP) -> None: - """注册无人机控制工具""" - - @mcp.tool() - def spawn_drone( - ctx: Context, - location: List[float], - rotation: List[float] = None - ) -> Dict[str, Any]: - """ - 生成无人机(模拟实现) - - 注意:CARLA 原生不支持无人机,此功能使用特殊 Actor 模拟。 - - Args: - location: 生成位置 [x, y, z] - rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] - - Returns: - 生成结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - rotation = rotation or [0, 0, 0] - - # 使用静态道具模拟无人机 - # 在实际项目中,可以使用自定义的无人机模型 - blueprint = conn.blueprint_library.find("static.prop.box01") - - if not blueprint: - # 尝试其他可用的静态道具 - props = list(conn.blueprint_library.filter("static.prop.*")) - if props: - blueprint = props[0] - else: - return {"success": False, "error": "未找到可用的无人机蓝图"} - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 生成无人机 - drone = conn.world.spawn_actor(blueprint, transform) - - if drone: - conn.track_actor(drone.id) - logger.info(f"生成无人机成功: {drone.id}") - return { - "success": True, - "drone_id": drone.id, - "location": location, - "note": "无人机为模拟实现,使用静态道具代替" - } - else: - return {"success": False, "error": "生成无人机失败"} - - except Exception as e: - logger.error(f"生成无人机失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def get_drone_state(ctx: Context, drone_id: int) -> Dict[str, Any]: - """ - 获取无人机状态 - - Args: - drone_id: 无人机 ID - - Returns: - 无人机状态信息 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - drone = conn.world.get_actor(drone_id) - if not drone: - return {"success": False, "error": f"未找到无人机: {drone_id}"} - - transform = drone.get_transform() - velocity = drone.get_velocity() - - result = { - "success": True, - "drone_id": drone_id, - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - }, - "rotation": { - "pitch": transform.rotation.pitch, - "yaw": transform.rotation.yaw, - "roll": transform.rotation.roll - }, - "velocity": { - "x": velocity.x, - "y": velocity.y, - "z": velocity.z - }, - "altitude": transform.location.z - } - - logger.info(f"获取无人机 {drone_id} 状态成功") - return result - - except Exception as e: - logger.error(f"获取无人机状态失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_drone_destination( - ctx: Context, - drone_id: int, - destination: List[float] - ) -> Dict[str, Any]: - """ - 设置无人机目标位置 - - Args: - drone_id: 无人机 ID - destination: 目标位置 [x, y, z] - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - drone = conn.world.get_actor(drone_id) - if not drone: - return {"success": False, "error": f"未找到无人机: {drone_id}"} - - # 直接设置位置(简化实现) - # 在实际项目中,应该实现平滑的飞行路径 - current_transform = drone.get_transform() - current_transform.location = carla.Location( - x=destination[0], - y=destination[1], - z=destination[2] - ) - drone.set_transform(current_transform) - - logger.info(f"设置无人机 {drone_id} 目标位置: {destination}") - return { - "success": True, - "drone_id": drone_id, - "destination": destination, - "note": "位置已直接设置,实际项目中应实现平滑飞行" - } - - except Exception as e: - logger.error(f"设置无人机目标位置失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def drone_hover(ctx: Context, drone_id: int) -> Dict[str, Any]: - """ - 无人机悬停 - - Args: - drone_id: 无人机 ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - drone = conn.world.get_actor(drone_id) - if not drone: - return {"success": False, "error": f"未找到无人机: {drone_id}"} - - # 静态道具默认就是悬停状态 - transform = drone.get_transform() - - logger.info(f"无人机 {drone_id} 悬停中") - return { - "success": True, - "drone_id": drone_id, - "status": "hovering", - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - } - } - - except Exception as e: - logger.error(f"无人机悬停失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def drone_land(ctx: Context, drone_id: int) -> Dict[str, Any]: - """ - 无人机降落 - - Args: - drone_id: 无人机 ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - drone = conn.world.get_actor(drone_id) - if not drone: - return {"success": False, "error": f"未找到无人机: {drone_id}"} - - # 将无人机降落到地面 - transform = drone.get_transform() - transform.location.z = 0.5 # 降落到接近地面的高度 - drone.set_transform(transform) - - logger.info(f"无人机 {drone_id} 降落成功") - return { - "success": True, - "drone_id": drone_id, - "status": "landed", - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - } - } - - except Exception as e: - logger.error(f"无人机降落失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def destroy_drone(ctx: Context, drone_id: int) -> Dict[str, Any]: - """ - 销毁无人机 - - Args: - drone_id: 无人机 ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - drone = conn.world.get_actor(drone_id) - if not drone: - return {"success": False, "error": f"未找到无人机: {drone_id}"} - - drone.destroy() - conn.untrack_actor(drone_id) - - logger.info(f"销毁无人机成功: {drone_id}") - return {"success": True, "drone_id": drone_id} - - except Exception as e: - logger.error(f"销毁无人机失败: {e}") - return {"success": False, "error": str(e)} - - logger.info("无人机控制工具注册完成") From 285181b7e2556bb9ccd65eb2209ee5a0ea3f52a2 Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:54:44 +0800 Subject: [PATCH 5/9] Delete sim/sensor_tools.py --- sim/sensor_tools.py | 327 -------------------------------------------- 1 file changed, 327 deletions(-) delete mode 100644 sim/sensor_tools.py diff --git a/sim/sensor_tools.py b/sim/sensor_tools.py deleted file mode 100644 index f28ed68..0000000 --- a/sim/sensor_tools.py +++ /dev/null @@ -1,327 +0,0 @@ -""" -编辑器控制工具模块 - -提供场景编辑、Actor 管理、地图加载等功能。 -""" - -import logging -from typing import Dict, List, Any, Optional -from mcp.server.fastmcp import FastMCP, Context - -logger = logging.getLogger("hutb_mcp.tools.editor") - - -def register_editor_tools(mcp: FastMCP) -> None: - """注册编辑器控制工具""" - - @mcp.tool() - def get_actors_in_level(ctx: Context) -> List[Dict[str, Any]]: - """ - 获取当前场景中所有 Actor 的列表 - - Returns: - Actor 列表,每个 Actor 包含 id、type_id、name 等信息 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - logger.error("未连接到仿真器") - return [] - - actors = conn.world.get_actors() - result = [] - - for actor in actors: - result.append({ - "id": actor.id, - "type_id": actor.type_id, - "name": actor.attributes.get("role_name", ""), - "location": { - "x": actor.get_location().x, - "y": actor.get_location().y, - "z": actor.get_location().z - } - }) - - logger.info(f"获取到 {len(result)} 个 Actor") - return result - - except Exception as e: - logger.error(f"获取 Actor 列表失败: {e}") - return [] - - @mcp.tool() - def get_current_map(ctx: Context) -> str: - """ - 获取当前加载的地图名称 - - Returns: - 地图名称字符串 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - logger.error("未连接到仿真器") - return "" - - map_name = conn.map.name - logger.info(f"当前地图: {map_name}") - return map_name - - except Exception as e: - logger.error(f"获取地图名称失败: {e}") - return "" - - @mcp.tool() - def load_map(ctx: Context, map_name: str) -> Dict[str, Any]: - """ - 加载指定地图 - - Args: - map_name: 地图名称,如 "Town10" 或 "Town01" - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - logger.info(f"正在加载地图: {map_name}") - - # 清理当前资源 - conn.cleanup_all() - - # 加载新地图 - conn.client.load_world(map_name) - - # 更新世界和地图引用 - conn.world = conn.client.get_world() - conn.map = conn.world.get_map() - conn.blueprint_library = conn.world.get_blueprint_library() - - logger.info(f"地图 {map_name} 加载成功") - return {"success": True, "map": map_name} - - except Exception as e: - logger.error(f"加载地图失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def spawn_actor( - ctx: Context, - actor_type: str, - location: List[float], - rotation: List[float] = None - ) -> Dict[str, Any]: - """ - 在场景中生成 Actor - - Args: - actor_type: Actor 类型,如 "static.prop.bench" - location: 位置 [x, y, z] - rotation: 旋转 [pitch, yaw, roll],默认 [0, 0, 0] - - Returns: - 生成结果,包含 Actor ID - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - rotation = rotation or [0, 0, 0] - - # 查找蓝图 - blueprint = conn.blueprint_library.find(actor_type) - if not blueprint: - return {"success": False, "error": f"未找到蓝图: {actor_type}"} - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 生成 Actor - actor = conn.world.spawn_actor(blueprint, transform) - - if actor: - conn.track_actor(actor.id) - logger.info(f"生成 Actor 成功: {actor.id} ({actor_type})") - return { - "success": True, - "actor_id": actor.id, - "type": actor_type, - "location": location - } - else: - return {"success": False, "error": "生成 Actor 失败"} - - except Exception as e: - logger.error(f"生成 Actor 失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def delete_actor(ctx: Context, actor_id: int) -> Dict[str, Any]: - """ - 删除指定 Actor - - Args: - actor_id: Actor ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - actor = conn.world.get_actor(actor_id) - if not actor: - return {"success": False, "error": f"未找到 Actor: {actor_id}"} - - actor.destroy() - conn.untrack_actor(actor_id) - - logger.info(f"删除 Actor 成功: {actor_id}") - return {"success": True, "actor_id": actor_id} - - except Exception as e: - logger.error(f"删除 Actor 失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_actor_transform( - ctx: Context, - actor_id: int, - location: List[float] = None, - rotation: List[float] = None - ) -> Dict[str, Any]: - """ - 设置 Actor 的位置和旋转 - - Args: - actor_id: Actor ID - location: 新位置 [x, y, z] - rotation: 新旋转 [pitch, yaw, roll] - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - actor = conn.world.get_actor(actor_id) - if not actor: - return {"success": False, "error": f"未找到 Actor: {actor_id}"} - - current_transform = actor.get_transform() - - # 更新位置 - if location: - current_transform.location = carla.Location( - x=location[0], y=location[1], z=location[2] - ) - - # 更新旋转 - if rotation: - current_transform.rotation = carla.Rotation( - pitch=rotation[0], yaw=rotation[1], roll=rotation[2] - ) - - actor.set_transform(current_transform) - - logger.info(f"设置 Actor {actor_id} 变换成功") - return { - "success": True, - "actor_id": actor_id, - "location": [ - current_transform.location.x, - current_transform.location.y, - current_transform.location.z - ], - "rotation": [ - current_transform.rotation.pitch, - current_transform.rotation.yaw, - current_transform.rotation.roll - ] - } - - except Exception as e: - logger.error(f"设置 Actor 变换失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def get_actor_properties(ctx: Context, actor_id: int) -> Dict[str, Any]: - """ - 获取 Actor 的属性信息 - - Args: - actor_id: Actor ID - - Returns: - Actor 属性字典 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - actor = conn.world.get_actor(actor_id) - if not actor: - return {"success": False, "error": f"未找到 Actor: {actor_id}"} - - transform = actor.get_transform() - velocity = actor.get_velocity() - - result = { - "success": True, - "id": actor.id, - "type_id": actor.type_id, - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - }, - "rotation": { - "pitch": transform.rotation.pitch, - "yaw": transform.rotation.yaw, - "roll": transform.rotation.roll - }, - "velocity": { - "x": velocity.x, - "y": velocity.y, - "z": velocity.z - }, - "attributes": dict(actor.attributes) - } - - logger.info(f"获取 Actor {actor_id} 属性成功") - return result - - except Exception as e: - logger.error(f"获取 Actor 属性失败: {e}") - return {"success": False, "error": str(e)} - - logger.info("编辑器控制工具注册完成") From 20968495b487d90d92d84f991fabb74a3dd9201d Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:54:55 +0800 Subject: [PATCH 6/9] Delete sim/vehicle_tools.py --- sim/vehicle_tools.py | 347 ------------------------------------------- 1 file changed, 347 deletions(-) delete mode 100644 sim/vehicle_tools.py diff --git a/sim/vehicle_tools.py b/sim/vehicle_tools.py deleted file mode 100644 index 6c7646f..0000000 --- a/sim/vehicle_tools.py +++ /dev/null @@ -1,347 +0,0 @@ -""" -传感器管理工具模块 - -提供相机、激光雷达、雷达等传感器的创建和管理功能。 -""" - -import logging -from typing import Dict, List, Any, Optional -from mcp.server.fastmcp import FastMCP, Context - -logger = logging.getLogger("hutb_mcp.tools.sensor") - - -def register_sensor_tools(mcp: FastMCP) -> None: - """注册传感器管理工具""" - - @mcp.tool() - def attach_camera( - ctx: Context, - vehicle_id: int, - camera_type: str = "rgb", - location: List[float] = None, - rotation: List[float] = None, - image_size_x: int = 800, - image_size_y: int = 600, - fov: float = 90.0 - ) -> Dict[str, Any]: - """ - 为车辆附加相机传感器 - - Args: - vehicle_id: 车辆 ID - camera_type: 相机类型,可选值: rgb, depth, semantic_segmentation - location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2] - rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] - image_size_x: 图像宽度 - image_size_y: 图像高度 - fov: 视场角 - - Returns: - 传感器信息 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - location = location or [0, 0, 2] - rotation = rotation or [0, 0, 0] - - # 获取车辆 - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - # 确定相机蓝图 - camera_blueprints = { - "rgb": "sensor.camera.rgb", - "depth": "sensor.camera.depth", - "semantic_segmentation": "sensor.camera.semantic_segmentation" - } - - if camera_type not in camera_blueprints: - return { - "success": False, - "error": f"未知相机类型: {camera_type},可选值: {list(camera_blueprints.keys())}" - } - - # 获取蓝图 - blueprint = conn.blueprint_library.find(camera_blueprints[camera_type]) - - # 设置属性 - blueprint.set_attribute("image_size_x", str(image_size_x)) - blueprint.set_attribute("image_size_y", str(image_size_y)) - blueprint.set_attribute("fov", str(fov)) - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 附加传感器 - sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) - - if sensor: - conn.track_sensor(sensor.id) - logger.info(f"附加相机成功: {sensor.id} ({camera_type})") - return { - "success": True, - "sensor_id": sensor.id, - "type": camera_type, - "parent_id": vehicle_id, - "image_size": [image_size_x, image_size_y], - "fov": fov - } - else: - return {"success": False, "error": "附加相机失败"} - - except Exception as e: - logger.error(f"附加相机失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def attach_lidar( - ctx: Context, - vehicle_id: int, - location: List[float] = None, - rotation: List[float] = None, - channels: int = 32, - range: float = 100.0, - points_per_second: int = 56000, - rotation_frequency: float = 10.0, - upper_fov: float = 10.0, - lower_fov: float = -30.0 - ) -> Dict[str, Any]: - """ - 为车辆附加激光雷达传感器 - - Args: - vehicle_id: 车辆 ID - location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2.5] - rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] - channels: 激光通道数 - range: 探测范围(米) - points_per_second: 每秒点数 - rotation_frequency: 旋转频率(Hz) - upper_fov: 上视场角 - lower_fov: 下视场角 - - Returns: - 传感器信息 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - location = location or [0, 0, 2.5] - rotation = rotation or [0, 0, 0] - - # 获取车辆 - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - # 获取蓝图 - blueprint = conn.blueprint_library.find("sensor.lidar.ray_cast") - - # 设置属性 - blueprint.set_attribute("channels", str(channels)) - blueprint.set_attribute("range", str(range)) - blueprint.set_attribute("points_per_second", str(points_per_second)) - blueprint.set_attribute("rotation_frequency", str(rotation_frequency)) - blueprint.set_attribute("upper_fov", str(upper_fov)) - blueprint.set_attribute("lower_fov", str(lower_fov)) - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 附加传感器 - sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) - - if sensor: - conn.track_sensor(sensor.id) - logger.info(f"附加激光雷达成功: {sensor.id}") - return { - "success": True, - "sensor_id": sensor.id, - "type": "lidar", - "parent_id": vehicle_id, - "channels": channels, - "range": range - } - else: - return {"success": False, "error": "附加激光雷达失败"} - - except Exception as e: - logger.error(f"附加激光雷达失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def attach_radar( - ctx: Context, - vehicle_id: int, - location: List[float] = None, - rotation: List[float] = None, - horizontal_fov: float = 30.0, - vertical_fov: float = 10.0, - range: float = 100.0, - points_per_second: int = 1500 - ) -> Dict[str, Any]: - """ - 为车辆附加雷达传感器 - - Args: - vehicle_id: 车辆 ID - location: 相对于车辆的位置 [x, y, z],默认 [2, 0, 1] - rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] - horizontal_fov: 水平视场角 - vertical_fov: 垂直视场角 - range: 探测范围(米) - points_per_second: 每秒点数 - - Returns: - 传感器信息 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - location = location or [2, 0, 1] - rotation = rotation or [0, 0, 0] - - # 获取车辆 - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - # 获取蓝图 - blueprint = conn.blueprint_library.find("sensor.other.radar") - - # 设置属性 - blueprint.set_attribute("horizontal_fov", str(horizontal_fov)) - blueprint.set_attribute("vertical_fov", str(vertical_fov)) - blueprint.set_attribute("range", str(range)) - blueprint.set_attribute("points_per_second", str(points_per_second)) - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 附加传感器 - sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) - - if sensor: - conn.track_sensor(sensor.id) - logger.info(f"附加雷达成功: {sensor.id}") - return { - "success": True, - "sensor_id": sensor.id, - "type": "radar", - "parent_id": vehicle_id, - "horizontal_fov": horizontal_fov, - "vertical_fov": vertical_fov, - "range": range - } - else: - return {"success": False, "error": "附加雷达失败"} - - except Exception as e: - logger.error(f"附加雷达失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def destroy_sensor(ctx: Context, sensor_id: int) -> Dict[str, Any]: - """ - 销毁传感器 - - Args: - sensor_id: 传感器 ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - sensor = conn.world.get_actor(sensor_id) - if not sensor: - return {"success": False, "error": f"未找到传感器: {sensor_id}"} - - sensor.destroy() - conn.untrack_sensor(sensor_id) - - logger.info(f"销毁传感器成功: {sensor_id}") - return {"success": True, "sensor_id": sensor_id} - - except Exception as e: - logger.error(f"销毁传感器失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def list_vehicle_sensors(ctx: Context, vehicle_id: int) -> List[Dict[str, Any]]: - """ - 列出车辆上的所有传感器 - - Args: - vehicle_id: 车辆 ID - - Returns: - 传感器列表 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return [] - - # 获取所有传感器类型的 Actor - sensors = conn.world.get_actors().filter("sensor.*") - - result = [] - for sensor in sensors: - # 检查是否附加到指定车辆 - parent = sensor.parent - if parent and parent.id == vehicle_id: - transform = sensor.get_transform() - result.append({ - "id": sensor.id, - "type_id": sensor.type_id, - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - }, - "attributes": dict(sensor.attributes) - }) - - logger.info(f"车辆 {vehicle_id} 有 {len(result)} 个传感器") - return result - - except Exception as e: - logger.error(f"列出传感器失败: {e}") - return [] - - logger.info("传感器管理工具注册完成") From 7302b80330978a9d8c7db2dfa64f0147de610710 Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:55:05 +0800 Subject: [PATCH 7/9] Delete sim/weather_tools.py --- sim/weather_tools.py | 317 ------------------------------------------- 1 file changed, 317 deletions(-) delete mode 100644 sim/weather_tools.py diff --git a/sim/weather_tools.py b/sim/weather_tools.py deleted file mode 100644 index 242336f..0000000 --- a/sim/weather_tools.py +++ /dev/null @@ -1,317 +0,0 @@ -""" -车辆控制工具模块 - -提供车辆生成、控制、状态获取等功能。 -""" - -import logging -from typing import Dict, List, Any, Optional -from mcp.server.fastmcp import FastMCP, Context - -logger = logging.getLogger("hutb_mcp.tools.vehicle") - - -def register_vehicle_tools(mcp: FastMCP) -> None: - """注册车辆控制工具""" - - @mcp.tool() - def get_vehicle_blueprints(ctx: Context) -> List[str]: - """ - 获取所有可用的车辆蓝图列表 - - Returns: - 车辆蓝图 ID 列表 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - logger.error("未连接到仿真器") - return [] - - blueprints = conn.blueprint_library.filter("vehicle.*") - result = [bp.id for bp in blueprints] - - logger.info(f"获取到 {len(result)} 个车辆蓝图") - return result - - except Exception as e: - logger.error(f"获取车辆蓝图失败: {e}") - return [] - - @mcp.tool() - def spawn_vehicle( - ctx: Context, - blueprint_id: str, - location: List[float], - rotation: List[float] = None - ) -> Dict[str, Any]: - """ - 生成车辆 - - Args: - blueprint_id: 车辆蓝图 ID,如 "vehicle.tesla.model3" - location: 生成位置 [x, y, z] - rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] - - Returns: - 生成结果,包含车辆 ID - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - rotation = rotation or [0, 0, 0] - - # 查找蓝图 - blueprint = conn.blueprint_library.find(blueprint_id) - if not blueprint: - return {"success": False, "error": f"未找到车辆蓝图: {blueprint_id}"} - - # 设置随机颜色(如果支持) - if blueprint.has_attribute("color"): - color = blueprint.get_attribute("color").recommended_values - if color: - blueprint.set_attribute("color", color[0]) - - # 创建变换 - transform = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), - carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) - ) - - # 生成车辆 - vehicle = conn.world.spawn_actor(blueprint, transform) - - if vehicle: - conn.track_vehicle(vehicle.id) - logger.info(f"生成车辆成功: {vehicle.id} ({blueprint_id})") - return { - "success": True, - "vehicle_id": vehicle.id, - "blueprint": blueprint_id, - "location": location - } - else: - return {"success": False, "error": "生成车辆失败,可能位置有碰撞"} - - except Exception as e: - logger.error(f"生成车辆失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def destroy_vehicle(ctx: Context, vehicle_id: int) -> Dict[str, Any]: - """ - 销毁车辆 - - Args: - vehicle_id: 车辆 ID - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - vehicle.destroy() - conn.untrack_vehicle(vehicle_id) - - logger.info(f"销毁车辆成功: {vehicle_id}") - return {"success": True, "vehicle_id": vehicle_id} - - except Exception as e: - logger.error(f"销毁车辆失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def set_vehicle_autopilot( - ctx: Context, - vehicle_id: int, - enabled: bool - ) -> Dict[str, Any]: - """ - 设置车辆自动驾驶模式 - - Args: - vehicle_id: 车辆 ID - enabled: 是否启用自动驾驶 - - Returns: - 操作结果 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - vehicle.set_autopilot(enabled) - - logger.info(f"设置车辆 {vehicle_id} 自动驾驶: {enabled}") - return { - "success": True, - "vehicle_id": vehicle_id, - "autopilot": enabled - } - - except Exception as e: - logger.error(f"设置自动驾驶失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def apply_vehicle_control( - ctx: Context, - vehicle_id: int, - throttle: float = 0.0, - steer: float = 0.0, - brake: float = 0.0, - hand_brake: bool = False, - reverse: bool = False - ) -> Dict[str, Any]: - """ - 应用车辆控制 - - Args: - vehicle_id: 车辆 ID - throttle: 油门 (0.0-1.0) - steer: 转向 (-1.0 到 1.0,负值左转,正值右转) - brake: 刹车 (0.0-1.0) - hand_brake: 手刹 - reverse: 倒车 - - Returns: - 操作结果 - """ - from ..connection import get_connection - import carla - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - # 限制参数范围 - throttle = max(0.0, min(1.0, throttle)) - steer = max(-1.0, min(1.0, steer)) - brake = max(0.0, min(1.0, brake)) - - # 创建控制对象 - control = carla.VehicleControl( - throttle=throttle, - steer=steer, - brake=brake, - hand_brake=hand_brake, - reverse=reverse - ) - - vehicle.apply_control(control) - - logger.info(f"应用车辆 {vehicle_id} 控制: throttle={throttle}, steer={steer}, brake={brake}") - return { - "success": True, - "vehicle_id": vehicle_id, - "control": { - "throttle": throttle, - "steer": steer, - "brake": brake, - "hand_brake": hand_brake, - "reverse": reverse - } - } - - except Exception as e: - logger.error(f"应用车辆控制失败: {e}") - return {"success": False, "error": str(e)} - - @mcp.tool() - def get_vehicle_state(ctx: Context, vehicle_id: int) -> Dict[str, Any]: - """ - 获取车辆状态 - - Args: - vehicle_id: 车辆 ID - - Returns: - 车辆状态信息 - """ - from ..connection import get_connection - - try: - conn = get_connection() - if not conn.is_connected(): - return {"success": False, "error": "未连接到仿真器"} - - vehicle = conn.world.get_actor(vehicle_id) - if not vehicle: - return {"success": False, "error": f"未找到车辆: {vehicle_id}"} - - transform = vehicle.get_transform() - velocity = vehicle.get_velocity() - acceleration = vehicle.get_acceleration() - angular_velocity = vehicle.get_angular_velocity() - - # 计算速度大小 (km/h) - speed = 3.6 * (velocity.x**2 + velocity.y**2 + velocity.z**2)**0.5 - - result = { - "success": True, - "vehicle_id": vehicle_id, - "type_id": vehicle.type_id, - "location": { - "x": transform.location.x, - "y": transform.location.y, - "z": transform.location.z - }, - "rotation": { - "pitch": transform.rotation.pitch, - "yaw": transform.rotation.yaw, - "roll": transform.rotation.roll - }, - "velocity": { - "x": velocity.x, - "y": velocity.y, - "z": velocity.z - }, - "speed_kmh": speed, - "acceleration": { - "x": acceleration.x, - "y": acceleration.y, - "z": acceleration.z - }, - "angular_velocity": { - "x": angular_velocity.x, - "y": angular_velocity.y, - "z": angular_velocity.z - } - } - - logger.info(f"获取车辆 {vehicle_id} 状态成功") - return result - - except Exception as e: - logger.error(f"获取车辆状态失败: {e}") - return {"success": False, "error": str(e)} - - logger.info("车辆控制工具注册完成") From cdcbc0aecf597b4a47bd5d2aaed357babc4b925a Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 11:56:05 +0800 Subject: [PATCH 8/9] Add files via upload --- sim/__init__.py | 24 +++ sim/air_tools.py | 303 ++++++++++++++++++++++++++++++++++ sim/editor_tools.py | 327 ++++++++++++++++++++++++++++++++++++ sim/sensor_tools.py | 347 ++++++++++++++++++++++++++++++++++++++ sim/vehicle_tools.py | 317 +++++++++++++++++++++++++++++++++++ sim/weather_tools.py | 385 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1703 insertions(+) create mode 100644 sim/__init__.py create mode 100644 sim/air_tools.py create mode 100644 sim/editor_tools.py create mode 100644 sim/sensor_tools.py create mode 100644 sim/vehicle_tools.py create mode 100644 sim/weather_tools.py diff --git a/sim/__init__.py b/sim/__init__.py new file mode 100644 index 0000000..3803095 --- /dev/null +++ b/sim/__init__.py @@ -0,0 +1,24 @@ +""" +HUTB MCP 工具模块 + +包含所有 MCP 工具的实现: +- vehicle_tools: 车辆控制工具 +- air_tools: 无人机控制工具 +- editor_tools: 编辑器控制工具 +- weather_tools: 天气控制工具 +- sensor_tools: 传感器管理工具 +""" + +from .vehicle_tools import register_vehicle_tools +from .air_tools import register_air_tools +from .editor_tools import register_editor_tools +from .weather_tools import register_weather_tools +from .sensor_tools import register_sensor_tools + +__all__ = [ + "register_vehicle_tools", + "register_air_tools", + "register_editor_tools", + "register_weather_tools", + "register_sensor_tools", +] diff --git a/sim/air_tools.py b/sim/air_tools.py new file mode 100644 index 0000000..a285c20 --- /dev/null +++ b/sim/air_tools.py @@ -0,0 +1,303 @@ +""" +无人机控制工具模块 + +提供无人机生成、飞行控制等功能。 +注意:HUTB/CARLA 原生不支持无人机,此模块提供基于 Walker 的模拟实现。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.air") + + +def register_air_tools(mcp: FastMCP) -> None: + """注册无人机控制工具""" + + @mcp.tool() + def spawn_drone( + ctx: Context, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 生成无人机(模拟实现) + + 注意:CARLA 原生不支持无人机,此功能使用特殊 Actor 模拟。 + + Args: + location: 生成位置 [x, y, z] + rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 使用静态道具模拟无人机 + # 在实际项目中,可以使用自定义的无人机模型 + blueprint = conn.blueprint_library.find("static.prop.box01") + + if not blueprint: + # 尝试其他可用的静态道具 + props = list(conn.blueprint_library.filter("static.prop.*")) + if props: + blueprint = props[0] + else: + return {"success": False, "error": "未找到可用的无人机蓝图"} + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成无人机 + drone = conn.world.spawn_actor(blueprint, transform) + + if drone: + conn.track_actor(drone.id) + logger.info(f"生成无人机成功: {drone.id}") + return { + "success": True, + "drone_id": drone.id, + "location": location, + "note": "无人机为模拟实现,使用静态道具代替" + } + else: + return {"success": False, "error": "生成无人机失败"} + + except Exception as e: + logger.error(f"生成无人机失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_drone_state(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 获取无人机状态 + + Args: + drone_id: 无人机 ID + + Returns: + 无人机状态信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + transform = drone.get_transform() + velocity = drone.get_velocity() + + result = { + "success": True, + "drone_id": drone_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "altitude": transform.location.z + } + + logger.info(f"获取无人机 {drone_id} 状态成功") + return result + + except Exception as e: + logger.error(f"获取无人机状态失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_drone_destination( + ctx: Context, + drone_id: int, + destination: List[float] + ) -> Dict[str, Any]: + """ + 设置无人机目标位置 + + Args: + drone_id: 无人机 ID + destination: 目标位置 [x, y, z] + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 直接设置位置(简化实现) + # 在实际项目中,应该实现平滑的飞行路径 + current_transform = drone.get_transform() + current_transform.location = carla.Location( + x=destination[0], + y=destination[1], + z=destination[2] + ) + drone.set_transform(current_transform) + + logger.info(f"设置无人机 {drone_id} 目标位置: {destination}") + return { + "success": True, + "drone_id": drone_id, + "destination": destination, + "note": "位置已直接设置,实际项目中应实现平滑飞行" + } + + except Exception as e: + logger.error(f"设置无人机目标位置失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def drone_hover(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 无人机悬停 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 静态道具默认就是悬停状态 + transform = drone.get_transform() + + logger.info(f"无人机 {drone_id} 悬停中") + return { + "success": True, + "drone_id": drone_id, + "status": "hovering", + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + } + } + + except Exception as e: + logger.error(f"无人机悬停失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def drone_land(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 无人机降落 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + # 将无人机降落到地面 + transform = drone.get_transform() + transform.location.z = 0.5 # 降落到接近地面的高度 + drone.set_transform(transform) + + logger.info(f"无人机 {drone_id} 降落成功") + return { + "success": True, + "drone_id": drone_id, + "status": "landed", + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + } + } + + except Exception as e: + logger.error(f"无人机降落失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_drone(ctx: Context, drone_id: int) -> Dict[str, Any]: + """ + 销毁无人机 + + Args: + drone_id: 无人机 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + drone = conn.world.get_actor(drone_id) + if not drone: + return {"success": False, "error": f"未找到无人机: {drone_id}"} + + drone.destroy() + conn.untrack_actor(drone_id) + + logger.info(f"销毁无人机成功: {drone_id}") + return {"success": True, "drone_id": drone_id} + + except Exception as e: + logger.error(f"销毁无人机失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("无人机控制工具注册完成") diff --git a/sim/editor_tools.py b/sim/editor_tools.py new file mode 100644 index 0000000..f28ed68 --- /dev/null +++ b/sim/editor_tools.py @@ -0,0 +1,327 @@ +""" +编辑器控制工具模块 + +提供场景编辑、Actor 管理、地图加载等功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.editor") + + +def register_editor_tools(mcp: FastMCP) -> None: + """注册编辑器控制工具""" + + @mcp.tool() + def get_actors_in_level(ctx: Context) -> List[Dict[str, Any]]: + """ + 获取当前场景中所有 Actor 的列表 + + Returns: + Actor 列表,每个 Actor 包含 id、type_id、name 等信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return [] + + actors = conn.world.get_actors() + result = [] + + for actor in actors: + result.append({ + "id": actor.id, + "type_id": actor.type_id, + "name": actor.attributes.get("role_name", ""), + "location": { + "x": actor.get_location().x, + "y": actor.get_location().y, + "z": actor.get_location().z + } + }) + + logger.info(f"获取到 {len(result)} 个 Actor") + return result + + except Exception as e: + logger.error(f"获取 Actor 列表失败: {e}") + return [] + + @mcp.tool() + def get_current_map(ctx: Context) -> str: + """ + 获取当前加载的地图名称 + + Returns: + 地图名称字符串 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return "" + + map_name = conn.map.name + logger.info(f"当前地图: {map_name}") + return map_name + + except Exception as e: + logger.error(f"获取地图名称失败: {e}") + return "" + + @mcp.tool() + def load_map(ctx: Context, map_name: str) -> Dict[str, Any]: + """ + 加载指定地图 + + Args: + map_name: 地图名称,如 "Town10" 或 "Town01" + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + logger.info(f"正在加载地图: {map_name}") + + # 清理当前资源 + conn.cleanup_all() + + # 加载新地图 + conn.client.load_world(map_name) + + # 更新世界和地图引用 + conn.world = conn.client.get_world() + conn.map = conn.world.get_map() + conn.blueprint_library = conn.world.get_blueprint_library() + + logger.info(f"地图 {map_name} 加载成功") + return {"success": True, "map": map_name} + + except Exception as e: + logger.error(f"加载地图失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def spawn_actor( + ctx: Context, + actor_type: str, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 在场景中生成 Actor + + Args: + actor_type: Actor 类型,如 "static.prop.bench" + location: 位置 [x, y, z] + rotation: 旋转 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果,包含 Actor ID + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 查找蓝图 + blueprint = conn.blueprint_library.find(actor_type) + if not blueprint: + return {"success": False, "error": f"未找到蓝图: {actor_type}"} + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成 Actor + actor = conn.world.spawn_actor(blueprint, transform) + + if actor: + conn.track_actor(actor.id) + logger.info(f"生成 Actor 成功: {actor.id} ({actor_type})") + return { + "success": True, + "actor_id": actor.id, + "type": actor_type, + "location": location + } + else: + return {"success": False, "error": "生成 Actor 失败"} + + except Exception as e: + logger.error(f"生成 Actor 失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def delete_actor(ctx: Context, actor_id: int) -> Dict[str, Any]: + """ + 删除指定 Actor + + Args: + actor_id: Actor ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + actor.destroy() + conn.untrack_actor(actor_id) + + logger.info(f"删除 Actor 成功: {actor_id}") + return {"success": True, "actor_id": actor_id} + + except Exception as e: + logger.error(f"删除 Actor 失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_actor_transform( + ctx: Context, + actor_id: int, + location: List[float] = None, + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 设置 Actor 的位置和旋转 + + Args: + actor_id: Actor ID + location: 新位置 [x, y, z] + rotation: 新旋转 [pitch, yaw, roll] + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + current_transform = actor.get_transform() + + # 更新位置 + if location: + current_transform.location = carla.Location( + x=location[0], y=location[1], z=location[2] + ) + + # 更新旋转 + if rotation: + current_transform.rotation = carla.Rotation( + pitch=rotation[0], yaw=rotation[1], roll=rotation[2] + ) + + actor.set_transform(current_transform) + + logger.info(f"设置 Actor {actor_id} 变换成功") + return { + "success": True, + "actor_id": actor_id, + "location": [ + current_transform.location.x, + current_transform.location.y, + current_transform.location.z + ], + "rotation": [ + current_transform.rotation.pitch, + current_transform.rotation.yaw, + current_transform.rotation.roll + ] + } + + except Exception as e: + logger.error(f"设置 Actor 变换失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_actor_properties(ctx: Context, actor_id: int) -> Dict[str, Any]: + """ + 获取 Actor 的属性信息 + + Args: + actor_id: Actor ID + + Returns: + Actor 属性字典 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + actor = conn.world.get_actor(actor_id) + if not actor: + return {"success": False, "error": f"未找到 Actor: {actor_id}"} + + transform = actor.get_transform() + velocity = actor.get_velocity() + + result = { + "success": True, + "id": actor.id, + "type_id": actor.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "attributes": dict(actor.attributes) + } + + logger.info(f"获取 Actor {actor_id} 属性成功") + return result + + except Exception as e: + logger.error(f"获取 Actor 属性失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("编辑器控制工具注册完成") diff --git a/sim/sensor_tools.py b/sim/sensor_tools.py new file mode 100644 index 0000000..6c7646f --- /dev/null +++ b/sim/sensor_tools.py @@ -0,0 +1,347 @@ +""" +传感器管理工具模块 + +提供相机、激光雷达、雷达等传感器的创建和管理功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.sensor") + + +def register_sensor_tools(mcp: FastMCP) -> None: + """注册传感器管理工具""" + + @mcp.tool() + def attach_camera( + ctx: Context, + vehicle_id: int, + camera_type: str = "rgb", + location: List[float] = None, + rotation: List[float] = None, + image_size_x: int = 800, + image_size_y: int = 600, + fov: float = 90.0 + ) -> Dict[str, Any]: + """ + 为车辆附加相机传感器 + + Args: + vehicle_id: 车辆 ID + camera_type: 相机类型,可选值: rgb, depth, semantic_segmentation + location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + image_size_x: 图像宽度 + image_size_y: 图像高度 + fov: 视场角 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [0, 0, 2] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 确定相机蓝图 + camera_blueprints = { + "rgb": "sensor.camera.rgb", + "depth": "sensor.camera.depth", + "semantic_segmentation": "sensor.camera.semantic_segmentation" + } + + if camera_type not in camera_blueprints: + return { + "success": False, + "error": f"未知相机类型: {camera_type},可选值: {list(camera_blueprints.keys())}" + } + + # 获取蓝图 + blueprint = conn.blueprint_library.find(camera_blueprints[camera_type]) + + # 设置属性 + blueprint.set_attribute("image_size_x", str(image_size_x)) + blueprint.set_attribute("image_size_y", str(image_size_y)) + blueprint.set_attribute("fov", str(fov)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加相机成功: {sensor.id} ({camera_type})") + return { + "success": True, + "sensor_id": sensor.id, + "type": camera_type, + "parent_id": vehicle_id, + "image_size": [image_size_x, image_size_y], + "fov": fov + } + else: + return {"success": False, "error": "附加相机失败"} + + except Exception as e: + logger.error(f"附加相机失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def attach_lidar( + ctx: Context, + vehicle_id: int, + location: List[float] = None, + rotation: List[float] = None, + channels: int = 32, + range: float = 100.0, + points_per_second: int = 56000, + rotation_frequency: float = 10.0, + upper_fov: float = 10.0, + lower_fov: float = -30.0 + ) -> Dict[str, Any]: + """ + 为车辆附加激光雷达传感器 + + Args: + vehicle_id: 车辆 ID + location: 相对于车辆的位置 [x, y, z],默认 [0, 0, 2.5] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + channels: 激光通道数 + range: 探测范围(米) + points_per_second: 每秒点数 + rotation_frequency: 旋转频率(Hz) + upper_fov: 上视场角 + lower_fov: 下视场角 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [0, 0, 2.5] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 获取蓝图 + blueprint = conn.blueprint_library.find("sensor.lidar.ray_cast") + + # 设置属性 + blueprint.set_attribute("channels", str(channels)) + blueprint.set_attribute("range", str(range)) + blueprint.set_attribute("points_per_second", str(points_per_second)) + blueprint.set_attribute("rotation_frequency", str(rotation_frequency)) + blueprint.set_attribute("upper_fov", str(upper_fov)) + blueprint.set_attribute("lower_fov", str(lower_fov)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加激光雷达成功: {sensor.id}") + return { + "success": True, + "sensor_id": sensor.id, + "type": "lidar", + "parent_id": vehicle_id, + "channels": channels, + "range": range + } + else: + return {"success": False, "error": "附加激光雷达失败"} + + except Exception as e: + logger.error(f"附加激光雷达失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def attach_radar( + ctx: Context, + vehicle_id: int, + location: List[float] = None, + rotation: List[float] = None, + horizontal_fov: float = 30.0, + vertical_fov: float = 10.0, + range: float = 100.0, + points_per_second: int = 1500 + ) -> Dict[str, Any]: + """ + 为车辆附加雷达传感器 + + Args: + vehicle_id: 车辆 ID + location: 相对于车辆的位置 [x, y, z],默认 [2, 0, 1] + rotation: 相对于车辆的旋转 [pitch, yaw, roll],默认 [0, 0, 0] + horizontal_fov: 水平视场角 + vertical_fov: 垂直视场角 + range: 探测范围(米) + points_per_second: 每秒点数 + + Returns: + 传感器信息 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + location = location or [2, 0, 1] + rotation = rotation or [0, 0, 0] + + # 获取车辆 + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 获取蓝图 + blueprint = conn.blueprint_library.find("sensor.other.radar") + + # 设置属性 + blueprint.set_attribute("horizontal_fov", str(horizontal_fov)) + blueprint.set_attribute("vertical_fov", str(vertical_fov)) + blueprint.set_attribute("range", str(range)) + blueprint.set_attribute("points_per_second", str(points_per_second)) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 附加传感器 + sensor = conn.world.spawn_actor(blueprint, transform, attach_to=vehicle) + + if sensor: + conn.track_sensor(sensor.id) + logger.info(f"附加雷达成功: {sensor.id}") + return { + "success": True, + "sensor_id": sensor.id, + "type": "radar", + "parent_id": vehicle_id, + "horizontal_fov": horizontal_fov, + "vertical_fov": vertical_fov, + "range": range + } + else: + return {"success": False, "error": "附加雷达失败"} + + except Exception as e: + logger.error(f"附加雷达失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_sensor(ctx: Context, sensor_id: int) -> Dict[str, Any]: + """ + 销毁传感器 + + Args: + sensor_id: 传感器 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + sensor = conn.world.get_actor(sensor_id) + if not sensor: + return {"success": False, "error": f"未找到传感器: {sensor_id}"} + + sensor.destroy() + conn.untrack_sensor(sensor_id) + + logger.info(f"销毁传感器成功: {sensor_id}") + return {"success": True, "sensor_id": sensor_id} + + except Exception as e: + logger.error(f"销毁传感器失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def list_vehicle_sensors(ctx: Context, vehicle_id: int) -> List[Dict[str, Any]]: + """ + 列出车辆上的所有传感器 + + Args: + vehicle_id: 车辆 ID + + Returns: + 传感器列表 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return [] + + # 获取所有传感器类型的 Actor + sensors = conn.world.get_actors().filter("sensor.*") + + result = [] + for sensor in sensors: + # 检查是否附加到指定车辆 + parent = sensor.parent + if parent and parent.id == vehicle_id: + transform = sensor.get_transform() + result.append({ + "id": sensor.id, + "type_id": sensor.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "attributes": dict(sensor.attributes) + }) + + logger.info(f"车辆 {vehicle_id} 有 {len(result)} 个传感器") + return result + + except Exception as e: + logger.error(f"列出传感器失败: {e}") + return [] + + logger.info("传感器管理工具注册完成") diff --git a/sim/vehicle_tools.py b/sim/vehicle_tools.py new file mode 100644 index 0000000..242336f --- /dev/null +++ b/sim/vehicle_tools.py @@ -0,0 +1,317 @@ +""" +车辆控制工具模块 + +提供车辆生成、控制、状态获取等功能。 +""" + +import logging +from typing import Dict, List, Any, Optional +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.vehicle") + + +def register_vehicle_tools(mcp: FastMCP) -> None: + """注册车辆控制工具""" + + @mcp.tool() + def get_vehicle_blueprints(ctx: Context) -> List[str]: + """ + 获取所有可用的车辆蓝图列表 + + Returns: + 车辆蓝图 ID 列表 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + logger.error("未连接到仿真器") + return [] + + blueprints = conn.blueprint_library.filter("vehicle.*") + result = [bp.id for bp in blueprints] + + logger.info(f"获取到 {len(result)} 个车辆蓝图") + return result + + except Exception as e: + logger.error(f"获取车辆蓝图失败: {e}") + return [] + + @mcp.tool() + def spawn_vehicle( + ctx: Context, + blueprint_id: str, + location: List[float], + rotation: List[float] = None + ) -> Dict[str, Any]: + """ + 生成车辆 + + Args: + blueprint_id: 车辆蓝图 ID,如 "vehicle.tesla.model3" + location: 生成位置 [x, y, z] + rotation: 旋转角度 [pitch, yaw, roll],默认 [0, 0, 0] + + Returns: + 生成结果,包含车辆 ID + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + rotation = rotation or [0, 0, 0] + + # 查找蓝图 + blueprint = conn.blueprint_library.find(blueprint_id) + if not blueprint: + return {"success": False, "error": f"未找到车辆蓝图: {blueprint_id}"} + + # 设置随机颜色(如果支持) + if blueprint.has_attribute("color"): + color = blueprint.get_attribute("color").recommended_values + if color: + blueprint.set_attribute("color", color[0]) + + # 创建变换 + transform = carla.Transform( + carla.Location(x=location[0], y=location[1], z=location[2]), + carla.Rotation(pitch=rotation[0], yaw=rotation[1], roll=rotation[2]) + ) + + # 生成车辆 + vehicle = conn.world.spawn_actor(blueprint, transform) + + if vehicle: + conn.track_vehicle(vehicle.id) + logger.info(f"生成车辆成功: {vehicle.id} ({blueprint_id})") + return { + "success": True, + "vehicle_id": vehicle.id, + "blueprint": blueprint_id, + "location": location + } + else: + return {"success": False, "error": "生成车辆失败,可能位置有碰撞"} + + except Exception as e: + logger.error(f"生成车辆失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def destroy_vehicle(ctx: Context, vehicle_id: int) -> Dict[str, Any]: + """ + 销毁车辆 + + Args: + vehicle_id: 车辆 ID + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + vehicle.destroy() + conn.untrack_vehicle(vehicle_id) + + logger.info(f"销毁车辆成功: {vehicle_id}") + return {"success": True, "vehicle_id": vehicle_id} + + except Exception as e: + logger.error(f"销毁车辆失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_vehicle_autopilot( + ctx: Context, + vehicle_id: int, + enabled: bool + ) -> Dict[str, Any]: + """ + 设置车辆自动驾驶模式 + + Args: + vehicle_id: 车辆 ID + enabled: 是否启用自动驾驶 + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + vehicle.set_autopilot(enabled) + + logger.info(f"设置车辆 {vehicle_id} 自动驾驶: {enabled}") + return { + "success": True, + "vehicle_id": vehicle_id, + "autopilot": enabled + } + + except Exception as e: + logger.error(f"设置自动驾驶失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def apply_vehicle_control( + ctx: Context, + vehicle_id: int, + throttle: float = 0.0, + steer: float = 0.0, + brake: float = 0.0, + hand_brake: bool = False, + reverse: bool = False + ) -> Dict[str, Any]: + """ + 应用车辆控制 + + Args: + vehicle_id: 车辆 ID + throttle: 油门 (0.0-1.0) + steer: 转向 (-1.0 到 1.0,负值左转,正值右转) + brake: 刹车 (0.0-1.0) + hand_brake: 手刹 + reverse: 倒车 + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + # 限制参数范围 + throttle = max(0.0, min(1.0, throttle)) + steer = max(-1.0, min(1.0, steer)) + brake = max(0.0, min(1.0, brake)) + + # 创建控制对象 + control = carla.VehicleControl( + throttle=throttle, + steer=steer, + brake=brake, + hand_brake=hand_brake, + reverse=reverse + ) + + vehicle.apply_control(control) + + logger.info(f"应用车辆 {vehicle_id} 控制: throttle={throttle}, steer={steer}, brake={brake}") + return { + "success": True, + "vehicle_id": vehicle_id, + "control": { + "throttle": throttle, + "steer": steer, + "brake": brake, + "hand_brake": hand_brake, + "reverse": reverse + } + } + + except Exception as e: + logger.error(f"应用车辆控制失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def get_vehicle_state(ctx: Context, vehicle_id: int) -> Dict[str, Any]: + """ + 获取车辆状态 + + Args: + vehicle_id: 车辆 ID + + Returns: + 车辆状态信息 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + vehicle = conn.world.get_actor(vehicle_id) + if not vehicle: + return {"success": False, "error": f"未找到车辆: {vehicle_id}"} + + transform = vehicle.get_transform() + velocity = vehicle.get_velocity() + acceleration = vehicle.get_acceleration() + angular_velocity = vehicle.get_angular_velocity() + + # 计算速度大小 (km/h) + speed = 3.6 * (velocity.x**2 + velocity.y**2 + velocity.z**2)**0.5 + + result = { + "success": True, + "vehicle_id": vehicle_id, + "type_id": vehicle.type_id, + "location": { + "x": transform.location.x, + "y": transform.location.y, + "z": transform.location.z + }, + "rotation": { + "pitch": transform.rotation.pitch, + "yaw": transform.rotation.yaw, + "roll": transform.rotation.roll + }, + "velocity": { + "x": velocity.x, + "y": velocity.y, + "z": velocity.z + }, + "speed_kmh": speed, + "acceleration": { + "x": acceleration.x, + "y": acceleration.y, + "z": acceleration.z + }, + "angular_velocity": { + "x": angular_velocity.x, + "y": angular_velocity.y, + "z": angular_velocity.z + } + } + + logger.info(f"获取车辆 {vehicle_id} 状态成功") + return result + + except Exception as e: + logger.error(f"获取车辆状态失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("车辆控制工具注册完成") diff --git a/sim/weather_tools.py b/sim/weather_tools.py new file mode 100644 index 0000000..e96d69b --- /dev/null +++ b/sim/weather_tools.py @@ -0,0 +1,385 @@ +""" +天气控制工具模块 + +提供天气参数设置、预设天气、时间控制等功能。 +""" + +import logging +from typing import Dict, Any +from mcp.server.fastmcp import FastMCP, Context + +logger = logging.getLogger("hutb_mcp.tools.weather") + +# 天气预设 +WEATHER_PRESETS = { + "clear": { + "cloudiness": 0.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 0.0, + "sun_altitude_angle": 70.0, + "fog_density": 0.0, + "wetness": 0.0 + }, + "cloudy": { + "cloudiness": 80.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 20.0, + "sun_altitude_angle": 45.0, + "fog_density": 0.0, + "wetness": 0.0 + }, + "rainy": { + "cloudiness": 100.0, + "precipitation": 80.0, + "precipitation_deposits": 50.0, + "wind_intensity": 30.0, + "sun_altitude_angle": 30.0, + "fog_density": 10.0, + "wetness": 80.0 + }, + "foggy": { + "cloudiness": 50.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 10.0, + "sun_altitude_angle": 30.0, + "fog_density": 80.0, + "wetness": 20.0 + }, + "stormy": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 10.0, + "fog_density": 30.0, + "wetness": 100.0 + } +} + + +def register_weather_tools(mcp: FastMCP) -> None: + """注册天气控制工具""" + + @mcp.tool() + def get_weather(ctx: Context) -> Dict[str, Any]: + """ + 获取当前天气参数 + + Returns: + 天气参数字典 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + weather = conn.world.get_weather() + + result = { + "success": True, + "cloudiness": weather.cloudiness, + "precipitation": weather.precipitation, + "precipitation_deposits": weather.precipitation_deposits, + "wind_intensity": weather.wind_intensity, + "sun_azimuth_angle": weather.sun_azimuth_angle, + "sun_altitude_angle": weather.sun_altitude_angle, + "fog_density": weather.fog_density, + "fog_distance": weather.fog_distance, + "wetness": weather.wetness, + "fog_falloff": weather.fog_falloff, + "scattering_intensity": weather.scattering_intensity, + "mie_scattering_scale": weather.mie_scattering_scale, + "rayleigh_scattering_scale": weather.rayleigh_scattering_scale + } + + logger.info("获取天气参数成功") + return result + + except Exception as e: + logger.error(f"获取天气参数失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_weather( + ctx: Context, + cloudiness: float = None, + precipitation: float = None, + precipitation_deposits: float = None, + wind_intensity: float = None, + sun_azimuth_angle: float = None, + sun_altitude_angle: float = None, + fog_density: float = None, + fog_distance: float = None, + wetness: float = None + ) -> Dict[str, Any]: + """ + 设置天气参数 + + Args: + cloudiness: 云量 (0-100) + precipitation: 降水量 (0-100) + precipitation_deposits: 降水沉积 (0-100) + wind_intensity: 风力强度 (0-100) + sun_azimuth_angle: 太阳方位角 (0-360) + sun_altitude_angle: 太阳高度角 (-90 到 90) + fog_density: 雾密度 (0-100) + fog_distance: 雾距离 + wetness: 湿度 (0-100) + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + # 获取当前天气 + weather = conn.world.get_weather() + + # 更新指定参数(带范围限制) + if cloudiness is not None: + weather.cloudiness = max(0.0, min(100.0, cloudiness)) + if precipitation is not None: + weather.precipitation = max(0.0, min(100.0, precipitation)) + if precipitation_deposits is not None: + weather.precipitation_deposits = max(0.0, min(100.0, precipitation_deposits)) + if wind_intensity is not None: + weather.wind_intensity = max(0.0, min(100.0, wind_intensity)) + if sun_azimuth_angle is not None: + weather.sun_azimuth_angle = sun_azimuth_angle % 360.0 + if sun_altitude_angle is not None: + weather.sun_altitude_angle = max(-90.0, min(90.0, sun_altitude_angle)) + if fog_density is not None: + weather.fog_density = max(0.0, min(100.0, fog_density)) + if fog_distance is not None: + weather.fog_distance = max(0.0, fog_distance) + if wetness is not None: + weather.wetness = max(0.0, min(100.0, wetness)) + + # 应用天气 + conn.world.set_weather(weather) + + logger.info("设置天气参数成功") + return { + "success": True, + "cloudiness": weather.cloudiness, + "precipitation": weather.precipitation, + "sun_altitude_angle": weather.sun_altitude_angle, + "fog_density": weather.fog_density, + "wetness": weather.wetness + } + + except Exception as e: + logger.error(f"设置天气参数失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_weather_preset(ctx: Context, preset: str) -> Dict[str, Any]: + """ + 应用天气预设 + + Args: + preset: 预设名称,可选值: clear, cloudy, rainy, foggy, stormy + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + preset = preset.lower() + if preset not in WEATHER_PRESETS: + return { + "success": False, + "error": f"未知预设: {preset},可选值: {list(WEATHER_PRESETS.keys())}" + } + + params = WEATHER_PRESETS[preset] + + # 创建天气对象 + weather = carla.WeatherParameters( + cloudiness=params["cloudiness"], + precipitation=params["precipitation"], + precipitation_deposits=params["precipitation_deposits"], + wind_intensity=params["wind_intensity"], + sun_altitude_angle=params["sun_altitude_angle"], + fog_density=params["fog_density"], + wetness=params["wetness"] + ) + + conn.world.set_weather(weather) + + logger.info(f"应用天气预设成功: {preset}") + return { + "success": True, + "preset": preset, + "parameters": params + } + + except Exception as e: + logger.error(f"应用天气预设失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_time_of_day(ctx: Context, hour: int, minute: int = 0) -> Dict[str, Any]: + """ + 设置一天中的时间(通过调整太阳位置) + + Args: + hour: 小时 (0-23) + minute: 分钟 (0-59) + + Returns: + 操作结果 + """ + from ..connection import get_connection + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + # 限制范围 + hour = max(0, min(23, hour)) + minute = max(0, min(59, minute)) + + # 计算太阳高度角 + # 简化模型:6:00 日出 (0°), 12:00 正午 (90°), 18:00 日落 (0°) + time_decimal = hour + minute / 60.0 + + if 6 <= time_decimal <= 18: + # 白天 + sun_altitude = 90.0 * (1 - abs(time_decimal - 12) / 6) + else: + # 夜晚 + sun_altitude = -45.0 + + # 计算太阳方位角 + sun_azimuth = (time_decimal - 6) * 15 # 每小时15度 + + weather = conn.world.get_weather() + weather.sun_altitude_angle = sun_altitude + weather.sun_azimuth_angle = sun_azimuth % 360 + conn.world.set_weather(weather) + + logger.info(f"设置时间成功: {hour:02d}:{minute:02d}") + return { + "success": True, + "time": f"{hour:02d}:{minute:02d}", + "sun_altitude_angle": sun_altitude, + "sun_azimuth_angle": sun_azimuth % 360 + } + + except Exception as e: + logger.error(f"设置时间失败: {e}") + return {"success": False, "error": str(e)} + + @mcp.tool() + def set_extreme_weather(ctx: Context, weather_type: str) -> Dict[str, Any]: + """ + 设置极端天气(用于压力测试) + + Args: + weather_type: 极端天气类型,可选值: + - heavy_rain: 暴雨 + - dense_fog: 浓雾 + - blizzard: 暴风雪 + - sandstorm: 沙尘暴 + + Returns: + 操作结果 + """ + from ..connection import get_connection + import carla + + extreme_presets = { + "heavy_rain": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 80.0, + "sun_altitude_angle": 5.0, + "fog_density": 20.0, + "wetness": 100.0 + }, + "dense_fog": { + "cloudiness": 100.0, + "precipitation": 0.0, + "precipitation_deposits": 0.0, + "wind_intensity": 5.0, + "sun_altitude_angle": 20.0, + "fog_density": 100.0, + "wetness": 50.0 + }, + "blizzard": { + "cloudiness": 100.0, + "precipitation": 100.0, + "precipitation_deposits": 100.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 10.0, + "fog_density": 50.0, + "wetness": 0.0 + }, + "sandstorm": { + "cloudiness": 80.0, + "precipitation": 0.0, + "precipitation_deposits": 50.0, + "wind_intensity": 100.0, + "sun_altitude_angle": 30.0, + "fog_density": 70.0, + "wetness": 0.0 + } + } + + try: + conn = get_connection() + if not conn.is_connected(): + return {"success": False, "error": "未连接到仿真器"} + + weather_type = weather_type.lower() + if weather_type not in extreme_presets: + return { + "success": False, + "error": f"未知极端天气类型: {weather_type},可选值: {list(extreme_presets.keys())}" + } + + params = extreme_presets[weather_type] + + weather = carla.WeatherParameters( + cloudiness=params["cloudiness"], + precipitation=params["precipitation"], + precipitation_deposits=params["precipitation_deposits"], + wind_intensity=params["wind_intensity"], + sun_altitude_angle=params["sun_altitude_angle"], + fog_density=params["fog_density"], + wetness=params["wetness"] + ) + + conn.world.set_weather(weather) + + logger.info(f"设置极端天气成功: {weather_type}") + return { + "success": True, + "weather_type": weather_type, + "parameters": params + } + + except Exception as e: + logger.error(f"设置极端天气失败: {e}") + return {"success": False, "error": str(e)} + + logger.info("天气控制工具注册完成") From b3be9b92e04f6874225014f4c2e37730c93bd50b Mon Sep 17 00:00:00 2001 From: xuyangyang <1220249771@qq.com> Date: Tue, 16 Jun 2026 12:03:57 +0800 Subject: [PATCH 9/9] Add files via upload --- sim/pyproject.toml | 61 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/sim/pyproject.toml b/sim/pyproject.toml index a20df47..5005503 100644 --- a/sim/pyproject.toml +++ b/sim/pyproject.toml @@ -1,10 +1,63 @@ [project] -name = "carla-mcp" +name = "hutb-mcp" version = "0.1.0" -description = "Add your description here" +description = "基于 FastMCP 的 HUTB 人车仿真器交互控制系统" readme = "README.md" requires-python = ">=3.10" +license = {text = "MIT"} +authors = [ + {name = "徐杨杨", email = "xuyangyang@hutb.edu.cn"} +] +keywords = ["mcp", "carla", "simulator", "fastmcp", "autonomous-driving"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + dependencies = [ - "carla>=0.9.15", - "mcp>=1.6.0", + "mcp[cli]>=1.4.1", + "python-dotenv>=1.0.0", + "pydantic>=2.0.0", +] + +# 注意: carla 需要从本地 wheel 文件安装 +# pip install D:/hutb/PythonAPI/carla/dist/hutb-2.9.16-cp310-cp310-win_amd64.whl + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-asyncio>=0.21.0", + "hypothesis>=6.0.0", + "black>=23.0.0", + "isort>=5.0.0", + "mypy>=1.0.0", ] + +[project.scripts] +hutb-mcp = "hutb_mcp:main" + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +include = ["hutb_mcp*"] + +[tool.black] +line-length = 100 +target-version = ['py310'] + +[tool.isort] +profile = "black" +line_length = 100 + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true