锥桶自定义更新
This commit is contained in:
113
cone_editor.py
Normal file
113
cone_editor.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user