Edouard@2745: #!/usr/bin/env python Edouard@2745: # -*- coding: utf-8 -*- Edouard@2745: Edouard@2745: # This file is part of Beremiz Edouard@2745: # Copyright (C) 2019: Edouard TISSERANT Edouard@2745: # Edouard@2745: # See COPYING file for copyrights details. Edouard@2745: Edouard@2745: from __future__ import absolute_import Edouard@2745: import os Edouard@2745: import shutil Edouard@2763: from itertools import izip, imap Edouard@2822: from pprint import pformat Edouard@2789: import hashlib Edouard@2816: import weakref Edouard@2823: import shlex Edouard@2745: Edouard@2745: import wx Edouard@2816: import wx.dataview as dv Edouard@2816: Edouard@2816: from lxml import etree Edouard@2816: from lxml.etree import XSLTApplyError Edouard@2745: Edouard@2745: import util.paths as paths Edouard@2745: from POULibrary import POULibrary Edouard@2756: from docutil import open_svg, get_inkscape_path Edouard@2745: Edouard@2756: from util.ProcessLogger import ProcessLogger Edouard@2764: from runtime.typemapping import DebugTypesSize Edouard@2767: import targets Edouard@2816: from editors.ConfTreeNodeEditor import ConfTreeNodeEditor Edouard@2816: from XSLTransform import XSLTransform Edouard@2756: Edouard@2749: HMI_TYPES_DESC = { Edouard@2814: "HMI_NODE":{}, Edouard@2749: "HMI_STRING":{}, Edouard@2749: "HMI_INT":{}, edouard@2826: "HMI_BOOL":{} Edouard@2749: } Edouard@2749: Edouard@2749: HMI_TYPES = HMI_TYPES_DESC.keys() Edouard@2745: Edouard@2753: Edouard@2753: ScriptDirectory = paths.AbsDir(__file__) Edouard@2753: Edouard@2757: class HMITreeNode(object): Edouard@2822: def __init__(self, path, name, nodetype, iectype = None, vartype = None, cpath = None, hmiclass = None): Edouard@2757: self.path = path Edouard@2757: self.name = name Edouard@2757: self.nodetype = nodetype Edouard@2814: self.hmiclass = hmiclass Edouard@2764: Edouard@2764: if iectype is not None: Edouard@2764: self.iectype = iectype Edouard@2764: self.vartype = vartype Edouard@2822: self.cpath = cpath Edouard@2822: Edouard@2814: if nodetype in ["HMI_NODE", "HMI_ROOT"]: Edouard@2757: self.children = [] Edouard@2757: Edouard@2757: def pprint(self, indent = 0): Edouard@2757: res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n" Edouard@2817: if hasattr(self, "children"): Edouard@2757: res += "\n".join([child.pprint(indent = indent + 1) Edouard@2757: for child in self.children]) Edouard@2757: res += "\n" Edouard@2817: Edouard@2757: return res Edouard@2757: Edouard@2757: def place_node(self, node): Edouard@2757: best_child = None Edouard@2757: known_best_match = 0 Edouard@2817: for child in self.children: Edouard@2762: if child.path is not None: Edouard@2762: in_common = 0 Edouard@2762: for child_path_item, node_path_item in izip(child.path, node.path): Edouard@2762: if child_path_item == node_path_item: Edouard@2762: in_common +=1 Edouard@2762: else: Edouard@2762: break Edouard@2762: if in_common > known_best_match: Edouard@2762: known_best_match = in_common Edouard@2762: best_child = child Edouard@2814: if best_child is not None and best_child.nodetype == "HMI_NODE": Edouard@2757: best_child.place_node(node) Edouard@2757: else: Edouard@2757: self.children.append(node) Edouard@2817: Edouard@2788: def etree(self, add_hash=False): Edouard@2763: Edouard@2763: attribs = dict(name=self.name) Edouard@2763: if self.path is not None: Edouard@2788: attribs["path"] = ".".join(self.path) Edouard@2788: Edouard@2815: if self.hmiclass is not None: Edouard@2815: attribs["class"] = self.hmiclass Edouard@2815: Edouard@2788: if add_hash: Edouard@2788: attribs["hash"] = ",".join(map(str,self.hash())) Edouard@2763: Edouard@2763: res = etree.Element(self.nodetype, **attribs) Edouard@2763: Edouard@2817: if hasattr(self, "children"): Edouard@2763: for child_etree in imap(lambda c:c.etree(), self.children): Edouard@2763: res.append(child_etree) Edouard@2763: Edouard@2763: return res Edouard@2763: Edouard@2764: def traverse(self): Edouard@2764: yield self Edouard@2817: if hasattr(self, "children"): Edouard@2764: for c in self.children: Edouard@2764: for yoodl in c.traverse(): Edouard@2764: yield yoodl Edouard@2764: Edouard@2788: Edouard@2788: def hash(self): Edouard@2788: """ Produce a hash, any change in HMI tree structure change that hash """ Edouard@2788: s = hashlib.new('md5') Edouard@2788: self._hash(s) Edouard@2788: # limit size to HMI_HASH_SIZE as in svghmi.c Edouard@2817: return map(ord,s.digest())[:8] Edouard@2788: Edouard@2788: def _hash(self, s): Edouard@2788: s.update(str((self.name,self.nodetype))) Edouard@2817: if hasattr(self, "children"): Edouard@2788: for c in self.children: Edouard@2788: c._hash(s) Edouard@2788: Edouard@2763: # module scope for HMITree root Edouard@2763: # so that CTN can use HMITree deduced in Library Edouard@2817: # note: this only works because library's Generate_C is Edouard@2763: # systematicaly invoked before CTN's CTNGenerate_C Edouard@2763: Edouard@2763: hmi_tree_root = None Edouard@2757: Edouard@2817: on_hmitree_update = None Edouard@2816: Edouard@2822: SPECIAL_NODES = [("heartbeat", "HMI_INT")] Edouard@2822: # ("current_page", "HMI_STRING")]) Edouard@2822: Edouard@2745: class SVGHMILibrary(POULibrary): Edouard@2745: def GetLibraryPath(self): Edouard@2750: return paths.AbsNeighbourFile(__file__, "pous.xml") Edouard@2745: Edouard@2749: def Generate_C(self, buildpath, varlist, IECCFLAGS): Edouard@2822: global hmi_tree_root, on_hmitree_update Edouard@2749: Edouard@2757: """ Edouard@2757: PLC Instance Tree: Edouard@2757: prog0 Edouard@2757: +->v1 HMI_INT Edouard@2757: +->v2 HMI_INT Edouard@2757: +->fb0 (type mhoo) Edouard@2814: | +->va HMI_NODE Edouard@2757: | +->v3 HMI_INT Edouard@2757: | +->v4 HMI_INT Edouard@2757: | Edouard@2757: +->fb1 (type mhoo) Edouard@2814: | +->va HMI_NODE Edouard@2757: | +->v3 HMI_INT Edouard@2757: | +->v4 HMI_INT Edouard@2757: | Edouard@2757: +->fb2 Edouard@2757: +->v5 HMI_IN Edouard@2757: Edouard@2757: HMI tree: Edouard@2757: hmi0 Edouard@2757: +->v1 Edouard@2757: +->v2 Edouard@2814: +->fb0 class:va Edouard@2757: | +-> v3 Edouard@2757: | +-> v4 Edouard@2757: | Edouard@2814: +->fb1 class:va Edouard@2757: | +-> v3 Edouard@2757: | +-> v4 Edouard@2757: | Edouard@2757: +->v5 Edouard@2757: Edouard@2757: """ Edouard@2757: Edouard@2749: # Filter known HMI types Edouard@2749: hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES] Edouard@2758: Edouard@2758: hmi_tree_root = HMITreeNode(None, "/", "HMI_ROOT") Edouard@2757: Edouard@2757: # deduce HMI tree from PLC HMI_* instances Edouard@2757: for v in hmi_types_instances: Edouard@2822: path = v["IEC_path"].split(".") Edouard@2758: # ignores variables starting with _TMP_ Edouard@2758: if path[-1].startswith("_TMP_"): Edouard@2758: continue Edouard@2814: derived = v["derived"] Edouard@2814: kwargs={} Edouard@2814: if derived == "HMI_NODE": Edouard@2822: # TODO : make problem if HMI_NODE used in CONFIG or RESOURCE Edouard@2814: name = path[-2] Edouard@2814: kwargs['hmiclass'] = path[-1] Edouard@2814: else: Edouard@2814: name = path[-1] Edouard@2822: new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs) Edouard@2757: hmi_tree_root.place_node(new_node) Edouard@2757: Edouard@2817: if on_hmitree_update is not None: Edouard@2817: on_hmitree_update() Edouard@2816: Edouard@2764: variable_decl_array = [] Edouard@2764: extern_variables_declarations = [] Edouard@2765: buf_index = 0 Edouard@2775: item_count = 0 Edouard@2822: found_heartbeat = False Edouard@2822: Edouard@2822: hearbeat_IEC_path = ['CONFIG', 'HEARTBEAT'] Edouard@2828: Edouard@2764: for node in hmi_tree_root.traverse(): Edouard@2828: if not found_heartbeat and node.path == hearbeat_IEC_path: Edouard@2822: hmi_tree_hearbeat_index = item_count Edouard@2822: found_heartbeat = True Edouard@2822: extern_variables_declarations += [ Edouard@2822: "#define heartbeat_index "+str(hmi_tree_hearbeat_index) Edouard@2822: ] Edouard@2822: if hasattr(node, "iectype") and node.nodetype != "HMI_NODE": Edouard@2764: sz = DebugTypesSize.get(node.iectype, 0) Edouard@2764: variable_decl_array += [ Edouard@2822: "{&(" + node.cpath + "), " + node.iectype + { Edouard@2764: "EXT": "_P_ENUM", Edouard@2764: "IN": "_P_ENUM", Edouard@2764: "MEM": "_O_ENUM", Edouard@2764: "OUT": "_O_ENUM", Edouard@2764: "VAR": "_ENUM" Edouard@2765: }[node.vartype] + ", " + Edouard@2768: str(buf_index) + ", 0, }"] Edouard@2765: buf_index += sz Edouard@2775: item_count += 1 Edouard@2764: if len(node.path) == 1: Edouard@2764: extern_variables_declarations += [ Edouard@2764: "extern __IEC_" + node.iectype + "_" + Edouard@2764: "t" if node.vartype is "VAR" else "p" Edouard@2822: + node.cpath + ";"] Edouard@2828: Edouard@2822: assert(found_heartbeat) Edouard@2764: Edouard@2764: # TODO : filter only requiered external declarations Edouard@2828: for v in varlist: Edouard@2828: if v["C_path"].find('.') < 0: Edouard@2764: extern_variables_declarations += [ Edouard@2764: "extern %(type)s %(C_path)s;" % v] Edouard@2764: Edouard@2764: # TODO check if programs need to be declared separately Edouard@2764: # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" % Edouard@2764: # p for p in self._ProgramList]), Edouard@2764: Edouard@2771: # C code to observe/access HMI tree variables Edouard@2749: svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c") Edouard@2749: svghmi_c_file = open(svghmi_c_filepath, 'r') Edouard@2749: svghmi_c_code = svghmi_c_file.read() Edouard@2749: svghmi_c_file.close() Edouard@2817: svghmi_c_code = svghmi_c_code % { Edouard@2764: "variable_decl_array": ",\n".join(variable_decl_array), Edouard@2764: "extern_variables_declarations": "\n".join(extern_variables_declarations), Edouard@2767: "buffer_size": buf_index, Edouard@2775: "item_count": item_count, Edouard@2768: "var_access_code": targets.GetCode("var_access.c"), Edouard@2788: "PLC_ticktime": self.GetCTR().GetTicktime(), Edouard@2789: "hmi_hash_ints": ",".join(map(str,hmi_tree_root.hash())) Edouard@2764: } Edouard@2749: Edouard@2749: gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c") Edouard@2749: gen_svghmi_c = open(gen_svghmi_c_path, 'w') Edouard@2749: gen_svghmi_c.write(svghmi_c_code) Edouard@2749: gen_svghmi_c.close() Edouard@2749: Edouard@2771: # Python based WebSocket HMITree Server Edouard@2771: svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r') Edouard@2771: svghmiservercode = svghmiserverfile.read() Edouard@2771: svghmiserverfile.close() Edouard@2771: Edouard@2771: runtimefile_path = os.path.join(buildpath, "runtime_svghmi.py") Edouard@2771: runtimefile = open(runtimefile_path, 'w') Edouard@2771: runtimefile.write(svghmiservercode) Edouard@2771: runtimefile.close() Edouard@2771: Edouard@2771: return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "", Edouard@2771: ("runtime_svghmi0.py", open(runtimefile_path, "rb"))) Edouard@2745: Edouard@2816: Edouard@2818: class HMITreeSelector(wx.TreeCtrl): Edouard@2818: def __init__(self, parent): Edouard@2818: global on_hmitree_update Edouard@2818: wx.TreeCtrl.__init__(self,parent,style=wx.TR_MULTIPLE)# | wx.TR_HIDE_ROOT) Edouard@2818: Edouard@2818: isz = (16,16) Edouard@2818: self.il = il = wx.ImageList(*isz) Edouard@2818: self.fldridx = il.AddIcon(wx.ArtProvider.GetIcon(wx.ART_FOLDER, wx.ART_OTHER, isz)) Edouard@2818: self.fldropenidx = il.AddIcon(wx.ArtProvider.GetIcon(wx.ART_FOLDER_OPEN, wx.ART_OTHER, isz)) Edouard@2818: self.fileidx = il.AddIcon(wx.ArtProvider.GetIcon(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz)) Edouard@2818: self.SetImageList(il) Edouard@2818: Edouard@2818: on_hmitree_update = self.SVGHMIEditorUpdater() Edouard@2818: self.MakeTree() Edouard@2818: Edouard@2818: def _recurseTree(self, current_hmitree_root, current_tc_root): Edouard@2818: for c in current_hmitree_root.children: Edouard@2818: if hasattr(c, "children"): Edouard@2818: display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \ Edouard@2818: if c.hmiclass is not None else c.name Edouard@2818: tc_child = self.AppendItem(current_tc_root, display_name) Edouard@2818: self.SetPyData(tc_child, None) Edouard@2818: self.SetItemImage(tc_child, self.fldridx, wx.TreeItemIcon_Normal) Edouard@2818: self.SetItemImage(tc_child, self.fldropenidx, wx.TreeItemIcon_Expanded) Edouard@2818: Edouard@2818: self._recurseTree(c,tc_child) Edouard@2818: else: Edouard@2818: display_name = '{} {}'.format(c.nodetype[4:], c.name) Edouard@2818: tc_child = self.AppendItem(current_tc_root, display_name) Edouard@2818: self.SetPyData(tc_child, None) Edouard@2818: self.SetItemImage(tc_child, self.fileidx, wx.TreeItemIcon_Normal) Edouard@2818: self.SetItemImage(tc_child, self.fileidx, wx.TreeItemIcon_Expanded) Edouard@2818: Edouard@2818: def MakeTree(self): Edouard@2818: global hmi_tree_root Edouard@2818: Edouard@2818: self.Freeze() Edouard@2818: Edouard@2818: self.root = None Edouard@2818: self.DeleteAllItems() Edouard@2818: Edouard@2818: root_display_name = _("Please build to see HMI Tree") if hmi_tree_root is None else "HMI" Edouard@2818: self.root = self.AddRoot(root_display_name) Edouard@2818: self.SetPyData(self.root, None) Edouard@2818: self.SetItemImage(self.root, self.fldridx, wx.TreeItemIcon_Normal) Edouard@2818: self.SetItemImage(self.root, self.fldropenidx, wx.TreeItemIcon_Expanded) Edouard@2818: Edouard@2818: if hmi_tree_root is not None: Edouard@2818: self._recurseTree(hmi_tree_root, self.root) Edouard@2818: Edouard@2818: self.Thaw() Edouard@2816: Edouard@2816: def SVGHMIEditorUpdater(self): Edouard@2816: selfref = weakref.ref(self) Edouard@2816: def SVGHMIEditorUpdate(): Edouard@2816: o = selfref() Edouard@2816: if o is not None: Edouard@2816: wx.CallAfter(o.MakeTree) Edouard@2816: return SVGHMIEditorUpdate Edouard@2816: Edouard@2818: class HMITreeView(wx.SplitterWindow): Edouard@2818: Edouard@2818: def __init__(self, parent): Edouard@2818: wx.SplitterWindow.__init__(self, parent, Edouard@2818: style=wx.SUNKEN_BORDER | wx.SP_3D) Edouard@2818: Edouard@2818: self.SelectionTree = HMITreeSelector(self) Edouard@2818: #self.Staging = wx.Panel(self) Edouard@2818: #self.SplitHorizontally(self.SelectionTree, self.Staging, 200) Edouard@2818: self.Initialize(self.SelectionTree) Edouard@2818: Edouard@2818: Edouard@2818: class SVGHMIEditor(ConfTreeNodeEditor): Edouard@2818: CONFNODEEDITOR_TABS = [ Edouard@2818: (_("HMI Tree"), "CreateHMITreeView")] Edouard@2818: Edouard@2816: def CreateHMITreeView(self, parent): Edouard@2818: #self.HMITreeView = HMITreeView(self) Edouard@2818: return HMITreeSelector(parent) Edouard@2818: Edouard@2816: Edouard@2745: class SVGHMI(object): Edouard@2771: XSD = """ Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2823: Edouard@2823: Edouard@2823: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: """ Edouard@2816: Edouard@2816: EditorType = SVGHMIEditor Edouard@2745: Edouard@2745: ConfNodeMethods = [ Edouard@2745: { Edouard@2745: "bitmap": "ImportSVG", Edouard@2745: "name": _("Import SVG"), Edouard@2745: "tooltip": _("Import SVG"), Edouard@2745: "method": "_ImportSVG" Edouard@2745: }, Edouard@2745: { Edouard@2745: "bitmap": "ImportSVG", # should be something different Edouard@2745: "name": _("Inkscape"), Edouard@2745: "tooltip": _("Edit HMI"), Edouard@2745: "method": "_StartInkscape" Edouard@2745: }, Edouard@2771: Edouard@2788: # TODO : Launch POEdit button Edouard@2788: # PO -> SVG layers button Edouard@2788: # SVG layers -> PO Edouard@2788: Edouard@2771: # TODO : HMITree button Edouard@2771: # - can drag'n'drop variabes to Inkscape Edouard@2771: Edouard@2745: ] Edouard@2745: Edouard@2745: def _getSVGpath(self, project_path=None): Edouard@2745: if project_path is None: Edouard@2745: project_path = self.CTNPath() Edouard@2781: return os.path.join(project_path, "svghmi.svg") Edouard@2745: Edouard@2745: Edouard@2745: def OnCTNSave(self, from_project_path=None): Edouard@2745: if from_project_path is not None: Edouard@2745: shutil.copyfile(self._getSVGpath(from_project_path), Edouard@2745: self._getSVGpath()) Edouard@2750: return True Edouard@2745: Edouard@2753: def GetSVGGeometry(self): Edouard@2756: # invoke inskscape -S, csv-parse output, produce elements Edouard@2756: InkscapeGeomColumns = ["Id", "x", "y", "w", "h"] Edouard@2756: Edouard@2756: inkpath = get_inkscape_path() Edouard@2756: svgpath = self._getSVGpath() Edouard@2756: _status, result, _err_result = ProcessLogger(None, Edouard@2756: inkpath + " -S " + svgpath, Edouard@2756: no_stdout=True, Edouard@2756: no_stderr=True).spin() Edouard@2756: res = [] Edouard@2756: for line in result.split(): Edouard@2756: strippedline = line.strip() Edouard@2756: attrs = dict( Edouard@2756: zip(InkscapeGeomColumns, line.strip().split(','))) Edouard@2756: Edouard@2756: res.append(etree.Element("bbox", **attrs)) Edouard@2756: Edouard@2756: return res Edouard@2753: Edouard@2763: def GetHMITree(self): Edouard@2763: global hmi_tree_root Edouard@2788: res = [hmi_tree_root.etree(add_hash=True)] Edouard@2763: return res Edouard@2763: Edouard@2745: def CTNGenerate_C(self, buildpath, locations): Edouard@2745: """ Edouard@2745: Return C code generated by iec2c compiler Edouard@2745: when _generate_softPLC have been called Edouard@2745: @param locations: ignored Edouard@2745: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND Edouard@2745: """ Edouard@2745: Edouard@2771: location_str = "_".join(map(str, self.GetCurrentLocation())) Edouard@2771: view_name = self.BaseParams.getName() Edouard@2771: Edouard@2745: svgfile = self._getSVGpath() Edouard@2771: Edouard@2771: res = ([], "", False) Edouard@2771: Edouard@2812: target_fname = "svghmi_"+location_str+".xhtml" Edouard@2771: Edouard@2771: target_path = os.path.join(self._getBuildPath(), target_fname) Edouard@2812: target_file = open(target_path, 'wb') Edouard@2771: Edouard@2745: if os.path.exists(svgfile): Edouard@2753: Edouard@2753: # TODO : move to __init__ Edouard@2753: transform = XSLTransform(os.path.join(ScriptDirectory, "gen_index_xhtml.xslt"), Edouard@2763: [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry()), Edouard@2763: ("GetHMITree", lambda *_ignored:self.GetHMITree())]) Edouard@2753: Edouard@2753: Edouard@2753: # load svg as a DOM with Etree Edouard@2753: svgdom = etree.parse(svgfile) Edouard@2753: Edouard@2753: # call xslt transform on Inkscape's SVG to generate XHTML Edouard@2814: try: Edouard@2814: result = transform.transform(svgdom) Edouard@2814: except XSLTApplyError as e: Edouard@2814: self.FatalError("SVGHMI " + view_name + ": " + e.message) Edouard@2817: Edouard@2771: result.write(target_file, encoding="utf-8") Edouard@2764: # print(str(result)) Edouard@2764: # print(transform.xslt.error_log) Edouard@2753: Edouard@2753: # TODO Edouard@2745: # - Errors on HMI semantics Edouard@2745: # - ... maybe something to have a global view of what is declared in SVG. Edouard@2753: Edouard@2745: else: Edouard@2817: # TODO : use default svg that expose the HMI tree as-is Edouard@2771: target_file.write(""" Edouard@2771: Edouard@2771: Edouard@2771:

