Last active 1 month ago

Revision 93c27e6abff0982cde27bd4ecd6e9d32f6789289

RunoutTime.py Raw
1import tkinter as tk
2from tkinter import ttk
3
4UPDATE_MS = 1000 # 1 second
5
6
7class 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
32class 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
285root = tk.Tk()
286app = RunoutSimulator(root)
287root.mainloop()