Completed
Push — master ( 6f923a...cff1b8 )
by Olivier
01:33
created

ModelManager._open_ua_model()   A

Complexity

Conditions 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
1
import logging
2
import os
3
import xml.etree.ElementTree as Et
4
5
6
from PyQt5.QtCore import pyqtSignal, QObject, QSettings
7
8
from opcua import ua
9
from opcua import copy_node
10
from opcua.common.instantiate import instantiate
11
from opcua.common.ua_utils import get_node_children
12
13
from uawidgets.utils import trycatchslot
14
15
from uamodeler.server_manager import ServerManager
16
17
logger = logging.getLogger(__name__)
18
19
20
class ModelManager(QObject):
21
    """
22
    Manage our model. loads xml, start and close, add nodes
23
    No dialogs at that level, only api
24
    """
25
26
    error = pyqtSignal(Exception)
27
    titleChanged = pyqtSignal(str)
28
    modelChanged = pyqtSignal()
29
30
    def __init__(self, modeler):
31
        QObject.__init__(self, modeler)
32
        self.modeler = modeler
33
        self.server_mgr = ServerManager(self.modeler.ui.actionUseOpenUa)
34
        self.new_nodes = []  # the added nodes we will save
35
        self.current_path = None
36
        self.settings = QSettings()
37
        self.modified = False
38
        self.modeler.attrs_ui.attr_written.connect(self._attr_written)
39
40
    def delete_node(self, node):
41
        if node:
42
            nodes = get_node_children(node)
43
            for n in nodes:
44
                n.delete()
45
                if n in self.new_nodes:
46
                    self.new_nodes.remove(n)
47
            self.modeler.tree_ui.remove_current_item()
48
49
    def paste_node(self, node):
50
        parent = self.modeler.get_current_node()
51
        try:
52
            added_nodes = copy_node(parent, node)
53
        except Exception as ex:
54
            self.show_error(ex)
55
            raise
56
        self.new_nodes.extend(added_nodes)
57
        self.modeler.tree_ui.reload_current()
58
        self.modeler.show_refs()
59
        self.modified = True
60
61
    def close_model(self, force=False):
62
        if not force and self.modified:
63
            raise RuntimeError("Model is modified, use force to close it")
64
        self.modeler.actions.disable_all_actions()
65
        self.server_mgr.stop_server()
66
        self.current_path = None
67
        self.modified = False
68
        self.titleChanged.emit("")
69
        self.modeler.clear_all_widgets()
70
71
    def new_model(self):
72
        if self.modified:
73
            raise RuntimeError("Model is modified, cannot create new model")
74
        del (self.new_nodes[:])  # empty list while keeping reference
75
76
        endpoint = "opc.tcp://0.0.0.0:48400/freeopcua/uamodeler/"
77
        logger.info("Starting server on %s", endpoint)
78
        self.server_mgr.start_server(endpoint)
79
80
        self.modeler.tree_ui.set_root_node(self.server_mgr.nodes.root)
81
        self.modeler.idx_ui.set_node(self.server_mgr.get_node(ua.ObjectIds.Server_NamespaceArray))
82
        self.modeler.nodesets_ui.set_server_mgr(self.server_mgr)
83
        self.modified = False
84
        self.modeler.actions.enable_model_actions()
85
        self.current_path = None
86
        self.titleChanged.emit("No Name")
87
        return True
88
89
    def import_xml(self, path):
90
        new_nodes = self.server_mgr.import_xml(path)
91
        self.new_nodes.extend([self.server_mgr.get_node(node) for node in new_nodes])
92
        self.modified = True
93
        # we maybe should only reload the imported nodes
94
        self.modeler.tree_ui.reload()
95
        self.modeler.idx_ui.reload()
96
        return path
97
98
    def open_xml(self, path):
99
        self.new_model()
100
        try:
101
            self._open_xml(path)
102
        except:
103
            self.close_model(force=True)
104
            raise
105
106
    def _open_xml(self, path):
107
        path = self.import_xml(path)
108
        self.modified = False
109
        self.current_path = path
110
        self.titleChanged.emit(self.current_path)
111
112
    def open(self, path):
113
        if path.endswith(".xml"):
114
            self.open_xml(path)
115
        else:
116
            self.open_ua_model(path)
117
118
    def open_ua_model(self, path):
119
        self.new_model()
120
        try:
121
            self._open_ua_model(path)
122
        except:
123
            self.close_model(force=True)
124
            raise
125
126
    def _open_ua_model(self, path):
127
        tree = Et.parse(path)
128
        root = tree.getroot()
129
        for ref_el in root.findall("Reference"):
130
            refpath = ref_el.attrib['path']
131
            self.modeler.nodesets_ui.import_nodeset(refpath)
132
        mod_el = root.find("Model")
133
        xmlpath = mod_el.attrib['path']
