def __init__(self, acd: ACD, resolution: str, wheel): self.__calc = TireTemp( acd.get_temp_curve(ac.getCarTyreCompound(0), wheel)) # Initial size is 160x256 super(Temps, self).__init__(176.0, 0.0, 160.0, 256.0, 16.0) self.resize(resolution)
def __init__(self, acd: ACD, resolution: str, wheel): self.__calc = TireTemp( acd.get_temp_curve(ac.getCarTyreCompound(0), wheel)) # Initial size is 160x256 super(Tire, self).__init__(176.0, 0.0, 160.0, 256.0) self._back.color = Colors.white if Tire.texture_id == 0: Tire.texture_id = ac.newTexture( "apps/python/LiveTelemetry/img/tire.png") self.resize(resolution)
def loadTireData(): global Options try: tyresFile = os.path.join(os.path.dirname(__file__), "tyres-data.json") with open(tyresFile, "r") as f: tyreData = json.load(f) except OSError: ac.log("CamberExtravaganza ERROR: loadTireData tyres-data.json not found") else: try: carName = ac.getCarName(0) tyreCompound = ac.getCarTyreCompound(0) Options["tireRadius"] = tyreData[carName][tyreCompound]["radius"] dcamber0 = tyreData[carName][tyreCompound]["dcamber0"] dcamber1 = tyreData[carName][tyreCompound]["dcamber1"] Options["targetCamber"] = math.degrees(dcamber0 / (2 * dcamber1)) Options["dcamber0"] = dcamber0 Options["dcamber1"] = dcamber1 Options["LS_EXPY"] = tyreData[carName][tyreCompound]["ls_expy"] ac.console("Tyre data found for " + carName + " " + tyreCompound) except KeyError: # Doesn't exist in official, look for custom data try: tyresFile = os.path.join(os.path.dirname(__file__), "tyres-data-custom.json") with open(tyresFile, "r") as f: tyreData = json.load(f) Options["tireRadius"] = tyreData[carName][tyreCompound]["radius"] dcamber0 = tyreData[carName][tyreCompound]["dcamber0"] dcamber1 = tyreData[carName][tyreCompound]["dcamber1"] Options["targetCamber"] = math.degrees(dcamber0 / (2 * dcamber1)) Options["dcamber0"] = dcamber0 Options["dcamber1"] = dcamber1 Options["LS_EXPY"] = tyreData[carName][tyreCompound]["ls_expy"] except (OSError, KeyError) as e: Options["tireRadius"] = 1 Options["targetCamber"] = 999 Options["dcamber0"] = 999 Options["dcamber1"] = -999 Options["LS_EXPY"] = 1 ac.log("CamberExtravaganza ERROR: loadTireData: No custom tyre data found for this car") else: ac.console("Custom tyre data found for " + carName + " " + tyreCompound)
def __init__(self, acd: ACD, resolution: str, wheel, window_id: int): self.__calc = TirePsi( acd.get_ideal_pressure(ac.getCarTyreCompound(0), wheel)) # Initial size is 85x85 super(Pressure, self).__init__(70.0 if wheel.is_left() else 382.0, 171.0, 60.0, 60.0) self._back.color = Colors.white if Pressure.texture_id == 0: Pressure.texture_id = ac.newTexture( "apps/python/LiveTelemetry/img/pressure.png") self.__lb = ac.addLabel(window_id, "") ac.setFontAlignment(self.__lb, "center") self.resize(resolution)
def loadTireData(): global Options tyreDataPath = os.path.join(os.path.dirname(__file__), "tyres_data") tyreData = {} for td in os.listdir(tyreDataPath): if td.endswith('.json'): with open(os.path.join(tyreDataPath, td), 'r') as f: try: newData = json.load(f) except ValueError as e: ac.log("CamberExtravaganza ERROR: Invalid JSON: " + td) else: tyreData.update(newData) carName = ac.getCarName(0) tyreCompound = ac.getCarTyreCompound(0) parseTyreData(carName, tyreCompound, tyreData)
def acUpdate(deltaT): # TIMERS global timer0, timer1, timer2 # VARIABLES global totalDrivers global drivers global fastest_lap global race_started, replay_started, quali_started global qualify_session_time global replay_file global replay_data # Widgets global leaderboardWindow, driverWidget, driverComparisonWidget # LABELS global leaderboard global lapCountTimerLabel, leaderboardBaseLabel, leaderboardInfoBackgroundLabel, leaderboardBackgroundLabel global flagLabel # ============================ # UPDATE TIMERS timer0 += deltaT timer1 += deltaT timer2 += deltaT # ============================ # ================================================================ # RACES # ================================================================ if info.graphics.session == 2: # 10 times per second if timer2 > 0.1: timer2 = 0 # ============================= # SAVE SPLIT TIMES for d in drivers: if ac.isConnected(d.id) == 0: continue current_split = d.get_split_id(ac.getCarState(d.id, acsys.CS.NormalizedSplinePosition)) if d.current_split != current_split: # save split time at each split of the track d.split_times[current_split-1] = time.time() d.current_split = current_split # 3 times per second if timer1 > 0.3: # CHECK RACE RESTART if race_started and info.graphics.completedLaps == 0 and info.graphics.iCurrentTime == 0: race_started = False # CHECK RACE START if not race_started and info.graphics.iCurrentTime > 0: # RESET THINGS fastest_lap = MAX_LAP_TIME LeaderboardRow.FASTEST_LAP_ID = -1 for row in leaderboard: # clean the fastest lap marker row.mark_fastest_lap() # in case we are coming from a qualify ac.setFontColor(lapCountTimerLabel, 0.86, 0.86, 0.86, 1) ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_BASE_RACE) ac.setVisible(lapCountTimerLabel, 1) race_started = True quali_started = False for d in drivers: d.starting_position = ac.getCarLeaderboardPosition(d.id) d.tyre = ac.getCarTyreCompound(d.id) d.pits = 0 replay_file = setup_replay_file(drivers, info.graphics.numberOfLaps) # save starting info on the drivers # ============================ # POSITION UPDATE for i in range(totalDrivers): pos = ac.getCarRealTimeLeaderboardPosition(i) connected = ac.isConnected(i) if connected == 0: # mark unconnected drivers leaderboard[pos].mark_out() drivers[i].out = True else: leaderboard[pos].mark_in() drivers[i].out = False leaderboard[pos].update_name(i) # OVERTAKE if pos != drivers[i].position: # there was an overtake drivers[i].timer = FC.OVERTAKE_POSITION_LABEL_TIMER # set timer if pos < drivers[i].position: leaderboard[pos].mark_green_position() elif pos > drivers[i].position: leaderboard[pos].mark_red_position() elif drivers[i].timer <= 0: leaderboard[pos].mark_white_position() else: drivers[i].timer -= timer1 drivers[i].position = pos # END OVERTAKE # ============================ # FASTEST LAP BANNER TIMER if fastest_lap_banner.timer > 0: fastest_lap_banner.timer -= timer1 fastest_lap_banner.hide() # ============================ # FLAGS if info.graphics.flag == 1: ac.setBackgroundTexture(flagLabel, FC.BLUE_FLAG) ac.setVisible(flagLabel, 1) elif info.graphics.flag == 2: ac.setBackgroundTexture(flagLabel, FC.YELLOW_FLAG) ac.setVisible(flagLabel, 1) elif info.graphics.flag == 5: ac.setBackgroundTexture(flagLabel, FC.CHECKERED_FLAG) ac.setVisible(flagLabel, 1) elif info.graphics.flag == 0: ac.setVisible(flagLabel, 0) timer1 = 0 # Once per second if timer0 > 1: timer0 = 0 ac.setBackgroundOpacity(leaderboardWindow, 0) ac.setBackgroundOpacity(driverWidget.window, 0) ac.setBackgroundOpacity(driverComparisonWidget.window, 0) ac.setBackgroundOpacity(fastest_lap_banner.window, 0) # ============================ # SERVER LAP for i in range(totalDrivers): drivers[i].current_lap = ac.getCarState(i, acsys.CS.LapCount) lc = max((drivers[i].current_lap for i in range(totalDrivers))) + 1 if lc >= info.graphics.numberOfLaps: ac.setVisible(lapCountTimerLabel, 0) ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_FINAL_LAP) else: ac.setText(lapCountTimerLabel, "%d / %d" % (lc, info.graphics.numberOfLaps)) # =========================== # CALCULATE TIME DIFERENCES dPosition = sorted(drivers, key=lambda x: x.position) if LeaderboardRow.update_type == INFO_TYPE.GAPS: for i in range(1, len(dPosition)): driver_ahead, driver = dPosition[i-1], dPosition[i] timeDiff = driver.split_times[driver.current_split - 1] - driver_ahead.split_times[driver.current_split - 1] if timeDiff < 0: continue # ignore these times, happens on overtakes if driver.position > totalDrivers: continue # might try to update before it is possible driver.timeDiff = timeDiff if timeDiff > 60: leaderboard[driver.position].update_time("+1 MIN") else: leaderboard[driver.position].update_time("+" + time_to_string(timeDiff*1000)) leaderboard[0].update_time("Interval") # Force it elif LeaderboardRow.update_type == INFO_TYPE.POSITIONS: for d in dPosition: posDiff = d.starting_position - d.position - 1 leaderboard[d.position].update_positions(posDiff) # ============================ # MARK FASTEST LAP if lc > FC.FASTEST_LAP_STARTING_LAP: for d in drivers: lap = ac.getCarState(d.id, acsys.CS.BestLap) if lap != 0 and lap < fastest_lap: fastest_lap = lap fastest_lap_banner.show(lap, ac.getDriverName(d.id)) LeaderboardRow.FASTEST_LAP_ID = d.id if replay_file: write_fastest_lap(replay_file, info.graphics.completedLaps, info.graphics.iCurrentTime, d, fastest_lap) for row in leaderboard: row.mark_fastest_lap() # ============================ # PITS MARKER for row in leaderboard: if ac.isCarInPitline(row.driverId) == 1: row.mark_enter_pits() drivers[row.driverId].tyre = ac.getCarTyreCompound(row.driverId) # maybe will change tyre else: row.mark_left_pits() if time.time() - drivers[row.driverId].pit_time > 20 and ac.isCarInPit(row.driverId): drivers[row.driverId].pits += 1 drivers[row.driverId].pit_time = time.time() # ============================ # CHANGE CAR FOCUS AND DRIVER WIDGET if race_started: id = ac.getFocusedCar() if drivers[id].position <= totalDrivers: # in case it wasnt updated yet driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits) if drivers[id].position == 0: driverComparisonWidget.hide() else: for d in drivers: # find driver ahead if d.position == drivers[id].position - 1: driverComparisonWidget.show(d.id, d.position, id, drivers[id].position, drivers[id].timeDiff*1000) break else: driverWidget.hide() driverComparisonWidget.hide() # ======================================================== # SAVE DRIVER STATUS IN A FILE TO LOAD ON REPLAY if replay_file: write_driver_info(replay_file, info.graphics.completedLaps, info.graphics.iCurrentTime, drivers) # ======================================================== # ================================================================ # QUALIFY / PRACTICE # ================================================================ elif info.graphics.session == 1 or (info.graphics.session == 0 and info.graphics.status != 1): # 3 times per second if timer1 > 0.3: # ============================================= # QUALIFY RESTART if quali_started and qualify_session_time - info.graphics.sessionTimeLeft < 1: quali_started = False # ============================================= # QUALIFY START if not quali_started: ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_BASE_QUALI) if (info.graphics.session == 0 and info.graphics.status != 0): ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_BASE_PRACTICE) ac.setFontColor(lapCountTimerLabel, 0.86, 0.86, 0.86, 1) qualify_session_time = info.graphics.sessionTimeLeft fastest_lap = MAX_LAP_TIME LeaderboardRow.FASTEST_LAP_ID = -1 quali_started = True race_started = False # ============================================= # SAVE BEST LAPS FOR EACH DRIVER for i in range(totalDrivers): lap = ac.getCarState(i, acsys.CS.BestLap) if lap != 0: drivers[i].best_lap = lap if lap != 0 and lap < fastest_lap: fastest_lap = lap fastest_lap_banner.show(lap, ac.getDriverName(i)) # MARK IN/OUT DRIVERS connected = ac.isConnected(i) if connected == 0: # mark unconnected drivers drivers[i].out = True else: drivers[i].out = False # ============================================= # MANAGE LEADERBOARD # Sorting: sort drivers by this order 1. in or out drivers, 2. best lap, 3. driver id dPosition = sorted(drivers, key=lambda d: (d.out, d.best_lap, d.id)) for i in range(totalDrivers): if dPosition[i].out: leaderboard[i].mark_out() else: leaderboard[i].mark_in() leaderboard[i].update_name(dPosition[i].id) if dPosition[i].best_lap == MAX_LAP_TIME: leaderboard[i].update_time("NO TIME") elif i == 0: leaderboard[i].update_time(time_to_string(dPosition[i].best_lap)) else: timeDiff = dPosition[i].best_lap - dPosition[0].best_lap if timeDiff > 60000: leaderboard[i].update_time("+1 MIN") else: leaderboard[i].update_time("+" + time_to_string(timeDiff)) # OVERTAKES if i != dPosition[i].position: # there was an overtake on this driver dPosition[i].timer = FC.OVERTAKE_POSITION_LABEL_TIMER if i < dPosition[i].position: leaderboard[i].mark_green_position() elif i > dPosition[i].position: leaderboard[i].mark_red_position() elif dPosition[i].timer <= 0: leaderboard[i].mark_white_position() else: dPosition[i].timer -= timer1 dPosition[i].position = i # END OVERTAKE # ============================ # FASTEST LAP BANNER TIMER if fastest_lap_banner.timer > 0: fastest_lap_banner.timer -= timer1 fastest_lap_banner.hide() timer1 = 0 # Once per second if timer0 > 1: timer0 = 0 ac.setBackgroundOpacity(leaderboardWindow, 0) ac.setBackgroundOpacity(driverWidget.window, 0) ac.setBackgroundOpacity(driverComparisonWidget.window, 0) ac.setBackgroundOpacity(fastest_lap_banner.window, 0) if quali_started: if info.graphics.sessionTimeLeft < 0: ac.setText(lapCountTimerLabel, "0:00") else: timeText = time_to_string(info.graphics.sessionTimeLeft)[:-4] ac.setText(lapCountTimerLabel, "0:00"[:-len(timeText)] + timeText) if info.graphics.sessionTimeLeft < qualify_session_time / 5: ac.setFontColor(lapCountTimerLabel, 1,0,0,1) driverWidget.hide() driverComparisonWidget.hide() # ================================================================ # REPLAYS # ================================================================ elif info.graphics.status == 1: # three times per second if timer1 > 0.3: if not replay_started: if info.graphics.iCurrentTime > 0: replay_data = load_replay_file(drivers) replay_started = True # ============================ # FASTEST LAP BANNER TIMER if fastest_lap_banner.timer > 0: fastest_lap_banner.timer -= timer1 fastest_lap_banner.hide() # ============================ # GET DATA FOR THIS UPDATE if replay_data: new_positions = lookup_data(info.graphics.completedLaps, info.graphics.iCurrentTime, replay_data, drivers) # ============================ # POSITION UPDATE for i in range(totalDrivers): pos = new_positions[i] if drivers[i].out: # mark unconnected drivers leaderboard[pos].mark_out() else: leaderboard[pos].mark_in() leaderboard[pos].update_name(i) # OVERTAKE if pos != drivers[i].position: # there was an overtake drivers[i].timer = FC.OVERTAKE_POSITION_LABEL_TIMER # set timer if pos < drivers[i].position: leaderboard[pos].mark_green_position() elif pos > drivers[i].position: leaderboard[pos].mark_red_position() elif drivers[i].timer <= 0: leaderboard[pos].mark_white_position() else: drivers[i].timer -= timer1 drivers[i].position = pos # END OVERTAKE timer1 = 0 # Once per second if timer0 > 1: timer0 = 0 ac.setBackgroundOpacity(leaderboardWindow, 0) ac.setBackgroundOpacity(driverWidget.window, 0) ac.setBackgroundOpacity(driverComparisonWidget.window, 0) ac.setBackgroundOpacity(fastest_lap_banner.window, 0) # ============================ # GET FASTEST LAP UPDATE if replay_data: fl_data = lookup_fastest_lap(info.graphics.completedLaps, info.graphics.iCurrentTime, replay_data) if fl_data: display_time = FC.FASTEST_LAP_DISPLAY_TIME - (info.graphics.iCurrentTime - fl_data[0]) / 1000 fastest_lap_banner.show(fl_data[2], ac.getDriverName(fl_data[1]), timer=display_time) # display only for the time left # ============================ # SERVER LAP if replay_data: lc = max((drivers[i].current_lap for i in range(totalDrivers))) + 1 if lc >= replay_data['nLaps']: ac.setVisible(lapCountTimerLabel, 0) ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_FINAL_LAP) else: ac.setText(lapCountTimerLabel, "%d / %d" % (lc, replay_data['nLaps'])) ac.setVisible(lapCountTimerLabel, 1) ac.setBackgroundTexture(leaderboardBaseLabel, FC.LEADERBOARD_BASE_RACE) # ============================ # PITS MARKER for row in leaderboard: if ac.isCarInPitline(row.driverId) == 1: row.mark_enter_pits() else: row.mark_left_pits() # ============================ # DRIVER WIDGET UPDATE if replay_started: id = ac.getFocusedCar() if drivers[id].position <= totalDrivers: # in case it wasnt updated yet driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits) if drivers[id].position == 0: driverComparisonWidget.hide() else: for d in drivers: # find driver ahead if d.position == drivers[id].position - 1: driverComparisonWidget.show(d.id, d.position, id, drivers[id].position, drivers[id].timeDiff*1000) break else: driverWidget.hide() driverComparisonWidget.hide() # ============================ # UPDATE TIMES if replay_data: for row in leaderboard: if LeaderboardRow.update_type == INFO_TYPE.GAPS: row.update_time("+" + time_to_string(drivers[row.driverId].timeDiff*1000)) if row.row == 0: row.update_time("Interval") # Force it elif LeaderboardRow.update_type == INFO_TYPE.POSITIONS: posDiff = drivers[row.driverId].starting_position - drivers[row.driverId].position - 1 row.update_positions(posDiff)
def onFormRender(deltaT): global doRender, CamberIndicators, Options, Labels, redrawText if not doRender: return ac.glColor4f(0.9, 0.9, 0.9, 0.9) #~ ac.setText(Labels["targetCamber"], "{0:.1f}°".format(Options["targetCamber"])) # Suspension travel to find body position relative to tires radius = Options["tireRadius"] pixelsPerMeter = Options["tireHeight"] / radius w,x,y,z = ac.getCarState(0, acsys.CS.SuspensionTravel) dyFL = w * pixelsPerMeter dyFR = x * pixelsPerMeter dyRL = y * pixelsPerMeter dyRR = z * pixelsPerMeter # Draw front "car body" xFR = CamberIndicators["FR"].xPosition xFL = CamberIndicators["FL"].xPosition + Options["tireHeight"] y = CamberIndicators["FR"].yPosition - Options["tireHeight"] / 2 yFL = y + dyFL yFR = y + dyFR h = Options["tireHeight"] / 4 ac.glColor4f(0.9, 0.9, 0.9, 0.9) ac.glBegin(acsys.GL.Lines) ac.glVertex2f(xFL, yFL + h) ac.glVertex2f(xFR, yFR + h) ac.glVertex2f(xFR, yFR + h) ac.glVertex2f(xFR, yFR - h) ac.glVertex2f(xFR, yFR - h) ac.glVertex2f(xFL, yFL - h) ac.glVertex2f(xFL, yFL - h) ac.glVertex2f(xFL, yFL + h) ac.glEnd() # Draw rear "car body" xRR = CamberIndicators["RR"].xPosition xRL = CamberIndicators["RL"].xPosition + Options["tireHeight"] y = CamberIndicators["RR"].yPosition - Options["tireHeight"] / 2 yRL = y + dyRL yRR = y + dyRR h = Options["tireHeight"] / 4 ac.glColor4f(0.9, 0.9, 0.9, 0.9) ac.glBegin(acsys.GL.Lines) ac.glVertex2f(xRL, yRL + h) ac.glVertex2f(xRR, yRR + h) ac.glVertex2f(xRR, yRR + h) ac.glVertex2f(xRR, yRR - h) ac.glVertex2f(xRR, yRR - h) ac.glVertex2f(xRL, yRL - h) ac.glVertex2f(xRL, yRL - h) ac.glVertex2f(xRL, yRL + h) ac.glEnd() # Draw flappy gauges h *= 0.75 CamberIndicators["FL"].drawTire(xFL, yFL, h, flip=True) CamberIndicators["FR"].drawTire(xFR, yFR, h) CamberIndicators["RL"].drawTire(xRL, yRL, h, flip=True) CamberIndicators["RR"].drawTire(xRR, yRR, h) # Draw history graphs if Options["drawGraphs"]: CamberIndicators["FL"].drawGraph(flip=True) CamberIndicators["FR"].drawGraph() CamberIndicators["RL"].drawGraph(flip=True) CamberIndicators["RR"].drawGraph() flC, frC, rlC, rrC = ac.getCarState(0, acsys.CS.CamberRad) CamberIndicators["FL"].setValue(flC, deltaT, Options["optimalCamberF"]) CamberIndicators["FR"].setValue(frC, deltaT, Options["optimalCamberF"]) CamberIndicators["RL"].setValue(rlC, deltaT, Options["optimalCamberR"]) CamberIndicators["RR"].setValue(rrC, deltaT, Options["optimalCamberR"]) # Check if tyre compound changed tyreCompound = ac.getCarTyreCompound(0) if tyreCompound != Options["tyreCompound"]: loadTireData() Options["tyreCompound"] = tyreCompound # Weight Front and Rear by lateral weight transfer filter = 0.97 flL, frL, rlL, rrL = ac.getCarState(0, acsys.CS.Load) outer = max(0.001, flL, frL) inner = min(flL, frL) camberSplit = abs(flC - frC) # DY_LS_FL = DY_REF * pow(TIRE_LOAD_FL / FZ0, LS_EXPY) ls_outer = pow(outer, Options["LS_EXPY"]) ls_inner = pow(inner, Options["LS_EXPY"]) weightXfer = ls_outer / (ls_inner + ls_outer) # (2*(1-w)*D1*rad(c)-(1-2*w)*D0)/(2*D1) oldTargetCamber = Options["optimalCamberF"] Options["optimalCamberF"] = math.degrees((2 * (1 - weightXfer) * Options["dcamber1"] * math.radians(camberSplit) - (1 - 2 * weightXfer) * Options["dcamber0"]) / (2 * Options["dcamber1"])) Options["optimalCamberF"] = filter * oldTargetCamber + (1 - filter) * Options["optimalCamberF"] outer = max(0.001, rlL, rrL) inner = min(rlL, rrL) camberSplit = abs(rlC - rrC) # DY_LS_FL = DY_REF * pow(TIRE_LOAD_FL / FZ0, LS_EXPY) ls_outer = pow(outer, Options["LS_EXPY"]) ls_inner = pow(inner, Options["LS_EXPY"]) weightXfer = ls_outer / (ls_inner + ls_outer) # (2*(1-w)*D1*rad(c)-(1-2*w)*D0)/(2*D1) oldTargetCamber = Options["optimalCamberR"] Options["optimalCamberR"] = math.degrees((2 * (1 - weightXfer) * Options["dcamber1"] * math.radians(camberSplit) - (1 - 2 * weightXfer) * Options["dcamber0"]) / (2 * Options["dcamber1"])) Options["optimalCamberR"] = filter * oldTargetCamber + (1 - filter) * Options["optimalCamberR"] ac.setText(Labels["targetCamberF"], "{0:.1f}°".format(Options["optimalCamberF"])) ac.setText(Labels["targetCamberR"], "{0:.1f}°".format(Options["optimalCamberR"])) if redrawText: updateTextInputs() redrawText = False