Completed
Pull Request — master (#51)
by Olivier
24s
created

ModelManager.open_xml()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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