# HG changeset patch # User Edouard Tisserant # Date 1589441049 -7200 # Node ID 7349063b19d8d0aecb894c8b6334fcc3c097a4bd # Parent 8f928cee01e536bdefeb7986274f83197de49ad8# Parent 113e2f2e324d31b7b5ff3a0add1dff04dd6a8e4f merge diff -r 113e2f2e324d -r 7349063b19d8 ConfigTreeNode.py --- a/ConfigTreeNode.py Wed May 13 22:25:22 2020 +0200 +++ b/ConfigTreeNode.py Thu May 14 09:24:09 2020 +0200 @@ -678,6 +678,7 @@ raise UserAddressedException(message) +# Exception type for problems that user has to take action in order to fix class UserAddressedException(Exception): pass diff -r 113e2f2e324d -r 7349063b19d8 POULibrary.py --- a/POULibrary.py Wed May 13 22:25:22 2020 +0200 +++ b/POULibrary.py Thu May 14 09:24:09 2020 +0200 @@ -26,6 +26,7 @@ from __future__ import absolute_import from weakref import ref +from ConfigTreeNode import UserAddressedException class POULibrary(object): def __init__(self, CTR, LibName, TypeStack): @@ -59,6 +60,11 @@ # Pure python or IEC libs doesn't produce C code return ((""), [], False), "" + def FatalError(self, message): + """ Raise an exception that will trigger error message intended to + the user, but without backtrace since it is not a software error """ + + raise UserAddressedException(message) def SimplePOULibraryFactory(path): class SimplePOULibrary(POULibrary): diff -r 113e2f2e324d -r 7349063b19d8 svghmi/svghmi.py --- a/svghmi/svghmi.py Wed May 13 22:25:22 2020 +0200 +++ b/svghmi/svghmi.py Thu May 14 09:24:09 2020 +0200 @@ -70,6 +70,7 @@ def place_node(self, node): best_child = None known_best_match = 0 + potential_siblings = {} for child in self.children: if child.path is not None: in_common = 0 @@ -86,10 +87,25 @@ in_common == len(child.path) - 1: known_best_match = in_common best_child = child + else: + potential_siblings[child.path[ + -2 if child.nodetype == "HMI_NODE" else -1]] = child if best_child is not None: - best_child.place_node(node) + if node.nodetype == "HMI_NODE" and best_child.path[:-1] == node.path[:-1]: + return "Duplicate_HMI_NODE", best_child + return best_child.place_node(node) else: + candidate_name = node.path[-2 if node.nodetype == "HMI_NODE" else -1] + if candidate_name in potential_siblings: + return "Non_Unique", potential_siblings[candidate_name] + + if node.nodetype == "HMI_NODE" and len(self.children) > 0: + prev = self.children[-1] + if prev.path[:-1] == node.path[:-1]: + return "Late_HMI_NODE",prev + self.children.append(node) + return None def etree(self, add_hash=False): @@ -195,7 +211,7 @@ for i,v in enumerate(hmi_types_instances): path = v["IEC_path"].split(".") derived = v["derived"] - if derived == "HMI_NODE" and ['CONFIG', 'HEARTBEAT'] : + if derived == "HMI_NODE": hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"]) hmi_types_instances.pop(i) break @@ -217,7 +233,32 @@ else: name = path[-1] new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs) - hmi_tree_root.place_node(new_node) + placement_result = hmi_tree_root.place_node(new_node) + if placement_result is not None: + cause, problematic_node = placement_result + if cause == "Non_Unique": + message = _("HMI tree nodes paths are not unique.\nConflicting variable: {} {}").format( + ".".join(problematic_node.path), + ".".join(new_node.path)) + + last_FB = None + for v in varlist: + if v["vartype"] == "FB": + last_FB = v + if v["C_path"] == problematic_node: + break + if last_FB is not None: + failing_parent = last_FB["type"] + message += "\n" + message += _("Solution: Add HMI_NODE at beginning of {}").format(failing_parent) + + elif cause in ["Late_HMI_NODE", "Duplicate_HMI_NODE"]: + cause, problematic_node = placement_result + message = _("There must be only one occurrence of HMI_NODE before any HMI_* variable in POU.\nConflicting variable: {} {}").format( + ".".join(problematic_node.path), + ".".join(new_node.path)) + + self.FatalError("SVGHMI : " + message) if on_hmitree_update is not None: on_hmitree_update()