RunoutTime.py
· 5.3 KiB · Python
Raw
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("IFU Loader", 2000, 2000, 60, 100),
Station("Carton Magazine", 1000, 1000, 60, 100),
Station("Case Magazine", 100, 100, 3, 10),
Station("Pallet Capacity", 48, 48, 3, 1),
Station("Spare Buffer", 500, 500, 20, 50),
]
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
)
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 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
)
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_
| 1 | import tkinter as tk |
| 2 | from tkinter import ttk |
| 3 | |
| 4 | UPDATE_MS = 1000 # 1 second |
| 5 | |
| 6 | |
| 7 | class Station: |
| 8 | def __init__(self, name, max_qty, start_qty, rate_per_min, refill_amount): |
| 9 | self.name = name |
| 10 | self.max_qty = max_qty |
| 11 | self.start_qty = start_qty |
| 12 | self.current_qty = start_qty |
| 13 | self.rate_per_min = rate_per_min |
| 14 | self.refill_amount = refill_amount |
| 15 | |
| 16 | def reset(self): |
| 17 | self.current_qty = self.start_qty |
| 18 | |
| 19 | def add(self): |
| 20 | self.current_qty = min( |
| 21 | self.max_qty, |
| 22 | self.current_qty + self.refill_amount |
| 23 | ) |
| 24 | |
| 25 | @property |
| 26 | def minutes_remaining(self): |
| 27 | if self.rate_per_min <= 0: |
| 28 | return 9999 |
| 29 | return self.current_qty / self.rate_per_min |
| 30 | |
| 31 | |
| 32 | class RunoutSimulator: |
| 33 | |
| 34 | def __init__(self, root): |
| 35 | self.root = root |
| 36 | self.root.title("Runout Simulator") |
| 37 | |
| 38 | self.running = False |
| 39 | self.speed_factor = 1.0 |
| 40 | |
| 41 | self.stations = [ |
| 42 | Station("Tray Infeed", 135, 135, 11, 27), |
| 43 | Station("IFU Loader", 2000, 2000, 60, 100), |
| 44 | Station("Carton Magazine", 1000, 1000, 60, 100), |
| 45 | Station("Case Magazine", 100, 100, 3, 10), |
| 46 | Station("Pallet Capacity", 48, 48, 3, 1), |
| 47 | Station("Spare Buffer", 500, 500, 20, 50), |
| 48 | ] |
| 49 | |
| 50 | self.station_widgets = [] |
| 51 | |
| 52 | self.build_ui() |
| 53 | self.update_display() |
| 54 | |
| 55 | def build_ui(self): |
| 56 | |
| 57 | controls = tk.Frame(self.root) |
| 58 | controls.pack(fill="x", padx=10, pady=5) |
| 59 | |
| 60 | tk.Button( |
| 61 | controls, |
| 62 | text="Start", |
| 63 | command=self.start |
| 64 | ).pack(side="left") |
| 65 | |
| 66 | tk.Button( |
| 67 | controls, |
| 68 | text="Pause", |
| 69 | command=self.pause |
| 70 | ).pack(side="left", padx=5) |
| 71 | |
| 72 | tk.Button( |
| 73 | controls, |
| 74 | text="Reset", |
| 75 | command=self.reset |
| 76 | ).pack(side="left") |
| 77 | |
| 78 | tk.Button( |
| 79 | controls, |
| 80 | text="Speed +10%", |
| 81 | command=self.speed_up |
| 82 | ).pack(side="left", padx=20) |
| 83 | |
| 84 | tk.Button( |
| 85 | controls, |
| 86 | text="Speed -10%", |
| 87 | command=self.speed_down |
| 88 | ).pack(side="left") |
| 89 | |
| 90 | self.speed_label = tk.Label( |
| 91 | controls, |
| 92 | text="Line Speed: 100%" |
| 93 | ) |
| 94 | self.speed_label.pack(side="left", padx=20) |
| 95 | |
| 96 | for station in self.stations: |
| 97 | |
| 98 | frame = tk.LabelFrame( |
| 99 | self.root, |
| 100 | text=station.name, |
| 101 | padx=5, |
| 102 | pady=5 |
| 103 | ) |
| 104 | frame.pack( |
| 105 | fill="x", |
| 106 | padx=10, |
| 107 | pady=5 |
| 108 | ) |
| 109 | |
| 110 | bar = ttk.Progressbar( |
| 111 | frame, |
| 112 | maximum=station.max_qty, |
| 113 | length=400 |
| 114 | ) |
| 115 | bar.pack(anchor="w") |
| 116 | |
| 117 | qty_label = tk.Label(frame) |
| 118 | qty_label.pack(anchor="w") |
| 119 | |
| 120 | time_label = tk.Label(frame) |
| 121 | time_label.pack(anchor="w") |
| 122 | |
| 123 | refill_btn = tk.Button( |
| 124 | frame, |
| 125 | text=f"+{station.refill_amount}", |
| 126 | command=station.add |
| 127 | ) |
| 128 | refill_btn.pack(anchor="e") |
| 129 | |
| 130 | self.station_widgets.append( |
| 131 | (bar, qty_label, time_label) |
| 132 | ) |
| 133 | |
| 134 | self.runout_label = tk.Label( |
| 135 | self.root, |
| 136 | font=("Arial", 12, "bold"), |
| 137 | justify="left" |
| 138 | ) |
| 139 | self.runout_label.pack( |
| 140 | anchor="w", |
| 141 | padx=10, |
| 142 | pady=10 |
| 143 | ) |
| 144 | |
| 145 | def start(self): |
| 146 | if not self.running: |
| 147 | self.running = True |
| 148 | self.tick() |
| 149 | |
| 150 | def pause(self): |
| 151 | self.running = False |
| 152 | |
| 153 | def reset(self): |
| 154 | self.running = False |
| 155 | |
| 156 | for station in self.stations: |
| 157 | station.reset() |
| 158 | |
| 159 | self.speed_factor = 1.0 |
| 160 | self.update_display() |
| 161 | |
| 162 | def speed_up(self): |
| 163 | self.speed_factor *= 1.10 |
| 164 | self.update_display() |
| 165 | |
| 166 | def speed_down(self): |
| 167 | self.speed_factor *= 0.90 |
| 168 | self.update_display() |
| 169 | |
| 170 | def tick(self): |
| 171 | |
| 172 | if not self.running: |
| 173 | return |
| 174 | |
| 175 | for station in self.stations: |
| 176 | |
| 177 | usage_per_second = ( |
| 178 | station.rate_per_min |
| 179 | * self.speed_factor |
| 180 | / 60 |
| 181 | ) |
| 182 | |
| 183 | station.current_qty = max( |
| 184 | 0, |
| 185 | station.current_qty - usage_per_second |
| 186 | ) |
| 187 | |
| 188 | self.update_display() |
| 189 | |
| 190 | self.root.after( |
| 191 | UPDATE_MS, |
| 192 | self.tick |
| 193 | ) |
| 194 | |
| 195 | def update_display(self): |
| 196 | |
| 197 | for station, widgets in zip( |
| 198 | self.stations, |
| 199 | self.station_widgets): |
| 200 | |
| 201 | bar, qty_label, time_label = widgets |
| 202 | |
| 203 | bar["value"] = station.current_qty |
| 204 | |
| 205 | qty_label.config( |
| 206 | text=f"Qty: {station.current_qty:.1f}/{station.max_qty}" |
| 207 | ) |
| 208 | |
| 209 | time_label.config( |
| 210 | text=f"Remaining: {station.minutes_remaining:.1f} min" |
| 211 | ) |
| 212 | |
| 213 | self.speed_label.config( |
| 214 | text=f"Line Speed: {self.speed_factor * 100:.0f}%" |
| 215 | ) |
| 216 | |
| 217 | ranking = sorted( |
| 218 | self.stations, |
| 219 | key=lambda s: s.minutes_remaining |
| 220 | ) |
| 221 | |
| 222 | text = "Runout Order\n\n" |
| 223 | |
| 224 | for i, station in enumerate(ranking, start=1): |
| 225 | text += ( |
| 226 | f"{i}. {station.name}" |
| 227 | f" - {station.minutes_ |