import tkinter as tk from tkinter import ttk UPDATE_MS = 1000 # 1 second class Station: def __init__(self, name, max_qty, start_qty, rate_per_min, refill_amount): self.name = name self.max_qty = max_qty self.start_qty = start_qty self.current_qty = start_qty self.rate_per_min = rate_per_min self.refill_amount = refill_amount def reset(self): self.current_qty = self.start_qty def add(self): self.current_qty = min( self.max_qty, self.current_qty + self.refill_amount ) @property def minutes_remaining(self): if self.rate_per_min <= 0: return 9999 return self.current_qty / self.rate_per_min class RunoutSimulator: def __init__(self, root): self.root = root self.root.title("Runout Simulator") self.running = False self.speed_factor = 1.0 self.stations = [ Station("Tray Infeed", 135, 135, 11, 27), Station("Tray Outfeed", 135, 135, 11, 27), Station("IFU Loader", 2000, 2000, 60, 100), Station("Carton Magazine", 1000, 1000, 60, 20), Station("Case Magazine", 100, 100, 3, 10), ] self.pallet_capacity = 60 self.pallet_cases = 0 self.pallet_rate = 3.33 self.station_widgets = [] self.build_ui() self.update_display() def build_ui(self): controls = tk.Frame(self.root) controls.pack(fill="x", padx=10, pady=5) tk.Button( controls, text="Start", command=self.start ).pack(side="left") tk.Button( controls, text="Pause", command=self.pause ).pack(side="left", padx=5) tk.Button( controls, text="Reset", command=self.reset ).pack(side="left") tk.Button( controls, text="Speed +10%", command=self.speed_up ).pack(side="left", padx=20) tk.Button( controls, text="Speed -10%", command=self.speed_down ).pack(side="left") self.speed_label = tk.Label( controls, text="Line Speed: 100%" ) self.speed_label.pack(side="left", padx=20) for station in self.stations: frame = tk.LabelFrame( self.root, text=station.name, padx=5, pady=5 ) frame.pack( fill="x", padx=10, pady=5 ) bar = ttk.Progressbar( frame, maximum=station.max_qty, length=400 ) bar.pack(anchor="w") qty_label = tk.Label(frame) qty_label.pack(anchor="w") time_label = tk.Label(frame) time_label.pack(anchor="w") refill_btn = tk.Button( frame, text=f"+{station.refill_amount}", command=station.add ) refill_btn.pack(anchor="e") self.station_widgets.append( (bar, qty_label, time_label) ) self.runout_label = tk.Label( self.root, font=("Arial", 12, "bold"), justify="left" ) self.runout_label.pack( anchor="w", padx=10, pady=10 ) pallet_frame = tk.LabelFrame( self.root, text="Finished Pallet", padx=5, pady=5 ) pallet_frame.pack(fill="x", padx=10, pady=5) self.pallet_bar = ttk.Progressbar( pallet_frame, maximum=self.pallet_capacity, length=400 ) self.pallet_bar.pack(anchor="w") self.pallet_label = tk.Label(pallet_frame) self.pallet_label.pack(anchor="w") self.pallet_button = tk.Button( pallet_frame, text="Replace Full Pallet", command=self.replace_pallet ) self.pallet_button.pack(anchor="e") def start(self): if not self.running: self.running = True self.tick() def pause(self): self.running = False def reset(self): self.running = False for station in self.stations: station.reset() self.speed_factor = 1.0 self.update_display() def speed_up(self): self.speed_factor *= 1.10 self.update_display() def speed_down(self): self.speed_factor *= 0.90 self.update_display() def replace_pallet(self): if self.pallet_cases >= self.pallet_capacity: self.pallet_cases = 0 def tick(self): if not self.running: return for station in self.stations: usage_per_second = ( station.rate_per_min * self.speed_factor / 60 ) station.current_qty = max( 0, station.current_qty - usage_per_second ) self.update_display() self.root.after( UPDATE_MS, self.tick ) self.pallet_cases += (self.pallet_rate * self.speed_factor / 60) if self.pallet_cases > self.pallet_capacity: self.pallet_cases = self.pallet_capacity def update_display(self): for station, widgets in zip( self.stations, self.station_widgets): bar, qty_label, time_label = widgets bar["value"] = station.current_qty qty_label.config( text=f"Qty: {station.current_qty:.1f}/{station.max_qty}" ) time_label.config( text=f"Remaining: {station.minutes_remaining:.1f} min" ) self.speed_label.config( text=f"Line Speed: {self.speed_factor * 100:.0f}%" ) ranking = sorted( self.stations, key=lambda s: s.minutes_remaining ) text = "Runout Order\n\n" for i, station in enumerate(ranking, start=1): text += ( f"{i}. {station.name}" f" - {station.minutes_remaining:.1f} min\n" ) self.runout_label.config(text=text) self.pallet_bar["value"] = self.pallet_cases self.pallet_label.config( text=f"{self.pallet_cases:.1f} / " f"{self.pallet_capacity} cases" ) if self.pallet_cases >= self.pallet_capacity: self.pallet_button.config(state="normal") else: self.pallet_button.config(state="disabled") root = tk.Tk() app = RunoutSimulator(root) root.mainloop()