RunoutTime.py
· 6.8 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("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.case_rate = 3
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.pallet_cases = 0
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.pallet_cases += (self.case_rate * self.speed_factor / 60)
if self.pallet_cases > self.pallet_capacity:
self.pallet_cases = self.pallet_capacity
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_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()
| 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("Tray Outfeed", 135, 135, 11, 27), |
| 44 | Station("IFU Loader", 2000, 2000, 60, 100), |
| 45 | Station("Carton Magazine", 1000, 1000, 60, 20), |
| 46 | Station("Case Magazine", 100, 100, 3, 10), |
| 47 | ] |
| 48 | |
| 49 | self.pallet_capacity = 60 |
| 50 | self.pallet_cases = 0 |
| 51 | self.case_rate = 3 |
| 52 | |
| 53 | self.station_widgets = [] |
| 54 | |
| 55 | self.build_ui() |
| 56 | self.update_display() |
| 57 | |
| 58 | def build_ui(self): |
| 59 | |
| 60 | controls = tk.Frame(self.root) |
| 61 | controls.pack(fill="x", padx=10, pady=5) |
| 62 | |
| 63 | tk.Button( |
| 64 | controls, |
| 65 | text="Start", |
| 66 | command=self.start |
| 67 | ).pack(side="left") |
| 68 | |
| 69 | tk.Button( |
| 70 | controls, |
| 71 | text="Pause", |
| 72 | command=self.pause |
| 73 | ).pack(side="left", padx=5) |
| 74 | |
| 75 | tk.Button( |
| 76 | controls, |
| 77 | text="Reset", |
| 78 | command=self.reset |
| 79 | ).pack(side="left") |
| 80 | |
| 81 | tk.Button( |
| 82 | controls, |
| 83 | text="Speed +10%", |
| 84 | command=self.speed_up |
| 85 | ).pack(side="left", padx=20) |
| 86 | |
| 87 | tk.Button( |
| 88 | controls, |
| 89 | text="Speed -10%", |
| 90 | command=self.speed_down |
| 91 | ).pack(side="left") |
| 92 | |
| 93 | self.speed_label = tk.Label( |
| 94 | controls, |
| 95 | text="Line Speed: 100%" |
| 96 | ) |
| 97 | self.speed_label.pack(side="left", padx=20) |
| 98 | |
| 99 | for station in self.stations: |
| 100 | |
| 101 | frame = tk.LabelFrame( |
| 102 | self.root, |
| 103 | text=station.name, |
| 104 | padx=5, |
| 105 | pady=5 |
| 106 | ) |
| 107 | frame.pack( |
| 108 | fill="x", |
| 109 | padx=10, |
| 110 | pady=5 |
| 111 | ) |
| 112 | |
| 113 | bar = ttk.Progressbar( |
| 114 | frame, |
| 115 | maximum=station.max_qty, |
| 116 | length=400 |
| 117 | ) |
| 118 | bar.pack(anchor="w") |
| 119 | |
| 120 | qty_label = tk.Label(frame) |
| 121 | qty_label.pack(anchor="w") |
| 122 | |
| 123 | time_label = tk.Label(frame) |
| 124 | time_label.pack(anchor="w") |
| 125 | |
| 126 | refill_btn = tk.Button( |
| 127 | frame, |
| 128 | text=f"+{station.refill_amount}", |
| 129 | command=station.add |
| 130 | ) |
| 131 | refill_btn.pack(anchor="e") |
| 132 | |
| 133 | self.station_widgets.append( |
| 134 | (bar, qty_label, time_label) |
| 135 | ) |
| 136 | |
| 137 | self.runout_label = tk.Label( |
| 138 | self.root, |
| 139 | font=("Arial", 12, "bold"), |
| 140 | justify="left" |
| 141 | ) |
| 142 | self.runout_label.pack( |
| 143 | anchor="w", |
| 144 | padx=10, |
| 145 | pady=10 |
| 146 | ) |
| 147 | |
| 148 | pallet_frame = tk.LabelFrame( |
| 149 | self.root, |
| 150 | text="Finished Pallet", |
| 151 | padx=5, |
| 152 | pady=5 |
| 153 | ) |
| 154 | pallet_frame.pack(fill="x", padx=10, pady=5) |
| 155 | |
| 156 | self.pallet_bar = ttk.Progressbar( |
| 157 | pallet_frame, |
| 158 | maximum=self.pallet_capacity, |
| 159 | length=400 |
| 160 | ) |
| 161 | self.pallet_bar.pack(anchor="w") |
| 162 | |
| 163 | self.pallet_label = tk.Label(pallet_frame) |
| 164 | self.pallet_label.pack(anchor="w") |
| 165 | |
| 166 | self.pallet_button = tk.Button( |
| 167 | pallet_frame, |
| 168 | text="Replace Full Pallet", |
| 169 | command=self.replace_pallet |
| 170 | ) |
| 171 | self.pallet_button.pack(anchor="e") |
| 172 | |
| 173 | def start(self): |
| 174 | if not self.running: |
| 175 | self.running = True |
| 176 | self.tick() |
| 177 | |
| 178 | def pause(self): |
| 179 | self.running = False |
| 180 | |
| 181 | def reset(self): |
| 182 | self.running = False |
| 183 | |
| 184 | for station in self.stations: |
| 185 | station.reset() |
| 186 | |
| 187 | self.pallet_cases = 0 |
| 188 | |
| 189 | self.speed_factor = 1.0 |
| 190 | self.update_display() |
| 191 | |
| 192 | def speed_up(self): |
| 193 | self.speed_factor *= 1.10 |
| 194 | self.update_display() |
| 195 | |
| 196 | def speed_down(self): |
| 197 | self.speed_factor *= 0.90 |
| 198 | self.update_display() |
| 199 | |
| 200 | def replace_pallet(self): |
| 201 | if self.pallet_cases >= self.pallet_capacity: |
| 202 | self.pallet_cases = 0 |
| 203 | |
| 204 | def tick(self): |
| 205 | |
| 206 | if not self.running: |
| 207 | return |
| 208 | |
| 209 | for station in self.stations: |
| 210 | |
| 211 | usage_per_second = ( |
| 212 | station.rate_per_min |
| 213 | * self.speed_factor |
| 214 | / 60 |
| 215 | ) |
| 216 | |
| 217 | station.current_qty = max( |
| 218 | 0, |
| 219 | station.current_qty - usage_per_second |
| 220 | ) |
| 221 | |
| 222 | self.pallet_cases += (self.case_rate * self.speed_factor / 60) |
| 223 | |
| 224 | if self.pallet_cases > self.pallet_capacity: |
| 225 | self.pallet_cases = self.pallet_capacity |
| 226 | |
| 227 | self.update_display() |
| 228 | |
| 229 | self.root.after( |
| 230 | UPDATE_MS, |
| 231 | self.tick |
| 232 | ) |
| 233 | |
| 234 | |
| 235 | def update_display(self): |
| 236 | |
| 237 | for station, widgets in zip( |
| 238 | self.stations, |
| 239 | self.station_widgets): |
| 240 | |
| 241 | bar, qty_label, time_label = widgets |
| 242 | |
| 243 | bar["value"] = station.current_qty |
| 244 | |
| 245 | qty_label.config( |
| 246 | text=f"Qty: {station.current_qty:.1f}/{station.max_qty}" |
| 247 | ) |
| 248 | |
| 249 | time_label.config( |
| 250 | text=f"Remaining: {station.minutes_remaining:.1f} min" |
| 251 | ) |
| 252 | |
| 253 | self.speed_label.config( |
| 254 | text=f"Line Speed: {self.speed_factor * 100:.0f}%" |
| 255 | ) |
| 256 | |
| 257 | ranking = sorted( |
| 258 | self.stations, |
| 259 | key=lambda s: s.minutes_remaining |
| 260 | ) |
| 261 | |
| 262 | text = "Runout Order\n\n" |
| 263 | |
| 264 | for i, station in enumerate(ranking, start=1): |
| 265 | text += ( |
| 266 | f"{i}. {station.name}" |
| 267 | f" - {station.minutes_remaining:.1f} min\n" |
| 268 | ) |
| 269 | |
| 270 | self.runout_label.config(text=text) |
| 271 | |
| 272 | self.pallet_bar["value"] = self.pallet_cases |
| 273 | |
| 274 | self.pallet_label.config( |
| 275 | text=f"{self.pallet_cases:.1f} / " |
| 276 | f"{self.pallet_capacity} cases" |
| 277 | ) |
| 278 | |
| 279 | if self.pallet_cases >= self.pallet_capacity: |
| 280 | self.pallet_button.config(state="normal") |
| 281 | else: |
| 282 | self.pallet_button.config(state="disabled") |
| 283 | |
| 284 | |
| 285 | root = tk.Tk() |
| 286 | app = RunoutSimulator(root) |
| 287 | root.mainloop() |