Completed
Push — master ( 4faa3a...3c67fb )
by Olivier
59s
created

trycatchslot()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
rs 9.4285
cc 3
1
#! /usr/bin/env python3
2
3
import sys
4
import os
5
import inspect
6
import logging
7
8
from PyQt5.QtCore import QTimer, QSettings, QModelIndex, Qt, QCoreApplication
9
from PyQt5.QtGui import QIcon, QFont
10
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QStyledItemDelegate, QMenu
11
12
from opcua import ua
13
from opcua import Server
14
from opcua import copy_node
15
from opcua.common.ua_utils import get_node_children
16
from opcua.common.xmlexporter import XmlExporter
17
from opcua.common.instantiate import instantiate
18
19
from uawidgets import resources
20
from uawidgets.attrs_widget import AttrsWidget
21
from uawidgets.tree_widget import TreeWidget
22
from uawidgets.refs_widget import RefsWidget
23
from uawidgets.new_node_dialogs import NewNodeBaseDialog, NewUaObjectDialog, NewUaVariableDialog, NewUaMethodDialog
24
from uamodeler.uamodeler_ui import Ui_UaModeler
25
from uamodeler.namespace_widget import NamespaceWidget
26
from uamodeler.refnodesets_widget import RefNodeSetsWidget
27
from uawidgets.utils import trycatchslot
28
29
30
logger = logging.getLogger(__name__)
31
32
33
class QtHandler(logging.Handler):
34
35
    def __init__(self, modeler):
36
        logging.Handler.__init__(self)
37
        self.modeler = modeler
38
39
    def emit(self, record):
40
        msg = self.format(record)
41
        self.modeler.ui.logTextEdit.append(msg)
42
43
44
class BoldDelegate(QStyledItemDelegate):
45
46
    def __init__(self, parent, model, added_node_list):
47
        QStyledItemDelegate.__init__(self, parent)
48
        self.added_node_list = added_node_list
49
        self.model = model
50
51
    def paint(self, painter, option, idx):
52
        new_idx = idx.sibling(idx.row(), 0)
53
        item = self.model.itemFromIndex(new_idx)
54
        if item and item.data() in self.added_node_list:
55
            option.font.setWeight(QFont.Bold)
56
        QStyledItemDelegate.paint(self, painter, option, idx)
57
58
59
class UaModeler(QMainWindow):
60
61
    def __init__(self):
62
        QMainWindow.__init__(self)
63
        self.ui = Ui_UaModeler()
64
        self.ui.setupUi(self)
65
        self.setWindowIcon(QIcon(":/network.svg"))
66
67
        # we only show statusbar in case of errors
68
        self.ui.statusBar.hide()
69
70
        # setup QSettings for application and get a settings object
71
        QCoreApplication.setOrganizationName("FreeOpcUa")
72
        QCoreApplication.setApplicationName("OpcUaModeler")
73
        self.settings = QSettings()
74
        self._last_dir = self.settings.value("last_dir", ".")
75
76
        self._restore_state()
77
78
        self.server = None
79
        self._new_nodes = []  # the added nodes we will save
80
        self._current_path = None
81
        self._modified = False
82
        self._copy_clipboard = None
83
84
        self.tree_ui = TreeWidget(self.ui.treeView)
85
        self.tree_ui.error.connect(self.show_error)
86
        delegate = BoldDelegate(self, self.tree_ui.model, self._new_nodes)
87
        self.ui.treeView.setItemDelegate(delegate)
88
        self.ui.treeView.selectionModel().currentChanged.connect(self._update_actions_state)
89
90
        self.refs_ui = RefsWidget(self.ui.refView)
91
        self.refs_ui.error.connect(self.show_error)
92
        self.attrs_ui = AttrsWidget(self.ui.attrView, show_timestamps=False)
93
        self.attrs_ui.error.connect(self.show_error)
94
        self.attrs_ui.attr_written.connect(self._attr_written)
95
        self.idx_ui = NamespaceWidget(self.ui.namespaceView)
96
        self.nodesets_ui = RefNodeSetsWidget(self.ui.refNodeSetsView)
97
        self.nodesets_ui.error.connect(self.show_error)
98
        self.nodesets_ui.nodeset_added.connect(self.nodesets_change)