134
        self._open_xml(xmlpath)
135
136
    def _get_path(self, path):
137
        if path is None:
138
            path = self.current_path
139
        if path is None:
140
            raise ValueError("No path is defined")
141
        self.current_path = os.path.splitext(path)[0] 
142
        self.titleChanged.emit(self.current_path)
143
        return self.current_path
144
145
    def save_xml(self, path=None):
146
        path = self._get_path(path)
147
        path += ".xml"
148
        logger.info("Saving nodes to %s", path)
149
        logger.info("Exporting  %s nodes: %s", len(self.new_nodes), self.new_nodes)
150
        logger.info("and namespaces: %s ", self.server_mgr.get_namespace_array()[1:])
151
        uris = self.server_mgr.get_namespace_array()[1:]
152
        self.server_mgr.export_xml(self.new_nodes, uris, path)
153
        self.modified = False
154
        logger.info("%s saved", path)
155
156
    def save_ua_model(self, path=None):
157
        path = self._get_path(path)
158
        model_path = path + ".uamodel"
159
        logger.info("Saving model to %s", model_path)
160
        etree = Et.ElementTree(Et.Element('UAModel'))
161
        node_el = Et.SubElement(etree.getroot(), "Model")
162
        node_el.attrib["path"] = path + ".xml"
163
        for refpath in self.modeler.nodesets_ui.nodesets:
164
            node_el = Et.SubElement(etree.getroot(), "Reference")
165
            node_el.attrib["path"] = refpath 
166
        etree.write(model_path, encoding='utf-8', xml_declaration=True)
167
168
    def _after_add(self, new_nodes):
169
        if isinstance(new_nodes, (list, tuple)):
170
            self.new_nodes.extend(new_nodes)
171
        else:
172
            self.new_nodes.append(new_nodes)
173
        self.modeler.tree_ui.reload_current()
174
        self.modeler.show_refs()
175
        self.modified = True
176
177
    def add_method(self, *args):
178
        logger.info("Creating method type with args: %s", args)
179
        parent = self.modeler.tree_ui.get_current_node()
180
        new_nodes = []
181
        new_node = parent.add_method(*args)
182
        new_nodes.append(new_node)
183
        new_nodes.extend(new_node.get_children())
184
        self._after_add(new_nodes)
185
186
    def add_object_type(self, *args):
187
        logger.info("Creating object type with args: %s", args)
188
        parent = self.modeler.tree_ui.get_current_node()
189
        new_node = parent.add_object_type(*args)
190
        self._after_add(new_node)
191
192
    def add_folder(self, *args):
193
        parent = self.modeler.tree_ui.get_current_node()
194
        logger.info("Creating folder with args: %s", args)
195
        new_node = parent.add_folder(*args)
196
        self._after_add(new_node)
197
        return new_node
198
199
    def add_object(self, *args):
200
        parent = self.modeler.tree_ui.get_current_node()
201
        logger.info("Creating object with args: %s", args)
202
        nodeid, bname, otype = args
203
        new_nodes = instantiate(parent, otype, bname=bname, nodeid=nodeid, dname=ua.LocalizedText(bname.Name))
204
        self._after_add(new_nodes)
205
        return new_nodes
206
207
    def add_data_type(self, *args):
208
        parent = self.modeler.tree_ui.get_current_node()
209
        logger.info("Creating data type with args: %s", args)
210
        new_node = parent.add_data_type(*args)
211
        self._after_add(new_node)
212
        return new_node
213
214
    def add_variable(self, *args):
215
        parent = self.modeler.tree_ui.get_current_node()
216
        logger.info("Creating variable with args: %s", args)
217
        new_node = parent.add_variable(*args)
218
        self._after_add(new_node)
219
        return new_node
220
221
    def add_property(self, *args):
222
        parent = self.modeler.tree_ui.get_current_node()
223
        logger.info("Creating property with args: %s", args)
224
        self.settings.setValue("last_datatype", args[4])
225
        new_node = parent.add_property(*args)
226
        self._after_add(new_node)
227
        return new_node
228
229
    def add_variable_type(self, *args):
230
        parent = self.modeler.tree_ui.get_current_node()
231
        logger.info("Creating variable type with args: %s", args)
232
        nodeid, bname, datatype = args
233
        new_node = parent.add_variable_type(nodeid, bname, datatype.nodeid)
234
        self._after_add(new_node)
235
        return new_node
236
237
    @trycatchslot
238
    def _attr_written(self, attr, dv):
239
        self.modified = True
240
        if attr == ua.AttributeIds.BrowseName:
241
            self.modeler.tree_ui.update_browse_name_current_item(dv.Value.Value)
242
        elif attr == ua.AttributeIds.DisplayName:
243
            self.modeler.tree_ui.update_display_name_current_item(dv.Value.Value)
244