From 7e7e95f9aed94c6ebc9a83b6cd76ed82f2271197 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 25 Jul 2017 11:02:03 +0100 Subject: [PATCH] Added icons for pandabox --- .../pandablocksmanagercontroller.py | 84 ++++- malcolm/modules/pandablocks/icons/CLOCKS.svg | 103 ++++++ malcolm/modules/pandablocks/icons/COUNTER.svg | 178 +++++++++++ malcolm/modules/pandablocks/icons/DIV.svg | 94 ++++++ malcolm/modules/pandablocks/icons/INENC.svg | 292 ++++++++++++++++++ malcolm/modules/pandablocks/icons/LUT.svg | 245 ++++++--------- malcolm/modules/pandablocks/icons/LVDSIN.svg | 119 +++++++ malcolm/modules/pandablocks/icons/LVDSOUT.svg | 118 +++++++ malcolm/modules/pandablocks/icons/OUTENC.svg | 276 +++++++++++++++++ malcolm/modules/pandablocks/icons/PULSE.svg | 110 +++++++ malcolm/modules/pandablocks/icons/TTLOUT.svg | 158 ++++++++++ setup.cfg | 2 +- .../test_pandablocksmanagercontroller.py | 39 ++- 13 files changed, 1664 insertions(+), 154 deletions(-) create mode 100644 malcolm/modules/pandablocks/icons/CLOCKS.svg create mode 100644 malcolm/modules/pandablocks/icons/COUNTER.svg create mode 100644 malcolm/modules/pandablocks/icons/DIV.svg create mode 100644 malcolm/modules/pandablocks/icons/INENC.svg create mode 100644 malcolm/modules/pandablocks/icons/LVDSIN.svg create mode 100644 malcolm/modules/pandablocks/icons/LVDSOUT.svg create mode 100644 malcolm/modules/pandablocks/icons/OUTENC.svg create mode 100644 malcolm/modules/pandablocks/icons/PULSE.svg create mode 100644 malcolm/modules/pandablocks/icons/TTLOUT.svg diff --git a/malcolm/modules/pandablocks/controllers/pandablocksmanagercontroller.py b/malcolm/modules/pandablocks/controllers/pandablocksmanagercontroller.py index 30d3c6958..bf062752b 100644 --- a/malcolm/modules/pandablocks/controllers/pandablocksmanagercontroller.py +++ b/malcolm/modules/pandablocks/controllers/pandablocksmanagercontroller.py @@ -1,7 +1,9 @@ import time import os +import operator +from xml.etree import cElementTree as ET -from malcolm.compat import OrderedDict, maybe_import_cothread +from malcolm.compat import OrderedDict, maybe_import_cothread, et_to_string from malcolm.core import method_also_takes, Queue, TimeoutError, \ call_with_params from malcolm.modules.builtin.controllers import BasicController, \ @@ -15,13 +17,17 @@ SVG_DIR = os.path.join(os.path.dirname(__file__), "..", "icons") +LUT_CONSTANTS = dict( + A=0xffff0000, B=0xff00ff00, C=0xf0f0f0f0, D=0xcccccccc, E=0xaaaaaaaa) + @method_also_takes( "hostname", StringMeta("Hostname of the box"), "localhost", "port", NumberMeta("uint32", "Port number of the server client"), 8888) class PandABlocksManagerController(ManagerController): def __init__(self, process, parts, params): - super(PandABlocksManagerController, self).__init__(process, parts, params) + super(PandABlocksManagerController, self).__init__( + process, parts, params) # {block_name: BlockData} self._blocks_data = {} # {block_name: {field_name: Part}} @@ -35,6 +41,9 @@ def __init__(self, process, parts, params): # fields that need to inherit UNITS, SCALE and OFFSET from upstream self._inherit_scale = {} self._inherit_offset = {} + # lut elements to be displayed or not + # {fnum: {id: visible}} + self._lut_elements = {} # changes left over from last time self.changes = OrderedDict() # The PandABlock client that does the comms @@ -144,7 +153,7 @@ def _make_parts(self, block_name, block_data): self._blocks_parts[block_name] = maker.parts # Set the initial block_url - self._set_icon_url(block_name) + self._set_icon_svg(block_name) # setup param pos on a block with pos_out to inherit SCALE OFFSET UNITS pos_fields = [] @@ -183,18 +192,77 @@ def _map_scale_offset(self, block_name, src_field, dest_field): self._mirrored_fields.setdefault(full_src_field, []).append( full_dest_field) - def _set_icon_url(self, block_name): + def _set_icon_svg(self, block_name): icon_attr = self._blocks_parts[block_name]["icon"].attr fname = block_name.rstrip("0123456789") + ".svg" svg_text = "" if fname in os.listdir(SVG_DIR): svg_text = open(os.path.join(SVG_DIR, fname)).read() - if fname == "LUT": + if fname == "LUT.svg": fnum = int(self.client.get_field(block_name, "FUNC.RAW")) - # TODO: Hide bits not needed here - pass + invis = self._get_lut_icon_elements(fnum) + root = ET.fromstring(svg_text) + for i in invis: + # Find the first parent which has a child with id i + parent = root.find('.//*[@id=%r]/..' % i) + # Find the child and remove it + child = parent.find('./*[@id=%r]' % i) + parent.remove(child) + svg_text = et_to_string(root) icon_attr.set_value(svg_text) + def _get_lut_icon_elements(self, fnum): + if not self._lut_elements: + # Generate the lut element table + # Do the general case funcs + funcs = [("AND", operator.and_), ("OR", operator.or_)] + for func, op in funcs: + for nargs in (2, 3, 4, 5): + # 2**nargs permutations + for permutation in range(2 ** nargs): + self._calc_visibility(func, op, nargs, permutation) + # Add in special cases for NOT + for ninp in "ABCDE": + invis = {"AND", "OR", "LUT"} + for inp in "ABCDE": + if inp != ninp: + invis.add(inp) + invis.add("not%s" % inp) + self._lut_elements[~LUT_CONSTANTS[ninp] & (2 ** 32 - 1)] = invis + # And catchall for LUT in 0 + invis = {"AND", "OR", "NOT"} + for inp in "ABCDE": + invis.add("not%s" % inp) + self._lut_elements[0] = invis + return self._lut_elements.get(fnum, self._lut_elements[0]) + + def _calc_visibility(self, func, op, nargs, permutations): + # Visibility dictionary defaults + invis = {"AND", "OR", "LUT", "NOT"} + invis.remove(func) + args = [] + for i, inp in enumerate("EDCBA"): + # xxxxx where x is 0 or 1 + # EDCBA + negations = format(permutations, '05b') + if (5 - i) > nargs: + # invisible + invis.add(inp) + invis.add("not%s" % inp) + else: + # visible + if negations[i] == "1": + args.append(~LUT_CONSTANTS[inp] & (2 ** 32 - 1)) + else: + invis.add("not%s" % inp) + args.append(LUT_CONSTANTS[inp]) + + # Insert into table + fnum = op(args[0], args[1]) + for a in args[2:]: + fnum = op(fnum, a) + self._lut_elements[fnum] = invis + def handle_changes(self, changes): for k, v in changes.items(): self.changes[k] = v @@ -210,7 +278,7 @@ def handle_changes(self, changes): self.changes.pop(full_field) # If it was LUT.FUNC then recalculate icon if block_name.startswith("LUT") and field_name == "FUNC": - self._set_icon_url(block_name) + self._set_icon_svg(block_name) def update_attribute(self, block_name, field_name, val): ret = None diff --git a/malcolm/modules/pandablocks/icons/CLOCKS.svg b/malcolm/modules/pandablocks/icons/CLOCKS.svg new file mode 100644 index 000000000..01949a45e --- /dev/null +++ b/malcolm/modules/pandablocks/icons/CLOCKS.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/COUNTER.svg b/malcolm/modules/pandablocks/icons/COUNTER.svg new file mode 100644 index 000000000..e15a968fe --- /dev/null +++ b/malcolm/modules/pandablocks/icons/COUNTER.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/DIV.svg b/malcolm/modules/pandablocks/icons/DIV.svg new file mode 100644 index 000000000..a90c7b72e --- /dev/null +++ b/malcolm/modules/pandablocks/icons/DIV.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/INENC.svg b/malcolm/modules/pandablocks/icons/INENC.svg new file mode 100644 index 000000000..eed1600b4 --- /dev/null +++ b/malcolm/modules/pandablocks/icons/INENC.svg @@ -0,0 +1,292 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/LUT.svg b/malcolm/modules/pandablocks/icons/LUT.svg index f475c801c..2736fb14a 100644 --- a/malcolm/modules/pandablocks/icons/LUT.svg +++ b/malcolm/modules/pandablocks/icons/LUT.svg @@ -13,53 +13,10 @@ height="140" id="svg2816" version="1.1" - inkscape:version="0.47 r22583" + inkscape:version="0.91 r13725" sodipodi:docname="LUT.svg"> - - - - - - - + id="defs2818" /> + inkscape:window-width="1861" + inkscape:window-height="1056" + inkscape:window-x="59" + inkscape:window-y="24" + inkscape:window-maximized="1" + inkscape:snap-grids="true" + inkscape:snap-to-guides="true" + inkscape:snap-bbox="true" + inkscape:snap-global="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:snap-intersection-paths="true" + inkscape:object-nodes="true"> - + ry="2.8303585" + rx="2.8176346" + cy="-18.169641" + cx="46.817635" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="notE" + inkscape:label="" /> + ry="2.9151783" + rx="2.8176346" + cy="-28.084822" + cx="46.817635" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="notD" + inkscape:label="" /> + ry="3" + rx="2.8176346" + cy="-38" + cx="46.817635" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="notC" + inkscape:label="" /> + ry="3.0848198" + rx="2.8176346" + cy="-47.91518" + cx="46.817635" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="notB" + inkscape:label="" /> + ry="3.1696434" + rx="2.8176346" /> + + + + + diff --git a/malcolm/modules/pandablocks/icons/LVDSIN.svg b/malcolm/modules/pandablocks/icons/LVDSIN.svg new file mode 100644 index 000000000..1cc0201bb --- /dev/null +++ b/malcolm/modules/pandablocks/icons/LVDSIN.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/LVDSOUT.svg b/malcolm/modules/pandablocks/icons/LVDSOUT.svg new file mode 100644 index 000000000..f3a1bfffc --- /dev/null +++ b/malcolm/modules/pandablocks/icons/LVDSOUT.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/OUTENC.svg b/malcolm/modules/pandablocks/icons/OUTENC.svg new file mode 100644 index 000000000..f808e00ec --- /dev/null +++ b/malcolm/modules/pandablocks/icons/OUTENC.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/PULSE.svg b/malcolm/modules/pandablocks/icons/PULSE.svg new file mode 100644 index 000000000..00ad919af --- /dev/null +++ b/malcolm/modules/pandablocks/icons/PULSE.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + diff --git a/malcolm/modules/pandablocks/icons/TTLOUT.svg b/malcolm/modules/pandablocks/icons/TTLOUT.svg new file mode 100644 index 000000000..51105bf26 --- /dev/null +++ b/malcolm/modules/pandablocks/icons/TTLOUT.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + image/svg+xml + + + + + Frederic TA + + + + + + + + + + + + + + + + + + + + diff --git a/setup.cfg b/setup.cfg index 1f0102bb0..b4d7d86bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [tool:pytest] -addopts = --tb=native -vv +addopts = --tb=native -vv --continue-on-collection-errors diff --git a/tests/test_modules/test_pandablocks/test_pandablocksmanagercontroller.py b/tests/test_modules/test_pandablocks/test_pandablocksmanagercontroller.py index c97b1ebd3..53bdfb9cd 100644 --- a/tests/test_modules/test_pandablocks/test_pandablocksmanagercontroller.py +++ b/tests/test_modules/test_pandablocks/test_pandablocksmanagercontroller.py @@ -1,6 +1,7 @@ from collections import OrderedDict import unittest -from mock import call, Mock, patch, ANY +from mock import call, Mock, patch, ANY, MagicMock +from xml.etree import cElementTree as ET from malcolm.core import call_with_params from malcolm.modules.pandablocks.controllers import PandABlocksManagerController @@ -134,3 +135,39 @@ def test_scale_offset_following(self): call('PCOMP.STEP.SCALE=1\n'), call('PCOMP.STEP.UNITS=\n') ] + + def test_lut(self): + # LUT symbol + assert self.o._get_lut_icon_elements(0) == { + 'AND', 'NOT', 'OR', 'notA', 'notB', 'notC', 'notD', 'notE'} + # A&B&C&D&E + assert self.o._get_lut_icon_elements(0x80000000) == { + 'LUT', 'NOT', 'OR', 'notA', 'notB', 'notC', 'notD', 'notE'} + # !A&!B&!C&!D&!E + assert self.o._get_lut_icon_elements(0x1) == { + 'LUT', 'NOT', 'OR'} + # A&!B + assert self.o._get_lut_icon_elements(0xff0000) == { + 'C', 'D', 'E', 'LUT', 'NOT', 'OR', 'notA', 'notC', 'notD', 'notE'} + # A&C should be LUT + assert self.o._get_lut_icon_elements(0xf0f00000) == { + 'AND', 'NOT', 'OR', 'notA', 'notB', 'notC', 'notD', 'notE'} + # !C + assert self.o._get_lut_icon_elements(0xf0f0f0f) == { + 'A', 'AND', 'B', 'D', 'E', 'LUT', 'OR', 'notA', 'notB', 'notC', 'notD', 'notE'}\ + # A|B + assert self.o._get_lut_icon_elements(0xffffff00) == { + 'AND', 'C', 'D', 'E', 'LUT', 'NOT', 'notA', 'notB', 'notC', 'notD', 'notE'} + + def test_symbol(self): + m = MagicMock() + self.o._blocks_parts["LUT1"] = dict(icon=m) + # !A&!B&!C&!D&!E + self.client.get_field.return_value = "1" + self.o._set_icon_svg("LUT1") + svg_text = m.attr.set_value.call_args[0][0] + root = ET.fromstring(svg_text) + assert len(root.findall(".//*[@id='A']")) == 1 + assert len(root.findall(".//*[@id='notA']")) == 1 + assert len(root.findall(".//*[@id='OR']")) == 0 + assert len(root.findall(".//*[@id='AND']")) == 1