Skip to content
Snippets Groups Projects
Commit 322157e7 authored by jlKronos01's avatar jlKronos01
Browse files

Create BeaconPositioningTest.py

parent 69a65ddd
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