Completed
Push — master ( 062d72...786cd1 )
by Olivier
59s
created

UaModeler._update_actions_state()   D

Complexity

Conditions 8

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

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