From 0a3ded5a1b1f0ea3e458bf7a8a26a44fe3e4f86f Mon Sep 17 00:00:00 2001 From: Astatin3 <77305074+Astatin3@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:43:48 -0600 Subject: [PATCH] Add exporting --- README.md | 6 +- main.py | 4 +- requirements.txt | 3 + src/buttonEditor.py | 10 +- src/export.py | 228 +++++++++++++++++++++++++++++++++++++++++++- src/pathEditor.py | 9 +- src/render.py | 2 +- 7 files changed, 249 insertions(+), 13 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 6f2e73b..ce7ef18 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,8 @@ python3 ./main.py - In button mode select buttons on the driver and operator controllers. ##### "Export" Tab: -- Click export, and save to a file \ No newline at end of file +- Click export, and save to a file + +### Known Bugs: +- Because the variables don't get transferred over yet, you must click on the button editor tab before exporting. +- The driver controller's movement stick is rotated by 90 degrees (Maybe) \ No newline at end of file diff --git a/main.py b/main.py index 0dd6f72..0ca8802 100644 --- a/main.py +++ b/main.py @@ -32,7 +32,7 @@ tabIndex = 0 tabs = [ pathEditor.pathEditor(render), buttonEditor.buttonEditor(render, pathEditor), - export.export(pg, render) + export.export(pg, render, buttonEditor) ] tabs[tabIndex].load() @@ -51,6 +51,7 @@ def addTab(i): def onClick(pos): global tabIndex + tabs[tabIndex].unload() tabIndex = i tabs[tabIndex].load() render.renderElements(pos) @@ -100,6 +101,7 @@ while running: elif event.type == pg.KEYDOWN: tabs[tabIndex].keyDown(event.key) if event.key == pg.K_TAB: + tabs[tabIndex].unload() tabIndex = (tabIndex + 1) % len(tabs) tabs[tabIndex].load() render.renderElements(pg.mouse.get_pos()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2806a78 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +pygame +crossfiledialog \ No newline at end of file diff --git a/src/buttonEditor.py b/src/buttonEditor.py index 67c4f5a..aff2cf8 100644 --- a/src/buttonEditor.py +++ b/src/buttonEditor.py @@ -17,6 +17,7 @@ keyFrames = [] matchLength = 15 TPS = 50 +tickTime = round(1/TPS*1000) matchTicks = matchLength * TPS displayTickResolution = 4 displayTicks = round(matchTicks / displayTickResolution) @@ -165,6 +166,9 @@ def getButtonFrameAtPos(index): def getRobotAtIndex(index): prevFrame, nextFrame = getSurroundingPosFrames(index) + # print(prevFrame) + # print(nextFrame) + if prevFrame == None and nextFrame == None: return (0,0), 0 if prevFrame == None: @@ -643,4 +647,8 @@ class buttonEditor: else: self.updateNodes(True) - self.refresh() \ No newline at end of file + self.refresh() + + + def unload(self): + pass \ No newline at end of file diff --git a/src/export.py b/src/export.py index 15fa8ca..1285b1e 100644 --- a/src/export.py +++ b/src/export.py @@ -1,16 +1,224 @@ +import crossfiledialog +import struct +import copy + pg = None -render = None +buttonEditor = None +events = [] + +#Save according to https://github.com/Team4388/2024AcrossTheRidgebotiverse/blob/Prep-For-Denver/src/main/java/frc4388/robot/commands/Autos/neo%20AutoRecoding%20format.txt + + +moveMultiplier = 0.1 +rotMultiplier = 1 + + +def buttonsToBytes(buttons): + data = 0 + for i in range(16): + data |= buttons[i] << i + return data.to_bytes(2, "little", signed=True) + + +def toByte(num): + if num > 255: + raise OverflowError + return num.to_bytes(1, 'big', signed=False) + + + +def toShort(num): + if num > 65535: + raise OverflowError + return num.to_bytes(2, 'big', signed=True) + + +def toInt(num): + return struct.pack('>i', num) + + +def toDouble(num): + return struct.pack('>d', num) + + + + +class xboxController: + def __init__(self): + self.buttons = [False for i in range(16)] + self.leftStick = (0,0) + self.rightStick = (0,0) + self.LT = -1 + self.RT = -1 + self.POV = -1 + + +def getPOVhat(up, down, left, right): + if up and right: + return 45 + elif right and down: + return 135 + elif down and left: + return 225 + elif left and up: + return 315 + elif up: + return 0 + elif right: + return 90 + elif down: + return 180 + elif left: + return 270 + else: + return -1 + + + + +def getSticksAtFrame(index): + + fractionIndex = round(index/buttonEditor.displayTickResolution) + + newpos, newrot = buttonEditor.getRobotAtIndex(fractionIndex) + oldpos, oldrot = buttonEditor.getRobotAtIndex(fractionIndex-1) + + # print(oldrot-newrot) + + diffPos = ((oldpos[0]-newpos[0])*moveMultiplier, (oldpos[1]-newpos[1])*moveMultiplier) + diffRot = (oldrot-newrot)*rotMultiplier + + if abs(diffPos[0]) > 1 or abs(diffPos[1]) > 1 or abs(diffRot) > 1: + print("Error! Robot moved too fast!, Try to edit 'Multiplier' values in export.py") + return (0, 0), 0 + + + print(diffPos) + + # print(diffRot) + + return diffPos, diffRot + + + +def getControllersAtFrame(index): + controllers = [xboxController(), xboxController()] + if index >= buttonEditor.matchTicks: + return controllers + + pos, rot = getSticksAtFrame(index) + + controllers[0].leftStick = pos + controllers[0].rightStick = (rot,0) + + btns = buttonEditor.getLeftButtonFrame(index) + if btns == None: + btns = buttonEditor.createBlankController() + else: + btns = btns['controllers'] + + for i in range(len(controllers)): + ctrlr = btns[i] + + controllers[i].buttons[0] = ctrlr['A'] + controllers[i].buttons[1] = ctrlr['B'] + controllers[i].buttons[2] = ctrlr['X'] + controllers[i].buttons[3] = ctrlr['Y'] + + controllers[i].buttons[4] = ctrlr['LB'] + controllers[i].buttons[5] = ctrlr['RB'] + + controllers[i].buttons[6] = ctrlr['Menu'] + controllers[i].buttons[7] = ctrlr['Windows'] + + controllers[i].buttons[8] = ctrlr['Left_Stick'] + controllers[i].buttons[9] = ctrlr['Right_Stick'] + + controllers[i].LT = (ctrlr['LT']*2)-1 + controllers[i].RT = (ctrlr['RT']*2)-1 + + controllers[i].POV = getPOVhat(ctrlr['Dpad_Up'], ctrlr['Dpad_Down'], + ctrlr['Dpad_Left'], ctrlr['Dpad_Right']) + + + + return controllers + + + +def getFrameData(index): + controllers = getControllersAtFrame(index) + data = b'' + for ctrlr in controllers: + # print(ctrlr.leftStick[0]) + data += toDouble(ctrlr.leftStick[0]) + data += toDouble(ctrlr.leftStick[1]) + data += toDouble(ctrlr.LT) + data += toDouble(ctrlr.RT) + data += toDouble(ctrlr.rightStick[0]) + data += toDouble(ctrlr.rightStick[1]) + + data += buttonsToBytes(ctrlr.buttons) + + # for btn in ctrlr.buttons: + # data += toBit(data) + # print(toBit(data)) + + data += toShort(ctrlr.POV) + + data += toInt(index * buttonEditor.tickTime) + + return data + + + +def getHeader(): + header = toByte(6) # Num Axes per controller + header += toByte(1) # Num POVs + header += toByte(2) # Num Controllers + header += toShort(buttonEditor.matchTicks) # Num Frames + return header + + + +def getData(): + data = b'' + for i in range(buttonEditor.matchTicks): + data += getFrameData(i) + return getHeader() + data + + + +def save(): + path = crossfiledialog.save_file('Save auto file', './') + # path = "./file.txt" + with open(path, "wb") as f: + f.write(getData()) class export: name = "Export" - def __init__(self, tmppg, tmprender): + def __init__(self, tmppg, tmprender, tmpbuttonEditor): global pg pg = tmppg global render render = tmprender + global buttonEditor + buttonEditor = tmpbuttonEditor - # render.addButton() + self.loaded = False + + def getIsSelected(): + return False + + def getIsVisible(): + return self.loaded + + def onClick(pos): + save() + + render.addButton((round(render.width/4),round(render.screen.get_height()/4),round(render.width/2),round(render.height/2)), + "Export", getIsSelected, getIsVisible, onClick) def mouseDown(self, pos): pass @@ -28,5 +236,17 @@ class export: pass def load(self): + self.loaded = True + + # for keyFrame in buttonEditor.keyFrames: + # keyFrame['timeIndex'] = keyFrame['timeIndex'] * buttonEditor.displayTickResolution + + render.clear() - pg.display.update() \ No newline at end of file + pg.display.update() + + def unload(self): + self.loaded = False + + # for keyFrame in buttonEditor.keyFrames: + # keyFrame['timeIndex'] = round(keyFrame['timeIndex'] / buttonEditor.displayTickResolution) \ No newline at end of file diff --git a/src/pathEditor.py b/src/pathEditor.py index 813393b..fc43aec 100644 --- a/src/pathEditor.py +++ b/src/pathEditor.py @@ -161,12 +161,11 @@ class pathEditor: nodeRotations.pop(clickIndex) refresh() - - def keyDown(self, key): pass - - def load(self): - refresh() \ No newline at end of file + refresh() + + def unload(self): + pass \ No newline at end of file diff --git a/src/render.py b/src/render.py index 4a55334..4b3d08a 100644 --- a/src/render.py +++ b/src/render.py @@ -159,7 +159,7 @@ class render(): def clickElement(self, pos): for elem in self.elements: - if elem['type'] == 'button' and self.isInRect(pos, elem['rect']): + if elem['type'] == 'button' and elem['getIsVisible']() and self.isInRect(pos, elem['rect']): elem['onClick'](pos)