Files
car_simulation/cone_editor.py
2026-06-16 22:45:15 +08:00

114 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Visual cone placement editor. Click on the 5m×5m map to place cones."""
import json
import os
import tkinter as tk
from PIL import Image, ImageTk
MAP_FILE = "智能车地图源文件.avif"
OUTPUT = "cones.json"
MAP_SIZE_M = 5.0 # meters
CANVAS_W = 800
CANVAS_H = 800
class ConeEditor:
def __init__(self, root):
self.root = root
root.title("锥桶编辑器 — 左键放置 / 右键删除 / 滚轮保存")
self.cones = [] # list of {"x": world_x, "y": world_y}
# Load map image
img = Image.open(MAP_FILE)
self.img_w, self.img_h = img.size
img = img.resize((CANVAS_W, CANVAS_H), Image.LANCZOS)
self.photo = ImageTk.PhotoImage(img)
# Canvas
self.canvas = tk.Canvas(root, width=CANVAS_W, height=CANVAS_H, cursor="crosshair")
self.canvas.pack()
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo)
# Info label
self.info = tk.Label(root, text="左键放置 | 右键删除最近 | 鼠标查看坐标")
self.info.pack()
# Buttons
btn_frame = tk.Frame(root)
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="保存 (cones.json)", command=self.save).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="清除全部", command=self.clear).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="加载已有", command=self.load_existing).pack(side=tk.LEFT, padx=5)
tk.Label(btn_frame, text=f"地图: {MAP_SIZE_M}m×{MAP_SIZE_M}m | (-2.5,-2.5) → (2.5,2.5)").pack(side=tk.LEFT, padx=10)
# Bindings
self.canvas.bind("<Button-1>", self.on_click)
self.canvas.bind("<Button-3>", self.on_right_click)
self.canvas.bind("<Motion>", self.on_move)
# Load existing if available
self.load_existing()
def px_to_world(self, px, py):
x = px / CANVAS_W * MAP_SIZE_M - MAP_SIZE_M / 2
y = MAP_SIZE_M / 2 - py / CANVAS_H * MAP_SIZE_M
return x, y
def world_to_px(self, wx, wy):
px = (wx + MAP_SIZE_M / 2) / MAP_SIZE_M * CANVAS_W
py = (MAP_SIZE_M / 2 - wy) / MAP_SIZE_M * CANVAS_H
return px, py
def on_move(self, event):
wx, wy = self.px_to_world(event.x, event.y)
self.info.config(text=f"坐标: ({wx:.3f}, {wy:.3f}) | 锥桶数量: {len(self.cones)}")
def on_click(self, event):
wx, wy = self.px_to_world(event.x, event.y)
# Clamp to map bounds
half = MAP_SIZE_M / 2
wx = max(-half, min(half, wx))
wy = max(-half, min(half, wy))
self.cones.append({"x": round(wx, 3), "y": round(wy, 3)})
self.redraw()
def on_right_click(self, event):
if not self.cones:
return
wx, wy = self.px_to_world(event.x, event.y)
# Remove nearest cone
idx = min(range(len(self.cones)), key=lambda i: (self.cones[i]["x"] - wx) ** 2 + (self.cones[i]["y"] - wy) ** 2)
self.cones.pop(idx)
self.redraw()
def redraw(self):
self.canvas.delete("cone")
r = 5
for c in self.cones:
px, py = self.world_to_px(c["x"], c["y"])
self.canvas.create_oval(px - r, py - r, px + r, py + r, fill="blue", outline="white", width=2, tags="cone")
def save(self):
with open(OUTPUT, "w") as f:
json.dump(self.cones, f, indent=2)
print(f"已保存 {len(self.cones)} 个锥桶到 {OUTPUT}")
self.info.config(text=f"已保存 {len(self.cones)} 个锥桶")
def clear(self):
self.cones.clear()
self.redraw()
print("已清除所有锥桶")
def load_existing(self):
if os.path.exists(OUTPUT):
with open(OUTPUT) as f:
self.cones = json.load(f)
self.redraw()
print(f"已加载 {len(self.cones)} 个锥桶")
if __name__ == "__main__":
root = tk.Tk()
app = ConeEditor(root)
root.mainloop()