itkdb-gtk 0.0.3__py3-none-any.whl → 0.20.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. itkdb_gtk/{sendShipments.py → CreateShipments.py} +74 -78
  2. itkdb_gtk/{getShipments.py → GetShipments.py} +99 -106
  3. itkdb_gtk/GlueWeight.py +45 -66
  4. itkdb_gtk/ITkDB.desktop +8 -0
  5. itkdb_gtk/ITkDB.svg +380 -0
  6. itkdb_gtk/ITkDBlogin.py +10 -6
  7. itkdb_gtk/ITkDButils.py +295 -57
  8. itkdb_gtk/PanelVisualInspection.py +590 -0
  9. itkdb_gtk/QRScanner.py +120 -0
  10. itkdb_gtk/SensorUtils.py +492 -0
  11. itkdb_gtk/ShowAttachments.py +267 -0
  12. itkdb_gtk/ShowComments.py +94 -0
  13. itkdb_gtk/ShowDefects.py +103 -0
  14. itkdb_gtk/UploadModuleIV.py +566 -0
  15. itkdb_gtk/UploadMultipleTests.py +746 -0
  16. itkdb_gtk/UploadTest.py +509 -0
  17. itkdb_gtk/VisualInspection.py +297 -0
  18. itkdb_gtk/WireBondGui.py +1304 -0
  19. itkdb_gtk/__init__.py +38 -12
  20. itkdb_gtk/dashBoard.py +292 -33
  21. itkdb_gtk/dbGtkUtils.py +356 -75
  22. itkdb_gtk/findComponent.py +242 -0
  23. itkdb_gtk/findVTRx.py +36 -0
  24. itkdb_gtk/readGoogleSheet.py +1 -2
  25. itkdb_gtk/untrash_component.py +35 -0
  26. {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/METADATA +21 -12
  27. itkdb_gtk-0.20.1.dist-info/RECORD +30 -0
  28. {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/WHEEL +1 -1
  29. itkdb_gtk-0.20.1.dist-info/entry_points.txt +12 -0
  30. itkdb_gtk/checkComponent.py +0 -131
  31. itkdb_gtk/groundingTest.py +0 -225
  32. itkdb_gtk/readAVSdata.py +0 -565
  33. itkdb_gtk/uploadPetalInformation.py +0 -604
  34. itkdb_gtk/uploadTest.py +0 -384
  35. itkdb_gtk-0.0.3.dist-info/RECORD +0 -19
  36. itkdb_gtk-0.0.3.dist-info/entry_points.txt +0 -7
  37. {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1304 @@
1
+ #!/usr/bin/env python3
2
+ """Wirebonding GUI for PSB."""
3
+
4
+ import sys
5
+ import re
6
+ import json
7
+ import copy
8
+ from pathlib import Path
9
+ from collections import namedtuple
10
+ import gi
11
+
12
+ gi.require_version("Gtk", "3.0")
13
+ from gi.repository import Gtk, Gio, Gdk
14
+
15
+ try:
16
+ import itkdb_gtk
17
+
18
+ except ImportError:
19
+ cwd = Path(__file__).parent.parent
20
+ sys.path.append(cwd.as_posix())
21
+ import itkdb_gtk
22
+
23
+ __HELP_LINK__="https://itkdb-gtk.docs.cern.ch/wirebondTest.html"
24
+
25
+ from itkdb_gtk import dbGtkUtils
26
+ from itkdb_gtk import ITkDBlogin, ITkDButils, UploadTest
27
+
28
+
29
+ #valid_channel = re.compile("(^[0-9]+)-([0-9]+)")
30
+ valid_channel = re.compile("^[0-9]+[\\s*\\,\\s,-[0-9]+]*")
31
+
32
+
33
+ test_parameters = {
34
+ "Repaired Row 1": "REPAIRED_FRONTEND_ROW1",
35
+ "Failed Row 1": "FAILED_FRONTEND_ROW1",
36
+ "Repaired Row 2": "REPAIRED_FRONTEND_ROW2",
37
+ "Failed Row 2": "FAILED_FRONTEND_ROW2",
38
+ "Repaired Row 3": "REPAIRED_FRONTEND_ROW3",
39
+ "Failed Row 3": "FAILED_FRONTEND_ROW3",
40
+ "Repaired Row 4": "REPAIRED_FRONTEND_ROW4",
41
+ "Failed Row 4": "FAILED_FRONTEND_ROW4",
42
+ "Repaired Hyb->PB": "REPAIRED_HYBRID_TO_PB",
43
+ "Failed HyB->PB": "FAILED_HYBRID_TO_PB",
44
+ "Repaired Module->Frame": "REPAIRED_MODULE_TO_FRAME",
45
+ "Failed Module->Frame": "FAILED_MODULE_TO_FRAME"
46
+ }
47
+
48
+ # module_param[iring][i_sensor][i_hybrid][i_row]
49
+ module_param = {
50
+ 0: [
51
+ [
52
+ [[255, 830], [1343, 1918], [2431, 3006], [3519, 4094]],
53
+ [[831, 1342], [1919, 2430], [3007, 3518], [4095, 4606]],
54
+ ]
55
+ ],
56
+ 1: [
57
+ [
58
+ [[271, 974], [1615, 2318], [2959, 3662], [4303, 5006]],
59
+ [[975, 1614], [2319, 2958], [3663, 4302], [5007, 5646]],
60
+ ]
61
+ ],
62
+ 2: [
63
+ [
64
+ [[201, 968], [969, 1736], [1737, 2504], [2505, 3272]]
65
+ ]
66
+ ],
67
+ 3: [
68
+ [
69
+ [[566, 1013], [2358, 2805], [4150, 4597], [5942, 6389]],
70
+ [[1462, 1909], [3254, 3701], [5046, 5493], [6838, 7285]]
71
+ ],
72
+ [
73
+ [[1014, 1461], [2806, 3253], [4598, 5045], [6390, 6837]],
74
+ [[1910, 2357], [3702, 4149], [5494, 5941], [7286, 7733]]
75
+ ]
76
+ ],
77
+ 4: [
78
+ [
79
+ [[318, 829], [1342, 1853], [2366, 2877], [3390, 3901]]
80
+ ],
81
+ [
82
+ [[830, 1341], [1854, 2365], [2878, 3389], [3902, 4413]]
83
+ ]
84
+ ],
85
+ 5: [
86
+ [
87
+ [[332, 907], [1484, 2059], [2636, 3211], [3788, 4363]]
88
+ ],
89
+ [
90
+ [[908, 1483], [2060, 2635], [3212, 3787], [4364, 4939]]
91
+ ]
92
+ ],
93
+ }
94
+
95
+ module_to_tf = {
96
+ 0: [],
97
+ 1: [],
98
+ 2: [],
99
+ 3: [],
100
+ 4: [],
101
+ 5: []
102
+ }
103
+
104
+ hyb_to_pb = {
105
+ 0: [],
106
+ 1: [],
107
+ 2: [],
108
+ 3: [],
109
+ 4: [],
110
+ 5: []
111
+ }
112
+
113
+
114
+ def range_to_list(V):
115
+ """Convert a range to a list.
116
+
117
+ ch1-ch2 or ch1:ch2 -> [ch1, ch1+1, ch1+2, ..., ch2] or
118
+ ch1:step:ch2 -> [ch1, ch1+sep, ch1+2*step, ..., ch2]
119
+ """
120
+ nfound = 0
121
+ for c in "-:,":
122
+ if c in V:
123
+ nfound += 1
124
+ break
125
+
126
+ if nfound == 0:
127
+ return [V]
128
+
129
+ out = []
130
+ values = V.split(',')
131
+ for V in values:
132
+ if '-' in V:
133
+ endpoints = list(map(int, V.split('-')))
134
+ endpoints.sort()
135
+ for i in range(endpoints[0], endpoints[1]+1):
136
+ out.append(str(i))
137
+ elif ':' in V:
138
+ endpoints = list(map(int, V.split(':')))
139
+ if len(endpoints) == 2:
140
+ endpoints.sort()
141
+ for i in range(endpoints[0], endpoints[1]+1):
142
+ out.append(str(i))
143
+
144
+ elif len(endpoints) == 3:
145
+ for i in range(endpoints[0], endpoints[2]+1, endpoints[1]):
146
+ out.append(str(i))
147
+
148
+ else:
149
+ print("Wring range specification. {}".format(V))
150
+ continue
151
+
152
+ else:
153
+ out.append(V)
154
+
155
+ return out
156
+
157
+ def count_items(items):
158
+ """Count number of channels from results."""
159
+ nitems = 0
160
+ for key in items.keys():
161
+ nitems += len(range_to_list(key))
162
+
163
+ return nitems
164
+
165
+ def find_holes(chan_list, min_chan=0, max_chan=999999):
166
+ """Find groups of consecutive channels."""
167
+ out = sorted(chan_list)
168
+ nchan = 0
169
+ last_chan = -1
170
+ ichan = -1
171
+ holes = []
172
+ for chan in out:
173
+ if chan < min_chan or chan > max_chan:
174
+ continue
175
+
176
+ if last_chan < 0:
177
+ last_chan = chan
178
+ continue
179
+
180
+ if chan - last_chan > 1:
181
+ if nchan:
182
+ holes.append([ichan, nchan])
183
+ nchan = 0
184
+ ichan = -1
185
+ else:
186
+ if last_chan < max_chan and chan >= max_chan:
187
+ # WE are in another sensor
188
+ holes.append([ichan, nchan])
189
+ nchan = 0
190
+ ichan = -1
191
+ last_chan = chan
192
+ continue
193
+
194
+ nchan += 1
195
+ if ichan < 0:
196
+ ichan = last_chan
197
+ nchan += 1
198
+
199
+ last_chan = chan
200
+
201
+ if nchan:
202
+ holes.append([ichan, nchan])
203
+
204
+ return holes
205
+
206
+
207
+ def wire2strip(iwire, irow, first_chan):
208
+ """From bond to strip number."""
209
+ if irow % 2:
210
+ istrip = 2*(iwire-first_chan) + 1
211
+ else:
212
+ istrip = 2*(iwire-first_chan)
213
+
214
+ return istrip
215
+
216
+
217
+ class Hole:
218
+ """A range of consecutive unconnected channels."""
219
+ def __init__(self, *args):
220
+ for i, name in enumerate(['sensor', 'hybrid', 'row', 'chan', 'width']):
221
+ setattr(self, name, args[i])
222
+
223
+ def __repr__(self):
224
+ return "sensor: {} hyb: {} row: {} chan: {} width: {}".format(
225
+ self.sensor, self.hybrid, self.row, self.chan, self.width
226
+ )
227
+
228
+ class HybridHoles:
229
+ """Holes in hybrid bonds.
230
+
231
+ Holes are defined by a list [first_chan, n_chan].
232
+ """
233
+
234
+ def __init__(self, param, win=None, hid=0):
235
+ """Initialization.
236
+
237
+ Args:
238
+ param: Hybrid wirebon parameters.
239
+
240
+ """
241
+ self.win = win
242
+ self.id = hid
243
+ self.param = param
244
+ self.nchan = 0
245
+ for min_chan, max_chan in param:
246
+ self.nchan += (max_chan-min_chan+1)
247
+
248
+ self.holes = [[] for irow in range(4)]
249
+ self.channels = [[] for irow in range(4)]
250
+ # Sensor strips for each of the strip rows "served" by a hybrid.
251
+ self.sensor_channels = [[], []]
252
+ self.sensor_holes = [[], []]
253
+
254
+ def ready(self):
255
+ """Call when all channels are in."""
256
+ for irow, C in enumerate(self.channels):
257
+ C.sort()
258
+ self.holes[irow] = find_holes(C)
259
+
260
+ for irow, S in enumerate(self.sensor_channels):
261
+ S.sort()
262
+ self.sensor_holes[irow] = find_holes(S)
263
+
264
+ def add_channel(self, irow, ichan)->bool:
265
+ """Add a new channel in row.
266
+
267
+ Args:
268
+ irow: row number
269
+ ichan: channel number
270
+
271
+ Returns:
272
+ True if added, False otherwise.
273
+
274
+ """
275
+ first_chan = self.param[irow][0]
276
+ last_chan = self.param[irow][1]
277
+ strip_row = int(irow/2)
278
+
279
+
280
+ if isinstance(ichan, list) or isinstance(ichan, tuple):
281
+ nadded = 0
282
+ for ich in ichan:
283
+ if first_chan <= ich <= last_chan:
284
+ self.channels[irow].append(ich)
285
+ nadded += 1
286
+
287
+ self.channels[irow] = sorted(self.channels[irow])
288
+ for iwire in self.channels[irow]:
289
+ istrip = wire2strip(iwire, irow, first_chan)
290
+ self.sensor_channels[strip_row].append(istrip)
291
+
292
+ return nadded>0
293
+
294
+ if first_chan <= ichan <= last_chan:
295
+ self.channels[irow].append(ichan)
296
+ istrip = wire2strip(ichan, irow, first_chan)
297
+ self.sensor_channels[strip_row].append(istrip)
298
+ return True
299
+
300
+ return False
301
+
302
+ def get_n_unconnected(self) -> list:
303
+ """Count number of unconnected channels.
304
+
305
+ Return a list, one item per row.
306
+ """
307
+ nchan = [len(C) for C in self.channels]
308
+ return nchan
309
+
310
+ def get_max_consecutive_from_list(self, holes):
311
+ """Return max widht of holes."""
312
+ mx_width = []
313
+ lst_holes = []
314
+ for irow, row in enumerate(holes):
315
+ if len(row) == 0:
316
+ mxW = 0
317
+
318
+ else:
319
+ mxW = -1
320
+ for h in row:
321
+ lst_holes.append(Hole(0, self.id, irow, h[0], h[1]))
322
+ mxW = max(mxW, h[1])
323
+
324
+ mx_width.append(mxW)
325
+
326
+ return mx_width, lst_holes
327
+
328
+
329
+ def get_max_consecutive(self):
330
+ """Returns the largest 'hole'."""
331
+ mx_width, _ = self.get_max_consecutive_from_list(self.holes)
332
+ return mx_width
333
+
334
+ def get_max_sensor_consecutive(self):
335
+ """Return largest hole in sensor."""
336
+ mx_width, holes = self.get_max_consecutive_from_list(self.sensor_holes)
337
+ return mx_width, holes
338
+
339
+ class SensorHoles:
340
+ """Holes in sensor."""
341
+
342
+ def __init__(self, param, win=None, sid=0):
343
+ """Initialization.
344
+
345
+ Args:
346
+ param: sensor wirebon params
347
+ """
348
+ self.win = win
349
+ self.id = sid
350
+ self.param = param
351
+ self.nchan = 0
352
+ self.nhybrid = len(param)
353
+ self.hybrids = []
354
+ for i, P in enumerate(param):
355
+ H = HybridHoles(P, hid=i, win=win)
356
+ self.hybrids.append(H)
357
+ self.nchan += H.nchan
358
+
359
+ def ready(self):
360
+ """Call when all channels are in."""
361
+ for H in self.hybrids:
362
+ H.ready()
363
+
364
+ def get_n_hyb(self):
365
+ """Return number of hybrids."""
366
+ return len(self.hybrids)
367
+
368
+ def get_hybrid(self, i):
369
+ """Return i-th hybrid."""
370
+ return self.hybrids[i]
371
+
372
+ def get_max_consecutive(self):
373
+ """Max number of consecutive unconnected channels.
374
+
375
+ This is ordered by row.
376
+ """
377
+ mx_width = [0 for x in range(4)]
378
+ for hyb in self.hybrids:
379
+ mxW = hyb.get_max_consecutive()
380
+ for j, val in enumerate(mxW):
381
+ mx_width[j] = max(mx_width[j] , val)
382
+
383
+ return mx_width
384
+
385
+ def get_max_sensor_consecutive(self):
386
+ """MAx widht of holes in sensor."""
387
+ mx_width = -1
388
+ holes = []
389
+ for hyb in self.hybrids:
390
+ mxW, hyb_holes = hyb.get_max_sensor_consecutive()
391
+ for H in hyb_holes:
392
+ H.sensor = self.id
393
+
394
+ holes.extend(hyb_holes)
395
+ for v in mxW:
396
+ mx_width = max(mx_width, v)
397
+
398
+ return mx_width, holes
399
+
400
+ def get_n_unconnected(self):
401
+ """Count number of unconnected channels.
402
+
403
+ Return a list, one item per row.
404
+ """
405
+ nchan = [0, 0, 0, 0]
406
+ for hyb in self.hybrids:
407
+ nc = hyb.get_n_unconnected()
408
+ for i, n in enumerate(nc):
409
+ nchan[i] += n
410
+
411
+ return nchan
412
+
413
+
414
+ class ModuleHoles:
415
+ """Holes in Modules."""
416
+
417
+ def __init__(self, param, win=None):
418
+ """Initialization.
419
+
420
+ Args:
421
+ param: module wirebond params
422
+ """
423
+ self.win = win
424
+ self.nsensor = len(param)
425
+ self.nchan = 0
426
+ self.sensors = []
427
+ for i, P in enumerate(param):
428
+ S = SensorHoles(P, sid=i, win=win)
429
+ self.sensors.append(S)
430
+ self.nchan += S.nchan
431
+
432
+ def ready(self):
433
+ """Call when all channels are in."""
434
+ for S in self.sensors:
435
+ S.ready()
436
+
437
+ def get_max_consecutive(self):
438
+ """Max number of consecutive unconnected channels.
439
+
440
+ This is ordered by row.
441
+ """
442
+ mx_width = [-1, -1, -1, -1]
443
+ for S in self.sensors:
444
+ mxW = S.get_max_consecutive()
445
+ for j in range(4):
446
+ mx_width[j] = max(mx_width[j], mxW[j])
447
+
448
+ return mx_width
449
+
450
+ def get_max_sensor_consecutive(self):
451
+ """The maximum number of consecutive channels per strip row."""
452
+ mx_width = -1
453
+ module_holes = []
454
+ for S in self.sensors:
455
+ width, holes = S.get_max_sensor_consecutive()
456
+ module_holes.extend(holes)
457
+ mx_width = max(mx_width, width)
458
+
459
+ return mx_width, module_holes
460
+
461
+
462
+ def get_n_unconnected(self) -> list:
463
+ """Count number of unconnected channels.
464
+
465
+ Return a list, one item per row.
466
+ """
467
+ nchan = [0, 0, 0, 0]
468
+ for S in self.sensors:
469
+ nc = S.get_n_unconnected()
470
+ for i, n in enumerate(nc):
471
+ nchan[i] += n
472
+
473
+ return nchan
474
+
475
+ def get_total_unconnected(self) -> int:
476
+ """Return total number of unconnected wires."""
477
+ unc = self.get_n_unconnected()
478
+ out = 0
479
+ for v in unc:
480
+ out += v
481
+
482
+ return int(out)
483
+
484
+
485
+ def get_module_param(SN):
486
+ """Get parameters of module type.
487
+
488
+ Args:
489
+ SN: Serial Number
490
+
491
+ Returns:
492
+ list: list with bond numbers.
493
+ """
494
+ if len(SN) != 14 or SN[:3]!="20U":
495
+ raise ValueError("Wrong serial number {}".format(SN))
496
+
497
+ if SN[3:5] != "SE":
498
+ raise ValueError("Cannot handle barrel modules yet.")
499
+
500
+ mod_type = SN[5:7]
501
+ if mod_type[0] != "M":
502
+ raise ValueError("Does not seem to be a RingModule: {}".format(SN))
503
+
504
+ ring = int(mod_type[-1])
505
+ param = module_param[ring]
506
+
507
+ return param
508
+
509
+
510
+ class WireBond(dbGtkUtils.ITkDBWindow):
511
+ """Main window."""
512
+
513
+ def __init__(self, session, title="", help_link=__HELP_LINK__):
514
+ """Initialization."""
515
+ super().__init__(title=title, session=session, help_link=help_link)
516
+ self.pdb = None
517
+ self.models = {}
518
+ self.holes = {}
519
+ self.institute = self.pdb_user["institutions"][0]["code"]
520
+ self.inst_combo = None
521
+ self.module_SN = None
522
+ self.alternativeID = None
523
+ self.combo = None
524
+ self.tree = None
525
+ self.lut = {}
526
+ self.module_type = None
527
+ self.init_window()
528
+
529
+ def init_window(self):
530
+ """Creates the GUI."""
531
+ self.hb.props.title = "Wire Bond"
532
+
533
+ # Button to upload
534
+ button = Gtk.Button()
535
+ icon = Gio.ThemedIcon(name="document-send-symbolic")
536
+ image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
537
+ button.add(image)
538
+ button.set_tooltip_text("Click to upload test")
539
+ button.connect("clicked", self.upload_test)
540
+ self.hb.pack_end(button)
541
+
542
+ button = Gtk.Button()
543
+ icon = Gio.ThemedIcon(name="document-save-symbolic")
544
+ image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
545
+ button.add(image)
546
+ button.set_tooltip_text("Click to save test file")
547
+ button.connect("clicked", self.save_test)
548
+ self.hb.pack_end(button)
549
+
550
+ # Button to upload
551
+ button = Gtk.Button()
552
+ icon = Gio.ThemedIcon(name="document-open-symbolic")
553
+ image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
554
+ button.add(image)
555
+ button.set_tooltip_text("Click to read local data file.")
556
+ button.connect("clicked", self.read_file)
557
+ self.hb.pack_end(button)
558
+
559
+ # Data panel
560
+ grid = Gtk.Grid(column_spacing=5, row_spacing=1)
561
+
562
+ # The shipment receiver
563
+ institute = self.create_institute_combo(True)
564
+ institute.connect("changed", self.on_institute)
565
+ institute.set_tooltip_text("Select the Institute.")
566
+ dbGtkUtils.set_combo_iter(institute, self.institute)
567
+
568
+ lbl = Gtk.Label(label="Institute")
569
+ lbl.set_xalign(0)
570
+ grid.attach(lbl, 0, 0, 1, 1)
571
+ grid.attach(institute, 1, 0, 1, 1)
572
+ self.inst_combo = institute
573
+
574
+
575
+
576
+ # The lookup table
577
+ lbl = Gtk.Label(label="Lookup Table")
578
+ lbl.set_xalign(0)
579
+ grid.attach(lbl, 2, 0, 1, 1)
580
+
581
+ self.testF = Gtk.FileChooserButton()
582
+ self.testF.set_tooltip_text("Click to select Lookup table.")
583
+
584
+ grid.attach(self.testF, 3, 0, 1, 1)
585
+ self.testF.connect("file-set", self.on_lut)
586
+
587
+
588
+ for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN", "Date"]):
589
+ lbl = Gtk.Label(label=tit)
590
+ lbl.set_xalign(0)
591
+ grid.attach(lbl, 0, i+1, 1, 1)
592
+
593
+ self.operator = dbGtkUtils.new_small_text_entry()
594
+ self.machine = dbGtkUtils.new_small_text_entry()
595
+ self.batch = dbGtkUtils.new_small_text_entry()
596
+ #self.SN = dbGtkUtils.new_small_text_entry()
597
+
598
+ self.SN = itkdb_gtk.dbGtkUtils.TextEntry(small=True)
599
+ self.SN.connect("text-changed", self.on_SN_changed)
600
+
601
+
602
+ self.date = dbGtkUtils.TextEntry(small=True)
603
+ self.date.entry.set_text(ITkDButils.get_db_date())
604
+ self.date.connect("text_changed", self.new_date)
605
+
606
+ grid.attach(self.operator, 1, 1, 1, 1)
607
+ grid.attach(self.machine, 1, 2, 1, 1)
608
+ grid.attach(self.batch, 1, 3, 1, 1)
609
+ grid.attach(self.SN.entry, 1, 4, 1, 1)
610
+ grid.attach(self.date.widget, 1, 5, 1, 1)
611
+
612
+ self.mainBox.pack_start(grid, True, True, 0)
613
+
614
+ # Prepare combo and all the models
615
+ lbl = Gtk.Label(label="Choose section.")
616
+ lbl.set_xalign(0)
617
+ self.mainBox.pack_start(lbl, True, True, 0)
618
+
619
+ combo = self.create_combo()
620
+ self.mainBox.pack_start(combo, True, True, 0)
621
+
622
+ # The tree view
623
+ scrolled = self.create_tree_view()
624
+ self.mainBox.pack_start(scrolled, True, True, 5)
625
+
626
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
627
+ self.mainBox.pack_start(box, False, False, 0)
628
+ dbGtkUtils.add_button_to_container(box, "Add Bond",
629
+ "Click to add a new bond or bond range.",
630
+ self.add_item)
631
+
632
+ dbGtkUtils.add_button_to_container(box, "Remove Bond",
633
+ "Click to remove selected bond.",
634
+ self.remove_item)
635
+
636
+ #
637
+ # The text view and buffer
638
+ #
639
+ self.mainBox.pack_start(self.message_panel.frame, True, True, 0)
640
+ self.write_message("wirebond GUI\n")
641
+
642
+ def on_lut(self, fdlg):
643
+ """Get look-up table."""
644
+ fnam = Path(fdlg.get_filename())
645
+ if not fnam.exists():
646
+ dbGtkUtils.complain("Cannot open Luukup Table.",
647
+ "File {} does not exist.".format(fnam))
648
+ return
649
+
650
+ lut = {}
651
+ module_map = {}
652
+ section = None
653
+ i_local = 0
654
+ i_std = 1
655
+ indx = ["local", "standard"]
656
+ found_format = False
657
+ with open(fnam, 'r', encoding="UTF-8") as fin:
658
+ for line in fin:
659
+ line = line.strip()
660
+
661
+ # Remove comments.
662
+ ipos = line.find('#')
663
+ jpos = line.find("#!")
664
+ if jpos>=0 and ipos==jpos:
665
+ if found_format:
666
+ dbGtkUtils.complain("A second format line was found.",
667
+ "Onely one is allowed. stopr map parsing.")
668
+ return
669
+
670
+ indx = [x.lower() for x in line[ipos+2:].split()]
671
+ try:
672
+ i_local = indx.index("local")
673
+ i_std = indx.index("standard")
674
+ found_format = True
675
+ except ValueError:
676
+ dbGtkUtils.complain("Wrong format desciption string.",
677
+ "The words 'local' and 'standard' should be there.\n{}".format(line))
678
+ return
679
+
680
+ continue
681
+
682
+ if ipos >= 0:
683
+ line = line[:ipos].strip()
684
+
685
+ if len(line) == 0:
686
+ continue
687
+
688
+ # Check for new section
689
+ ipos = line.find(':')
690
+ if ipos >= 0:
691
+ section = line[:ipos].strip().upper()
692
+ if section in module_map:
693
+ dbGtkUtils.complain("Section {} already in map.".format(section), "Stop parsing bond Lookup table.")
694
+ return
695
+
696
+ lut = {}
697
+ module_map[section] = lut
698
+ continue
699
+
700
+ if section is None:
701
+ continue
702
+
703
+ values = line.split()
704
+ if len(values)!=len(indx):
705
+ dbGtkUtils.complain("Cannot read Lookup table.", "Wrong line format: {}".format(line))
706
+ return
707
+
708
+ v_local = range_to_list(values[i_local])
709
+ v_std = range_to_list(values[i_std])
710
+
711
+ if len(v_local) != len(v_std):
712
+ dbGtkUtils.complain("Wrong Lookup table.",
713
+ "Ranges have different length: {}".format(line))
714
+ return
715
+
716
+ for L, S in zip(v_local, v_std):
717
+ lut[L] = S
718
+
719
+ self.lut = module_map
720
+
721
+ def convert_channel(self, C):
722
+ """Convert channel according to LUT
723
+
724
+ Args:
725
+ C (str): channel number
726
+
727
+ """
728
+ try:
729
+ return self.lut[self.module_type][C]
730
+
731
+ except KeyError:
732
+ return C
733
+
734
+ def on_institute(self, combo):
735
+ """Institute changed."""
736
+ name = self.get_institute_from_combo(combo)
737
+ if name:
738
+ self.institute = name
739
+
740
+ def on_SN_changed(self, entry, value):
741
+ """New SN given. Ask in PDB,"""
742
+ if len(value) <= 0:
743
+ return
744
+
745
+ obj = itkdb_gtk.ITkDButils.get_DB_component(self.session, value)
746
+ if obj is not None and obj["serialNumber"] is not None:
747
+ entry.set_text(obj["serialNumber"])
748
+ alternativeID = obj["alternativeIdentifier"]
749
+ module_SN = obj["serialNumber"]
750
+ if len(module_SN) == 14 and module_SN.startswith("20USEM"):
751
+ self.module_SN = module_SN
752
+ self.alternativeID = alternativeID
753
+ self.module_type = module_SN[5:7]
754
+
755
+ else:
756
+ itkdb_gtk.dbGtkUtils.complain("Invalid SN: {}".format(module_SN), "Not a Ring module")
757
+
758
+ else:
759
+ itkdb_gtk.dbGtkUtils.complain("Invalid SN", value)
760
+
761
+ def on_name_combo_changed(self, combo):
762
+ """Change model in TreeView."""
763
+ tree_iter = combo.get_active_iter()
764
+ if tree_iter is not None:
765
+ model = combo.get_model()
766
+ param = model[tree_iter][1]
767
+ view_model = self.models[param]
768
+ self.tree.set_model(view_model)
769
+ else:
770
+ self.write_message("Cannot find model for {}\n".format(param))
771
+
772
+ def create_combo(self):
773
+ """Create the combo."""
774
+ model = Gtk.ListStore(str, str)
775
+ for txt, param in test_parameters.items():
776
+ model.append([txt, param])
777
+
778
+ M = Gtk.ListStore(str, str)
779
+ M.append(["", ""])
780
+ self.models[param] = M
781
+
782
+ self.combo = Gtk.ComboBox.new_with_model_and_entry(model)
783
+ self.combo.set_entry_text_column(0)
784
+ self.combo.set_active(0)
785
+ self.combo.connect("changed", self.on_name_combo_changed)
786
+ return self.combo
787
+
788
+ def create_tree_view(self, size=150):
789
+ """Creates the tree vew with the attachmens."""
790
+ tree_iter = self.combo.get_active_iter()
791
+ combo_model = self.combo.get_model()
792
+ param = combo_model[tree_iter][1]
793
+ model = self.models[param]
794
+
795
+ self.tree = Gtk.TreeView(model=model)
796
+ self.tree.connect("button-press-event", self.button_pressed)
797
+
798
+ scrolled = Gtk.ScrolledWindow()
799
+ scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
800
+ scrolled.add(self.tree)
801
+ scrolled.set_size_request(-1, size)
802
+
803
+ renderer = Gtk.CellRendererText()
804
+ renderer.set_property("editable", True)
805
+ renderer.connect("edited", self.channel_edited)
806
+ column = Gtk.TreeViewColumn("Channel", renderer, text=0)
807
+ self.tree.append_column(column)
808
+
809
+ renderer = Gtk.CellRendererText()
810
+ renderer.set_property("editable", True)
811
+ renderer.connect("edited", self.failure_edited)
812
+ column = Gtk.TreeViewColumn("Failure", renderer, text=1)
813
+ self.tree.append_column(column)
814
+
815
+ return scrolled
816
+
817
+ def text_edited(self, icol, path, text):
818
+ """Text has been edited in the TreeView"""
819
+ if len(text) == 0:
820
+ return
821
+
822
+ model = self.tree.get_model()
823
+
824
+ n_child = model.iter_n_children()
825
+ current_child = int(path)
826
+ if n_child-current_child == 1:
827
+ model.append(["", ""])
828
+
829
+ model[path][icol] = text
830
+
831
+ def channel_edited(self, widget, path, text):
832
+ """Handles edition in channel number cell."""
833
+ if not text.isnumeric():
834
+ if valid_channel.match(text) is None:
835
+ dbGtkUtils.complain("Wrong channel number", "Invalid channel number: {}".format(text))
836
+ return
837
+
838
+ self.text_edited(0, path, text)
839
+
840
+ def failure_edited(self, widget, path, text):
841
+ """Handles edition in comment."""
842
+ self.text_edited(1, path, text)
843
+
844
+
845
+ def new_date(self, entry, value):
846
+ """new date given at input."""
847
+ d = dbGtkUtils.parse_date_as_string(value)
848
+ if d is not None:
849
+ self.date.set_text(d)
850
+
851
+ def button_pressed(self, tree, event):
852
+ """Button pressed."""
853
+ # double click shows attachments
854
+ if event.button == 1 and event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
855
+ #self.write_message("This is a double click.\n")
856
+ return
857
+
858
+ if event.button != 3:
859
+ return
860
+
861
+ # Create popup menu
862
+ select = self.tree.get_selection()
863
+ model, lv_iter = select.get_selected()
864
+ values = None
865
+ if lv_iter:
866
+ values = model[lv_iter]
867
+
868
+ else:
869
+ P = tree.get_path_at_pos(event.x, event.y)
870
+ if P:
871
+ lv_iter = model.get_iter(P[0])
872
+ values = model[lv_iter]
873
+
874
+ if not values:
875
+ return
876
+
877
+ menu = Gtk.Menu()
878
+
879
+ item_show = Gtk.MenuItem(label="Delete")
880
+ item_show.connect("activate", self.on_delete_item, (model, lv_iter, values))
881
+ menu.append(item_show)
882
+
883
+ menu.show_all()
884
+ menu.popup_at_pointer(event)
885
+
886
+ def on_delete_item(self, item, data):
887
+ """Delete bond in list view."""
888
+ model, lv_iter, _ = data
889
+ model.remove(lv_iter)
890
+
891
+ def remove_item(self, *args):
892
+ """REmoves selected bond."""
893
+ select = self.tree.get_selection()
894
+ model, lv_iter = select.get_selected()
895
+ if lv_iter:
896
+ model.remove(lv_iter)
897
+
898
+ def add_item(self, *args):
899
+ """Adds a new item in the current model."""
900
+ out = dbGtkUtils.get_a_list_of_values("Add Item", ["Channel", "Comment"])
901
+ if len(out) == 2:
902
+ model = self.tree.get_model()
903
+ model[-1] = out
904
+ model.append(["", ""])
905
+
906
+ def quit(self, *args):
907
+ """Quits the application."""
908
+ if self.pdb:
909
+ self.pdb.die()
910
+
911
+ self.hide()
912
+ self.destroy()
913
+
914
+ def compute_repaired(self, skeleton):
915
+ """Compute number of repaired."""
916
+ nrepaired = 0
917
+ for key, values in skeleton["results"].items():
918
+ if key.find("REPAIRED")<0 or key.find("ROW")<0:
919
+ continue
920
+
921
+ nrepaired += len(values)
922
+
923
+ if nrepaired>0:
924
+ skeleton["problems"] = True
925
+ skeleton["comments"].append("Number of repaired FE bonds: {}".format(nrepaired))
926
+
927
+ return nrepaired
928
+
929
+ def compute_hybrid_to_pb(self, skeleton):
930
+ """Compute number of failures and repairs."""
931
+ n = count_items(skeleton["results"]["REPAIRED_HYBRID_TO_PB"])
932
+ n = count_items(skeleton["results"]["FAILED_HYBRID_TO_PB"])
933
+ if n:
934
+ msg = "Hybrid to PB: {} failing bonds.".format(n)
935
+ skeleton["comments"].append(msg)
936
+ skeleton["passed"] = False
937
+ self.write_message("{}\n".format(msg))
938
+ for key in skeleton["results"]["FAILED_HYBRID_TO_PB"]:
939
+ defect = {
940
+ "description": "Unbonded: Hybrid to PB",
941
+ "name": "UNBODED",
942
+ "properties": {
943
+ "wire_number": range_to_list(key)
944
+ }
945
+ }
946
+ skeleton["defects"].append(defect)
947
+
948
+
949
+ def compute_module_to_frame(self, skeleton):
950
+ """Compute number of failures and repairs."""
951
+ n = count_items(skeleton["results"]["REPAIRED_MODULE_TO_FRAME"])
952
+ n = count_items(skeleton["results"]["FAILED_MODULE_TO_FRAME"])
953
+ if n:
954
+ msg = "Module to test frame: {} failing bonds.".format(n)
955
+ skeleton["comments"].append(msg)
956
+ skeleton["passed"] = False
957
+ self.write_message("{}\n".format(msg))
958
+ for key in skeleton["results"]["FAILED_MODULE_TO_FRAME"]:
959
+ defect = {
960
+ "description": "Unbonded: Module to Frame",
961
+ "name": "UNBODED",
962
+ "properties": {
963
+ "wire_number": range_to_list(key)
964
+ }
965
+ }
966
+ skeleton["defects"].append(defect)
967
+
968
+ def compute_unconnected(self, results):
969
+ """Compute number of unconnected."""
970
+ try:
971
+ param = get_module_param(self.SN.get_text())
972
+ except ValueError as E:
973
+ dbGtkUtils.complain("Wrong SN number", str(E))
974
+ return None
975
+
976
+ M = ModuleHoles(param=param, win=self)
977
+
978
+ defects = []
979
+ for test, values in results.items():
980
+ if test.find("FAILED") < 0:
981
+ continue
982
+ if test.find("ROW") < 0:
983
+ continue
984
+
985
+ irow = int(test[-1]) - 1
986
+
987
+ # Get list of all channels with wirebond notation.
988
+ out = [int(x) for x in values.keys()]
989
+ # Translate to sensor, hybrids, etc.
990
+ for S in M.sensors:
991
+ for H in S.hybrids:
992
+ H.add_channel(irow, out)
993
+ H.holes[irow] = find_holes(H.channels[irow])
994
+
995
+ # add defects
996
+ defects.append(
997
+ {
998
+ "description": "Unbonded Channel",
999
+ "name": "UNBONDED",
1000
+ "properties": {
1001
+ "front_end_row": irow,
1002
+ "wire_number": out
1003
+ }
1004
+ }
1005
+ )
1006
+
1007
+ # Now get sensor strips.
1008
+ M.ready()
1009
+ unconnected = M.get_n_unconnected()
1010
+ mx_consecutive = M.get_max_consecutive()
1011
+ mx_sensor_width, module_holes = M.get_max_sensor_consecutive()
1012
+
1013
+ out = {}
1014
+ out["comments"] = []
1015
+ out["defects"] = defects
1016
+ for irow in range(4):
1017
+ key = "MAX_CONT_UNCON_ROW{}".format(irow+1)
1018
+ out[key] = mx_consecutive[irow]
1019
+
1020
+ self.write_message("Found {} clusters of unconnected strips in sensor.\n".format(len(module_holes)))
1021
+ for H in module_holes:
1022
+ self.write_message("{}\n".format(H))
1023
+
1024
+ if mx_sensor_width > 0:
1025
+ self.write_message("Max width of consecutive unconnected strips: {}\n". format(mx_sensor_width))
1026
+
1027
+ out["MAX_UNCON_SENSOR_CHAN"] = mx_sensor_width
1028
+ if mx_sensor_width > 8:
1029
+ out["passed"] = False
1030
+ out["comments"].append("Too many consecutive sensor strips unconnected: {}".format(mx_sensor_width))
1031
+
1032
+ nstrips = 0
1033
+ for v in unconnected:
1034
+ nstrips += v
1035
+
1036
+ percent = 100*nstrips/M.nchan
1037
+ out["TOTAL_PERC_UNCON_SENSOR_CHAN"] = percent
1038
+ if out["TOTAL_PERC_UNCON_SENSOR_CHAN"] > 1.0:
1039
+ out["passed"] = False
1040
+ out["comments"].append("More than 1%% of channels unconnected: {:.1f}%%".format(percent))
1041
+
1042
+ return out
1043
+
1044
+ def get_unconnected(self, skeleton):
1045
+ """Fill the test DTO with unconnected information."""
1046
+ out = self.compute_unconnected(skeleton["results"])
1047
+ if out is None:
1048
+ raise ValueError("Wrong SN")
1049
+
1050
+ for key, val in out.items():
1051
+ if key in ["passed", "problems"]:
1052
+ skeleton[key] = out[key]
1053
+ continue
1054
+
1055
+ if key == "comments":
1056
+ for C in out[key]:
1057
+ skeleton[key].append(C)
1058
+
1059
+ continue
1060
+
1061
+ if key == "defects":
1062
+ for D in out[key]:
1063
+ skeleton[key].append(D)
1064
+
1065
+ continue
1066
+
1067
+
1068
+ skeleton["results"][key] = val
1069
+
1070
+ def read_file(self, *args):
1071
+ """Read local data file."""
1072
+ dialog = Gtk.FileChooserDialog(
1073
+ title="Please choose a file",
1074
+ parent=self,
1075
+ action=Gtk.FileChooserAction.OPEN
1076
+ )
1077
+ dialog.add_buttons(
1078
+ Gtk.STOCK_CANCEL,
1079
+ Gtk.ResponseType.CANCEL,
1080
+ Gtk.STOCK_OPEN,
1081
+ Gtk.ResponseType.OK,
1082
+ )
1083
+ filter_text = Gtk.FileFilter()
1084
+ filter_text.set_name("JSON files")
1085
+ filter_text.add_mime_type("application/json")
1086
+ dialog.add_filter(filter_text)
1087
+
1088
+ response = dialog.run()
1089
+ if response == Gtk.ResponseType.OK:
1090
+ ofile = dialog.get_filename()
1091
+ self.write_message("Loading data from {}\n".format(ofile))
1092
+ with open(ofile, 'r', encoding="utf-8") as data_file:
1093
+ data = json.load(data_file)
1094
+ self.parse(data)
1095
+
1096
+ dialog.hide()
1097
+ dialog.destroy()
1098
+
1099
+ def parse(self, data):
1100
+ """Parses a JSon dictionary."""
1101
+ try:
1102
+ dbGtkUtils.set_combo_iter(self.inst_combo, data["institution"])
1103
+ except KeyError:
1104
+ self.write_message("institution value is not in the loaded file\n.")
1105
+
1106
+ self.operator.set_text(data["properties"]["OPERATOR"])
1107
+ self.machine.set_text(data["properties"]["BOND_MACHINE"])
1108
+ self.batch.set_text(data["properties"]["BONDWIRE_BATCH"])
1109
+ self.SN.set_text(data["component"])
1110
+ self.date.set_text(data["date"])
1111
+ for key, val in data["results"].items():
1112
+ model = self.models[key]
1113
+ model.clear()
1114
+ for chan, msg in val.items():
1115
+ model.append([chan, msg])
1116
+
1117
+ model.append(["", ""])
1118
+
1119
+ def get_list_of_channels(self, data):
1120
+ """Creates the lists of channels."""
1121
+ for key, model in self.models.items():
1122
+ lv_iter = model.get_iter_first()
1123
+ out = {}
1124
+ while lv_iter:
1125
+ chan, comm = model[lv_iter]
1126
+ if len(chan) > 0:
1127
+ out[chan] = comm
1128
+
1129
+ lv_iter = model.iter_next(lv_iter)
1130
+
1131
+ data["results"][key] = out
1132
+
1133
+ def fix_list_of_channels(self, data):
1134
+ """Expand ranges and, eventually, apply LUT.
1135
+
1136
+ Args:
1137
+ data: The test payload.
1138
+ """
1139
+ for tit, section in data["results"].items():
1140
+ if not isinstance(section, dict):
1141
+ continue
1142
+
1143
+ range_items = []
1144
+ added_items = []
1145
+ for key, comment in section.items():
1146
+ values = list(map(str.strip, key.split(',')))
1147
+ if ',' in key or '-' in key:
1148
+ range_items.append(key)
1149
+
1150
+ else:
1151
+ continue
1152
+
1153
+ for V in values:
1154
+ for i in range_to_list(V):
1155
+ added_items.append((str(i), comment))
1156
+
1157
+ for key in range_items:
1158
+ section.pop(key)
1159
+
1160
+ for key, comm in added_items:
1161
+ section[key] = comm
1162
+
1163
+ if len(self.lut)>0 and len(section)>0:
1164
+ tmp = copy.deepcopy(section)
1165
+ section.clear()
1166
+ for key, val in tmp.items():
1167
+ section[self.convert_channel(key)] = val
1168
+
1169
+
1170
+ def save_test(self, *args):
1171
+ """Save Test file."""
1172
+ dialog = Gtk.FileChooserDialog(
1173
+ title="Please choose a file", parent=self, action=Gtk.FileChooserAction.SAVE
1174
+ )
1175
+ dialog.add_buttons(
1176
+ Gtk.STOCK_CANCEL,
1177
+ Gtk.ResponseType.CANCEL,
1178
+ Gtk.STOCK_OPEN,
1179
+ Gtk.ResponseType.OK,
1180
+ )
1181
+ filter_text = Gtk.FileFilter()
1182
+ filter_text.set_name("JSON files")
1183
+ filter_text.add_mime_type("application/json")
1184
+ dialog.add_filter(filter_text)
1185
+
1186
+ response = dialog.run()
1187
+ if response == Gtk.ResponseType.OK:
1188
+ ofile = dialog.get_filename()
1189
+ data = self.get_test_data()
1190
+ with open(ofile, 'w', encoding="UTF-8") as of:
1191
+ json.dump(data, of, indent=3)
1192
+
1193
+ dialog.hide()
1194
+ dialog.destroy()
1195
+
1196
+ def get_test_data(self):
1197
+ """Get the test data."""
1198
+ data = {
1199
+ "institution": self.institute,
1200
+ "date": ITkDButils.get_db_date(),
1201
+ "runNumber": "1",
1202
+ "properties": {},
1203
+ "results": {},
1204
+ "comments": [],
1205
+ "defects": []
1206
+ }
1207
+ self.get_test_header(data)
1208
+ self.get_list_of_channels(data)
1209
+ return data
1210
+
1211
+ def get_test_header(self, data):
1212
+ """Get Basic test data."""
1213
+ SN = self.SN.get_text()
1214
+ operator = self.operator.get_text()
1215
+ machine = self.machine.get_text()
1216
+ batch = self.batch.get_text()
1217
+
1218
+ if not SN or len(SN)==0:
1219
+ SN = dbGtkUtils.get_a_value("Module Serial Number",
1220
+ "Module serial Number is missing")
1221
+
1222
+ if len(operator) == 0 or len(machine) == 0:
1223
+ values = dbGtkUtils.get_a_list_of_values(
1224
+ "Missing Values",
1225
+ ["SN", "Operator", "Wire Bonder"],
1226
+ defaults=[SN, operator, machine],
1227
+ )
1228
+ if len(values) == 4:
1229
+ SN, operator, machine = values
1230
+ else:
1231
+ self.write_message("Something went wrong while requesting missing information.\n")
1232
+
1233
+ data["component"] = SN
1234
+ data["properties"]["OPERATOR"] = operator
1235
+ data["properties"]["BOND_MACHINE"] = machine
1236
+ data["properties"]["BONDWIRE_BATCH"] = batch
1237
+ data["institution"] = self.institute
1238
+ if data["runNumber"] == "-1":
1239
+ data["runNumber"] = "1"
1240
+
1241
+ data["date"] = self.date.get_text()
1242
+
1243
+
1244
+
1245
+ def upload_test(self, *args):
1246
+ """Upload test."""
1247
+ if self.session is None:
1248
+ self.pdb = ITkDBlogin.ITkDBlogin()
1249
+ client = self.pdb.get_client()
1250
+ if client is None:
1251
+ dbGtkUtils.complain("Could not connect to DB with provided credentials.")
1252
+ self.pdb.die()
1253
+
1254
+ else:
1255
+ self.session = client
1256
+
1257
+ defaults = {
1258
+ "institution": self.institute,
1259
+ "runNumber": "1",
1260
+ "date": ITkDButils.get_db_date()
1261
+ }
1262
+ skeleton = ITkDButils.get_test_skeleton(self.session, "MODULE", "MODULE_WIRE_BONDING", defaults)
1263
+
1264
+ self.get_test_header(skeleton)
1265
+ self.get_list_of_channels(skeleton)
1266
+ self.fix_list_of_channels(skeleton)
1267
+ try:
1268
+ self.get_unconnected(skeleton)
1269
+ self.compute_repaired(skeleton)
1270
+ self.compute_hybrid_to_pb(skeleton)
1271
+ self.compute_module_to_frame(skeleton)
1272
+
1273
+ except ValueError:
1274
+ return
1275
+
1276
+ uploadW = UploadTest.UploadTest(self.session, payload=skeleton)
1277
+ # uploadW.run()
1278
+
1279
+
1280
+ def main():
1281
+ """Main entry."""
1282
+ dlg = ITkDBlogin.ITkDBlogin()
1283
+ client = dlg.get_client()
1284
+ if client is None:
1285
+ print("Could not connect to DB with provided credentials.")
1286
+ dlg.die()
1287
+ sys.exit()
1288
+
1289
+ client.user_gui = dlg
1290
+
1291
+
1292
+ win = WireBond(client, title="WireBond")
1293
+ win.connect("destroy", Gtk.main_quit)
1294
+ win.show_all()
1295
+ try:
1296
+ Gtk.main()
1297
+
1298
+ except KeyboardInterrupt:
1299
+ print("Arrrgggg!!!")
1300
+
1301
+ dlg.die()
1302
+
1303
+ if __name__ == "__main__":
1304
+ main()