RunoutTime.py
· 6.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, rate_per_min, refill_amount):
self.name = name
self.max_qty = max_qty
self.current_qty = max_qty
self.rate_per_min = rate_per_min
self.refill_amount = refill_amount
def reset(self):
self.current_qty = self.max_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.base_line_speed = 60 # devices/min
self.stations = [
Station("Tray Infeed", 135, 11, 27),
Station("Tray Outfeed", 135, 11, 27),
Station("IFU Loader", 2000, 60, 100),
Station("Carton Magazine", 1000, 60, 20),
Station("Case Magazine", 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,
)
bar.pack(fill="x", expand=True)
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)
)
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,
)
self.pallet_bar.pack(fill="x", expand=True)
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"
)
current_speed = self.base_line_speed * self.speed_factor
self.speed_label.config(
text=f"Line Speed: {current_speed:.0f} devices/min "
f"({self.speed_factor * 100:.0f}%)"
)
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, rate_per_min, refill_amount): |
| 9 | self.name = name |
| 10 | self.max_qty = max_qty |
| 11 | self.current_qty = max_qty |
| 12 | self.rate_per_min = rate_per_min |
| 13 | self.refill_amount = refill_amount |
| 14 | |
| 15 | def reset(self): |
| 16 | self.current_qty = self.max_qty |
| 17 | |
| 18 | def add(self): |
| 19 | self.current_qty = min( |
| 20 | self.max_qty, |
| 21 | self.current_qty + self.refill_amount |
| 22 | ) |
| 23 | |
| 24 | @property |
| 25 | def minutes_remaining(self): |
| 26 | if self.rate_per_min <= 0: |
| 27 | return 9999 |
| 28 | return self.current_qty / self.rate_per_min |
| 29 | |
| 30 | |
| 31 | class RunoutSimulator: |
| 32 | |
| 33 | def __init__(self, root): |
| 34 | self.root = root |
| 35 | self.root.title("Runout Simulator") |
| 36 | |
| 37 | self.running = False |
| 38 | self.speed_factor = 1.0 |
| 39 | self.base_line_speed = 60 # devices/min |
| 40 | |
| 41 | self.stations = [ |
| 42 | Station("Tray Infeed", 135, 11, 27), |
| 43 | Station("Tray Outfeed", 135, 11, 27), |
| 44 | Station("IFU Loader", 2000, 60, 100), |
| 45 | Station("Carton Magazine", 1000, 60, 20), |
| 46 | Station("Case Magazine", 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 | ) |
| 117 | bar.pack(fill="x", expand=True) |
| 118 | |
| 119 | qty_label = tk.Label(frame) |
| 120 | qty_label.pack(anchor="w") |
| 121 | |
| 122 | time_label = tk.Label(frame) |
| 123 | time_label.pack(anchor="w") |
| 124 | |
| 125 | refill_btn = tk.Button( |
| 126 | frame, |
| 127 | text=f"+{station.refill_amount}", |
| 128 | command=station.add |
| 129 | ) |
| 130 | refill_btn.pack(anchor="e") |
| 131 | |
| 132 | self.station_widgets.append( |
| 133 | (bar, qty_label, time_label) |
| 134 | ) |
| 135 | |
| 136 | pallet_frame = tk.LabelFrame( |
| 137 | self.root, |
| 138 | text="Finished Pallet", |
| 139 | padx=5, |
| 140 | pady=5 |
| 141 | ) |
| 142 | pallet_frame.pack(fill="x", padx=10, pady=5) |
| 143 | |
| 144 | self.pallet_bar = ttk.Progressbar( |
| 145 | pallet_frame, |
| 146 | maximum=self.pallet_capacity, |
| 147 | ) |
| 148 | self.pallet_bar.pack(fill="x", expand=True) |
| 149 | |
| 150 | self.pallet_label = tk.Label(pallet_frame) |
| 151 | self.pallet_label.pack(anchor="w") |
| 152 | |
| 153 | self.pallet_button = tk.Button( |
| 154 | pallet_frame, |
| 155 | text="Replace Full Pallet", |
| 156 | command=self.replace_pallet |
| 157 | ) |
| 158 | self.pallet_button.pack(anchor="e") |
| 159 | |
| 160 | def start(self): |
| 161 | if not self.running: |
| 162 | self.running = True |
| 163 | self.tick() |
| 164 | |
| 165 | def pause(self): |
| 166 | self.running = False |
| 167 | |
| 168 | def reset(self): |
| 169 | self.running = False |
| 170 | |
| 171 | for station in self.stations: |
| 172 | station.reset() |
| 173 | |
| 174 | self.pallet_cases = 0 |
| 175 | |
| 176 | self.speed_factor = 1.0 |
| 177 | self.update_display() |
| 178 | |
| 179 | def speed_up(self): |
| 180 | self.speed_factor *= 1.10 |
| 181 | self.update_display() |
| 182 | |
| 183 | def speed_down(self): |
| 184 | self.speed_factor *= 0.90 |
| 185 | self.update_display() |
| 186 | |
| 187 | def replace_pallet(self): |
| 188 | if self.pallet_cases >= self.pallet_capacity: |
| 189 | self.pallet_cases = 0 |
| 190 | |
| 191 | def tick(self): |
| 192 | |
| 193 | if not self.running: |
| 194 | return |
| 195 | |
| 196 | for station in self.stations: |
| 197 | |
| 198 | usage_per_second = ( |
| 199 | station.rate_per_min |
| 200 | * self.speed_factor |
| 201 | / 60 |
| 202 | ) |
| 203 | |
| 204 | station.current_qty = max( |
| 205 | 0, |
| 206 | station.current_qty - usage_per_second |
| 207 | ) |
| 208 | |
| 209 | self.pallet_cases += (self.case_rate * self.speed_factor / 60) |
| 210 | |
| 211 | if self.pallet_cases > self.pallet_capacity: |
| 212 | self.pallet_cases = self.pallet_capacity |
| 213 | |
| 214 | self.update_display() |
| 215 | |
| 216 | self.root.after( |
| 217 | UPDATE_MS, |
| 218 | self.tick |
| 219 | ) |
| 220 | |
| 221 | |
| 222 | def update_display(self): |
| 223 | |
| 224 | for station, widgets in zip( |
| 225 | self.stations, |
| 226 | self.station_widgets): |
| 227 | |
| 228 | bar, qty_label, time_label = widgets |
| 229 | |
| 230 | bar["value"] = station.current_qty |
| 231 | |
| 232 | qty_label.config( |
| 233 | text=f"Qty: {station.current_qty:.1f}/{station.max_qty}" |
| 234 | ) |
| 235 | |
| 236 | time_label.config( |
| 237 | text=f"Remaining: {station.minutes_remaining:.1f} min" |
| 238 | ) |
| 239 | |
| 240 | current_speed = self.base_line_speed * self.speed_factor |
| 241 | |
| 242 | self.speed_label.config( |
| 243 | text=f"Line Speed: {current_speed:.0f} devices/min " |
| 244 | f"({self.speed_factor * 100:.0f}%)" |
| 245 | ) |
| 246 | |
| 247 | self.pallet_bar["value"] = self.pallet_cases |
| 248 | |
| 249 | self.pallet_label.config( |
| 250 | text=f"{self.pallet_cases:.1f} / " |
| 251 | f"{self.pallet_capacity} cases" |
| 252 | ) |
| 253 | |
| 254 | if self.pallet_cases >= self.pallet_capacity: |
| 255 | self.pallet_button.config(state="normal") |
| 256 | else: |
| 257 | self.pallet_button.config(state="disabled") |
| 258 | |
| 259 | |
| 260 | root = tk.Tk() |
| 261 | app = RunoutSimulator(root) |
| 262 | root.mainloop() |