99
        self.nodesets_ui.nodeset_removed.connect(self.nodesets_change)
100
101
        self.ui.treeView.activated.connect(self.show_refs)
102
        self.ui.treeView.clicked.connect(self.show_refs)
103
        self.ui.treeView.activated.connect(self.show_attrs)
104
        self.ui.treeView.clicked.connect(self.show_attrs)
105
106
        # fix icon stuff
107
        self.ui.actionNew.setIcon(QIcon(":/new.svg"))
108
        self.ui.actionOpen.setIcon(QIcon(":/open.svg"))
109
        self.ui.actionCopy.setIcon(QIcon(":/copy.svg"))
110
        self.ui.actionPaste.setIcon(QIcon(":/paste.svg"))
111
        self.ui.actionDelete.setIcon(QIcon(":/delete.svg"))
112
        self.ui.actionSave.setIcon(QIcon(":/save.svg"))
113
        self.ui.actionAddFolder.setIcon(QIcon(":/folder.svg"))
114
        self.ui.actionAddObject.setIcon(QIcon(":/object.svg"))
115
        self.ui.actionAddMethod.setIcon(QIcon(":/method.svg"))
116
        self.ui.actionAddObjectType.setIcon(QIcon(":/object_type.svg"))
117
        self.ui.actionAddProperty.setIcon(QIcon(":/property.svg"))
118
        self.ui.actionAddVariable.setIcon(QIcon(":/variable.svg"))
119
        self.ui.actionAddVariableType.setIcon(QIcon(":/variable_type.svg"))
120
        self.ui.actionAddDataType.setIcon(QIcon(":/data_type.svg"))
121
        self.ui.actionAddReferenceType.setIcon(QIcon(":/reference_type.svg"))
122
123
        self.setup_context_menu_tree()
124
125
        # actions
126
        self.ui.actionNew.triggered.connect(self._new)
127
        self.ui.actionOpen.triggered.connect(self._open)
128
        self.ui.actionCopy.triggered.connect(self._copy)
129
        self.ui.actionPaste.triggered.connect(self._paste)
130
        self.ui.actionDelete.triggered.connect(self._delete)
131
        self.ui.actionImport.triggered.connect(self._import_slot)
132
        self.ui.actionSave.triggered.connect(self._save_slot)
133
        self.ui.actionSaveAs.triggered.connect(self._save_as)
134
        self.ui.actionCloseModel.triggered.connect(self._close_model_slot)
135
        self.ui.actionAddObjectType.triggered.connect(self._add_object_type)
136
        self.ui.actionAddObject.triggered.connect(self._add_object)
137
        self.ui.actionAddFolder.triggered.connect(self._add_folder)
138
        self.ui.actionAddMethod.triggered.connect(self._add_method)
139
        self.ui.actionAddDataType.triggered.connect(self._add_data_type)
140
        self.ui.actionAddVariable.triggered.connect(self._add_variable)
141
        self.ui.actionAddVariableType.triggered.connect(self._add_variable_type)
142
        self.ui.actionAddProperty.triggered.connect(self._add_property)
143
144
        self._disable_actions()
145
146
    def _update_actions_state(self, current, previous):
147
        node = self.tree_ui.get_current_node(current)
148
        if not node or node == self.server.nodes.root:
149
            self._disable_add_actions()
150
            return
151
        path = node.get_path()
152
        nodeclass = node.get_node_class()
153
154
        if self.server.nodes.base_object_type in path:
155
            self.ui.actionAddObjectType.setEnabled(True)
156
        else:
157
            self.ui.actionAddObjectType.setEnabled(False)
158
159
        if self.server.nodes.base_variable_type in path:
160
            self.ui.actionAddVariableType.setEnabled(True)
161
        else:
162
            self.ui.actionAddVariableType.setEnabled(False)
163
164
        if self.server.nodes.base_data_type in path:
165
            self.ui.actionAddDataType.setEnabled(True)
166
            return  # not other nodes should be added here
167
        else:
168
            self.ui.actionAddDataType.setEnabled(False)
169
170
        if nodeclass != ua.NodeClass.Variable:
171
            self.ui.actionAddFolder.setEnabled(True)
172
            self.ui.actionAddObject.setEnabled(True)
173
            self.ui.actionAddVariable.setEnabled(True)
