Last active 1 month ago

Revision 64e63eacfc6b6b0aa4ac6c276ce2185b47d92bce

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.pallet_rate = 3.33
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.speed_factor = 1.0
188 self.update_display()
189
190 def speed_up(self):
191 self.speed_factor *= 1.10
192 self.update_display()
193
194 def speed_down(self):
195 self.speed_factor *= 0.90
196 self.update_display()
197
198 def replace_pallet(self):
199 if self.pallet_cases >= self.pallet_capacity:
200 self.pallet_cases = 0
201
202 def tick(self):
203
204 if not self.running:
205 return
206
207 for station in self.stations:
208
209 usage_per_second = (
210 station.rate_per_min
211 * self.speed_factor
212 / 60
213 )
214
215 station.current_qty = max(
216 0,
217 station.current_qty - usage_per_second
218 )
219
220 self.update_display()
221
222 self.root.after(
223 UPDATE_MS,
224 self.tick
225 )
226
227 self.pallet_cases += (self.pallet_rate * self.speed_factor / 60)
228
229 if self.pallet_cases > self.pallet_capacity:
230 self.pallet_cases = self.pallet_capacity
231
232 def update_display(self):
233
234 for station, widgets in zip(
235 self.stations,
236 self.station_widgets):
237
238 bar, qty_label, time_label = widgets
239
240 bar["value"] = station.current_qty
241
242 qty_label.config(
243 text=f"Qty: {station.current_qty:.1f}/{station.max_qty}"
244 )
245
246 time_label.config(
247 text=f"Remaining: {station.minutes_remaining:.1f} min"
248 )
249
250 self.speed_label.config(
251 text=f"Line Speed: {self.speed_factor * 100:.0f}%"
252 )
253
254 ranking = sorted(
255 self.stations,
256 key=lambda s: s.minutes_remaining
257 )
258
259 text = "Runout Order\n\n"
260
261 for i, station in enumerate(ranking, start=1):
262 text += (
263 f"{i}. {station.name}"
264 f" - {station.minutes_remaining:.1f} min\n"
265 )
266
267 self.runout_label.config(text=text)
268
269 self.pallet_bar["value"] = self.pallet_cases
270
271 self.pallet_label.config(
272 text=f"{self.pallet_cases:.1f} / "
273 f"{self.pallet_capacity} cases"
274 )
275
276 if self.pallet_cases >= self.pallet_capacity:
277 self.pallet_button.config(state="normal")
278 else:
279 self.pallet_button.config(state="disabled")
280
281
282root = tk.Tk()
283app = RunoutSimulator(root)
284root.mainloop()