Advertisement
StSav012

gas system

Feb 4th, 2025 (edited)
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 64.25 KB | Source Code | 0 0
  1. import enum
  2. import logging
  3. import sys
  4. from functools import lru_cache
  5. from re import search
  6. from typing import Any, ItemsView, Match, Sequence
  7. from xml.etree import ElementTree
  8.  
  9. from PySide6.QtCore import QEvent, QObject, QPointF, QRect, QRectF, QSize, Qt, Signal
  10. from PySide6.QtGui import QGuiApplication, QMouseEvent
  11. from PySide6.QtSvgWidgets import QSvgWidget
  12. from PySide6.QtWidgets import QApplication, QLayout, QLayoutItem, QWidget
  13.  
  14.  
  15. CLICK_TOLERANCE: float = 0.05
  16.  
  17. # NB: There should be no empty line at the beginning, hence the backslash after the triple quotes.
  18. SVG_DATA: str = """\
  19. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
  20. <!-- Created in defiance of Inkscape (http://www.inkscape.org/) -->
  21.  
  22. <svg viewBox="0 0 276 332"
  23.     version="1.1"
  24.     style="stroke-width:0.5px;font-size:8px;font-family:sans-serif;
  25.     --foreground:#000;--background:#fff;
  26.     --air:#26788c;--low-vacuum:#8c268c;--high-vacuum:var(--foreground);
  27.     --undefined-color:#8c8c8c;--off-color:#bf4040;--on-color:#40a6bf;--fault-color:#ff1a1a;"
  28.     xmlns="http://www.w3.org/2000/svg">
  29.    <defs>
  30.        <style>
  31.            /* a hotfix to center the texts vertically */
  32.            text#x4_text,text#x6_text{transform:translate(0,3)}
  33.        </style>
  34.        <linearGradient id="getting-off-color">
  35.            <stop offset="0.05" stop-color="var(--on-color)"/>
  36.            <stop offset="0.95" stop-color="var(--off-color)"/>
  37.        </linearGradient>
  38.        <linearGradient id="getting-on-color">
  39.            <stop offset="0.05" stop-color="var(--off-color)"/>
  40.            <stop offset="0.95" stop-color="var(--on-color)"/>
  41.        </linearGradient>
  42.  
  43.        <path id="arrow_end" display="marker"
  44.              vector-effect="non-scaling-stroke"
  45.              d="M -1,-1 L 1,0 L -1,1 z"/>
  46.        <circle id="tiny_circle" display="marker"
  47.                cx="0" cy="0"
  48.                r="1"
  49.                style="fill:white;stroke-width:0.5px;stroke:black"/>
  50.  
  51.        <path id="valve_template"
  52.              stroke="var(--foreground)"
  53.              vector-effect="non-scaling-stroke"
  54.              d="M -16,-8 V 8 L 16,-8 V 8 Z
  55.                 M 0,0 -2,-1 v -3 h -3 v -6 H 5 v 6 H 2 v 3 z
  56.                 M 0,0 2,1 V 4 H -2 V 1 Z m -5,-4 v -6 H 5 v 6 z"
  57.              display="block"/>
  58.        <g id="x4_valve_template"
  59.           style="stroke:var(--foreground)">
  60.            <circle cy="0"
  61.                    cx="0"
  62.                    r="16"
  63.                    vector-effect="non-scaling-stroke"/>
  64.            <path style="fill:none;stroke-width:2px;stroke:black"
  65.                  d="M 0,-16 A 16,16,0,0,1,16,0
  66.                     M 0,16 A 16,16,0,0,1,-16,0"
  67.                  transform="scale(0.75)"/>
  68.            <use href="#tiny_circle"
  69.                 transform="rotate(0) scale(0.75) translate(16, 0)"/>
  70.            <use href="#tiny_circle"
  71.                 transform="rotate(90) scale(0.75) translate(16, 0)"/>
  72.            <use href="#tiny_circle"
  73.                 transform="rotate(180) scale(0.75) translate(16, 0)"/>
  74.            <use href="#tiny_circle"
  75.                 transform="rotate(270) scale(0.75) translate(16, 0)"/>
  76.        </g>
  77.        <g id="x6_valve_template"
  78.           style="stroke:var(--foreground)">
  79.            <circle cy="0"
  80.                    cx="0"
  81.                    r="16"
  82.                    vector-effect="non-scaling-stroke"/>
  83.            <path style="fill:none;stroke-width:2px;stroke:black"
  84.                  d="M 8,-13.85640646055102 A 16,16,0,0,1,16,0
  85.                     M -16,0 A 16,16,0,0,1,-8,-13.85640646055102
  86.                     M 8,13.85640646055102 A 16,16,0,0,1,-8,13.85640646055102"
  87.                  transform="scale(0.75)"/>
  88.            <use href="#tiny_circle"
  89.                 transform="rotate(0) scale(0.75) translate(16, 0)"/>
  90.            <use href="#tiny_circle"
  91.                 transform="rotate(60) scale(0.75) translate(16, 0)"/>
  92.            <use href="#tiny_circle"
  93.                 transform="rotate(120) scale(0.75) translate(16, 0)"/>
  94.            <use href="#tiny_circle"
  95.                 transform="rotate(180) scale(0.75) translate(16, 0)"/>
  96.            <use href="#tiny_circle"
  97.                 transform="rotate(240) scale(0.75) translate(16, 0)"/>
  98.            <use href="#tiny_circle"
  99.                 transform="rotate(300) scale(0.75) translate(16, 0)"/>
  100.        </g>
  101.        <g id="turbo_molecular_pump_template"
  102.           stroke="var(--foreground)">
  103.            <rect width="32" height="32"
  104.                  x="-16" y="-16"
  105.                  vector-effect="non-scaling-stroke"/>
  106.            <circle cy="0" cx="0"
  107.                    r="8"/>
  108.            <path fill="none"
  109.                  d="m -6,-4 v 8 m 2,-8 v 8 m 2,-8 v 8 m 2,-8 v 8 m 2,-8 v 8 m 2,-8 v 8 m 2,-8 v 8"/>
  110.            <path fill="none"
  111.                  d="m 9,-7 7,7 -7,7"/>
  112.        </g>
  113.        <g id="mechanical_pump_template"
  114.           stroke="var(--foreground)">
  115.            <rect width="32" height="32"
  116.                  x="-16" y="-16"
  117.                  vector-effect="non-scaling-stroke"/>
  118.            <circle cx="0" cy="0"
  119.                    r="8"
  120.                    fill="none"/>
  121.            <path fill="none"
  122.                  d="m 0,-16 -7,7"/>
  123.            <path fill="none"
  124.                  d="m 0,-16 7,7"/>
  125.            <use href="#arrow_end"
  126.                 fill="var(--foreground)"
  127.                 transform="translate(-7, -9) rotate(135) scale(1.5)"/>
  128.            <use href="#arrow_end"
  129.                 fill="var(--foreground)"
  130.                 transform="translate(7, -9) rotate(45) scale(1.5)"/>
  131.        </g>
  132.  
  133.        <use id="undefined_valve"
  134.             href="#valve_template"
  135.             fill="var(--undefined-color)"/>
  136.        <use id="fault_valve"
  137.             href="#valve_template"
  138.             fill="var(--fault-color)"/>
  139.        <use id="closed_valve"
  140.             href="#valve_template"
  141.             fill="var(--off-color)"/>
  142.        <use id="closing_valve"
  143.             href="#valve_template"
  144.             fill="url(#getting-off-color)"/>
  145.        <use id="opened_valve"
  146.             href="#valve_template"
  147.             fill="var(--on-color)"/>
  148.        <use id="opening_valve"
  149.             href="#valve_template"
  150.             fill="url(#getting-on-color)"/>
  151.  
  152.        <g id="opened_fine_valve"
  153.           style="fill:#bf40bf;stroke:var(--foreground)">
  154.            <path vector-effect="non-scaling-stroke"
  155.                  d="M -16,-8 V 8 L 16,-8 V 8 Z
  156.                     M 0,0 -2,-1 v -5 h -3 v -6 H 5 v 6 H 2 v 5 z
  157.                     M 0,0 2,1 V 2 H -2 V 1 Z m -5,-6 v -6 H 5 v 6 z m 3,7 h 4 m -4,-2 h 4 m -4,-2 h 4 m -4,-2 h 4"/>
  158.            <path d="m 16,-8 8,-4"/>
  159.            <use href="#arrow_end"
  160.                 transform="translate(24, -12) rotate(-26.56505117707799) scale(2)"/>
  161.        </g>
  162.        <g id="half_opened_fine_valve"
  163.           style="fill:#71c837;stroke:var(--foreground)">
  164.            <path vector-effect="non-scaling-stroke"
  165.                  d="M -16,-8 V 8 L 16,-8 V 8 Z
  166.                     M 0,0 -2,-1 v -5 h -3 v -6 H 5 v 6 H 2 v 5 z
  167.                     M 0,0 2,1 V 2 H -2 V 1 Z m -5,-6 v -6 H 5 v 6 z m 3,7 h 4 m -4,-2 h 4 m -4,-2 h 4 m -4,-2 h 4"/>
  168.            <path d="m 16,-8 8,-4"/>
  169.            <use href="#arrow_end"
  170.                 transform="translate(24, -12) rotate(-26.56505117707799) scale(2)"/>
  171.        </g>
  172.        <g id="closed_fine_valve"
  173.           style="fill:var(--off-color);stroke:var(--foreground)">
  174.            <path vector-effect="non-scaling-stroke"
  175.                  d="M -16,-8 V 8 L 16,-8 V 8 Z
  176.                     M 0,0 -2,-1 v -3 h -3 v -6 H 5 v 6 H 2 v 3 z
  177.                     M 0,0 2,1 V 4 H -2 V 1 Z m -5,-4 v -6 H 5 v 6 z m 3,7 H 2 M -2,1 h 4 m -4,-2 h 4 m -4,-2 h 4"/>
  178.            <path d="m 16,-8 8,-4"/>
  179.            <use href="#arrow_end"
  180.                 transform="translate(24, -12) rotate(-26.56505117707799) scale(2)"/>
  181.        </g>
  182.  
  183.        <use id="x4_valve_undefined"
  184.             href="#x4_valve_template"
  185.             fill="var(--undefined-color)"
  186.             transform="rotate(45)"
  187.             x="0"
  188.             y="0"
  189.             width="100%"
  190.             height="100%"/>
  191.        <use id="x4_valve_fault"
  192.             href="#x4_valve_template"
  193.             fill="var(--fault-color)"
  194.             transform="rotate(45)"
  195.             x="0"
  196.             y="0"
  197.             width="100%"
  198.             height="100%"/>
  199.        <use id="x4_valve_off"
  200.             href="#x4_valve_template"
  201.             fill="var(--background)"
  202.             x="0"
  203.             y="0"
  204.             width="100%"
  205.             height="100%"/>
  206.        <use id="x4_valve_getting_off"
  207.             href="#x4_valve_template"
  208.             fill="var(--background)"
  209.             transform="rotate(45)"
  210.             x="0"
  211.             y="0"
  212.             width="100%"
  213.             height="100%"/>
  214.        <use id="x4_valve_on"
  215.             href="#x4_valve_template"
  216.             fill="var(--background)"
  217.             transform="rotate(90)"
  218.             x="0"
  219.             y="0"
  220.             width="100%"
  221.             height="100%"/>
  222.        <use id="x4_valve_getting_on"
  223.             href="#x4_valve_template"
  224.             fill="var(--background)"
  225.             transform="rotate(45)"
  226.             x="0"
  227.             y="0"
  228.             width="100%"
  229.             height="100%"/>
  230.  
  231.        <use id="x6_valve_undefined"
  232.             href="#x6_valve_template"
  233.             fill="var(--undefined-color)"
  234.             transform="rotate(30)"
  235.             x="0"
  236.             y="0"
  237.             width="100%"
  238.             height="100%"/>
  239.        <use id="x6_valve_fault"
  240.             href="#x6_valve_template"
  241.             fill="var(--fault-color)"
  242.             transform="rotate(30)"
  243.             x="0"
  244.             y="0"
  245.             width="100%"
  246.             height="100%"/>
  247.        <use id="x6_valve_off"
  248.             href="#x6_valve_template"
  249.             fill="var(--background)"
  250.             transform="rotate(60)"
  251.             x="0"
  252.             y="0"
  253.             width="100%"
  254.             height="100%"/>
  255.        <use id="x6_valve_getting_off"
  256.             href="#x6_valve_template"
  257.             fill="var(--background)"
  258.             transform="rotate(30)"
  259.             x="0"
  260.             y="0"
  261.             width="100%"
  262.             height="100%"/>
  263.        <use id="x6_valve_on"
  264.             href="#x6_valve_template"
  265.             fill="var(--background)"
  266.             x="0"
  267.             y="0"
  268.             width="100%"
  269.             height="100%"/>
  270.        <use id="x6_valve_getting_on"
  271.             href="#x6_valve_template"
  272.             fill="var(--background)"
  273.             transform="rotate(30)"
  274.             x="0"
  275.             y="0"
  276.             width="100%"
  277.             height="100%"/>
  278.  
  279.        <g id="vacuum_gauge"
  280.           style="stroke:var(--foreground)">
  281.            <circle cy="0"
  282.                    cx="0"
  283.                    r="8"
  284.                    vector-effect="non-scaling-stroke"
  285.                    fill="white"/>
  286.            <path style="stroke:black"
  287.                  d="M 4,4 -4,-4"/>
  288.            <use href="#arrow_end"
  289.                 stroke="black"
  290.                 fill="black"
  291.                 transform="translate(-4, -4) rotate(-135) scale(1.5)"/>
  292.        </g>
  293.  
  294.        <use id="turbo_molecular_pump_undefined"
  295.             href="#turbo_molecular_pump_template"
  296.             fill="var(--undefined-color)"/>
  297.        <use id="turbo_molecular_pump_fault"
  298.             href="#turbo_molecular_pump_template"
  299.             fill="var(--fault-color)"/>
  300.        <use id="turbo_molecular_pump_off"
  301.             href="#turbo_molecular_pump_template"
  302.             fill="var(--off-color)"/>
  303.        <use id="turbo_molecular_pump_getting_off"
  304.             href="#turbo_molecular_pump_template"
  305.             fill="url(#getting-off-color)"/>
  306.        <use id="turbo_molecular_pump_on"
  307.             href="#turbo_molecular_pump_template"
  308.             fill="var(--on-color)"/>
  309.        <use id="turbo_molecular_pump_getting_on"
  310.             href="#turbo_molecular_pump_template"
  311.             fill="url(#getting-on-color)"/>
  312.  
  313.        <use id="mechanical_pump_undefined"
  314.             href="#mechanical_pump_template"
  315.             fill="var(--undefined-color)"/>
  316.        <use id="mechanical_pump_fault"
  317.             href="#mechanical_pump_template"
  318.             fill="var(--fault-color)"/>
  319.        <use id="mechanical_pump_off"
  320.             href="#mechanical_pump_template"
  321.             fill="var(--off-color)"/>
  322.        <use id="mechanical_pump_getting_off"
  323.             href="#mechanical_pump_template"
  324.             fill="url(#getting-off-color)"/>
  325.        <use id="mechanical_pump_on"
  326.             href="#mechanical_pump_template"
  327.             fill="var(--on-color)"/>
  328.        <use id="mechanical_pump_getting_on"
  329.             href="#mechanical_pump_template"
  330.             fill="url(#getting-on-color)"/>
  331.  
  332.        <g id="tds_undefined"
  333.           fill="var(--undefined-color)">
  334.            <rect width="32" height="32"
  335.                  x="-16" y="-16"
  336.                  vector-effect="non-scaling-stroke"
  337.                  stroke="var(--foreground)"/>
  338.            <path stroke="var(--foreground)" fill="none"
  339.                  d="M 2,0
  340.                     a 2,2 1 0 0  0.5, 0.5
  341.                     a 2,2 0 0 1  0.5, 0.5
  342.                     a 3,3 0 0 1 -6  , 0
  343.                     a 2,2 0 0 1  0.5,-0.5
  344.                     a 2,2 1 0 0  0.5,-0.5
  345.                     v -12
  346.                     a 2,2 0 0 1 4,0
  347.                     v 12"/>
  348.            <path stroke="black" fill="none"
  349.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  350.        </g>
  351.        <g id="tds_fault"
  352.           fill="var(--fault-color)">
  353.            <rect width="32" height="32"
  354.                  x="-16" y="-16"
  355.                  vector-effect="non-scaling-stroke"
  356.                  stroke="var(--foreground)"/>
  357.            <path stroke="var(--foreground)" fill="white"
  358.                  d="M 2,0
  359.                     a 2,2 1 0 0  0.5, 0.5
  360.                     a 2,2 0 0 1  0.5, 0.5
  361.                     a 3,3 0 0 1 -6  , 0
  362.                     a 2,2 0 0 1  0.5,-0.5
  363.                     a 2,2 1 0 0  0.5,-0.5
  364.                     v -12
  365.                     a 2,2 0 0 1 4,0
  366.                     v 12"/>
  367.            <path stroke="black" fill="none"
  368.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  369.        </g>
  370.        <g id="tds_off"
  371.           fill="var(--background)">
  372.            <rect width="32" height="32"
  373.                  x="-16" y="-16"
  374.                  vector-effect="non-scaling-stroke"
  375.                  stroke="var(--foreground)"/>
  376.            <path stroke="var(--foreground)" fill="white"
  377.                  d="M 2,0
  378.                     a 2,2 1 0 0  0.5, 0.5
  379.                     a 2,2 0 0 1  0.5, 0.5
  380.                     a 3,3 0 0 1 -6  , 0
  381.                     a 2,2 0 0 1  0.5,-0.5
  382.                     a 2,2 1 0 0  0.5,-0.5
  383.                     v -12
  384.                     a 2,2 0 0 1 4,0
  385.                     v 12"/>
  386.            <path stroke="var(--foreground)" fill="#2a2aff"
  387.                  d="M 2,-2
  388.                     v 2
  389.                     a 2,2 1 0 0  0.5, 0.5
  390.                     a 2,2 0 0 1  0.5, 0.5
  391.                     a 3,3 0 0 1 -6  , 0
  392.                     a 2,2 0 0 1  0.5,-0.5
  393.                     a 2,2 1 0 0  0.5,-0.5
  394.                     v -2"/>
  395.            <path stroke="black" fill="none"
  396.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  397.        </g>
  398.        <g id="tds_getting_off"
  399.           fill="var(--background)">
  400.            <rect width="32" height="32"
  401.                  x="-16" y="-16"
  402.                  vector-effect="non-scaling-stroke"
  403.                  stroke="var(--foreground)"/>
  404.            <path stroke="var(--foreground)" fill="white"
  405.                  d="M 2,0
  406.                     a 2,2 1 0 0  0.5, 0.5
  407.                     a 2,2 0 0 1  0.5, 0.5
  408.                     a 3,3 0 0 1 -6  , 0
  409.                     a 2,2 0 0 1  0.5,-0.5
  410.                     a 2,2 1 0 0  0.5,-0.5
  411.                     v -12
  412.                     a 2,2 0 0 1 4,0
  413.                     v 12"/>
  414.            <path stroke="var(--foreground)" fill="#008000"
  415.                  d="M 2,-6
  416.                     v 6
  417.                     a 2,2 1 0 0  0.5, 0.5
  418.                     a 2,2 0 0 1  0.5, 0.5
  419.                     a 3,3 0 0 1 -6  , 0
  420.                     a 2,2 0 0 1  0.5,-0.5
  421.                     a 2,2 1 0 0  0.5,-0.5
  422.                     v -6"/>
  423.            <path stroke="black" fill="none"
  424.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  425.        </g>
  426.        <g id="tds_on"
  427.           fill="white">
  428.            <rect width="32" height="32"
  429.                  x="-16" y="-16"
  430.                  vector-effect="non-scaling-stroke"
  431.                  stroke="var(--foreground)"/>
  432.            <path stroke="var(--foreground)" fill="white"
  433.                  d="M 2,0
  434.                     a 2,2 1 0 0  0.5, 0.5
  435.                     a 2,2 0 0 1  0.5, 0.5
  436.                     a 3,3 0 0 1 -6  , 0
  437.                     a 2,2 0 0 1  0.5,-0.5
  438.                     a 2,2 1 0 0  0.5,-0.5
  439.                     v -12
  440.                     a 2,2 0 0 1 4,0
  441.                     v 12"/>
  442.            <path stroke="var(--foreground)" fill="#ff2a2a"
  443.                  d="M 2,-10
  444.                     v 10
  445.                     a 2,2 1 0 0  0.5, 0.5
  446.                     a 2,2 0 0 1  0.5, 0.5
  447.                     a 3,3 0 0 1 -6  , 0
  448.                     a 2,2 0 0 1  0.5,-0.5
  449.                     a 2,2 1 0 0  0.5,-0.5
  450.                     v -10"/>
  451.            <path stroke="black" fill="none"
  452.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  453.        </g>
  454.        <g id="tds_getting_on"
  455.           fill="white">
  456.            <rect width="32" height="32"
  457.                  x="-16" y="-16"
  458.                  vector-effect="non-scaling-stroke"
  459.                  stroke="var(--foreground)"/>
  460.            <path stroke="var(--foreground)" fill="white"
  461.                  d="M 2,0
  462.                     a 2,2 1 0 0  0.5, 0.5
  463.                     a 2,2 0 0 1  0.5, 0.5
  464.                     a 3,3 0 0 1 -6  , 0
  465.                     a 2,2 0 0 1  0.5,-0.5
  466.                     a 2,2 1 0 0  0.5,-0.5
  467.                     v -12
  468.                     a 2,2 0 0 1 4,0
  469.                     v 12"/>
  470.            <path stroke="var(--foreground)" fill="#2aff2a"
  471.                  d="M 2,-6
  472.                     v 6
  473.                     a 2,2 1 0 0  0.5, 0.5
  474.                     a 2,2 0 0 1  0.5, 0.5
  475.                     a 3,3 0 0 1 -6  , 0
  476.                     a 2,2 0 0 1  0.5,-0.5
  477.                     a 2,2 1 0 0  0.5,-0.5
  478.                     v -6"/>
  479.            <path stroke="black" fill="none"
  480.                  d="M 0,-11 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2 m -2,2 h 2"/>
  481.        </g>
  482.  
  483.        <path id="inlet"
  484.              d="m 0,0 h -8 m 2,4 h -2 v -8 h 2"/>
  485.        <path id="outlet"
  486.              d="m 0,0 h 12 m 0,4 V -4 M 8,4 v -8"/>
  487.    </defs>
  488.    <g id="paths"
  489.       style="fill:none;stroke-width:2px;">
  490.        <path id="cavity-v7_path"
  491.              style="stroke:var(--high-vacuum)"
  492.              d="M 32,285 h 84 v -2 h -84 m 26,4 v -6 h 4 v 6 z m 2,-2 v -32 m 0,32 v 40 h 28 m 14,-38 v -6 h 4 v 6 z m 2,-2 v -32 h 12"/>
  493.        <path id="v1-cavity_path"
  494.              style="stroke:var(--high-vacuum)"
  495.              d="M 88,24 h -12 m -32,0 h -12"/>
  496.        <path id="v7-pump1_path"
  497.              style="stroke:var(--high-vacuum)"
  498.              d="M 148,285 h 24 v -2 h -24 m 10,4 v -6 h 4 v 6 z m 2,-2 v -32 h -12"/>
  499.        <path id="pump1-v6_path"
  500.              style="stroke:var(--high-vacuum)"
  501.              d="M 216,285 l -12 -1 l 12 -1 z"/>
  502.  
  503.        <path id="v5-x6_path"
  504.              style="stroke:var(--high-vacuum)"
  505.              d="M 120,64 h 24"/>
  506.        <path id="x6-x4-2_path"
  507.              style="stroke:var(--high-vacuum)"
  508.              d="M 160,64 m 8,-13.856406460551018 l 8,-13.856406460551018 h 12 V 124 h -40"/>
  509.        <path id="x6-tds2_path"
  510.              style="stroke:var(--high-vacuum)"
  511.              d="M 160,64 m -8,-13.856406460551018 l -8,-13.856406460551018 V 24 h 80 v 32
  512.                 M 160,64 m 8,13.856406460551018 l 8,13.856406460551018 h 8 a 4,4,0,0,1,8,0 h 32 V 80"/>
  513.  
  514.        <path id="v2-tds1_path"
  515.              style="stroke:var(--high-vacuum)"
  516.              d="M 216,196 h -84 v -12"/>
  517.        <path id="tds1-x4_path"
  518.              style="stroke:var(--high-vacuum)"
  519.              d="M 132,152 v -12"/>
  520.        <path id="x4-v1_path"
  521.              style="stroke:var(--high-vacuum)"
  522.              d="M 132,108 v -12 a 4,4,0,0,0,0,-8 v -20 a 4,4,0,0,0,0,-8 v -36 h -12"/>
  523.        <path id="x6-x4-1_path"
  524.              style="stroke:var(--high-vacuum)"
  525.              d="M 160,64 m -8,13.856406460551018 l -8,13.856406460551018 h -40 V 124 h 12"/>
  526.  
  527.        <path id="v3-v4_path"
  528.              style="stroke:var(--high-vacuum)"
  529.              d="M 204,164 v -32"/>
  530.        <path id="v2-v3_path"
  531.              style="stroke:var(--high-vacuum)"
  532.              d="M 204,196 v -32"/>
  533.  
  534.        <path id="v5-pump2_path"
  535.              style="stroke:var(--high-vacuum)"
  536.              d="M 88,64 h -8 M 80,63 h -5 v 154 h 184 v 66 h -12 M 80,65 h -3 v 150 h 184 v 70 h -14 m 10,2 v -6 h 6 v 6 z m 2,-2 v 16 h 2 v -16"/>
  537.  
  538.        <path id="v4_path"
  539.              style="stroke:var(--high-vacuum)"
  540.              d="M 216,132 h -12"/>
  541.        <path id="v3_path"
  542.              style="stroke:var(--high-vacuum)"
  543.              d="M 216,164 h -12 m -2,2 v -4 h 4 v 4 z"/>
  544.        <path id="v2_path"
  545.              style="stroke:var(--high-vacuum)"
  546.              d="M 216,196 h -12 m -2,2 v -4 h 4 v 4 z"/>
  547.  
  548.        <use id="v2_inlet"
  549.             href="#inlet"
  550.             style="stroke:var(--air)"
  551.             transform="translate(248,196) rotate(180)"
  552.             x="0"
  553.             y="0"
  554.             width="100%"
  555.             height="100%"/>
  556.        <use id="v3_inlet"
  557.             href="#inlet"
  558.             style="stroke:var(--air)"
  559.             transform="translate(248,164) rotate(180)"
  560.             x="0"
  561.             y="0"
  562.             width="100%"
  563.             height="100%"/>
  564.        <use id="v4_inlet"
  565.             href="#inlet"
  566.             style="stroke:var(--air)"
  567.             transform="translate(248,132) rotate(180)"
  568.             x="0"
  569.             y="0"
  570.             width="100%"
  571.             height="100%"/>
  572.        <use id="v9_outlet"
  573.             href="#outlet"
  574.             style="stroke:var(--air)"
  575.             transform="translate(120,324)"
  576.             x="0"
  577.             y="0"
  578.             width="100%"
  579.             height="100%"/>
  580.    </g>
  581.    <g id="cavity"
  582.       transform="translate(16,156) rotate(-90)">
  583.        <rect style="fill:#b2b2b2;stroke:var(--foreground)"
  584.              id="cavity_body"
  585.              width="280"
  586.              height="32"
  587.              x="-140"
  588.              y="-16"
  589.              vector-effect="non-scaling-stroke"/>
  590.        <switch>
  591.            <text id="cavity_text"
  592.                  alignment-baseline="middle"
  593.                  text-anchor="middle"
  594.                  style="fill:black;"
  595.                  x="0"
  596.                  y="0"
  597.                  systemLanguage="de">
  598.                Hohlraum
  599.            </text>
  600.            <text id="cavity_text"
  601.                  alignment-baseline="middle"
  602.                  text-anchor="middle"
  603.                  style="fill:black;"
  604.                  x="0"
  605.                  y="0">
  606.                cavity
  607.            </text>
  608.        </switch>
  609.    </g>
  610.    <g id="v_fine"
  611.       transform="translate(60,24) scale(-1, 1)">
  612.        <use id="v_fine_body"
  613.             href="#half_opened_fine_valve"
  614.             x="0"
  615.             y="0"
  616.             width="100%"
  617.             height="100%"/>
  618.    </g>
  619.    <g id="v1"
  620.       transform="translate(104,24)">
  621.        <use id="v1_body"
  622.             href="#undefined_valve"
  623.             x="0"
  624.             y="0"
  625.             width="100%"
  626.             height="100%"/>
  627.        <text id="v1_text"
  628.              text-anchor="middle"
  629.              style="fill:var(--foreground);"
  630.              x="0"
  631.              y="-16">
  632.            V1
  633.        </text>
  634.    </g>
  635.    <g id="v2"
  636.       transform="translate(232,196)">
  637.        <use id="v2_body"
  638.             href="#undefined_valve"
  639.             x="0"
  640.             y="0"
  641.             width="100%"
  642.             height="100%"/>
  643.        <text id="v2_text"
  644.              text-anchor="middle"
  645.              style="fill:var(--foreground);"
  646.              x="0"
  647.              y="-16">
  648.            V2
  649.        </text>
  650.    </g>
  651.    <g id="v3"
  652.       transform="translate(232,164)">
  653.        <use id="v3_body"
  654.             href="#undefined_valve"
  655.             x="0"
  656.             y="0"
  657.             width="100%"
  658.             height="100%"/>
  659.        <text id="v3_text"
  660.              text-anchor="middle"
  661.              style="fill:var(--foreground);"
  662.              x="0"
  663.              y="-16">
  664.            V3
  665.        </text>
  666.    </g>
  667.    <g id="v4"
  668.       transform="translate(232,132)">
  669.        <use id="v4_body"
  670.             href="#undefined_valve"
  671.             x="0"
  672.             y="0"
  673.             width="100%"
  674.             height="100%"/>
  675.        <text id="v4_text"
  676.              text-anchor="middle"
  677.              style="fill:var(--foreground);"
  678.              x="0"
  679.              y="-16">
  680.            V4
  681.        </text>
  682.    </g>
  683.    <g id="v5"
  684.       transform="translate(104,64)">
  685.        <use id="v5_body"
  686.             href="#undefined_valve"
  687.             x="0"
  688.             y="0"
  689.             width="100%"
  690.             height="100%"/>
  691.        <text id="v5_text"
  692.              text-anchor="middle"
  693.              style="fill:var(--foreground);"
  694.              x="0"
  695.              y="-16">
  696.            V5
  697.        </text>
  698.    </g>
  699.    <g id="v6"
  700.       transform="translate(232,284)">
  701.        <use id="v6_body"
  702.             href="#undefined_valve"
  703.             x="0"
  704.             y="0"
  705.             width="100%"
  706.             height="100%"/>
  707.        <text id="v6_text"
  708.              text-anchor="middle"
  709.              style="fill:var(--foreground);"
  710.              x="0"
  711.              y="-16">
  712.            V6
  713.        </text>
  714.    </g>
  715.    <g id="v8"
  716.       transform="translate(132,252)">
  717.        <use id="v8_body"
  718.             href="#undefined_valve"
  719.             x="0"
  720.             y="0"
  721.             width="100%"
  722.             height="100%"/>
  723.        <text id="v8_text"
  724.              text-anchor="middle"
  725.              style="fill:var(--foreground);"
  726.              x="0"
  727.              y="-16">
  728.            V8
  729.        </text>
  730.    </g>
  731.    <g id="v7"
  732.       transform="translate(132,284)">
  733.        <use id="v7_body"
  734.             href="#undefined_valve"
  735.             x="0"
  736.             y="0"
  737.             width="100%"
  738.             height="100%"/>
  739.        <text id="v7_text"
  740.              text-anchor="middle"
  741.              style="fill:var(--foreground);"
  742.              x="0"
  743.              y="-16">
  744.            V7
  745.        </text>
  746.    </g>
  747.    <g id="v9"
  748.       transform="translate(104,324)">
  749.        <use id="v9_body"
  750.             href="#undefined_valve"
  751.             x="0"
  752.             y="0"
  753.             width="100%"
  754.             height="100%"/>
  755.        <text id="v9_text"
  756.              text-anchor="middle"
  757.              style="fill:var(--foreground);"
  758.              x="0"
  759.              y="-16">
  760.            V9
  761.        </text>
  762.    </g>
  763.    <g id="x4"
  764.       transform="translate(132,124)">
  765.        <use id="x4_body"
  766.             href="#x4_valve_undefined"
  767.             x="0"
  768.             y="0"
  769.             width="100%"
  770.             height="100%"/>
  771.        <text id="x4_text"
  772.              alignment-baseline="central"
  773.              text-anchor="middle"
  774.              style="fill:var(--foreground);"
  775.              x="0"
  776.              y="0">
  777.            X4
  778.        </text>
  779.    </g>
  780.    <g id="x6"
  781.       transform="translate(160,64)">
  782.        <use id="x6_body"
  783.             href="#x6_valve_undefined"
  784.             x="0"
  785.             y="0"
  786.             width="100%"
  787.             height="100%"/>
  788.        <text id="x6_text"
  789.              alignment-baseline="central"
  790.              text-anchor="middle"
  791.              style="fill:var(--foreground);"
  792.              x="0"
  793.              y="0">
  794.            X6
  795.        </text>
  796.    </g>
  797.    <g id="p1"
  798.       transform="translate(60,244)">
  799.        <use id="p1_body"
  800.             href="#vacuum_gauge"
  801.             y="0"
  802.             x="0"
  803.             height="100%"
  804.             width="100%"/>
  805.        <text id="p1_text"
  806.              text-anchor="middle"
  807.              style="fill:var(--foreground);"
  808.              x="0"
  809.              y="-12">
  810.            P1
  811.        </text>
  812.        <switch>
  813.            <text id="p1_value"
  814.                  alignment-baseline="middle"
  815.                  text-anchor="start"
  816.                  style="fill:var(--foreground);"
  817.                  x="10"
  818.                  y="0"
  819.                  systemLanguage="de">
  820.                atm
  821.            </text>
  822.            <text id="p1_value"
  823.                  alignment-baseline="middle"
  824.                  text-anchor="start"
  825.                  style="fill:var(--foreground);"
  826.                  x="10"
  827.                  y="0">
  828.                atm
  829.            </text>
  830.        </switch>
  831.    </g>
  832.    <g id="pump1"
  833.       transform="translate(188,284)">
  834.        <use id="pump1_body"
  835.             href="#turbo_molecular_pump_undefined"
  836.             y="0"
  837.             x="0"
  838.             height="100%"
  839.             width="100%"/>
  840.        <switch>
  841.            <text id="pump1_value"
  842.                  text-anchor="middle"
  843.                  style="fill:var(--foreground);"
  844.                  x="0"
  845.                  y="28"
  846.                  systemLanguage="de">
  847.                0 U/min
  848.            </text>
  849.            <text id="pump1_value"
  850.                  text-anchor="middle"
  851.                  style="fill:var(--foreground);"
  852.                  x="0"
  853.                  y="28">
  854.                0 rpm
  855.            </text>
  856.        </switch>
  857.    </g>
  858.    <g id="pump2"
  859.       transform="translate(260,316)">
  860.        <use id="pump2_body"
  861.             href="#mechanical_pump_undefined"
  862.             y="0"
  863.             x="0"
  864.             height="100%"
  865.             width="100%"/>
  866.    </g>
  867.    <g id="tds1"
  868.       transform="translate(132,168)">
  869.        <use id="tds1_body"
  870.             href="#tds_undefined"
  871.             y="0"
  872.             x="0"
  873.             height="100%"
  874.             width="100%"/>
  875.        <switch>
  876.            <text id="tds1_text"
  877.                  text-anchor="middle"
  878.                  style="fill:var(--foreground);"
  879.                  x="0"
  880.                  y="12"
  881.                  systemLanguage="de">
  882.                TDS1
  883.            </text>
  884.            <text id="tds1_text"
  885.                  text-anchor="middle"
  886.                  style="fill:var(--foreground);"
  887.                  x="0"
  888.                  y="12">
  889.                TDS1
  890.            </text>
  891.        </switch>
  892.        <text id="tds1_value"
  893.              text-anchor="start"
  894.              alignment-baseline="central"
  895.              style="fill:var(--foreground);"
  896.              x="20"
  897.              y="6">
  898.            t°C
  899.        </text>
  900.        <text id="tds1_target"
  901.              text-anchor="start"
  902.              alignment-baseline="central"
  903.              style="fill:var(--foreground);"
  904.              x="20"
  905.              y="-6">
  906.            T°C
  907.        </text>
  908.    </g>
  909.    <g id="tds2"
  910.       transform="translate(224,64)">
  911.        <use id="tds2_body"
  912.             href="#tds_undefined"
  913.             y="0"
  914.             x="0"
  915.             height="100%"
  916.             width="100%"/>
  917.        <switch>
  918.            <text id="tds2_text"
  919.                  text-anchor="middle"
  920.                  style="fill:var(--foreground);"
  921.                  x="0"
  922.                  y="12"
  923.                  systemLanguage="de">
  924.                TDS2
  925.            </text>
  926.            <text id="tds2_text"
  927.                  text-anchor="middle"
  928.                  style="fill:var(--foreground);"
  929.                  x="0"
  930.                  y="12">
  931.                TDS2
  932.            </text>
  933.        </switch>
  934.        <text id="tds2_value"
  935.              text-anchor="start"
  936.              alignment-baseline="central"
  937.              style="fill:var(--foreground);"
  938.              x="20"
  939.              y="6">
  940.            t°C
  941.        </text>
  942.        <text id="tds2_target"
  943.              text-anchor="start"
  944.              alignment-baseline="central"
  945.              style="fill:var(--foreground);"
  946.              x="20"
  947.              y="-6">
  948.            T°C
  949.        </text>
  950.    </g>
  951. </svg>
  952. """
  953.  
  954.  
  955. def next_item[_T](s: Sequence[_T], current_item: _T) -> _T:
  956.     if not s:
  957.         raise ValueError("The sequence cannot be empty")
  958.     if current_item not in s:
  959.         return s[0]
  960.     return s[(s.index(current_item) + 1) % len(s)]
  961.  
  962.  
  963. class CSS:
  964.     def __init__(self, text: str = "") -> None:
  965.         self._data: dict[str, str] = dict()
  966.         self.update(text)
  967.  
  968.     def update(self, text: str) -> None:
  969.         text_lines: list[str] = text.split(";")
  970.         line: str
  971.         key: str
  972.         value: str
  973.         for line in text_lines:
  974.             if ":" not in line:
  975.                 continue
  976.             key, value = line.split(":", maxsplit=1)
  977.             self._data[key.strip()] = value.strip()
  978.  
  979.     def items(self) -> ItemsView[str, str]:
  980.         return self._data.items()
  981.  
  982.     def __str__(self) -> str:
  983.         return "; ".join(f"{key}:{value}" for key, value in self._data.items())
  984.  
  985.     def __getitem__(self, item: str) -> str:
  986.         if item not in self._data:
  987.             raise KeyError
  988.         return self._data[item]
  989.  
  990.     def __setitem__(self, key: str, value: str) -> None:
  991.         value = str(value)
  992.         self._data[str(key)] = value
  993.  
  994.     def __len__(self) -> int:
  995.         return len(self._data)
  996.  
  997.     def __bool__(self) -> bool:
  998.         return bool(self._data)
  999.  
  1000.     def __contains__(self, item: str) -> bool:
  1001.         return item in self._data
  1002.  
  1003.  
  1004. class DeviceState(enum.IntEnum):
  1005.     Invalid = -1
  1006.     Initialization = 0
  1007.     Fault = 1
  1008.     Off = 2
  1009.     GettingOff = 3
  1010.     On = 4
  1011.     GettingOn = 5
  1012.  
  1013.  
  1014. class AspectLayout(QLayout):
  1015.     """Credits: https://gist.github.com/sjdv1982/75899d10e6983b878f63083e3c47b39b"""
  1016.  
  1017.     def __init__(self, aspect: float, parent: QWidget | None = None) -> None:
  1018.         self.aspect: float = aspect
  1019.         self.item: QLayoutItem | None = None
  1020.         super().__init__(parent)
  1021.         self.setContentsMargins(0, 0, 0, 0)
  1022.  
  1023.     def addItem(self, item: QLayoutItem) -> None:
  1024.         assert self.item is None, "AspectLayout can contain only 1 item"
  1025.         self.item = item
  1026.  
  1027.     def itemAt(self, index: int) -> QLayoutItem | None:
  1028.         if index != 0:
  1029.             return None
  1030.         return self.item
  1031.  
  1032.     def takeAt(self, index: int) -> QLayoutItem | None:
  1033.         if index != 0:
  1034.             return None
  1035.         result: QLayoutItem | None = self.item
  1036.         self.item = None
  1037.         return result
  1038.  
  1039.     def setGeometry(self, rect: QRect) -> None:
  1040.         super().setGeometry(rect)
  1041.         margins: tuple[int, int, int, int] = self.getContentsMargins()
  1042.         if self.item is not None:
  1043.             available_width: int = rect.width() - margins[1] - margins[3]
  1044.             available_height: int = rect.height() - margins[0] - margins[2]
  1045.             height: float = available_height
  1046.             width: float = height * self.aspect
  1047.             x: float
  1048.             y: float
  1049.             if width > available_width:
  1050.                 x = margins[1]
  1051.                 width = available_width
  1052.                 height = width / self.aspect
  1053.                 if self.item.alignment() & Qt.AlignmentFlag.AlignTop:
  1054.                     y = margins[0]
  1055.                 elif self.item.alignment() & Qt.AlignmentFlag.AlignBottom:
  1056.                     y = rect.height() - margins[2] - height
  1057.                 else:
  1058.                     y = margins[0] + (available_height - height) / 2
  1059.             else:
  1060.                 y = margins[0]
  1061.                 if self.item.alignment() & Qt.AlignmentFlag.AlignLeft:
  1062.                     x = margins[1]
  1063.                 elif self.item.alignment() & Qt.AlignmentFlag.AlignRight:
  1064.                     x = rect.width() - margins[3] - width
  1065.                 else:
  1066.                     x = margins[1] + (available_width - width) / 2
  1067.             self.item.widget().setGeometry(
  1068.                 round(rect.x() + x),
  1069.                 round(rect.y() + y),
  1070.                 round(width),
  1071.                 round(height),
  1072.             )
  1073.  
  1074.     def sizeHint(self) -> QSize:
  1075.         margins: tuple[int, int, int, int] = self.getContentsMargins()
  1076.         if self.item is None:
  1077.             return QSize(margins[0] + margins[2], margins[1] + margins[3])
  1078.         s: QSize = self.item.sizeHint()
  1079.         return QSize(
  1080.             margins[0] + margins[2] + s.width(),
  1081.             margins[1] + margins[3] + s.height(),
  1082.         )
  1083.  
  1084.     def minimumSize(self) -> QSize:
  1085.         margins: tuple[int, int, int, int] = self.getContentsMargins()
  1086.         if self.item is None:
  1087.             return QSize(margins[0] + margins[2], margins[1] + margins[3])
  1088.         s: QSize = self.item.minimumSize()
  1089.         return QSize(
  1090.             margins[0] + margins[2] + s.width(),
  1091.             margins[1] + margins[3] + s.height(),
  1092.         )
  1093.  
  1094.     def expandingDirections(self) -> Qt.Orientation:
  1095.         return Qt.Orientation.Horizontal | Qt.Orientation.Vertical
  1096.  
  1097.     def count(self) -> int:
  1098.         if self.item is None:
  1099.             return 0
  1100.         else:
  1101.             return 1
  1102.  
  1103.     def hasHeightForWidth(self) -> bool:
  1104.         return True
  1105.  
  1106.     def heightForWidth(self, width: int) -> int:
  1107.         margins: tuple[int, int, int, int] = self.getContentsMargins()
  1108.         height: float = (
  1109.             (width - margins[1] - margins[3]) / self.aspect + margins[0] + margins[2]
  1110.         )
  1111.         return round(height)
  1112.  
  1113.  
  1114. class Scheme(QWidget):
  1115.     toggled: Signal = Signal(str, DeviceState, name="toggled")
  1116.  
  1117.     class PipeColor(enum.StrEnum):
  1118.         Air = "var(--air)"
  1119.         LowVacuum = "var(--low-vacuum)"
  1120.         HighVacuum = "var(--high-vacuum)"
  1121.  
  1122.     def __init__(self, parent: QWidget | None = None) -> None:
  1123.         super().__init__(parent)
  1124.         self.svg: ElementTree.Element = ElementTree.XML(SVG_DATA)
  1125.         self.svg_namespace: str = self.svg.tag[1 : self.svg.tag.index("}")]
  1126.         self.css: CSS = CSS(self.svg.get("style") or "")
  1127.         match QGuiApplication.styleHints().colorScheme():
  1128.             case Qt.ColorScheme.Light:
  1129.                 self.css["--foreground"] = "#000"
  1130.                 self.css["--background"] = "#fff"
  1131.             case Qt.ColorScheme.Dark:
  1132.                 self.css["--foreground"] = "#fff"
  1133.                 self.css["--background"] = "#000"
  1134.         self.svg.set("style", str(self.css))
  1135.  
  1136.         layout: AspectLayout = AspectLayout(
  1137.             self._view_box().width() / self._view_box().height(), self
  1138.         )
  1139.         self.svg_widget: QSvgWidget = QSvgWidget(self)
  1140.         self.svg_widget.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
  1141.         layout.addWidget(self.svg_widget)
  1142.         self.svg_widget.setMinimumSize(self._view_box().size().toSize())
  1143.         self.svg_widget.renderer().setAspectRatioMode(
  1144.             Qt.AspectRatioMode.KeepAspectRatio
  1145.         )
  1146.  
  1147.         self.valve_states: dict[str, DeviceState] = {
  1148.             "v1": DeviceState.Initialization,
  1149.             "v2": DeviceState.Initialization,
  1150.             "v3": DeviceState.Initialization,
  1151.             "v4": DeviceState.Initialization,
  1152.             "v5": DeviceState.Initialization,
  1153.             "v6": DeviceState.Initialization,
  1154.             "v7": DeviceState.Initialization,
  1155.             "v8": DeviceState.Initialization,
  1156.             "v9": DeviceState.Initialization,
  1157.             "x4": DeviceState.Initialization,
  1158.             "x6": DeviceState.Initialization,
  1159.         }
  1160.         self.valve_features: dict[str, dict[str, Any]] = {
  1161.             "v_fine": {
  1162.                 "images": {
  1163.                     DeviceState.Off: "closed_fine_valve",
  1164.                     DeviceState.On: "opened_fine_valve",
  1165.                 }
  1166.             },
  1167.             "x4": {
  1168.                 "images": {
  1169.                     DeviceState.Initialization: "x4_valve_undefined",
  1170.                     DeviceState.Fault: "x4_valve_fault",
  1171.                     DeviceState.Off: "x4_valve_off",
  1172.                     DeviceState.GettingOff: "x4_valve_getting_off",
  1173.                     DeviceState.On: "x4_valve_on",
  1174.                     DeviceState.GettingOn: "x4_valve_getting_on",
  1175.                     DeviceState.Invalid: "x4_valve_fault",
  1176.                 }
  1177.             },
  1178.             "x6": {
  1179.                 "images": {
  1180.                     DeviceState.Initialization: "x6_valve_undefined",
  1181.                     DeviceState.Fault: "x6_valve_fault",
  1182.                     DeviceState.Off: "x6_valve_off",
  1183.                     DeviceState.GettingOff: "x6_valve_getting_off",
  1184.                     DeviceState.On: "x6_valve_on",
  1185.                     DeviceState.GettingOn: "x6_valve_getting_on",
  1186.                     DeviceState.Invalid: "x6_valve_fault",
  1187.                 }
  1188.             },
  1189.         }
  1190.         self.pump_states: dict[str, DeviceState] = {
  1191.             "pump1": DeviceState.Initialization,
  1192.             "pump2": DeviceState.Initialization,
  1193.         }
  1194.         self.pump_features: dict[str, dict[str, Any]] = {
  1195.             "pump1": {
  1196.                 "images": {
  1197.                     DeviceState.Initialization: "turbo_molecular_pump_undefined",
  1198.                     DeviceState.Fault: "turbo_molecular_pump_fault",
  1199.                     DeviceState.Off: "turbo_molecular_pump_off",
  1200.                     DeviceState.GettingOff: "turbo_molecular_pump_getting_off",
  1201.                     DeviceState.On: "turbo_molecular_pump_on",
  1202.                     DeviceState.GettingOn: "turbo_molecular_pump_getting_on",
  1203.                     DeviceState.Invalid: "turbo_molecular_pump_fault",
  1204.                 }
  1205.             },
  1206.             "pump2": {
  1207.                 "images": {
  1208.                     DeviceState.Initialization: "mechanical_pump_undefined",
  1209.                     DeviceState.Fault: "mechanical_pump_fault",
  1210.                     DeviceState.Off: "mechanical_pump_off",
  1211.                     DeviceState.GettingOff: "mechanical_pump_getting_off",
  1212.                     DeviceState.On: "mechanical_pump_on",
  1213.                     DeviceState.GettingOn: "mechanical_pump_getting_on",
  1214.                     DeviceState.Invalid: "mechanical_pump_fault",
  1215.                 }
  1216.             },
  1217.         }
  1218.         self.tds_states: dict[str, DeviceState] = {
  1219.             "tds1": DeviceState.Initialization,
  1220.             "tds2": DeviceState.Initialization,
  1221.         }
  1222.         self.tds_features: dict[str, dict[str, Any]] = {
  1223.             "tds1": {},
  1224.             "tds2": {},
  1225.         }
  1226.  
  1227.         valve_name: str
  1228.         valve_state: DeviceState
  1229.         for valve_name, valve_state in self.valve_states.items():
  1230.             self.change_valve_state(valve_name, valve_state)
  1231.         self.color_paths()
  1232.         self.redraw_svg()
  1233.  
  1234.         self.adjustSize()
  1235.  
  1236.         self.svg_widget.setMouseTracking(False)
  1237.         (self.svg_widget.focusProxy() or self.svg_widget).installEventFilter(self)
  1238.  
  1239.         self.locked: bool = True
  1240.  
  1241.     def eventFilter(self, source: QObject, event: QEvent) -> bool:
  1242.         """return True to stop the event processing chain"""
  1243.         if isinstance(event, QMouseEvent):
  1244.             if not self.locked and event.type() == QEvent.Type.MouseButtonRelease:
  1245.                 svg_width: float = min(
  1246.                     float(self.svg_widget.width()),
  1247.                     self._view_box().width()
  1248.                     / self._view_box().height()
  1249.                     * self.svg_widget.height(),
  1250.                 )
  1251.                 svg_height: float = min(
  1252.                     float(self.svg_widget.height()),
  1253.                     self.svg_widget.width()
  1254.                     / self._view_box().width()
  1255.                     * self._view_box().height(),
  1256.                 )
  1257.                 x: float = (
  1258.                     event.position().x() - (self.svg_widget.width() - svg_width) / 2.0
  1259.                 )
  1260.                 y: float = (
  1261.                     event.position().y() - (self.svg_widget.height() - svg_height) / 2.0
  1262.                 )
  1263.                 item: str | None = self.item_at(
  1264.                     QPointF(x / svg_width, y / svg_height), tolerance=CLICK_TOLERANCE
  1265.                 )
  1266.                 if item:
  1267.                     self.toggle_next_state(item)
  1268.             return True
  1269.         return super().eventFilter(source, event)
  1270.  
  1271.     def resolve_css_vars(self, svg: str) -> str:
  1272.         for key, value in self.css.items():
  1273.             svg = svg.replace(f"var({key})", value)
  1274.         while True:
  1275.             match: None | Match[str] = search(r"var\((?P<var>--.+?)\)", svg)
  1276.             if match is not None:
  1277.                 svg = svg.replace(
  1278.                     f'var({match.group("var")})', self.css[match.group("var")]
  1279.                 )
  1280.             else:
  1281.                 return svg
  1282.  
  1283.     def toggle_next_state(self, item_name: str) -> None:
  1284.         states: list[DeviceState] = [DeviceState.Off, DeviceState.On]
  1285.         next_state: DeviceState
  1286.         if item_name in self.valve_states:
  1287.             next_state = next_item(states, self.valve_states[item_name])
  1288.             self.change_valve_state(item_name, next_state)
  1289.         elif item_name in self.pump_states:
  1290.             next_state = next_item(states, self.pump_states[item_name])
  1291.             self.change_pump_state(item_name, next_state)
  1292.         elif item_name in self.tds_states:
  1293.             next_state = next_item(states, self.tds_states[item_name])
  1294.             self.change_tds_state(item_name, next_state)
  1295.         else:
  1296.             return
  1297.  
  1298.         if __name__ == "__main__":
  1299.             self.color_paths()
  1300.             self.redraw_svg()
  1301.  
  1302.         self.toggled.emit(item_name, next_state)
  1303.  
  1304.     @lru_cache(maxsize=None, typed=True)
  1305.     def _relative_x_path(self, tag_name: str, **attrs: str) -> str:
  1306.         attr_key: str
  1307.         attr_value: str
  1308.         attrs_string: str = "".join(
  1309.             f'[@{attr_key}="{attr_value}"]' for attr_key, attr_value in attrs.items()
  1310.         )
  1311.         if "{" in tag_name:
  1312.             return f"./{tag_name}" + attrs_string
  1313.         return f"./{{{self.svg_namespace}}}{tag_name}" + attrs_string
  1314.  
  1315.     @lru_cache(maxsize=None, typed=True)
  1316.     def _get_inner_items(
  1317.         self, item_name: str, tag_name: str, suffix: str
  1318.     ) -> list[ElementTree.Element]:
  1319.         group: ElementTree.Element | None = self.svg.find(
  1320.             self._relative_x_path("g", id=item_name)
  1321.         )
  1322.         if group is None:
  1323.             return []
  1324.         text_path: str = self._relative_x_path(
  1325.             tag_name=tag_name, id=f"{item_name}_{suffix}"
  1326.         )
  1327.         elements: list[ElementTree.Element] = group.findall(text_path)
  1328.         if elements:
  1329.             return elements
  1330.         return sum((element.findall(text_path) for element in group), [])
  1331.  
  1332.     @lru_cache(maxsize=None, typed=True)
  1333.     def _get_body(self, item_name: str) -> ElementTree.Element | None:
  1334.         try:
  1335.             return self._get_inner_items(item_name, tag_name="use", suffix="body")[0]
  1336.         except IndexError:
  1337.             return None
  1338.  
  1339.     @lru_cache(maxsize=None, typed=True)
  1340.     def _get_values(self, item_name: str) -> list[ElementTree.Element]:
  1341.         return self._get_inner_items(item_name, tag_name="text", suffix="value")
  1342.  
  1343.     @lru_cache(maxsize=None, typed=True)
  1344.     def _get_targets(self, item_name: str) -> list[ElementTree.Element]:
  1345.         return self._get_inner_items(item_name, tag_name="text", suffix="target")
  1346.  
  1347.     @lru_cache(maxsize=None, typed=True)
  1348.     def _get_path(self, path_name: str) -> ElementTree.Element | None:
  1349.         paths_group: ElementTree.Element | None = self.svg.find(
  1350.             self._relative_x_path("g", id="paths")
  1351.         )
  1352.         if paths_group is None:
  1353.             return None
  1354.         return paths_group.find(self._relative_x_path("path", id=path_name))
  1355.  
  1356.     @lru_cache(maxsize=None, typed=True)
  1357.     def _view_box(self) -> QRectF:
  1358.         view_box: str | None = self.svg.get("viewBox")
  1359.         if not view_box:
  1360.             return QRectF()
  1361.         return QRectF(*map(float, view_box.split()))
  1362.  
  1363.     @lru_cache(maxsize=None, typed=True)
  1364.     def _item_location(self, item_name: str) -> QPointF:
  1365.         """returns the relative location of an item on the scheme"""
  1366.         element: ElementTree.Element | None = self.svg.find(
  1367.             self._relative_x_path("g", id=item_name)
  1368.         )
  1369.         if element is None:
  1370.             return QPointF()
  1371.         if "transform" not in element.attrib:
  1372.             return QPointF()
  1373.         transform: str | None = element.get("transform")
  1374.         if not transform:
  1375.             return QPointF()
  1376.         translate_match: Match[str] | None = search(
  1377.             r"translate\s*\((?P<x>\d*\.?\d*),[\s\n]*(?P<y>\d*\.?\d*)\)",
  1378.             transform.casefold(),
  1379.         )
  1380.         if translate_match is None:
  1381.             return QPointF()
  1382.  
  1383.         view_box: QRectF = self._view_box()
  1384.         translate: QPointF = QPointF(
  1385.             float(translate_match.group("x")), float(translate_match.group("y"))
  1386.         )
  1387.         return QPointF(
  1388.             (translate.x() - view_box.x()) / view_box.width(),
  1389.             (translate.y() - view_box.y()) / view_box.height(),
  1390.         )
  1391.  
  1392.     def item_at(self, point: QPointF, tolerance: float) -> str | None:
  1393.         element: ElementTree.Element
  1394.         for element in self.svg.iterfind(self._relative_x_path("g")):
  1395.             if "id" not in element.attrib:
  1396.                 continue
  1397.             element_position: QPointF = self._item_location(element.get("id"))
  1398.             if element_position.isNull():
  1399.                 continue
  1400.             if (
  1401.                 abs(element_position.x() - point.x()) < tolerance
  1402.                 and abs(element_position.y() - point.y()) < tolerance
  1403.             ):
  1404.                 return element.get("id")
  1405.         return None
  1406.  
  1407.     def change_valve_state(self, valve_name: str, new_state: DeviceState) -> None:
  1408.         valve_name = valve_name.casefold()
  1409.         images: dict[DeviceState, str] = {
  1410.             DeviceState.Initialization: "undefined_valve",
  1411.             DeviceState.Fault: "fault_valve",
  1412.             DeviceState.Off: "closed_valve",
  1413.             DeviceState.GettingOff: "closing_valve",
  1414.             DeviceState.On: "opened_valve",
  1415.             DeviceState.GettingOn: "opening_valve",
  1416.             DeviceState.Invalid: "fault_valve",
  1417.         }
  1418.         images.update(self.valve_features.get(valve_name, dict()).get("images", dict()))
  1419.         valve_body: ElementTree.Element | None = self._get_body(valve_name)
  1420.         if valve_body is None:
  1421.             return
  1422.         if new_state in images:
  1423.             valve_body.set("href", "#" + images[new_state])
  1424.         else:
  1425.             valve_body.set("href", "")
  1426.         self.valve_states[valve_name] = new_state
  1427.  
  1428.     def change_pump_state(self, pump_name: str, new_state: DeviceState) -> None:
  1429.         pump_name = pump_name.casefold()
  1430.         images: dict[DeviceState, str] = {
  1431.             DeviceState.Initialization: "pump_undefined",
  1432.             DeviceState.Fault: "pump_fault",
  1433.             DeviceState.Off: "pump_off",
  1434.             DeviceState.GettingOff: "pump_getting_off",
  1435.             DeviceState.On: "pump_on",
  1436.             DeviceState.GettingOn: "pump_getting_on",
  1437.             DeviceState.Invalid: "pump_fault",
  1438.         }
  1439.         images.update(self.pump_features.get(pump_name, dict()).get("images", dict()))
  1440.         pump_body: ElementTree.Element | None = self._get_body(pump_name)
  1441.         if pump_body is None:
  1442.             return
  1443.         pump_body.set("href", "#" + images[new_state])
  1444.         self.pump_states[pump_name] = new_state
  1445.  
  1446.     def change_tds_state(self, tds_name: str, new_state: DeviceState) -> None:
  1447.         tds_name = tds_name.casefold()
  1448.         images: dict[DeviceState, str] = {
  1449.             DeviceState.Initialization: "tds_undefined",
  1450.             DeviceState.Fault: "tds_fault",
  1451.             DeviceState.Off: "tds_off",
  1452.             DeviceState.GettingOff: "tds_getting_off",
  1453.             DeviceState.On: "tds_on",
  1454.             DeviceState.GettingOn: "tds_getting_on",
  1455.             DeviceState.Invalid: "tds_fault",
  1456.         }
  1457.         images.update(self.tds_features.get(tds_name, dict()).get("images", dict()))
  1458.         tds_body: ElementTree.Element | None = self._get_body(tds_name)
  1459.         if tds_body is None:
  1460.             return
  1461.         tds_body.set("href", "#" + images[new_state])
  1462.         self.tds_states[tds_name] = new_state
  1463.  
  1464.     def write_value(self, item_name: str, value: str) -> None:
  1465.         for item_text in self._get_values(item_name):
  1466.             item_text.text = value
  1467.  
  1468.     def write_target(self, item_name: str, value: str) -> None:
  1469.         for item_text in self._get_targets(item_name):
  1470.             item_text.text = value
  1471.  
  1472.     def color_path(self, path_name: str, color: str) -> None:
  1473.         path: ElementTree.Element | None = self._get_path(path_name)
  1474.         if path is None:
  1475.             logging.warning(f"path {path_name!r} missing")
  1476.             return
  1477.         style: CSS = CSS(path.get("style") or "")
  1478.         style["stroke"] = color
  1479.         path.set("style", str(style))
  1480.  
  1481.     def redraw_svg(self) -> None:
  1482.         self.svg_widget.load(
  1483.             self.resolve_css_vars(ElementTree.tostring(self.svg).decode()).encode()
  1484.         )
  1485.  
  1486.     def color_paths(self) -> None:
  1487.         if (
  1488.             self.valve_states["v2"] == DeviceState.On
  1489.             or self.valve_states["v3"] == DeviceState.On
  1490.             or self.valve_states["v4"] == DeviceState.On
  1491.         ):
  1492.             if self.valve_states["v2"] == DeviceState.On:
  1493.                 self.color_path("v2_path", Scheme.PipeColor.Air)
  1494.             elif self.valve_states["v2"] == DeviceState.Off:
  1495.                 self.color_path("v2_path", Scheme.PipeColor.HighVacuum)
  1496.             if self.valve_states["v3"] == DeviceState.On:
  1497.                 self.color_path("v3_path", Scheme.PipeColor.Air)
  1498.                 self.color_path("v2-v3_path", Scheme.PipeColor.Air)
  1499.             elif self.valve_states["v3"] == DeviceState.Off:
  1500.                 self.color_path("v3_path", Scheme.PipeColor.HighVacuum)
  1501.             if self.valve_states["v4"] == DeviceState.On:
  1502.                 self.color_path("v4_path", Scheme.PipeColor.Air)
  1503.                 self.color_path("v2-v3_path", Scheme.PipeColor.Air)
  1504.                 self.color_path("v3-v4_path", Scheme.PipeColor.Air)
  1505.             elif self.valve_states["v4"] == DeviceState.Off:
  1506.                 self.color_path("v4_path", Scheme.PipeColor.HighVacuum)
  1507.                 self.color_path("v3-v4_path", Scheme.PipeColor.HighVacuum)
  1508.                 if self.valve_states["v3"] == DeviceState.Off:
  1509.                     self.color_path("v2-v3_path", Scheme.PipeColor.HighVacuum)
  1510.             self.color_path("v2-tds1_path", Scheme.PipeColor.Air)
  1511.             self.color_path("tds1-x4_path", Scheme.PipeColor.Air)
  1512.             if self.valve_states["x4"] == DeviceState.Off:
  1513.                 self.color_path("x6-x4-1_path", Scheme.PipeColor.Air)
  1514.                 if self.valve_states["x6"] == DeviceState.On:
  1515.                     self.color_path("x6-tds2_path", Scheme.PipeColor.Air)
  1516.                     self.color_path("v5-x6_path", Scheme.PipeColor.Air)
  1517.                     if self.valve_states["v5"] == DeviceState.On:
  1518.                         self.color_path("v5-pump2_path", Scheme.PipeColor.Air)
  1519.                         if self.valve_states["v6"] == DeviceState.On:
  1520.                             self.color_path("pump1-v6_path", Scheme.PipeColor.Air)
  1521.                             self.color_path("v7-pump1_path", Scheme.PipeColor.Air)
  1522.                             if (
  1523.                                 self.valve_states["v7"] == DeviceState.On
  1524.                                 or self.valve_states["v8"] == DeviceState.On
  1525.                             ):
  1526.                                 self.color_path("cavity-v7_path", Scheme.PipeColor.Air)
  1527.                                 self.color_path("v1-cavity_path", Scheme.PipeColor.Air)
  1528.                                 if self.valve_states["v1"] == DeviceState.On:
  1529.                                     self.color_path("x4-v1_path", Scheme.PipeColor.Air)
  1530.                                     self.color_path(
  1531.                                         "x6-x4-2_path", Scheme.PipeColor.Air
  1532.                                     )
  1533.                     elif (
  1534.                         self.valve_states["v5"] == DeviceState.Off
  1535.                         and self.pump_states["pump2"] == DeviceState.On
  1536.                     ):
  1537.                         if self.valve_states["v6"] == DeviceState.Off:
  1538.                             self.color_path(
  1539.                                 "v5-pump2_path", Scheme.PipeColor.HighVacuum
  1540.                             )
  1541.                         elif self.valve_states["v6"] == DeviceState.On:
  1542.                             if (
  1543.                                 self.valve_states["v7"] == DeviceState.Off
  1544.                                 and self.valve_states["v8"] == DeviceState.Off
  1545.                             ):
  1546.                                 self.color_path(
  1547.                                     "v5-pump2_path", Scheme.PipeColor.HighVacuum
  1548.                                 )
  1549.                                 self.color_path(
  1550.                                     "pump1-v6_path", Scheme.PipeColor.HighVacuum
  1551.                                 )
  1552.                                 self.color_path(
  1553.                                     "v7-pump1_path", Scheme.PipeColor.HighVacuum
  1554.                                 )
  1555.                             elif self.valve_states["v9"] == DeviceState.Off:
  1556.                                 self.color_path(
  1557.                                     "v5-pump2_path", Scheme.PipeColor.HighVacuum
  1558.                                 )
  1559.                                 self.color_path(
  1560.                                     "pump1-v6_path", Scheme.PipeColor.HighVacuum
  1561.                                 )
  1562.                                 self.color_path(
  1563.                                     "v7-pump1_path", Scheme.PipeColor.HighVacuum
  1564.                                 )
  1565.                                 self.color_path(
  1566.                                     "cavity-v7_path", Scheme.PipeColor.HighVacuum
  1567.                                 )
  1568.                                 self.color_path(
  1569.                                     "v1-cavity_path", Scheme.PipeColor.HighVacuum
  1570.                                 )
  1571.                                 if self.valve_states["v1"] == DeviceState.On:
  1572.                                     self.color_path(
  1573.                                         "x4-v1_path", Scheme.PipeColor.HighVacuum
  1574.                                     )
  1575.                                     self.color_path(
  1576.                                         "x6-x4-2_path", Scheme.PipeColor.HighVacuum
  1577.                                     )
  1578.                 elif self.valve_states["x6"] == DeviceState.Off:
  1579.                     self.color_path("v5-x6_path", Scheme.PipeColor.Air)
  1580.                     if self.valve_states["v5"] == DeviceState.On:
  1581.                         self.color_path("v5-pump2_path", Scheme.PipeColor.Air)
  1582.                         if self.valve_states["v6"] == DeviceState.On:
  1583.                             self.color_path("pump1-v6_path", Scheme.PipeColor.Air)
  1584.                             self.color_path("v7-pump1_path", Scheme.PipeColor.Air)
  1585.                             if (
  1586.                                 self.valve_states["v7"] == DeviceState.On
  1587.                                 or self.valve_states["v8"] == DeviceState.On
  1588.                             ):
  1589.                                 self.color_path("cavity-v7_path", Scheme.PipeColor.Air)
  1590.                                 self.color_path("v1-cavity_path", Scheme.PipeColor.Air)
  1591.                                 if self.valve_states["v1"] == DeviceState.On:
  1592.                                     self.color_path("x4-v1_path", Scheme.PipeColor.Air)
  1593.                                     self.color_path(
  1594.                                         "x6-x4-2_path", Scheme.PipeColor.Air
  1595.                                     )
  1596.  
  1597.             elif self.valve_states["x4"] == DeviceState.On:
  1598.                 self.color_path("x6-x4-2_path", Scheme.PipeColor.Air)
  1599.                 if self.valve_states["x6"] == DeviceState.Off:
  1600.                     self.color_path("x6-tds2_path", Scheme.PipeColor.Air)
  1601.  
  1602.         elif (
  1603.             self.valve_states["v2"] == DeviceState.Off
  1604.             and self.valve_states["v3"] == DeviceState.Off
  1605.             and self.valve_states["v4"] == DeviceState.Off
  1606.             and self.pump_states["pump2"] == DeviceState.On
  1607.         ):
  1608.             if (
  1609.                 self.valve_states["v9"] == DeviceState.Off
  1610.                 and self.valve_states["v6"] == DeviceState.On
  1611.                 and (
  1612.                     self.valve_states["v8"] == DeviceState.On
  1613.                     or self.valve_states["v7"] == DeviceState.On
  1614.                 )
  1615.                 and self.valve_states["v1"] == DeviceState.On
  1616.                 and self.valve_states["x4"] == DeviceState.On
  1617.                 and self.valve_states["x6"] == DeviceState.Off
  1618.             ):
  1619.                 for in_valve in ("v2", "v3", "v4"):
  1620.                     self.color_path(f"{in_valve}_path", Scheme.PipeColor.HighVacuum)
  1621.                 self.color_path("v2-v3_path", Scheme.PipeColor.HighVacuum)
  1622.                 self.color_path("v3-v4_path", Scheme.PipeColor.HighVacuum)
  1623.                 self.color_path("v2-tds1_path", Scheme.PipeColor.HighVacuum)
  1624.                 self.color_path("tds1-x4_path", Scheme.PipeColor.HighVacuum)
  1625.                 self.color_path("x6-x4-2_path", Scheme.PipeColor.HighVacuum)
  1626.                 self.color_path("x6-tds2_path", Scheme.PipeColor.HighVacuum)
  1627.  
  1628.             if (
  1629.                 self.valve_states["v5"] == DeviceState.On
  1630.                 and self.valve_states["x4"] == DeviceState.Off
  1631.                 and (
  1632.                     self.valve_states["v9"] == DeviceState.Off
  1633.                     or self.valve_states["v6"] == DeviceState.Off
  1634.                     or (
  1635.                         self.valve_states["v8"] == DeviceState.Off
  1636.                         and self.valve_states["v7"] == DeviceState.Off
  1637.                     )
  1638.                 )
  1639.             ):
  1640.                 for in_valve in ("v2", "v3", "v4"):
  1641.                     self.color_path(f"{in_valve}_path", Scheme.PipeColor.HighVacuum)
  1642.                 self.color_path("v2-v3_path", Scheme.PipeColor.HighVacuum)
  1643.                 self.color_path("v3-v4_path", Scheme.PipeColor.HighVacuum)
  1644.                 self.color_path("v2-tds1_path", Scheme.PipeColor.HighVacuum)
  1645.                 self.color_path("tds1-x4_path", Scheme.PipeColor.HighVacuum)
  1646.                 self.color_path("x6-x4-1_path", Scheme.PipeColor.HighVacuum)
  1647.                 self.color_path("v5-pump2_path", Scheme.PipeColor.HighVacuum)
  1648.                 self.color_path("v5-x6_path", Scheme.PipeColor.HighVacuum)
  1649.                 if self.valve_states["x6"] == DeviceState.On:
  1650.                     self.color_path("x6-tds2_path", Scheme.PipeColor.HighVacuum)
  1651.                 if self.valve_states["v6"] == DeviceState.On:
  1652.                     self.color_path("pump1-v6_path", Scheme.PipeColor.HighVacuum)
  1653.                     self.color_path("v7-pump1_path", Scheme.PipeColor.HighVacuum)
  1654.                     if self.valve_states["v9"] == DeviceState.Off and (
  1655.                         self.valve_states["v7"] == DeviceState.On
  1656.                         or self.valve_states["v8"] == DeviceState.On
  1657.                     ):
  1658.                         self.color_path("cavity-v7_path", Scheme.PipeColor.HighVacuum)
  1659.                         self.color_path("v1-cavity_path", Scheme.PipeColor.HighVacuum)
  1660.                         if self.valve_states["v1"] == DeviceState.On:
  1661.                             self.color_path("x4-v1_path", Scheme.PipeColor.HighVacuum)
  1662.                             self.color_path("x6-x4-2_path", Scheme.PipeColor.HighVacuum)
  1663.         ""
  1664.  
  1665.  
  1666. if __name__ == "__main__":
  1667.     QApplication(sys.argv)
  1668.     w: Scheme = Scheme()
  1669.     w.locked = False
  1670.     w.show()
  1671.     QApplication.exec()
  1672.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement