Skip to content
Snippets Groups Projects
Commit 543ca546 authored by Paul-Winpenny's avatar Paul-Winpenny
Browse files

Merge remote-tracking branch 'origin/UWB'

parents 5a7fc23f 322157e7
No related branches found
No related tags found
No related merge requests found
import tkinter as tk, random
from tkinter import ttk
class Blip:
def __init__(self, app, pos, id, color, outlineColorSelected, outlineColorDeselected):
self.app = app
canvas = app.canvas
self.pos = x, y = list(pos)
self.id = id
self.color = color
self.radius = 10
self.outlineColorSelected = outlineColorSelected
self.outlineColorDeselected = outlineColorDeselected
self.circle = canvas.create_oval(x - self.radius, y - self.radius, x + self.radius, y + self.radius, fill = color, outline = "")
self.text = canvas.create_text(x, y - self.radius, text = ",".join(map(str, self.app.worldCoords(pos))), anchor = "s")
self.selected = False
def isInside(self, pos):
return bool(self.app.distance(pos, self.pos) <= self.radius)
def select(self):
self.app.canvas.itemconfig(self.circle, outline = self.outlineColorSelected, width = 2)
def deselect(self):
self.app.canvas.itemconfig(self.circle, outline = self.outlineColorDeselected)
def __del__(self):
self.app.canvas.delete(self.circle)
self.app.canvas.delete(self.text)
class SetupBlip(Blip):
def __init__(self, app, pos, mode, id = 0, movable = True):
super().__init__(app, pos, id, "purple" if mode == "beacon" else "green", "red", "")
self.mode = mode
self.movable = movable
self.isShowingCircle = True
canvas = app.canvas
x, y = pos
if mode == "beacon":
self.isShowingCircle = True
self.idText = canvas.create_text(x, y, text = str(id), fill = "white")
distance = app.distance(self.pos, app.tag.pos)
self.distanceCircle = canvas.create_oval(self.pos[0] - distance, self.pos[1] - distance, self.pos[0] + distance, self.pos[1] + distance, outline = "grey", dash = 3)
self.distanceLine = canvas.create_line(*self.pos, *app.tag.pos, fill = "grey", dash = 3)
canvas.tag_lower(self.distanceCircle)
canvas.tag_lower(self.distanceLine)
def disableDragging(self):
self.movable = False
self.outlineColorDeselected = "grey"
self.app.canvas.itemconfigure(self.circle, fill = "", outline = self.outlineColorDeselected if self == self.app.selectedBlip else self.outlineColorDeselected, width = 2)
if self.mode == "beacon":
self.app.canvas.itemconfigure(self.idText, fill = "black")
def enableDragging(self):
self.movable = True
self.outlineColorDeselected = ""
self.app.canvas.itemconfigure(self.circle, fill = self.color, outline = self.outlineColorDeselected if self == self.app.selectedBlip else self.outlineColorDeselected, width = 2)
if self.mode == "beacon":
self.app.canvas.itemconfigure(self.idText, fill = "white")
def showCircle(self):
self.isShowingCircle = True
self.app.canvas.itemconfigure(self.distanceLine, state = "normal")
self.app.canvas.itemconfigure(self.distanceCircle, state = "normal")
def hideCircle(self):
self.isShowingCircle = False
self.app.canvas.itemconfigure(self.distanceLine, state = "hidden")
self.app.canvas.itemconfigure(self.distanceCircle, state = "hidden")
def move(self, delta):
self.pos = [self.pos[0] + delta[0], self.pos[1] + delta[1]]
self.update()
if self.mode == "tag":
self.app.updateBeacons()
def update(self):
x, y = self.pos
self.app.canvas.coords(self.circle, x - self.radius, y - self.radius, x + self.radius, y + self.radius)
self.app.canvas.coords(self.text, x, y - self.radius)
self.app.canvas.itemconfig(self.text, text = ",".join(map(str, self.app.worldCoords(self.pos))))
if self.mode == "beacon":
self.app.canvas.coords(self.idText, x, y)
distance = self.app.distance(self.pos, app.tag.pos)
self.app.canvas.coords(self.distanceCircle, self.pos[0] - distance, self.pos[1] - distance, self.pos[0] + distance, self.pos[1] + distance)
self.app.canvas.coords(self.distanceLine, *self.pos, *self.app.tag.pos)
class RecordedBlip(Blip):
def __init__(self, app, pos, id, distances):
super().__init__(app, pos, id, "grey", "red", "")
app.canvas.tag_lower(self.circle)
app.canvas.tag_lower(self.text)
self.distances = distances
x, y = pos
self.distanceCircles = [app.canvas.create_oval(x - dist, y - dist, x + dist, y + dist, fill = "", outline = "grey", dash = 2, state = "hidden") for dist in distances]
self.distanceLines = [app.canvas.create_line(x, y, *app.beacons[i].pos, fill = "grey", dash = 2, state = "hidden") for i in range(len(distances))]
for circle, line in zip(self.distanceCircles, self.distanceLines):
app.canvas.tag_lower(circle)
app.canvas.tag_lower(line)
def showDistanceMarkers(self, distID = "all"):
if distID == "all":
for circle, line in zip(self.distanceCircles, self.distanceLines):
self.app.canvas.itemconfig(circle, state = "normal")
self.app.canvas.itemconfig(line, state = "normal")
else:
self.app.canvas.itemconfig(self.distanceCircles[distID], state = "normal")
self.app.canvas.itemconfig(self.distanceLines[distID], state = "normal")
def hideDistanceMarkers(self, distID = "all"):
if distID == "all":
for circle, line in zip(self.distanceCircles, self.distanceLines):
self.app.canvas.itemconfig(circle, state = "hidden")
self.app.canvas.itemconfig(line, state = "hidden")
else:
self.app.canvas.itemconfig(self.distanceCircles[distID], state = "hidden")
self.app.canvas.itemconfig(self.distanceLines[distID], state = "hidden")
def __del__(self):
self.app.canvas.delete(self.circle)
self.app.canvas.delete(self.text)
for circle, line in zip(self.distanceCircles, self.distanceLines):
self.app.canvas.delete(circle)
self.app.canvas.delete(line)
class App(tk.Tk):
def __init__(self, *args, **kwargs):
self.width, self.height = width, height = kwargs.pop("width", 800), kwargs.pop("height", 600)
self.scale = kwargs.pop("scale", 30)
super().__init__(*args, **kwargs)
self.title("Beacon positioning")
self.geometry("{}x{}+{}+{}".format(width, height, (self.winfo_screenwidth() - width) // 2, (self.winfo_screenheight() - height) // 2))
self.resizable(False, False)
self.options = options = ["Beacon 0", "Beacon 1", "Beacon 2", "Beacon 3", "Tag"]
self.selectStringvar = tk.StringVar(self, value = options)
self.grid_columnconfigure((0, 1), weight = 1)
self.grid_rowconfigure((3), weight = 1)
#Canvas
self.canvas = tk.Canvas(self, highlightthickness = 1, width = height, height = height, highlightbackground = "black")
self.canvas.grid(row = 0, column = 0, sticky = "nsw", rowspan = 6)
self.canvas.create_line(0, height / 2, height, height / 2, fill = "grey")
self.canvas.create_line(height / 2, 0, height / 2, height, fill = "grey")
self.canvas.bind("<ButtonPress-1>", self.click)
self.canvas.bind("<B1-Motion>", self.drag)
self.canvas.bind("<B1-ButtonRelease>", self.release)
# ttk.Label(self, text = """Instructions:
# Set up beacon positions by selecting and dragging them around.
# Move tag to desired position, and record a position. Repeat until 3 points have been recorded.
# Once the recording process begins, beacons can no longer be moved to reflect reality.""").grid(row = 4, column = 0, sticky = "nsew", rowspan = 2)
#Radio button selectors
self.radioButtonFrame = ttk.Labelframe(self, text = "Select blip")
self.radioButtonFrame.grid(row = 0, column = 1, sticky = "nsew", padx = 5, pady = 5)
self.radioButtons = {name: ttk.Radiobutton(self.radioButtonFrame, text = name, variable = self.selectStringvar, value = name, command = self.radioSelect) for i, name in enumerate(options)}
for i, radioButton in enumerate(self.radioButtons.values()):
radioButton.grid(row = i, column = 0, sticky = "new")
#Properties frame
self.propertiesFrame = ttk.Labelframe(self, text = "Properties")
self.propertiesFrame.grid(row = 1, column = 1, sticky = "nsew", padx = 5, pady = 5)
self.propertiesFrame.grid_columnconfigure(1, weight = 1, uniform = "column")
#Property frame labels
ttk.Label(self.propertiesFrame, text = "\tx: ", justify = "right").grid(row = 0, column = 0, sticky = "nse")
ttk.Label(self.propertiesFrame, text = "\ty: ", justify = "right").grid(row = 1, column = 0, sticky = "nse")
# ttk.Label(self.propertiesFrame, text = "Show circle: ", justify = "right").grid(row = 2, column = 0, sticky = "nse")
#Entry box X
self.xStringVar = tk.StringVar(self)
self.xStringVar.trace_add("write", self.writeX)
self.xEntry = ttk.Entry(self.propertiesFrame, textvariable = self.xStringVar)
self.xEntry.grid(row = 0, column = 1, sticky = "nsew", padx = 5)
#Entry box Y
self.yStringVar = tk.StringVar(self)
self.yStringVar.trace_add("write", self.writeY)
self.yEntry = ttk.Entry(self.propertiesFrame, textvariable = self.yStringVar)
self.yEntry.grid(row = 1, column = 1, sticky = "nsew", padx = 5)
#Checkbox
self.showCircleVar = tk.BooleanVar(self)
self.showCirclesCheckbox = ttk.Checkbutton(self.propertiesFrame, text = "Show distance radius", state = "disabled", variable = self.showCircleVar, command = self.checkboxCallback)
self.showCirclesCheckbox.grid(row = 2, column = 0, columnspan = 2, sticky = "nsw")
#Live distances frame
self.distancesFrame = ttk.Labelframe(self, text = "Distance to beacons")
self.distancesFrame.grid(row = 2, column = 1, sticky = "nsew", padx = 5, pady = 5)
self.distancesFrame.grid_columnconfigure(0, weight = 1)
#Distance labels
self.distanceLabels = {"Beacon {}".format(i): ttk.Label(self.distancesFrame, text = "Dist{}: -".format(i)) for i in range(4)}
for i, label in enumerate(self.distanceLabels.values()):
label.grid(row = i, column = 0, sticky = "nsew")
#Record button
self.recordButton = ttk.Button(self.distancesFrame, text = "Record", command = self.recordButtonCallback, state = "disabled")
self.recordButton.grid(row = len(self.distanceLabels), column = 0, sticky = "nsew")
#Recorded blip's properties frame
self.recordedBlipFrame = ttk.Labelframe(self, text = "Recorded positions properties")
self.recordedBlipFrame.grid(row = 3, column = 1, sticky = "nsew", padx = 5, pady = 5)
self.recordedBlipFrame.grid_columnconfigure((0, 1), weight = 1)
#Recorded blip's properties checkboxes
self.recordedBlipCheckboxVars = [tk.BooleanVar(self) for i in range(4)]
self.recordedBlipsCheckboxes = [ttk.Checkbutton(self.recordedBlipFrame, text = "Beacon {}".format(i), variable = self.recordedBlipCheckboxVars[i], state = "disabled", command = self.recordedBlipCheckboxCallback) for i in range(4)]
for i, checkbox in enumerate(self.recordedBlipsCheckboxes):
self.recordedBlipCheckboxVars[i].set(False)
checkbox.selection_clear()
checkbox.grid(row = i, column = 0, sticky = "nsew", columnspan = 2)
#Show all and hide all buttons
self.showAllButton = ttk.Button(self.recordedBlipFrame, text = "Show all", state = "disabled", command = self.showAllCallback)
self.showAllButton.grid(row = len(self.recordedBlipsCheckboxes), column = 0, sticky = "nsew")
self.hideAllButton = ttk.Button(self.recordedBlipFrame, text = "Hide all", state = "disabled", command = self.hideAllCallback)
self.hideAllButton.grid(row = len(self.recordedBlipsCheckboxes), column = 1, sticky = "nsew")
#Toggle all circles button
self.toggleAllCirclesButton = ttk.Button(self, text = "Hide all beacon circles", command = self.toggleAllCircles)
self.toggleAllCirclesButton.grid(row = 4, column = 1, sticky = "nsew")
#Reset button
ttk.Button(self, text = "Reset", command = self.reset).grid(row = 5, column = 1, sticky = "nsew")
self.selectedBlip = None
self.lastClickPos = None
self.dragging = False
self.tag = SetupBlip(self, [height // 2, height // 2], "tag")
self.beacons = [SetupBlip(self, pos, "beacon", i) for i, pos in enumerate([(height // 4, height // 4), (height - height // 4, height // 4), (height // 4, height - height // 4), (height - height // 4, height - height // 4)])]
self.recordedBlips = []
def distance(self, pos1, pos2):
x1, y1 = pos1
x2, y2 = pos2
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
def worldCoords(self, pixelCoords):
px, py = pixelCoords
x = ((px / self.height) - 0.5) * self.scale
y = -((py / self.height) - 0.5) * self.scale
return round(x, 3), round(y, 3)
def pixelCoords(self, worldCoords, mode):
if mode == "x":
return int(((worldCoords / self.scale) + 0.5) * self.height)
elif mode == "y":
return int(((-worldCoords / self.scale) + 0.5) * self.height)
def updateBeacons(self):
for beacon in self.beacons:
beacon.update()
def selectBlip(self, blip):
if self.selectedBlip:
self.selectedBlip.deselect()
self.selectedBlip = blip
blip.select()
self.selectStringvar.set(self.getSelectionName(self.selectedBlip))
worldCoords = self.worldCoords(self.selectedBlip.pos)
self.xStringVar.set(str(worldCoords[0]))
self.yStringVar.set(str(worldCoords[1]))
if self.selectedBlip in self.beacons:
self.showCirclesCheckbox.config(state = "normal")
self.recordButton.config(state = "disabled")
if self.selectedBlip.isShowingCircle:
self.showCircleVar.set(True)
else:
self.showCircleVar.set(False)
else:
self.showCircleVar.set(False)
self.showCirclesCheckbox.config(state = "disabled")
if len(self.recordedBlips) < 3:
self.recordButton.config(state = "normal")
self.updateDistanceLabels()
def deselectBlip(self):
if self.selectBlip:
self.selectedBlip.deselect()
self.selectedBlip = None
self.selectStringvar.set("")
self.xStringVar.set("")
self.yStringVar.set("")
self.showCircleVar.set(False)
self.showCirclesCheckbox.config(state = "disabled")
self.recordButton.config(state = "disabled")
self.updateDistanceLabels()
def click(self, event):
x, y = event.x, event.y
selectedBlips = list(filter(lambda blip: blip.isInside((x, y)), self.beacons + [self.tag] + self.recordedBlips))
if len(selectedBlips):
self.selectBlip(selectedBlips[0])
if self.selectedBlip not in self.recordedBlips:
if self.selectedBlip.movable:
self.lastClickPos = x, y
self.dragging = self.selectedBlip.movable
elif self.selectedBlip:
self.deselectBlip()
def drag(self, event):
if self.selectedBlip and self.selectedBlip not in self.recordedBlips:
if self.selectedBlip.movable:
pos = [event.x, event.y]
delta = pos[0] - self.lastClickPos[0], pos[1] - self.lastClickPos[1]
self.selectedBlip.move(delta)
worldCoords = self.worldCoords(self.selectedBlip.pos)
self.xStringVar.set(str(worldCoords[0]))
self.yStringVar.set(str(worldCoords[1]))
if self.selectedBlip == self.tag:
self.updateDistanceLabels()
self.lastClickPos = pos
def release(self, event):
self.dragging = False
def updateDistanceLabels(self):
if self.selectedBlip == self.tag:
for beacon in self.beacons:
dist = round(self.distance(self.worldCoords(beacon.pos), self.worldCoords(self.tag.pos)), 3)
self.distanceLabels[self.getSelectionName(beacon)].config(text = "Dist{}: {}".format(self.getSelectionName(beacon)[-1], dist))
elif self.selectedBlip in self.recordedBlips:
for i, dist in enumerate(self.selectedBlip.distances):
self.distanceLabels["Beacon {}".format(i)].config(text = "Dist{}: {}".format(i, dist))
else:
for name, label in self.distanceLabels.items():
label.config(text = "Dist{}: -".format(name[-1]))
def radioSelect(self): #on radio button selection callback
self.selectBlip(self.beacons[int(self.selectStringvar.get()[-1])] if self.selectStringvar.get() != "Tag" else self.tag)
def checkboxCallback(self):
if self.selectedBlip:
if self.showCircleVar.get():
self.selectedBlip.showCircle()
else:
self.selectedBlip.hideCircle()
def recordButtonCallback(self):
for beacon in self.beacons:
beacon.disableDragging()
self.xEntry.config(state = "disabled")
self.yEntry.config(state = "disabled")
if len(self.recordedBlips) < 3:
if len(self.recordedBlips) == 0:
self.showAllButton.config(state = "normal")
self.hideAllButton.config(state = "normal")
for checkbox in self.recordedBlipsCheckboxes:
checkbox.config(state = "normal")
self.recordedBlips.append(RecordedBlip(self, self.tag.pos, len(self.recordedBlips), [round(self.distance(self.tag.pos, beacon.pos), 3) for beacon in self.beacons]))
if len(self.recordedBlips) == 3:
self.recordButton.config(state = "disabled")
def recordedBlipCheckboxCallback(self):
values = [var.get() for var in self.recordedBlipCheckboxVars]
for i, value in enumerate(values):
for blip in self.recordedBlips:
if value:
blip.showDistanceMarkers(i)
else:
blip.hideDistanceMarkers(i)
def showAllCallback(self):
for var in self.recordedBlipCheckboxVars:
var.set(True)
for blip in self.recordedBlips:
blip.showDistanceMarkers()
def hideAllCallback(self):
for var in self.recordedBlipCheckboxVars:
var.set(False)
for blip in self.recordedBlips:
blip.hideDistanceMarkers()
def toggleAllCircles(self):
text = self.toggleAllCirclesButton.cget("text")
if text == "Hide all beacon circles":
self.toggleAllCirclesButton.config(text = "Show all beacon circles")
if self.selectedBlip != self.tag and self.selectedBlip != None:
self.showCircleVar.set(False)
for beacon in self.beacons:
beacon.hideCircle()
elif text == "Show all beacon circles":
self.toggleAllCirclesButton.config(text = "Hide all beacon circles")
if self.selectedBlip != self.tag and self.selectedBlip != None:
self.showCircleVar.set(True)
for beacon in self.beacons:
beacon.showCircle()
def writeX(self, var, index, mode):
strVal = self.xStringVar.get()
if self.selectedBlip and not self.dragging and self.selectedBlip not in self.recordedBlips:
if self.selectedBlip.movable:
try:
self.selectedBlip.pos[0] = self.pixelCoords(float(strVal), "x")
except ValueError:
pass
self.selectedBlip.update()
def writeY(self, var, index, mode):
strVal = self.yStringVar.get()
if self.selectedBlip and not self.dragging and self.selectedBlip not in self.recordedBlips:
if self.selectedBlip.movable:
try:
self.selectedBlip.pos[1] = self.pixelCoords(float(strVal), "y")
except ValueError:
pass
self.selectedBlip.update()
def getSelectionName(self, blip):
# return self.beacons.index(blip) if blip in self.beacons else len(self.beacons)
if blip not in self.recordedBlips:
return "Beacon {}".format(blip.id) if blip.mode == "beacon" else "Tag"
else:
return
def reset(self): #TBD: add reset of recorded blips
positions = [(self.height // 4, self.height // 4), (self.height - self.height // 4, self.height // 4), (self.height // 4, self.height - self.height // 4), (self.height - self.height // 4, self.height - self.height // 4)]
for beacon, pos in zip(self.beacons, positions):
beacon.pos = list(pos)
beacon.enableDragging()
beacon.showCircle()
self.tag.pos = [self.height // 2, self.height // 2]
self.tag.update()
self.updateBeacons()
self.recordButton.config(state = "normal")
self.xEntry.config(state = "normal")
self.yEntry.config(state = "normal")
self.showCircleVar.set(True)
if self.selectedBlip:
worldCoords = self.worldCoords(self.selectedBlip.pos)
self.xStringVar.set(str(worldCoords[0]))
self.yStringVar.set(str(worldCoords[1]))
for i in range(len(self.recordedBlips))[::-1]:
del self.recordedBlips[i]
self.showAllButton.config(state = "disabled")
self.hideAllButton.config(state = "disabled")
for var in self.recordedBlipCheckboxVars:
var.set(False)
for checkbox in self.recordedBlipsCheckboxes:
checkbox.config(state = "disabled")
app = App()
app.mainloop()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment