Last active 1 month ago

Revision 49ac26cb3916680f199f10844e8262fa5db510dc

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, 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
31class 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
260root = tk.Tk()
261app = RunoutSimulator(root)
262root.mainloop()