174
            self.ui.actionAddProperty.setEnabled(True)
175
            self.ui.actionAddMethod.setEnabled(True)
176
        else:
177
            self.ui.actionAddFolder.setEnabled(False)
178
            self.ui.actionAddObject.setEnabled(False)
179
            self.ui.actionAddVariable.setEnabled(False)
180
            self.ui.actionAddProperty.setEnabled(False)
181
            self.ui.actionAddMethod.setEnabled(False)
182
183
    def setup_context_menu_tree(self):
184
        self.ui.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
185
        self.ui.treeView.customContextMenuRequested.connect(self._show_context_menu_tree)
186
        self._contextMenu = QMenu()
187
188
        # tree view menu
189
        self._contextMenu.addAction(self.ui.actionCopy)
190
        self._contextMenu.addAction(self.ui.actionPaste)
191
        self._contextMenu.addAction(self.ui.actionDelete)
192
        self._contextMenu.addSeparator()
193
        self._contextMenu.addAction(self.ui.actionAddFolder)
194
        self._contextMenu.addAction(self.ui.actionAddObject)
195
        self._contextMenu.addAction(self.ui.actionAddVariable)
196
        self._contextMenu.addAction(self.ui.actionAddProperty)
197
        self._contextMenu.addAction(self.ui.actionAddMethod)
198
        self._contextMenu.addAction(self.ui.actionAddObjectType)
199
        self._contextMenu.addAction(self.ui.actionAddVariableType)
200
        self._contextMenu.addAction(self.ui.actionAddDataType)
201
202
    def _show_context_menu_tree(self, position):
203
        node = self.tree_ui.get_current_node()
204
        if node:
205
            self._contextMenu.exec_(self.ui.treeView.viewport().mapToGlobal(position))
206
207
    @trycatchslot
208
    def _delete(self):
209
        node = self.tree_ui.get_current_node()
210
        if node:
211
            nodes = get_node_children(node)
212
            for n in nodes:
213
                n.delete()
214
                if n in self._new_nodes:
215
                    self._new_nodes.remove(n)
216
            self.tree_ui.remove_current_item()
217
218
    @trycatchslot
219
    def _copy(self):
220
        node = self.tree_ui.get_current_node()
221
        if node:
222
223
            self._copy_clipboard = node
224
225
    @trycatchslot
226
    def _paste(self):
227
        if self._copy_clipboard:
228
            parent = self.tree_ui.get_current_node()
229
            try:
230
                added_nodes = copy_node(parent, self._copy_clipboard)
231
            except Exception as ex:
232
                self.show_error(ex)
233
                raise
234
            self._new_nodes.extend(added_nodes)
235
            self.tree_ui.reload_current()
236
            self.show_refs()
237
            self._modified = True
238
239
    def _attr_written(self, attr, dv):
240
        self._modified = True
241
        if attr == ua.AttributeIds.BrowseName:
242
            self.tree_ui.update_browse_name_current_item(dv.Value.Value)
243
        elif attr == ua.AttributeIds.DisplayName:
244
            self.tree_ui.update_display_name_current_item(dv.Value.Value)
245
      
246
    def _restore_state(self):
247
        self.resize(int(self.settings.value("main_window_width", 800)),
248
                    int(self.settings.value("main_window_height", 600)))
249
        #self.restoreState(self.settings.value("main_window_state", b"", type="QByteArray"))
250
        self.restoreState(self.settings.value("main_window_state", bytearray()))
251
        self.ui.splitterLeft.restoreState(self.settings.value("splitter_left", bytearray()))
252
        self.ui.splitterRight.restoreState(self.settings.value("splitter_right", bytearray()))
253
        self.ui.splitterCenter.restoreState(self.settings.value("splitter_center", bytearray()))
254
255
    def _disable_model_actions(self):
256
        self.ui.actionImport.setEnabled(False)
257
        self.ui.actionSave.setEnabled(False)
258
        self.ui.actionSaveAs.setEnabled(False)
259
260
    def _disable_actions(self):
261
        self._disable_add_actions()
262
        self._disable_model_actions()
263
264
    def _disable_add_actions(self):
265
        self.ui.actionAddObject.setEnabled(False)
266
        self.ui.actionAddFolder.setEnabled(False)