No SVG file provided

Edouard@2771: Edouard@2771: Edouard@2771: """) Edouard@2771: Edouard@2771: target_file.close() Edouard@2771: Edouard@2772: res += ((target_fname, open(target_path, "rb")),) Edouard@2828: Edouard@2823: svghmi_cmds = {} Edouard@2823: for thing in ["Start", "Stop", "Watchdog"]: Edouard@2823: given_command = self.GetParamsAttributes("SVGHMI.On"+thing)["value"] edouard@2824: svghmi_cmds[thing] = ( edouard@2824: "Popen(" + Edouard@2828: repr(shlex.split(given_command.format(port="8008", name=view_name))) + edouard@2824: ")") if given_command else "# no command given" Edouard@2772: Edouard@2771: runtimefile_path = os.path.join(buildpath, "runtime_svghmi1_%s.py" % location_str) Edouard@2771: runtimefile = open(runtimefile_path, 'w') Edouard@2771: runtimefile.write(""" Edouard@2828: # TODO : multi Edouard@2823: def watchdog_trigger(): Edouard@2828: {svghmi_cmds[Watchdog]} edouard@2824: edouard@2824: def _runtime_svghmi1_{location}_start(): edouard@2824: svghmi_root.putChild('{view_name}',File('{xhtml}', defaultType='application/xhtml+xml')) Edouard@2828: {svghmi_cmds[Start]} edouard@2824: edouard@2824: def _runtime_svghmi1_{location}_stop(): edouard@2824: svghmi_root.delEntity('{view_name}') Edouard@2828: {svghmi_cmds[Stop]} edouard@2824: edouard@2824: """.format(location=location_str, edouard@2824: xhtml=target_fname, edouard@2824: view_name=view_name, Edouard@2828: svghmi_cmds=svghmi_cmds)) Edouard@2771: Edouard@2771: runtimefile.close() Edouard@2771: Edouard@2771: res += (("runtime_svghmi1_%s.py" % location_str, open(runtimefile_path, "rb")),) Edouard@2745: Edouard@2745: return res Edouard@2745: Edouard@2745: def _ImportSVG(self): Edouard@2745: dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "", _("SVG files (*.svg)|*.svg|All files|*.*"), wx.OPEN) Edouard@2745: if dialog.ShowModal() == wx.ID_OK: Edouard@2745: svgpath = dialog.GetPath() Edouard@2745: if os.path.isfile(svgpath): Edouard@2745: shutil.copy(svgpath, self._getSVGpath()) Edouard@2745: else: Edouard@2745: self.GetCTRoot().logger.write_error(_("No such SVG file: %s\n") % svgpath) Edouard@2745: dialog.Destroy() Edouard@2745: Edouard@2745: def _StartInkscape(self): Edouard@2745: svgfile = self._getSVGpath() Edouard@2745: open_inkscape = True Edouard@2745: if not self.GetCTRoot().CheckProjectPathPerm(): Edouard@2745: dialog = wx.MessageDialog(self.GetCTRoot().AppFrame, Edouard@2745: _("You don't have write permissions.\nOpen Inkscape anyway ?"), Edouard@2745: _("Open Inkscape"), Edouard@2745: wx.YES_NO | wx.ICON_QUESTION) Edouard@2745: open_inkscape = dialog.ShowModal() == wx.ID_YES Edouard@2745: dialog.Destroy() Edouard@2745: if open_inkscape: Edouard@2745: if not os.path.isfile(svgfile): Edouard@2745: svgfile = None Edouard@2745: open_svg(svgfile) Edouard@2822: Edouard@2822: def CTNGlobalInstances(self): Edouard@2822: # view_name = self.BaseParams.getName() Edouard@2822: # return [ (view_name + "_" + name, iec_type, "") for name, iec_type in SPECIAL_NODES] Edouard@2822: # TODO : move to library level for multiple hmi Edouard@2822: return [(name, iec_type, "") for name, iec_type in SPECIAL_NODES] Edouard@2822: