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@2757: from itertools import izip Edouard@2757: from pprint import pprint, pformat Edouard@2745: Edouard@2745: import wx 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@2753: from lxml import etree Edouard@2745: Edouard@2756: from util.ProcessLogger import ProcessLogger Edouard@2756: Edouard@2749: HMI_TYPES_DESC = { Edouard@2749: "HMI_CLASS":{}, Edouard@2749: "HMI_LABEL":{}, Edouard@2749: "HMI_STRING":{}, Edouard@2749: "HMI_INT":{}, Edouard@2749: "HMI_REAL":{} Edouard@2749: } Edouard@2749: Edouard@2749: HMI_TYPES = HMI_TYPES_DESC.keys() Edouard@2745: Edouard@2753: from XSLTransform import XSLTransform Edouard@2753: Edouard@2753: ScriptDirectory = paths.AbsDir(__file__) Edouard@2753: Edouard@2757: class HMITreeNode(object): Edouard@2757: def __init__(self, path, name, nodetype): Edouard@2757: self.path = path Edouard@2757: self.name = name Edouard@2757: self.nodetype = nodetype Edouard@2757: if nodetype in ["HMI_LABEL", "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@2757: 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@2757: 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@2762: 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@2757: if best_child is not None and best_child.nodetype == "HMI_LABEL": Edouard@2757: best_child.place_node(node) Edouard@2757: else: Edouard@2757: self.children.append(node) Edouard@2757: Edouard@2757: 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@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@2757: | +->va HMI_LABEL Edouard@2757: | +->v3 HMI_INT Edouard@2757: | +->v4 HMI_INT Edouard@2757: | Edouard@2757: +->fb1 (type mhoo) Edouard@2757: | +->va HMI_LABEL 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@2757: +->fb0_va Edouard@2757: | +-> v3 Edouard@2757: | +-> v4 Edouard@2757: | Edouard@2757: +->fb1_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@2762: # add special nodes Edouard@2762: map(lambda (n,t): hmi_tree_root.children.append(HMITreeNode(None,n,t)), [ Edouard@2762: ("plc_status", "HMI_PLC_STATUS"), Edouard@2762: ("current_page", "HMI_CURRENT_PAGE")]) Edouard@2757: Edouard@2757: # deduce HMI tree from PLC HMI_* instances Edouard@2757: for v in hmi_types_instances: Edouard@2757: path = v["IEC_path"].split(".") Edouard@2758: # ignores variables starting with _TMP_ Edouard@2758: if path[-1].startswith("_TMP_"): Edouard@2758: continue Edouard@2757: new_node = HMITreeNode(path, path[-1], v["derived"]) Edouard@2757: hmi_tree_root.place_node(new_node) Edouard@2757: Edouard@2757: print(hmi_tree_root.pprint()) Edouard@2749: Edouard@2749: # TODO generate 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@2753: svghmi_c_code = svghmi_c_code % { "hmi_tree": "TODO !!!"} 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@2749: return (["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "" Edouard@2745: Edouard@2745: class SVGHMI(object): Edouard@2745: XSD = """ Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: """ 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@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@2745: # define name for SVG file containing gui layout Edouard@2745: return os.path.join(project_path, "gui.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: # TODO : move following line to __init__ 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@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@2749: # TODO fetch HMI tree from library Edouard@2745: Edouard@2745: svgfile = self._getSVGpath() 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@2753: [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry())]) 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@2753: result = transform.transform(svgdom) Edouard@2753: Edouard@2753: print(str(result)) Edouard@2753: 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@2745: # TODO : use default svg that expose the HMI tree as-is Edouard@2745: pass Edouard@2745: Edouard@2745: Edouard@2745: res = ([], "", False) Edouard@2745: Edouard@2745: targetpath = os.path.join(self._getBuildPath(), "target.xhtml") Edouard@2745: targetfile = open(targetpath, 'w') Edouard@2745: Edouard@2745: # TODO : DOM to string Edouard@2745: targetfile.write("TODO") Edouard@2745: targetfile.close() Edouard@2745: res += (("target.js", open(targetpath, "rb")),) Edouard@2745: Edouard@2745: # TODO add C code to expose HMI tree variables to shared memory Edouard@2745: # TODO generate a description of shared memory (xml or CSV) Edouard@2745: # that can be loaded by svghmi QTWeb* app or svghmi server Edouard@2745: 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)