267
        self.ui.actionAddVariable.setEnabled(False)
268
        self.ui.actionAddProperty.setEnabled(False)
269
        self.ui.actionAddDataType.setEnabled(False)
270
        self.ui.actionAddVariableType.setEnabled(False)
271
        self.ui.actionAddObjectType.setEnabled(False)
272
        self.ui.actionAddMethod.setEnabled(False)
273
274
    def _enable_model_actions(self):
275
        self.ui.actionImport.setEnabled(True)
276
        self.ui.actionSave.setEnabled(True)
277
        self.ui.actionSaveAs.setEnabled(True)
278
        #self.ui.actionAddObject.setEnabled(True)
279
        #self.ui.actionAddFolder.setEnabled(True)
280
        #self.ui.actionAddVariable.setEnabled(True)
281
        #self.ui.actionAddProperty.setEnabled(True)
282
        #self.ui.actionAddDataType.setEnabled(True)
283
        #self.ui.actionAddVariableType.setEnabled(True)
284
        #self.ui.actionAddObjectType.setEnabled(True)
285
286
    @trycatchslot
287
    def _close_model_slot(self):
288
        self._close_model()
289
290
    def _close_model(self):
291
        if not self.really_exit():
292
            return False
293
        self._disable_actions()
294
        self.tree_ui.clear()
295
        self.refs_ui.clear()
296
        self.attrs_ui.clear()
297
        self.idx_ui.clear()
298
        self.nodesets_ui.clear()
299
        self._current_path = None
300
        self._modified = False
301
        self._update_title()
302
        if self.server is not None:
303
            self.server.stop()
304
        self.server = None
305
        return True
306
307
    def _update_title(self):
308
        self.setWindowTitle("FreeOpcUa Modeler " + str(self._current_path))
309
310
    @trycatchslot
311
    def _new(self):
312
        if not self._close_model():
313
            return
314
        self._create_new_model()
315
316
    def _create_new_model(self):
317
        self.server = Server()
318
        endpoint = "opc.tcp://0.0.0.0:48400/freeopcua/uamodeler/"
319
        logger.info("Starting server on %s", endpoint)
320
        self.server.set_endpoint(endpoint)
321
        self.server.set_server_name("OpcUa Modeler Server")
322
        # now remove freeopcua namespace, not necessary when modeling and
323
        # ensures correct idx for exported nodesets
324
        ns_node = self.server.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
325
        nss = ns_node.get_value()
326
        ns_node.set_value(nss[1:])
327
328
        del(self._new_nodes[:])  # empty list while keeping reference
329
330
        self.server.start()
331
        self.tree_ui.set_root_node(self.server.get_root_node())
332
        self.idx_ui.set_node(self.server.get_node(ua.ObjectIds.Server_NamespaceArray))
333
        self.nodesets_ui.set_server(self.server)
334
        self._modified = False
335
        self._enable_model_actions()
336
        self._current_path = "NoName"
337
        self._update_title()
338
        return True
339
340
    @trycatchslot
341
    def _import_slot(self):
342
        self._import()
343
344
    def _import(self, path=None):
345
        if not path:
346
            path, ok = self._get_xml()
347
            if not ok:
348
                return None
349
        self._last_dir = os.path.dirname(path)
350
        try:
351
            new_nodes = self.server.import_xml(path)
352
            self._new_nodes.extend([self.server.get_node(node) for node in new_nodes])
353
            self._modified = True
354
        except Exception as ex:
355
            self.show_error(ex)
356
            raise
357
        # we maybe should only reload the imported nodes
358
        self.tree_ui.reload()
359
        self.idx_ui.reload()
360
        return path
361
362
    def _get_xml(self):
363
        return QFileDialog.getOpenFileName(self, caption="Open OPC UA XML", filter="XML Files (*.xml *.XML)", directory=self._last_dir)
364
365
    @trycatchslot
366
    def _open(self):
367
        if not self._close_model():
368
            return
369
        path, ok = self._get_xml()
370
        if not ok:
371
            return
372
        self._create_new_model()
373
        try:
374
            path = self._import(path)
375
        except:
376
            self._close_model()
377
            raise
378
        self._modified = False
379
        self._current_path = path
380
        self._update_title()
381
382
    @trycatchslot
383
    def _save_as(self):
384
        path, ok = QFileDialog.getSaveFileName(self, caption="Save OPC UA XML", filter="XML Files (*.xml *.XML)")
385
        if ok:
386
            self._current_path = path
387
            self._update_title()
388
            self._save()
389
390
    @trycatchslot
391
    def _save_slot(self):
392
        self._save()
393
394
    def _save(self):
395
        if not self._current_path or self._current_path == "NoName":
396
            path, ok = QFileDialog.getSaveFileName(self, caption="Save OPC UA XML", filter="XML Files (*.xml *.XML)")
397
            self._current_path = path
398
            if not ok:
399
                return
400
        logger.info("Saving to %s", self._current_path)
401
        logger.info("Exporting  %s nodes: %s", len(self._new_nodes), self._new_nodes)
402
        logger.info("and namespaces: %s ", self.server.get_namespace_array()[1:])
403
        exp = XmlExporter(self.server)
404
        uris = self.server.get_namespace_array()[1:]
405
        exp.build_etree(self._new_nodes, uris=uris)
406
        try:
407
            exp.write_xml(self._current_path)
408
        except Exception as ex:
409
            self.show_error(ex)
410
            raise
411
        self._modified = False
412
        self._update_title()
413
        self.show_msg(self._current_path + " saved")
414
415
    def really_exit(self):
416
        if self._modified:
417
            reply = QMessageBox.question(
418
                self,
419
                "OPC UA Modeler",
420
                "Model is modified, do you really want to close model?",
421
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
422
            )
423
            if reply != QMessageBox.Yes:
424
                return False
425
426
        return True
427
428
    def _after_add(self, new_nodes):
429
        if isinstance(new_nodes, (list, tuple)):
430
            self._new_nodes.extend(new_nodes)
431
        else:
432
            self._new_nodes.append(new_nodes)
433
        self.tree_ui.reload_current()
434
        self.show_refs()
435
        self._modified = True
436
437
    @trycatchslot
438
    def _add_method(self):
439
        parent = self.tree_ui.get_current_node()
440
        args, ok = NewUaMethodDialog.getArgs(self, "Add Method", self.server)
441
        if ok:
442
            logger.info("Creating method type with args: %s", args)
443
            new_nodes = []
444
            new_node = parent.add_method(*args)
445
            new_nodes.append(new_node)
446
            new_nodes.extend(new_node.get_children())
447
            self._after_add(new_nodes)
448
449
    @trycatchslot
450
    def _add_object_type(self):
451
        parent = self.tree_ui.get_current_node()
452
        args, ok = NewNodeBaseDialog.getArgs(self, "Add Object Type", self.server)
453
        if ok:
454
            logger.info("Creating object type with args: %s", args)
455
            new_node = parent.add_object_type(*args)
456
            self._after_add(new_node)
457
458
    @trycatchslot
459
    def _add_folder(self):
460
        parent = self.tree_ui.get_current_node()
461
        args, ok = NewNodeBaseDialog.getArgs(self, "Add Folder", self.server)
462
        if ok:
463
            logger.info("Creating folder with args: %s", args)
464
            new_node = parent.add_folder(*args)
465
            self._after_add(new_node)
466
467
    @trycatchslot
468
    def _add_object(self):
469
        parent = self.tree_ui.get_current_node()
470
        args, ok = NewUaObjectDialog.getArgs(self, "Add Object", self.server, base_node_type=self.server.get_node(ua.ObjectIds.BaseObjectType))
471
        if ok:
472
            logger.info("Creating object with args: %s", args)
473
            nodeid, bname, otype = args
474
            new_nodes = instantiate(parent, otype, bname=bname, nodeid=nodeid)
475
            self._after_add(new_nodes)
476
477
    @trycatchslot
478
    def _add_data_type(self):
479
        parent = self.tree_ui.get_current_node()
480
        args, ok = NewNodeBaseDialog.getArgs(self, "Add Data Type", self.server)
481
        if ok:
482
            logger.info("Creating data type with args: %s", args)
483
            new_node = parent.add_data_type(*args)
484
            self._after_add(new_node)
485
    
486
    @trycatchslot
487
    def _add_variable(self):
488
        parent = self.tree_ui.get_current_node()
489
        dtype = self.settings.value("last_datatype", None)
490
        args, ok = NewUaVariableDialog.getArgs(self, "Add Variable", self.server, default_value=9.99, dtype=dtype)
491
        if ok:
492
            logger.info("Creating variable with args: %s", args)
493
            self.settings.setValue("last_datatype", args[4])
494
            new_node = parent.add_variable(*args)
495
            self._after_add(new_node)
496
497
    @trycatchslot
498
    def _add_property(self):
499
        parent = self.tree_ui.get_current_node()
500
        dtype = self.settings.value("last_datatype", None)
501
        args, ok = NewUaVariableDialog.getArgs(self, "Add Property", self.server, default_value=9.99, dtype=dtype)
502
        if ok:
503
            logger.info("Creating property with args: %s", args)
504
            self.settings.setValue("last_datatype", args[4])
505
            new_node = parent.add_property(*args)
506
            self._after_add(new_node)
507
508
    @trycatchslot
509
    def _add_variable_type(self):
510
        parent = self.tree_ui.get_current_node()
511
        args, ok = NewUaObjectDialog.getArgs(self, "Add Variable Type", self.server, base_node_type=self.server.get_node(ua.ObjectIds.BaseVariableType))
512
        if ok:
513
            logger.info("Creating variable type with args: %s", args)
514
            nodeid, bname, datatype = args
515
            new_node = parent.add_variable_type(nodeid, bname, datatype.nodeid)
516
            self._after_add(new_node)
517
518
    @trycatchslot
519
    def show_refs(self, idx=None):
520
        node = self.get_current_node(idx)
521
        self.refs_ui.show_refs(node)
522
523
    @trycatchslot
524
    def show_attrs(self, idx=None):
525
        if not isinstance(idx, QModelIndex):
526
            idx = None
527
        node = self.get_current_node(idx)
528
        self.attrs_ui.show_attrs(node)
529
530
    def show_error(self, msg):
531
        self.ui.statusBar.show()
532
        self.ui.statusBar.setStyleSheet("QStatusBar { background-color : red; color : black; }")
533
        self.ui.statusBar.showMessage(str(msg))
534
        QTimer.singleShot(2500, self.ui.statusBar.hide)
535
536
    def show_msg(self, msg):
537
        self.ui.statusBar.show()
538
        self.ui.statusBar.setStyleSheet("QStatusBar { background-color : green; color : black; }")
539
        self.ui.statusBar.showMessage(str(msg))
540
        QTimer.singleShot(1500, self.ui.statusBar.hide)
541
542
    def get_current_node(self, idx=None):
543
        return self.tree_ui.get_current_node(idx)
544
    
545
    def nodesets_change(self, data):
546
        self.idx_ui.reload()
547
        self.tree_ui.reload()
548
        self.refs_ui.clear()
549
        self.attrs_ui.clear()
550
551
    def closeEvent(self, event):
552
        if not self._close_model():
553
            event.ignore()
554
            return
555
        self.attrs_ui.save_state()
556
        self.refs_ui.save_state()
557
        self.settings.setValue("last_dir", self._last_dir)
558
        self.settings.setValue("main_window_width", self.size().width())
559
        self.settings.setValue("main_window_height", self.size().height())
560
        self.settings.setValue("main_window_state", self.saveState())
561
        self.settings.setValue("splitter_left", self.ui.splitterLeft.saveState())
562
        self.settings.setValue("splitter_right", self.ui.splitterRight.saveState())
563
        self.settings.setValue("splitter_center", self.ui.splitterCenter.saveState())
564
        if self.server:
565
            self.server.stop()
566
        event.accept()
567
568
569
def main():
570
    app = QApplication(sys.argv)
571
    modeler = UaModeler()
572
    handler = QtHandler(modeler)
573
    handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
574
    handler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s')"))
575
    logging.getLogger().addHandler(handler)
576
    logging.getLogger("uamodeler").setLevel(logging.INFO)
577
    logging.getLogger("uawidgets").setLevel(logging.INFO)
578
    #logging.getLogger("opcua").setLevel(logging.INFO)  # to enable logging of ua server
579
    modeler.show()
580
    sys.exit(app.exec_())
581
582
583
if __name__ == "__main__":
584
    main()
585