diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..9a76a89 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 505a3b1..0b60d08 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,5 @@ build/ dist/ wheels/ *.egg-info - # Virtual environments .venv diff --git a/README.md b/README.md index e69de29..4289a71 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,176 @@ +# Origincar Simulation + +基于 MotrixSim 的阿克曼小车仿真,支持键盘控制和 HTTP API 局域网共享。 + +## 启动 + +```bash +uv run mxpython main.py +``` + +## HTTP API(端口 8765) + +仿真启动后,局域网内其他设备可通过 HTTP 获取摄像头图像、IMU 数据和发送控制指令。 + +### GET `/image` + +获取前置摄像头最新 JPEG 图像。浏览器可直接打开。 + +```bash +# 浏览器 +http://<仿真机IP>:8765/image + +# curl +curl http://192.168.1.100:8765/image -o frame.jpg + +# Python +import requests +r = requests.get("http://192.168.1.100:8765/image") +with open("frame.jpg", "wb") as f: + f.write(r.content) +``` + +### GET `/state` + +获取小车当前状态(JSON)。 + +```bash +curl http://192.168.1.100:8765/state +``` + +返回示例: + +```json +{ + "x": -2.0, + "y": -2.3, + "z": 0.08, + "yaw_deg": 0.0, + "speed_target": 1.0, + "speed_actual": 0.98, + "steer_target": 0.0, + "speed_amp": 1.0, + "steer_amp": 0.35 +} +``` + +### GET `/imu` + +获取 IMU 数据(含 MPU6050 噪声),JSON。 + +```bash +curl http://192.168.1.100:8765/imu +``` + +返回示例: + +```json +{ + "orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0}, + "angular_velocity": {"x": 0.001, "y": -0.003, "z": 0.008}, + "linear_acceleration": {"x": 0.02, "y": -0.01, "z": 9.81} +} +``` + +噪声参数(MPU6050 级别): +- 陀螺仪白噪声 σ = 0.01 rad/s,bias 随机游走 +- 加速度计白噪声 σ = 0.05 m/s²,bias 随机游走 + +### POST `/cmd` + +发送控制指令(JSON)。 + +```bash +curl -X POST http://192.168.1.100:8765/cmd \ + -H "Content-Type: application/json" \ + -d '{"speed": 1.0, "steer": 0.3}' +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `speed` | float | 目标速度 m/s,正=前进,负=后退 | +| `steer` | float | 目标转向角 rad,正=左转,负=右转 | + +**Python 客户端示例**: + +```python +import time +import requests + +IP = "192.168.1.100" + +# 获取图像 +r = requests.get(f"http://{IP}:8765/image") +with open("frame.jpg", "wb") as f: + f.write(r.content) + +# 获取状态 +state = requests.get(f"http://{IP}:8765/state").json() +print(f"位置: ({state['x']}, {state['y']}), 速度: {state['speed_actual']} m/s") + +# 发送控制指令 +requests.post(f"http://{IP}:8765/cmd", json={"speed": 1.0, "steer": 0.2}) +time.sleep(2) +requests.post(f"http://{IP}:8765/cmd", json={"speed": 0.0, "steer": 0.0}) +``` + +**ROS2 桥接示例**: + +```python +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Image as RosImage +from geometry_msgs.msg import Twist +from cv_bridge import CvBridge +import requests +import numpy as np +import cv2 + +IP = "192.168.1.100" + +class OrigincarBridge(Node): + def __init__(self): + super().__init__("origincar_bridge") + self.bridge = CvBridge() + self.pub = self.create_publisher(RosImage, "/image", 10) + self.sub = self.create_subscription(Twist, "/cmd_vel", self.cmd_cb, 10) + self.timer = self.create_timer(0.1, self.fetch_and_publish) + + def fetch_and_publish(self): + r = requests.get(f"http://{IP}:8765/image", timeout=1) + if r.status_code == 200: + arr = np.frombuffer(r.content, np.uint8) + cv_img = cv2.imdecode(arr, cv2.IMREAD_COLOR) + msg = self.bridge.cv2_to_imgmsg(cv_img, "bgr8") + self.pub.publish(msg) + + def cmd_cb(self, msg: Twist): + requests.post(f"http://{IP}:8765/cmd", json={ + "speed": msg.linear.x, + "steer": msg.angular.z + }) + +rclpy.init() +node = OrigincarBridge() +rclpy.spin(node) +``` + +## 键盘控制 + +| 按键 | 方式 | 作用 | +|------|------|------| +| W/S | 按住 | 前进/后退 | +| A/D | 按住 | 左转/右转 | +| ↑/↓ | 点按 | 速度幅度 ±0.2 m/s(0~2.0) | +| ←/→ | 点按 | 转向幅度 ±0.05 rad(0~0.5) | +| Space | 点按 | 截图保存到 `captures/` | + +键盘与 HTTP `/cmd` 可同时使用:按住 W/A/S/D 时键盘优先,松开后自动切回 HTTP 控制。 + +## 场景结构 + +``` +scene.xml — 5m×5m 地图 + 40cm 白色围栏 + 阿克曼小车 +main.py — 仿真控制 + HTTP API 服务 +origincar.urdf / origincar.xacro — 小车模型(参考) +``` diff --git a/captures/front_cam_0000.png b/captures/front_cam_0000.png new file mode 100644 index 0000000..bb50467 Binary files /dev/null and b/captures/front_cam_0000.png differ diff --git a/cone.obj b/cone.obj new file mode 100644 index 0000000..987a4dc --- /dev/null +++ b/cone.obj @@ -0,0 +1,219 @@ +# Traffic cone +v 0.100000 0.000000 0.000000 +v 0.098481 0.017365 0.000000 +v 0.093969 0.034202 0.000000 +v 0.086603 0.050000 0.000000 +v 0.076604 0.064279 0.000000 +v 0.064279 0.076604 0.000000 +v 0.050000 0.086603 0.000000 +v 0.034202 0.093969 0.000000 +v 0.017365 0.098481 0.000000 +v 0.000000 0.100000 0.000000 +v -0.017365 0.098481 0.000000 +v -0.034202 0.093969 0.000000 +v -0.050000 0.086603 0.000000 +v -0.064279 0.076604 0.000000 +v -0.076604 0.064279 0.000000 +v -0.086603 0.050000 0.000000 +v -0.093969 0.034202 0.000000 +v -0.098481 0.017365 0.000000 +v -0.100000 0.000000 0.000000 +v -0.098481 -0.017365 0.000000 +v -0.093969 -0.034202 0.000000 +v -0.086603 -0.050000 0.000000 +v -0.076604 -0.064279 0.000000 +v -0.064279 -0.076604 0.000000 +v -0.050000 -0.086603 0.000000 +v -0.034202 -0.093969 0.000000 +v -0.017365 -0.098481 0.000000 +v -0.000000 -0.100000 0.000000 +v 0.017365 -0.098481 0.000000 +v 0.034202 -0.093969 0.000000 +v 0.050000 -0.086603 0.000000 +v 0.064279 -0.076604 0.000000 +v 0.076604 -0.064279 0.000000 +v 0.086603 -0.050000 0.000000 +v 0.093969 -0.034202 0.000000 +v 0.098481 -0.017365 0.000000 +v 0.025000 0.000000 0.270000 +v 0.024620 0.004341 0.270000 +v 0.023492 0.008551 0.270000 +v 0.021651 0.012500 0.270000 +v 0.019151 0.016070 0.270000 +v 0.016070 0.019151 0.270000 +v 0.012500 0.021651 0.270000 +v 0.008551 0.023492 0.270000 +v 0.004341 0.024620 0.270000 +v 0.000000 0.025000 0.270000 +v -0.004341 0.024620 0.270000 +v -0.008551 0.023492 0.270000 +v -0.012500 0.021651 0.270000 +v -0.016070 0.019151 0.270000 +v -0.019151 0.016070 0.270000 +v -0.021651 0.012500 0.270000 +v -0.023492 0.008551 0.270000 +v -0.024620 0.004341 0.270000 +v -0.025000 0.000000 0.270000 +v -0.024620 -0.004341 0.270000 +v -0.023492 -0.008551 0.270000 +v -0.021651 -0.012500 0.270000 +v -0.019151 -0.016070 0.270000 +v -0.016070 -0.019151 0.270000 +v -0.012500 -0.021651 0.270000 +v -0.008551 -0.023492 0.270000 +v -0.004341 -0.024620 0.270000 +v -0.000000 -0.025000 0.270000 +v 0.004341 -0.024620 0.270000 +v 0.008551 -0.023492 0.270000 +v 0.012500 -0.021651 0.270000 +v 0.016070 -0.019151 0.270000 +v 0.019151 -0.016070 0.270000 +v 0.021651 -0.012500 0.270000 +v 0.023492 -0.008551 0.270000 +v 0.024620 -0.004341 0.270000 +v 0.000000 0.000000 0.000000 +v 0.000000 0.000000 0.270000 +f 1 2 38 +f 1 38 37 +f 2 3 39 +f 2 39 38 +f 3 4 40 +f 3 40 39 +f 4 5 41 +f 4 41 40 +f 5 6 42 +f 5 42 41 +f 6 7 43 +f 6 43 42 +f 7 8 44 +f 7 44 43 +f 8 9 45 +f 8 45 44 +f 9 10 46 +f 9 46 45 +f 10 11 47 +f 10 47 46 +f 11 12 48 +f 11 48 47 +f 12 13 49 +f 12 49 48 +f 13 14 50 +f 13 50 49 +f 14 15 51 +f 14 51 50 +f 15 16 52 +f 15 52 51 +f 16 17 53 +f 16 53 52 +f 17 18 54 +f 17 54 53 +f 18 19 55 +f 18 55 54 +f 19 20 56 +f 19 56 55 +f 20 21 57 +f 20 57 56 +f 21 22 58 +f 21 58 57 +f 22 23 59 +f 22 59 58 +f 23 24 60 +f 23 60 59 +f 24 25 61 +f 24 61 60 +f 25 26 62 +f 25 62 61 +f 26 27 63 +f 26 63 62 +f 27 28 64 +f 27 64 63 +f 28 29 65 +f 28 65 64 +f 29 30 66 +f 29 66 65 +f 30 31 67 +f 30 67 66 +f 31 32 68 +f 31 68 67 +f 32 33 69 +f 32 69 68 +f 33 34 70 +f 33 70 69 +f 34 35 71 +f 34 71 70 +f 35 36 72 +f 35 72 71 +f 36 1 37 +f 36 37 72 +f 73 2 1 +f 73 3 2 +f 73 4 3 +f 73 5 4 +f 73 6 5 +f 73 7 6 +f 73 8 7 +f 73 9 8 +f 73 10 9 +f 73 11 10 +f 73 12 11 +f 73 13 12 +f 73 14 13 +f 73 15 14 +f 73 16 15 +f 73 17 16 +f 73 18 17 +f 73 19 18 +f 73 20 19 +f 73 21 20 +f 73 22 21 +f 73 23 22 +f 73 24 23 +f 73 25 24 +f 73 26 25 +f 73 27 26 +f 73 28 27 +f 73 29 28 +f 73 30 29 +f 73 31 30 +f 73 32 31 +f 73 33 32 +f 73 34 33 +f 73 35 34 +f 73 36 35 +f 73 1 36 +f 74 37 38 +f 74 38 39 +f 74 39 40 +f 74 40 41 +f 74 41 42 +f 74 42 43 +f 74 43 44 +f 74 44 45 +f 74 45 46 +f 74 46 47 +f 74 47 48 +f 74 48 49 +f 74 49 50 +f 74 50 51 +f 74 51 52 +f 74 52 53 +f 74 53 54 +f 74 54 55 +f 74 55 56 +f 74 56 57 +f 74 57 58 +f 74 58 59 +f 74 59 60 +f 74 60 61 +f 74 61 62 +f 74 62 63 +f 74 63 64 +f 74 64 65 +f 74 65 66 +f 74 66 67 +f 74 67 68 +f 74 68 69 +f 74 69 70 +f 74 70 71 +f 74 71 72 +f 74 72 37 diff --git a/cone_white.obj b/cone_white.obj new file mode 100644 index 0000000..5006104 --- /dev/null +++ b/cone_white.obj @@ -0,0 +1,145 @@ +# White band +v 0.066700 0.000000 0.000000 +v 0.065687 0.011582 0.000000 +v 0.062677 0.022813 0.000000 +v 0.057764 0.033350 0.000000 +v 0.051095 0.042874 0.000000 +v 0.042874 0.051095 0.000000 +v 0.033350 0.057764 0.000000 +v 0.022813 0.062677 0.000000 +v 0.011582 0.065687 0.000000 +v 0.000000 0.066700 0.000000 +v -0.011582 0.065687 0.000000 +v -0.022813 0.062677 0.000000 +v -0.033350 0.057764 0.000000 +v -0.042874 0.051095 0.000000 +v -0.051095 0.042874 0.000000 +v -0.057764 0.033350 0.000000 +v -0.062677 0.022813 0.000000 +v -0.065687 0.011582 0.000000 +v -0.066700 0.000000 0.000000 +v -0.065687 -0.011582 0.000000 +v -0.062677 -0.022813 0.000000 +v -0.057764 -0.033350 0.000000 +v -0.051095 -0.042874 0.000000 +v -0.042874 -0.051095 0.000000 +v -0.033350 -0.057764 0.000000 +v -0.022813 -0.062677 0.000000 +v -0.011582 -0.065687 0.000000 +v -0.000000 -0.066700 0.000000 +v 0.011582 -0.065687 0.000000 +v 0.022813 -0.062677 0.000000 +v 0.033350 -0.057764 0.000000 +v 0.042874 -0.051095 0.000000 +v 0.051095 -0.042874 0.000000 +v 0.057764 -0.033350 0.000000 +v 0.062677 -0.022813 0.000000 +v 0.065687 -0.011582 0.000000 +v 0.038900 0.000000 0.100000 +v 0.038309 0.006755 0.100000 +v 0.036554 0.013305 0.100000 +v 0.033688 0.019450 0.100000 +v 0.029799 0.025004 0.100000 +v 0.025004 0.029799 0.100000 +v 0.019450 0.033688 0.100000 +v 0.013305 0.036554 0.100000 +v 0.006755 0.038309 0.100000 +v 0.000000 0.038900 0.100000 +v -0.006755 0.038309 0.100000 +v -0.013305 0.036554 0.100000 +v -0.019450 0.033688 0.100000 +v -0.025004 0.029799 0.100000 +v -0.029799 0.025004 0.100000 +v -0.033688 0.019450 0.100000 +v -0.036554 0.013305 0.100000 +v -0.038309 0.006755 0.100000 +v -0.038900 0.000000 0.100000 +v -0.038309 -0.006755 0.100000 +v -0.036554 -0.013305 0.100000 +v -0.033688 -0.019450 0.100000 +v -0.029799 -0.025004 0.100000 +v -0.025004 -0.029799 0.100000 +v -0.019450 -0.033688 0.100000 +v -0.013305 -0.036554 0.100000 +v -0.006755 -0.038309 0.100000 +v -0.000000 -0.038900 0.100000 +v 0.006755 -0.038309 0.100000 +v 0.013305 -0.036554 0.100000 +v 0.019450 -0.033688 0.100000 +v 0.025004 -0.029799 0.100000 +v 0.029799 -0.025004 0.100000 +v 0.033688 -0.019450 0.100000 +v 0.036554 -0.013305 0.100000 +v 0.038309 -0.006755 0.100000 +f 1 2 38 +f 1 38 37 +f 2 3 39 +f 2 39 38 +f 3 4 40 +f 3 40 39 +f 4 5 41 +f 4 41 40 +f 5 6 42 +f 5 42 41 +f 6 7 43 +f 6 43 42 +f 7 8 44 +f 7 44 43 +f 8 9 45 +f 8 45 44 +f 9 10 46 +f 9 46 45 +f 10 11 47 +f 10 47 46 +f 11 12 48 +f 11 48 47 +f 12 13 49 +f 12 49 48 +f 13 14 50 +f 13 50 49 +f 14 15 51 +f 14 51 50 +f 15 16 52 +f 15 52 51 +f 16 17 53 +f 16 53 52 +f 17 18 54 +f 17 54 53 +f 18 19 55 +f 18 55 54 +f 19 20 56 +f 19 56 55 +f 20 21 57 +f 20 57 56 +f 21 22 58 +f 21 58 57 +f 22 23 59 +f 22 59 58 +f 23 24 60 +f 23 60 59 +f 24 25 61 +f 24 61 60 +f 25 26 62 +f 25 62 61 +f 26 27 63 +f 26 63 62 +f 27 28 64 +f 27 64 63 +f 28 29 65 +f 28 65 64 +f 29 30 66 +f 29 66 65 +f 30 31 67 +f 30 67 66 +f 31 32 68 +f 31 68 67 +f 32 33 69 +f 32 69 68 +f 33 34 70 +f 33 70 69 +f 34 35 71 +f 34 71 70 +f 35 36 72 +f 35 72 71 +f 36 1 37 +f 36 37 72 diff --git a/main.py b/main.py index 72fddb5..08430d9 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,372 @@ +import io +import json +import math +import os +import random +import threading +from collections import deque +from http.server import BaseHTTPRequestHandler, HTTPServer + +import numpy as np +from PIL import Image as PILImage + +from motrixsim import SceneData, load_model, run, step +from motrixsim.render import CaptureTask, Layout, RenderApp + +# ── Ackermann geometry ────────────────────────────────────────────── +L = 0.1682 # wheelbase: front-rear axle distance +T = 0.189 # track width: left-right wheel distance + +PORT = 8765 + +# ── MPU6050 noise parameters ──────────────────────────────────────── +GYRO_NOISE_STD = 0.01 # rad/s +ACCEL_NOISE_STD = 0.05 # m/s² +GYRO_BIAS_WALK = 1e-5 # rad/s per step +ACCEL_BIAS_WALK = 1e-4 # m/s² per step + + +def ackermann_angles(steer_cmd_rad): + if abs(steer_cmd_rad) < 1e-6: + return 0.0, 0.0 + R = L / math.tan(abs(steer_cmd_rad)) + inner = math.atan(L / (R - T / 2)) + outer = math.atan(L / (R + T / 2)) + if steer_cmd_rad > 0: + return outer, inner + else: + return -inner, -outer + + +def quat_to_body_rotation(qw, qx, qy, qz): + """World-frame vector → body-frame vector using quaternion.""" + v = np.array + R = np.array([ + [1 - 2*(qy*qy + qz*qz), 2*(qx*qy + qw*qz), 2*(qx*qz - qw*qy)], + [ 2*(qx*qy - qw*qz), 1 - 2*(qx*qx + qz*qz), 2*(qy*qz + qw*qx)], + [ 2*(qx*qz + qw*qy), 2*(qy*qz - qw*qx), 1 - 2*(qx*qx + qy*qy)], + ]) + return R + + +class MPU6050Sim: + def __init__(self): + self.gyro_bias = np.zeros(3) + self.accel_bias = np.zeros(3) + + def apply(self, gyro_true, accel_true): + self.gyro_bias += np.random.normal(0, GYRO_BIAS_WALK, 3) + self.accel_bias += np.random.normal(0, ACCEL_BIAS_WALK, 3) + gyro = gyro_true + self.gyro_bias + np.random.normal(0, GYRO_NOISE_STD, 3) + accel = accel_true + self.accel_bias + np.random.normal(0, ACCEL_NOISE_STD, 3) + return gyro, accel + + +# ── Shared state (main ↔ HTTP thread) ────────────────────────────── +shared = { + "latest_jpg": None, # bytes + "cmd_speed": 0.0, # from POST /cmd + "cmd_steer": 0.0, # from POST /cmd + "imu_json": b"{}", + "state_json": b"{}", + "key_active": False, # True when W/A/S/D pressed +} +shared_lock = threading.Lock() + + +class HTTPHandler(BaseHTTPRequestHandler): + def log_message(self, format, *args): + pass # suppress logs + + def _send_json(self, data): + body = json.dumps(data).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def _send_jpg(self, jpg_bytes): + self.send_response(200) + self.send_header("Content-Type", "image/jpeg") + self.send_header("Content-Length", str(len(jpg_bytes))) + self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") + self.end_headers() + self.wfile.write(jpg_bytes) + + def do_GET(self): + if self.path == "/image": + with shared_lock: + jpg = shared["latest_jpg"] + if jpg: + self._send_jpg(jpg) + else: + self.send_response(204) + self.end_headers() + elif self.path == "/state": + with shared_lock: + body = shared["state_json"] + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + elif self.path == "/imu": + with shared_lock: + body = shared["imu_json"] + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + else: + self.send_response(404) + self.end_headers() + + def do_POST(self): + if self.path == "/cmd": + length = int(self.headers.get("Content-Length", 0)) + body = json.loads(self.rfile.read(length)) + with shared_lock: + shared["cmd_speed"] = float(body.get("speed", 0)) + shared["cmd_steer"] = float(body.get("steer", 0)) + self.send_response(200) + self.end_headers() + else: + self.send_response(404) + self.end_headers() + + +def start_http_server(): + server = HTTPServer(("0.0.0.0", PORT), HTTPHandler) + server.serve_forever() + + +# ── Main ──────────────────────────────────────────────────────────── def main(): - print("Hello from motrixsim-examples!") + # Start HTTP server in background thread + http_thread = threading.Thread(target=start_http_server, daemon=True) + http_thread.start() + + with RenderApp() as render: + render.opt.set_left_panel_vis(True) + + model = load_model("scene.xml") + + cameras = model.cameras + front_cam = cameras[0] + front_cam.set_render_target("image", 1280, 960) + + render.launch(model) + render.widgets.create_camera_viewport( + front_cam, layout=Layout(right=0, top=0, width=480, height=360) + ) + + data = SceneData(model) + init_pos = data.dof_pos.copy() + init_pos[0] = -2.0 + init_pos[1] = -2.3 + init_pos[5] = 0.0 + init_pos[6] = 1.0 + data.set_dof_pos(init_pos, model) + + # Drive actuators + drive_dl = model.get_actuator("down_left_drive") + drive_dr = model.get_actuator("down_right_drive") + drive_ul = model.get_actuator("up_left_drive") + drive_ur = model.get_actuator("up_right_drive") + + # Steering actuators + steer_ul = model.get_actuator("up_left_steer") + steer_ur = model.get_actuator("up_right_steer") + + speed = 0.0 + steer_cmd = 0.0 + speed_amp = 1.0 + steer_amp = 0.35 + frame = 0 + + base_link = model.get_link("base_link") + + # IMU state + imu = MPU6050Sim() + prev_vel_world = np.zeros(3) + timestep = float(model.options.timestep) + + # Continuous image capture + capture_task = None + capture_tasks: deque[tuple[int, CaptureTask]] = deque() + capture_index = 0 + + print("=" * 50) + print("Origincar — HTTP API on port", PORT) + print(" http://:{}/image — camera JPEG".format(PORT)) + print(" http://:{}/state — position & speed".format(PORT)) + print(" http://:{}/imu — IMU (MPU6050 noise)".format(PORT)) + print(" POST /cmd {\"speed\":1.0,\"steer\":0.3}") + print("=" * 50) + + def phys_step(): + nonlocal prev_vel_world + + # ── P-controller ── + vel = base_link.get_linear_velocity(data) + pose = base_link.get_pose(data) + qw, qx, qy, qz = pose[3], pose[4], pose[5], pose[6] + fwd_x = 1 - 2 * (qy * qy + qz * qz) + fwd_y = 2 * (qx * qy + qw * qz) + actual_speed = vel[0] * fwd_x + vel[1] * fwd_y + error = speed - actual_speed + kp = 5.0 + base_torque = max(-15.0, min(15.0, kp * error)) + + # ── Differential ── + if abs(steer_cmd) < 1e-6: + factor_l, factor_r = 1.0, 1.0 + else: + R_turn = L / math.tan(abs(steer_cmd)) + outer_f = (R_turn + T / 2) / R_turn + inner_f = (R_turn - T / 2) / R_turn + if steer_cmd > 0: + factor_l, factor_r = inner_f, outer_f + else: + factor_l, factor_r = outer_f, inner_f + + tq_l = max(-15.0, min(15.0, base_torque * factor_l)) + tq_r = max(-15.0, min(15.0, base_torque * factor_r)) + + drive_dl.set_ctrl(data, tq_l) + drive_dr.set_ctrl(data, -tq_r) + drive_ul.set_ctrl(data, tq_l) + drive_ur.set_ctrl(data, -tq_r) + + steer_l, steer_r = ackermann_angles(steer_cmd) + steer_ul.set_ctrl(data, steer_l) + steer_ur.set_ctrl(data, steer_r) + + step(model, data) + + # ── IMU with MPU6050 noise ── + ang_vel = np.array(base_link.get_angular_velocity(data)) # world frame + vel_world = np.array(vel) + accel_world = (vel_world - prev_vel_world) / timestep + prev_vel_world = vel_world + + # Rotate to body frame + R_w2b = quat_to_body_rotation(qw, qx, qy, qz) + ang_vel_body = R_w2b @ ang_vel + accel_body = R_w2b @ accel_world + + gyro_n, accel_n = imu.apply(ang_vel_body, accel_body) + + with shared_lock: + shared["imu_json"] = json.dumps({ + "orientation": {"w": float(qw), "x": float(qx), "y": float(qy), "z": float(qz)}, + "angular_velocity": {"x": float(gyro_n[0]), "y": float(gyro_n[1]), "z": float(gyro_n[2])}, + "linear_acceleration": {"x": float(accel_n[0]), "y": float(accel_n[1]), "z": float(accel_n[2])}, + }).encode() + + def render_step(): + nonlocal speed, steer_cmd, capture_index, frame, speed_amp, steer_amp + + render.sync(data) + inp = render.input + + frame += 1 + + # ── Keyboard controls (speed & steer independent) ── + if inp.is_key_just_pressed("up"): + speed_amp = min(speed_amp + 0.2, 2.0) + if inp.is_key_just_pressed("down"): + speed_amp = max(speed_amp - 0.2, 0.0) + if inp.is_key_just_pressed("left"): + steer_amp = min(steer_amp + 0.05, 0.5) + if inp.is_key_just_pressed("right"): + steer_amp = max(steer_amp - 0.05, 0.0) + + speed = 0.0 + if inp.is_key_pressed("w"): + speed = speed_amp + if inp.is_key_pressed("s"): + speed = -speed_amp + + steer_cmd = 0.0 # always reset — returns to center when A/D released + if inp.is_key_pressed("a"): + steer_cmd = steer_amp + if inp.is_key_pressed("d"): + steer_cmd = -steer_amp + + key_active = speed != 0.0 or steer_cmd != 0.0 + + # ── HTTP /cmd override (if keyboard not active) ── + if not key_active: + with shared_lock: + speed = shared["cmd_speed"] + steer_cmd = shared["cmd_steer"] + + with shared_lock: + shared["key_active"] = key_active + + # ── Continuous image capture (~30 Hz) ── + if frame % 2 == 0: + rcam = render.get_camera(0) + capture_tasks.append((capture_index, rcam.capture())) + capture_index += 1 + + while capture_tasks: + idx, task = capture_tasks[0] + if task.state != "pending": + capture_tasks.popleft() + try: + img = task.take_image() + if img is not None: + arr = img.pixels + if arr.shape[2] == 4: + pil = PILImage.fromarray(arr, "RGBA").convert("RGB") + else: + pil = PILImage.fromarray(arr, "RGB") + buf = io.BytesIO() + pil.save(buf, format="JPEG", quality=80) + with shared_lock: + shared["latest_jpg"] = buf.getvalue() + except Exception: + pass + else: + break + + # ── Console status ── + if frame % 60 == 0: + pose = base_link.get_pose(data) + x, y, z = pose[0], pose[1], pose[2] + qw, qx, qy, qz = pose[3], pose[4], pose[5], pose[6] + yaw = math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz)) + v = base_link.get_linear_velocity(data) + actual_spd = math.hypot(v[0], v[1]) + with shared_lock: + shared["state_json"] = json.dumps({ + "x": round(float(x), 3), "y": round(float(y), 3), "z": round(float(z), 3), + "yaw_deg": round(math.degrees(yaw), 1), + "speed_target": round(speed, 2), + "speed_actual": round(float(actual_spd), 2), + "steer_target": round(steer_cmd, 3), + "speed_amp": round(speed_amp, 1), + "steer_amp": round(steer_amp, 3), + }).encode() + print(f"[pos] x={x:+.3f} y={y:+.3f} z={z:+.3f} | yaw={math.degrees(yaw):+.1f}° " + f"spd={speed:.1f}/{actual_spd:.2f} steer={steer_cmd:+.2f}", flush=True) + + # ── Snapshot to disk ── + if inp.is_key_just_pressed("space"): + with shared_lock: + jpg = shared["latest_jpg"] + if jpg: + os.makedirs("captures", exist_ok=True) + path = f"captures/front_cam_{capture_index:04d}.jpg" + with open(path, "wb") as f: + f.write(jpg) + print(f"Captured: {path}") + + run.render_loop(model.options.timestep, 60, phys_step, render_step) if __name__ == "__main__": diff --git a/map.png b/map.png new file mode 100644 index 0000000..a480e08 Binary files /dev/null and b/map.png differ diff --git a/map_full.png b/map_full.png new file mode 100644 index 0000000..8958176 Binary files /dev/null and b/map_full.png differ diff --git a/pyproject.toml b/pyproject.toml index c4369cb..da56330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,4 +4,7 @@ version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" -dependencies = [] +dependencies = [ + "motrixsim-core", + "pillow", +] diff --git a/scene.xml b/scene.xml new file mode 100644 index 0000000..2c6fafa --- /dev/null +++ b/scene.xml @@ -0,0 +1,148 @@ + + + + diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..3d7d841 --- /dev/null +++ b/uv.lock @@ -0,0 +1,150 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "absl-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055, upload-time = "2024-01-16T22:14:26.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706, upload-time = "2024-01-16T22:14:24.055Z" }, +] + +[[package]] +name = "motrixsim-core" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "numpy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/fc/811c71d6053006ed6afb2a46fed61a6ca294cb9dc5d93bc99c55f05ceab9/motrixsim_core-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57f37d36b6399b82cd06e88285529c056e487f063edbb4f697aaeb052456dabd", size = 47082098, upload-time = "2026-06-08T14:30:54.736Z" }, + { url = "https://files.pythonhosted.org/packages/18/b6/3f0f9b85ea90d70f1ac0c82640c6ed59d83d11409723d909ba1c5a95934f/motrixsim_core-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0504de2c260380d40ed9393d699034a2cdaf7aff9cf6fe97d55f05b080f4bd0e", size = 54718509, upload-time = "2026-06-08T14:31:33.642Z" }, + { url = "https://files.pythonhosted.org/packages/61/12/d8476ee4da47b5c54ac1625b7645830b9116dbc436a2650a8e727a5aa0d7/motrixsim_core-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf76189cbf1db814a66694a32653d8f7423ee072122f8f8f57da23243f2f695b", size = 52207612, upload-time = "2026-06-08T14:31:43.047Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3e/f62c0c46cf89da0f174e39fb058ff9e71fa3dcd9ffb42b6d4c3923660700/motrixsim_core-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:e91340f961c71d989b34d6717b06a17c3f6a0b8930bc5d47e12a7e485cbd6903", size = 40402629, upload-time = "2026-06-08T14:30:26.842Z" }, +] + +[[package]] +name = "motrixsim-examples" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "motrixsim-core" }, + { name = "pillow" }, +] + +[package.metadata] +requires-dist = [ + { name = "motrixsim-core" }, + { name = "pillow" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] diff --git a/智能车地图源文件.avif b/智能车地图源文件.avif new file mode 100644 index 0000000..5d35bd5 Binary files /dev/null and b/智能车地图源文件.avif differ