From cf64c2537c5b91b70720b308d3c1a1a28e6c3d3d Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 25 Nov 2024 23:16:21 +0100 Subject: [PATCH 01/14] stateengine plugin: improve zoom/scroll handling (esp. on page update, using cookies now) --- stateengine/webif/static/svg-pan-zoom.min.js | 3 + stateengine/webif/templates/visu.html | 199 +++++++++++++++---- 2 files changed, 161 insertions(+), 41 deletions(-) create mode 100644 stateengine/webif/static/svg-pan-zoom.min.js diff --git a/stateengine/webif/static/svg-pan-zoom.min.js b/stateengine/webif/static/svg-pan-zoom.min.js new file mode 100644 index 000000000..cadc36e33 --- /dev/null +++ b/stateengine/webif/static/svg-pan-zoom.min.js @@ -0,0 +1,3 @@ +// svg-pan-zoom v3.6.2 +// https://github.com/bumbu/svg-pan-zoom +!function s(r,a,l){function u(e,t){if(!a[e]){if(!r[e]){var o="function"==typeof require&&require;if(!t&&o)return o(e,!0);if(h)return h(e,!0);var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}var i=a[e]={exports:{}};r[e][0].call(i.exports,function(t){return u(r[e][1][t]||t)},i,i.exports,s,r,a,l)}return a[e].exports}for(var h="function"==typeof require&&require,t=0;tthis.options.maxZoom*n.zoom&&(t=this.options.maxZoom*n.zoom/this.getZoom());var i=this.viewport.getCTM(),s=e.matrixTransform(i.inverse()),r=this.svg.createSVGMatrix().translate(s.x,s.y).scale(t).translate(-s.x,-s.y),a=i.multiply(r);a.a!==i.a&&this.viewport.setCTM(a)},i.prototype.zoom=function(t,e){this.zoomAtPoint(t,a.getSvgCenterPoint(this.svg,this.width,this.height),e)},i.prototype.publicZoom=function(t,e){e&&(t=this.computeFromRelativeZoom(t)),this.zoom(t,e)},i.prototype.publicZoomAtPoint=function(t,e,o){if(o&&(t=this.computeFromRelativeZoom(t)),"SVGPoint"!==r.getType(e)){if(!("x"in e&&"y"in e))throw new Error("Given point is invalid");e=a.createSVGPoint(this.svg,e.x,e.y)}this.zoomAtPoint(t,e,o)},i.prototype.getZoom=function(){return this.viewport.getZoom()},i.prototype.getRelativeZoom=function(){return this.viewport.getRelativeZoom()},i.prototype.computeFromRelativeZoom=function(t){return t*this.viewport.getOriginalState().zoom},i.prototype.resetZoom=function(){var t=this.viewport.getOriginalState();this.zoom(t.zoom,!0)},i.prototype.resetPan=function(){this.pan(this.viewport.getOriginalState())},i.prototype.reset=function(){this.resetZoom(),this.resetPan()},i.prototype.handleDblClick=function(t){var e;if((this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),this.options.controlIconsEnabled)&&-1<(t.target.getAttribute("class")||"").indexOf("svg-pan-zoom-control"))return!1;e=t.shiftKey?1/(2*(1+this.options.zoomScaleSensitivity)):2*(1+this.options.zoomScaleSensitivity);var o=a.getEventPoint(t,this.svg).matrixTransform(this.svg.getScreenCTM().inverse());this.zoomAtPoint(e,o)},i.prototype.handleMouseDown=function(t,e){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),r.mouseAndTouchNormalize(t,this.svg),this.options.dblClickZoomEnabled&&r.isDblClick(t,e)?this.handleDblClick(t):(this.state="pan",this.firstEventCTM=this.viewport.getCTM(),this.stateOrigin=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()))},i.prototype.handleMouseMove=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&this.options.panEnabled){var e=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()),o=this.firstEventCTM.translate(e.x-this.stateOrigin.x,e.y-this.stateOrigin.y);this.viewport.setCTM(o)}},i.prototype.handleMouseUp=function(t){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&(this.state="none")},i.prototype.fit=function(){var t=this.viewport.getViewBox(),e=Math.min(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.contain=function(){var t=this.viewport.getViewBox(),e=Math.max(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.center=function(){var t=this.viewport.getViewBox(),e=.5*(this.width-(t.width+2*t.x)*this.getZoom()),o=.5*(this.height-(t.height+2*t.y)*this.getZoom());this.getPublicInstance().pan({x:e,y:o})},i.prototype.updateBBox=function(){this.viewport.simpleViewBoxCache()},i.prototype.pan=function(t){var e=this.viewport.getCTM();e.e=t.x,e.f=t.y,this.viewport.setCTM(e)},i.prototype.panBy=function(t){var e=this.viewport.getCTM();e.e+=t.x,e.f+=t.y,this.viewport.setCTM(e)},i.prototype.getPan=function(){var t=this.viewport.getState();return{x:t.x,y:t.y}},i.prototype.resize=function(){var t=a.getBoundingClientRectNormalized(this.svg);this.width=t.width,this.height=t.height;var e=this.viewport;e.options.width=this.width,e.options.height=this.height,e.processCTM(),this.options.controlIconsEnabled&&(this.getPublicInstance().disableControlIcons(),this.getPublicInstance().enableControlIcons())},i.prototype.destroy=function(){var e=this;for(var t in this.beforeZoom=null,this.onZoom=null,this.beforePan=null,this.onPan=null,(this.onUpdatedCTM=null)!=this.options.customEventsHandler&&this.options.customEventsHandler.destroy({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()}),this.eventListeners)(this.options.eventsListenerElement||this.svg).removeEventListener(t,this.eventListeners[t],!this.options.preventMouseEventsDefault&&h);this.disableMouseWheelZoom(),this.getPublicInstance().disableControlIcons(),this.reset(),c=c.filter(function(t){return t.svg!==e.svg}),delete this.options,delete this.viewport,delete this.publicInstance,delete this.pi,this.getPublicInstance=function(){return null}},i.prototype.getPublicInstance=function(){var o=this;return this.publicInstance||(this.publicInstance=this.pi={enablePan:function(){return o.options.panEnabled=!0,o.pi},disablePan:function(){return o.options.panEnabled=!1,o.pi},isPanEnabled:function(){return!!o.options.panEnabled},pan:function(t){return o.pan(t),o.pi},panBy:function(t){return o.panBy(t),o.pi},getPan:function(){return o.getPan()},setBeforePan:function(t){return o.options.beforePan=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnPan:function(t){return o.options.onPan=null===t?null:r.proxy(t,o.publicInstance),o.pi},enableZoom:function(){return o.options.zoomEnabled=!0,o.pi},disableZoom:function(){return o.options.zoomEnabled=!1,o.pi},isZoomEnabled:function(){return!!o.options.zoomEnabled},enableControlIcons:function(){return o.options.controlIconsEnabled||(o.options.controlIconsEnabled=!0,s.enable(o)),o.pi},disableControlIcons:function(){return o.options.controlIconsEnabled&&(o.options.controlIconsEnabled=!1,s.disable(o)),o.pi},isControlIconsEnabled:function(){return!!o.options.controlIconsEnabled},enableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!0,o.pi},disableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!1,o.pi},isDblClickZoomEnabled:function(){return!!o.options.dblClickZoomEnabled},enableMouseWheelZoom:function(){return o.enableMouseWheelZoom(),o.pi},disableMouseWheelZoom:function(){return o.disableMouseWheelZoom(),o.pi},isMouseWheelZoomEnabled:function(){return!!o.options.mouseWheelZoomEnabled},setZoomScaleSensitivity:function(t){return o.options.zoomScaleSensitivity=t,o.pi},setMinZoom:function(t){return o.options.minZoom=t,o.pi},setMaxZoom:function(t){return o.options.maxZoom=t,o.pi},setBeforeZoom:function(t){return o.options.beforeZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnZoom:function(t){return o.options.onZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},zoom:function(t){return o.publicZoom(t,!0),o.pi},zoomBy:function(t){return o.publicZoom(t,!1),o.pi},zoomAtPoint:function(t,e){return o.publicZoomAtPoint(t,e,!0),o.pi},zoomAtPointBy:function(t,e){return o.publicZoomAtPoint(t,e,!1),o.pi},zoomIn:function(){return this.zoomBy(1+o.options.zoomScaleSensitivity),o.pi},zoomOut:function(){return this.zoomBy(1/(1+o.options.zoomScaleSensitivity)),o.pi},getZoom:function(){return o.getRelativeZoom()},setOnUpdatedCTM:function(t){return o.options.onUpdatedCTM=null===t?null:r.proxy(t,o.publicInstance),o.pi},resetZoom:function(){return o.resetZoom(),o.pi},resetPan:function(){return o.resetPan(),o.pi},reset:function(){return o.reset(),o.pi},fit:function(){return o.fit(),o.pi},contain:function(){return o.contain(),o.pi},center:function(){return o.center(),o.pi},updateBBox:function(){return o.updateBBox(),o.pi},resize:function(){return o.resize(),o.pi},getSizes:function(){return{width:o.width,height:o.height,realZoom:o.getZoom(),viewBox:o.viewport.getViewBox()}},destroy:function(){return o.destroy(),o.pi}}),this.publicInstance};var c=[];e.exports=function(t,e){var o=r.getSvg(t);if(null===o)return null;for(var n=c.length-1;0<=n;n--)if(c[n].svg===o)return c[n].instance.getPublicInstance();return c.push({svg:o,instance:new i(o,e)}),c[c.length-1].instance.getPublicInstance()}},{"./control-icons":1,"./shadow-viewport":2,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(t,e,o){var l=t("./utilities"),s="unknown";document.documentMode&&(s="ie"),e.exports={svgNS:"http://www.w3.org/2000/svg",xmlNS:"http://www.w3.org/XML/1998/namespace",xmlnsNS:"http://www.w3.org/2000/xmlns/",xlinkNS:"http://www.w3.org/1999/xlink",evNS:"http://www.w3.org/2001/xml-events",getBoundingClientRectNormalized:function(t){if(t.clientWidth&&t.clientHeight)return{width:t.clientWidth,height:t.clientHeight};if(t.getBoundingClientRect())return t.getBoundingClientRect();throw new Error("Cannot get BoundingClientRect for SVG.")},getOrCreateViewport:function(t,e){var o=null;if(!(o=l.isElement(e)?e:t.querySelector(e))){var n=Array.prototype.slice.call(t.childNodes||t.children).filter(function(t){return"defs"!==t.nodeName&&"#text"!==t.nodeName});1===n.length&&"g"===n[0].nodeName&&null===n[0].getAttribute("transform")&&(o=n[0])}if(!o){var i="viewport-"+(new Date).toISOString().replace(/\D/g,"");(o=document.createElementNS(this.svgNS,"g")).setAttribute("id",i);var s=t.childNodes||t.children;if(s&&0 {% endblock pluginstyles %} {% block pluginscripts %} - + + {% endblock pluginscripts %} {% set update_interval = 0 %} {% set logo_frame = false %} @@ -165,18 +284,16 @@ {{ _('Klicken zum Öffnen des SVG Files') }}
- Mittels Buttons und Slider kann jederzeit gezoomt werden. Ist die Zoom-Funktion aktiviert, - kann zusätzlich bei Halten der "Shift"-Taste mittels Mausrad in der Grafik gezoomt werden. - Die linke Maustaste ermöglicht dann auch ein Verschieben des Ausschnitts. Dabei werden - allerdings die Tooltips nicht angezeigt - hierfür ist die Zoom-Funktion zu deaktivieren. + Mittels Buttons und Slider kann jederzeit gezoomt werden. Sofern "Scrollen" nicht aktiv ist, kann + das Mausrad zum zielgerichteten Zoomen und die linke Maustaste zum Verschieben des Ausschnitts genutzt werden.
- + - {{_('Zoom/Pan aktiv')}} + {{_('Scrollen aktiv')}}
From dc9a928ce54440500b3c87ed0d282627d114579a Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 26 Nov 2024 11:57:49 +0100 Subject: [PATCH 02/14] stateengine: improve visu compactness --- stateengine/StateEngineWebif.py | 60 ++++++++++++++++++--------- stateengine/__init__.py | 6 ++- stateengine/plugin.yaml | 4 +- stateengine/webif/templates/visu.html | 4 +- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index 040ff6209..76b562e71 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -33,6 +33,23 @@ class WebInterface(StateEngineTools.SeItemChild): # Constructor # abitem: parent SeItem instance + + @property + def width(self): + return self.__widthfactor + + @width.setter + def width(self, value): + self.__widthfactor = value + + @property + def height(self): + return self.__heightfactor + + @height.setter + def height(self, value): + self.__heightfactor = value + def __init__(self, abitem): super().__init__(abitem) @@ -48,11 +65,13 @@ def __init__(self, abitem): self.__graph = pydotplus.Dot('StateEngine', graph_type='digraph', splines='false', overlap='scale', compound='false', imagepath='{}'.format(self.__img_path)) self.__graph.set_node_defaults(color='lightgray', style='filled', shape='box', - fontname='Helvetica', fontsize='10') + fontname='Helvetica', fontsize='10', pin='true') self.__graph.set_edge_defaults(color='darkgray', style='filled', shape='box', - fontname='Helvetica', fontsize='10') + fontname='Helvetica', fontsize='10', len=0.1) + self.__graph.set_graph_defaults(sep=+0) self.__nodes = {} - self.__scalefactor = 0.1 + self.__heightfactor = 1 + self.__widthfactor = 1 self.__textlimit = 105 self.__conditionset_count = 0 @@ -314,7 +333,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c label = 'first enter' if action_type in ['actions_enter', 'actions_enter_or_stay'] else 'staying at state' - position = '{},{}!'.format(0.38, new_y) + position = '{},{}!'.format(0.25 * self.__widthfactor, new_y) color = color_enter if label == 'first enter' else color_stay self.__nodes['{}_{}_state_{}'.format(state, conditionset, action_type)] = \ pydotplus.Node('{}_{}_state_{}'.format(state, conditionset, action_type), style="filled", fillcolor=color, @@ -360,7 +379,7 @@ def drawgraph(self, filename): color = "olivedrab" if state == self.__active_state \ else "gray" if i > list_index else "indianred2" - new_y -= 1 * self.__scalefactor + new_y -= 0.03 * self.__heightfactor position = '{},{}!'.format(0, new_y) if not i == 0: condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ @@ -370,7 +389,7 @@ def drawgraph(self, filename): self.__nodes['{}_above'.format(state)] = pydotplus.Node('{}_above'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above'.format(state)]) - position = '{},{}!'.format(0.3, new_y) + position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) self.__nodes['{}_above_right'.format(state)] = pydotplus.Node('{}_above_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above_right'.format(state)]) @@ -381,7 +400,7 @@ def drawgraph(self, filename): style='bold', color='black', label="False ", dir="none")) self.__graph.add_edge(pydotplus.Edge(state, self.__nodes['{}_above'.format(state)], style='bold', color='black', label="", dir="back")) - new_y -= 1 * self.__scalefactor + new_y -= 0.03 * self.__heightfactor position = '{},{}!'.format(0, new_y) #self._log_debug('state: {} {}',state, position) @@ -390,7 +409,7 @@ def drawgraph(self, filename): label='<
' '
{}
{}
>'.format( state, self.__states[state]['name'])) - position = '{},{}!'.format(0.3, new_y) + position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes[state]) @@ -464,17 +483,18 @@ def drawgraph(self, filename): actionlist_pass, action_tooltip_pass, action_tooltip_count_pass = \ self._actionlabel(state, 'actions_pass', conditionset, active) - new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor - position = '{},{}!'.format(0.3, new_y) + conditionset_positions.append(new_y) conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) - + nodeheight = len(re.findall(r"", conditionlist)) * 0.004 + new_y -= (0.01 + nodeheight) * self.__heightfactor + position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) label = 'no condition' if conditionset == '' else conditionset self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( '{}_{}'.format(state, conditionset), style="filled", fillcolor=color, shape="diamond", label=label, pos=position) #self._log_debug('Node {} {} drawn. Conditionlist {}', state, conditionset, conditionlist) - position = '{},{}!'.format(0.1, new_y) + position = '{},{}!'.format(0.06 * self.__widthfactor, new_y) xlabel = '1 tooltip' if condition_tooltip_count == 1\ else '{} tooltips'.format(condition_tooltip_count)\ if condition_tooltip_count > 1 else '' @@ -488,7 +508,7 @@ def drawgraph(self, filename): self.__graph.add_edge(parenthesis_edge) self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) - new_x = 0.55 + new_x = 0.35 * self.__widthfactor if not actionlist_enter == '': position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ @@ -503,7 +523,7 @@ def drawgraph(self, filename): self._add_actioncondition(state, conditionset, 'actions_enter', new_y, cond1, cond2) if not actionlist_stay == '': - new_y -= 0.05 if not actionlist_enter == '' else 0 + new_y -= 0.03 * self.__heightfactor if not actionlist_enter == '' else 0 position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ else '{} tooltips'.format(action_tooltip_count_stay)\ @@ -516,7 +536,7 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) self._add_actioncondition(state, conditionset, 'actions_stay', new_y, cond1, cond2) - position = '{},{}!'.format(0.55, new_y) + position = '{},{}!'.format(0.35 * self.__widthfactor, new_y) cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None @@ -550,8 +570,8 @@ def drawgraph(self, filename): previous_conditionset = self.__nodes['{}_{}'.format(state, conditionset)] if len(actions_leave) > 0: - new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor - position = '{},{}!'.format(0.3, new_y) + new_y -= 0.06 * self.__heightfactor if j == 0 else 0.08 * self.__heightfactor + position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) except Exception as ex: @@ -583,8 +603,8 @@ def drawgraph(self, filename): previous_conditionset = self.__nodes['{}_leave'.format(state)] if len(actions_pass) > 0: - new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor - position = '{},{}!'.format(0.3, new_y) + new_y -= 0.06 * self.__heightfactor if j == 0 else 0.08 * self.__heightfactor + position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) except Exception: @@ -616,5 +636,5 @@ def drawgraph(self, filename): previous_state = state - result = self.__graph.write_svg(filename, prog='fdp') + result = self.__graph.write_svg(filename, prog='neato') return result diff --git a/stateengine/__init__.py b/stateengine/__init__.py index 5b5296938..eba18c4da 100755 --- a/stateengine/__init__.py +++ b/stateengine/__init__.py @@ -47,7 +47,7 @@ class StateEngine(SmartPlugin): - PLUGIN_VERSION = '2.2.0' + PLUGIN_VERSION = '2.2.1' # Constructor # noinspection PyUnusedLocal,PyMissingConstructor @@ -207,10 +207,12 @@ def get_items(self): finallist.append(self._items[i]) return finallist - def get_graph(self, abitem, graphtype='link'): + def get_graph(self, abitem, graphtype='link', width=1, height=1): if isinstance(abitem, str): abitem = self._items[abitem] webif = StateEngineWebif.WebInterface(abitem) + webif.width = width + webif.height = height try: os.makedirs(self.path_join(self.get_plugin_dir(), 'webif/static/img/visualisations/')) except OSError: diff --git a/stateengine/plugin.yaml b/stateengine/plugin.yaml index be3495046..d92705241 100755 --- a/stateengine/plugin.yaml +++ b/stateengine/plugin.yaml @@ -35,11 +35,11 @@ plugin: - Python Modul pydotplus: ``pip3 install pydotplus``\n ' maintainer: onkelandy - tester: 'Morg42' + tester: 'Morg42, bmxp' state: ready support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1303071-stateengine-plugin-support - version: '2.2.0' + version: '2.2.1' sh_minversion: '1.6' multi_instance: False classname: StateEngine diff --git a/stateengine/webif/templates/visu.html b/stateengine/webif/templates/visu.html index 30fb9a516..aabb441f9 100755 --- a/stateengine/webif/templates/visu.html +++ b/stateengine/webif/templates/visu.html @@ -29,7 +29,7 @@ border: 3px solid white; padding: 10px; overflow: visible; - width:100%; + width:98%; height:100%; z-index: -1; visibility: hidden; @@ -298,7 +298,7 @@
- {{ p.get_graph(item, 'graph') }} + {{ p.get_graph(item, 'graph', 1, 1) }}
{% else %}
From d32b1e75d19dc21b736702a71d69c074a3c81fec Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 26 Nov 2024 22:17:40 +0100 Subject: [PATCH 03/14] stateengine: use sh.items.return_item instead of sh.return_item --- stateengine/StateEngineFunctions.py | 4 ++-- stateengine/StateEngineItem.py | 6 +++--- stateengine/StateEngineTools.py | 2 +- stateengine/webif/__init__.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/stateengine/StateEngineFunctions.py b/stateengine/StateEngineFunctions.py index 1e869d033..e714a73c3 100755 --- a/stateengine/StateEngineFunctions.py +++ b/stateengine/StateEngineFunctions.py @@ -97,7 +97,7 @@ def check_include_exclude(entry_type): elog.decrease_indent() return None - item = self.__sh.return_item(item_id) + item = self.__sh.items.return_item(item_id) if item is None: self.logger.error("manual_item_update_eval: item {0} not found!".format(item_id)) @@ -111,7 +111,7 @@ def check_include_exclude(entry_type): if "se_manual_logitem" in item.conf: elog_item_id = item.conf["se_manual_logitem"] - elog_item = self.__sh.return_item(elog_item_id) + elog_item = self.__sh.items.return_item(elog_item_id) if elog_item is None: self.logger.error("manual_item_update_item: se_manual_logitem {0} not found!".format(elog_item_id)) elog = StateEngineLogger.SeLoggerDummy() diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index b171a8823..d5c255c4b 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -2162,7 +2162,7 @@ def return_item(self, item_id): if isinstance(item_id, (StateEngineStruct.SeStruct, self.__itemClass)): return item_id, None if isinstance(item_id, StateEngineState.SeState): - return self.__sh.return_item(item_id.id), None + return self.__sh.items.return_item(item_id.id), None if item_id is None: _issue = "item_id is None" return None, [_issue] @@ -2203,7 +2203,7 @@ def return_item(self, item_id): self.__logger.warning(_issue) return None, [_issue] else: - item = self.__sh.return_item(item_id) + item = self.__sh.items.return_item(item_id) if item is None: _issue = "Item '{0}' not found.".format(item_id) self.__logger.warning(_issue) @@ -2226,7 +2226,7 @@ def return_item(self, item_id): rel_item_id = item_id[parent_level:] if rel_item_id != "": result += "." + rel_item_id - item = self.__sh.return_item(result) + item = self.__sh.items.return_item(result) if item is None: _issue = "Determined item '{0}' does not exist.".format(item_id) self.__logger.warning(_issue) diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index b423c9c6a..760d4ca5f 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -410,7 +410,7 @@ def get_original_caller(smarthome, elog, caller, source, item=None, eval_keyword else: original_source = "None" while partition_strip(original_caller, ":")[0] in eval_keyword: - original_item = smarthome.return_item(original_source) + original_item = smarthome.items.return_item(original_source) if original_item is None: elog.info("get_caller({0}, {1}): original item not found", caller, source) break diff --git a/stateengine/webif/__init__.py b/stateengine/webif/__init__.py index 809f8084b..7ed239b98 100755 --- a/stateengine/webif/__init__.py +++ b/stateengine/webif/__init__.py @@ -63,7 +63,7 @@ def index(self, action=None, item_id=None, item_path=None, reload=None, abitem=N :return: contents of the template after beeing rendered """ - item = self.plugin.get_sh().return_item(item_path) + item = self.plugin.get_sh().items.return_item(item_path) tmpl = self.tplenv.get_template('{}.html'.format(page)) pagelength = self.plugin.get_parameter_value('webif_pagelength') From 7f1434a8ef4b9f4e45073eddd34b456c14c5ce1e Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 26 Nov 2024 22:52:29 +0100 Subject: [PATCH 04/14] stateengine: important fix for issue tracking at init --- stateengine/StateEngineActions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stateengine/StateEngineActions.py b/stateengine/StateEngineActions.py index 75a64de47..e38219214 100755 --- a/stateengine/StateEngineActions.py +++ b/stateengine/StateEngineActions.py @@ -194,7 +194,7 @@ def update(self, attribute, value): _count += 1 _issue = StateEngineTools.flatten_list(_issue_list) except ValueError as ex: - _issue = {name: {'issue': ex, 'issueorigin': [{'state': 'unknown', 'action': self.__actions[name].function}], 'ignore': True}} + _issue = {name: {'issue': [str(ex)], 'attribute': func, 'issueorigin': [{'state': self.__state.id, 'action': self.__actions[name].function}], 'ignore': True}} if name in self.__actions: del self.__actions[name] self._log_warning("Ignoring action {0} because: {1}", attribute, ex) @@ -208,7 +208,7 @@ def __check_force_setting(self, name, value, function): if function not in ["set", "force"]: _issue = { name: {'issue': ['Parameter force not supported for this function'], - 'attribute': 'force', 'issueorigin': [{'state': 'unknown', 'action': function}]}} + 'attribute': 'force', 'issueorigin': [{'state': self.__state.id, 'action': function}]}} _issue = "Parameter 'force' not supported for this function" self._log_warning("Attribute 'se_action_{0}': Parameter 'force' not supported " "for function '{1}'", name, function) @@ -231,7 +231,7 @@ def __check_mode_setting(self, name, value, function, action): # Parameter mode is supported only for type "remove" if "remove" not in function: _issue = {name: {'issue': ['Parameter mode only supported for remove function'], 'attribute': 'mode', - 'issueorigin': [{'state': 'unknown', 'action': function}]}} + 'issueorigin': [{'state': self.__state.id, 'action': function}]}} self._log_warning("Attribute 'se_action_{0}': Parameter 'mode' not supported for function '{1}'", name, function) elif function in ["remove", "remove all from list"]: @@ -247,7 +247,7 @@ def __check_mode_setting(self, name, value, function, action): else: _issue = { name: {'issue': ['Parameter {} not allowed for mode!'.format(value)], 'attribute': 'mode', - 'issueorigin': [{'state': 'unknown', 'action': function}]}} + 'issueorigin': [{'state': self.__state.id, 'action': function}]}} self._log_warning( "Attribute 'se_action_{0}': Parameter '{1}' for 'mode' is wrong - can only be {2}", name, value, possible_mode_list) @@ -365,7 +365,7 @@ def __handle_combined_action_attribute(self, name, value_list): def remove_action(e): if name in self.__actions: del self.__actions[name] - i = {name: {'issue': [e], 'issueorigin': [{'state': 'unknown', 'action': parameter['function']}], 'ignore': True}} + i = {name: {'issue': [str(e)], 'attribute': 'unknown', 'issueorigin': [{'state': self.__state.id, 'action': parameter['function']}], 'ignore': True}} _issue_list.append(i) self._log_warning("Ignoring action {0} because: {1}", name, e) @@ -584,7 +584,7 @@ def complete(self, evals_items=None, use=None): try: _status.update(self.__actions[name].complete(evals_items, use)) except ValueError as ex: - _status.update({name: {'issue': ex, 'issueorigin': {'state': self.__state.id, 'action': 'unknown'}}}) + _status.update({name: {'issue': [str(ex)], 'issueorigin': {'state': self.__state.id, 'action': name}}}) raise ValueError("Completing State '{0}', Action '{1}': {2}".format(self.__state.id, name, ex)) return _status From cc78896ebf0f4c14e13e6488cf54eb48e97e7650 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Wed, 27 Nov 2024 01:33:45 +0100 Subject: [PATCH 05/14] stateengine: further issue handling fixes --- stateengine/StateEngineAction.py | 4 ++-- stateengine/StateEngineActions.py | 26 ++++++++++++-------------- stateengine/StateEngineState.py | 13 ++++++++++++- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index d6653982a..462c96acb 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -927,7 +927,7 @@ def __repr__(self): # value: Value of the set_(action_name) attribute def update(self, value): self.__byattr = value - _issue = {self._name: {'issue': None, 'attribute': self.__byattr, + _issue = {self._name: {'issue': None, 'attribute': [self.__byattr], 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue @@ -938,7 +938,7 @@ def complete(self, evals_items=None, use=None): self._abitem.set_variable('current.action_name', self._name) self._abitem.set_variable('current.state_name', self._state.name) self._scheduler_name = "{}-SeByAttrDelayTimer".format(self.__byattr) - _issue = {self._name: {'issue': None, 'attribute': self.__byattr, + _issue = {self._name: {'issue': None, 'attribute': [self.__byattr], 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} self._abitem.set_variable('current.action_name', '') self._abitem.set_variable('current.state_name', '') diff --git a/stateengine/StateEngineActions.py b/stateengine/StateEngineActions.py index e38219214..e7d2a0eb4 100755 --- a/stateengine/StateEngineActions.py +++ b/stateengine/StateEngineActions.py @@ -164,10 +164,10 @@ def update(self, attribute, value): # If we do not have the action yet (mode-attribute before action-attribute), ... self.__unassigned_modes[name] = value else: - _val, _issue = self.__actions[name].update_mode(value) + _issue = self.__actions[name].update_mode(value) if _issue: _issue_list.append(_issue) - _issue, _action = self.__check_mode_setting(name, _val, self.__actions[name].function, self.__actions[name]) + _issue, _action = self.__check_mode_setting(name, value, self.__actions[name].function, self.__actions[name]) if _issue: _issue_list.append(_issue) if _action: @@ -194,7 +194,7 @@ def update(self, attribute, value): _count += 1 _issue = StateEngineTools.flatten_list(_issue_list) except ValueError as ex: - _issue = {name: {'issue': [str(ex)], 'attribute': func, 'issueorigin': [{'state': self.__state.id, 'action': self.__actions[name].function}], 'ignore': True}} + _issue = {name: {'issue': [str(ex)], 'attribute': [func], 'issueorigin': [{'state': self.__state.id, 'action': self.__actions[name].function}], 'ignore': True}} if name in self.__actions: del self.__actions[name] self._log_warning("Ignoring action {0} because: {1}", attribute, ex) @@ -208,8 +208,7 @@ def __check_force_setting(self, name, value, function): if function not in ["set", "force"]: _issue = { name: {'issue': ['Parameter force not supported for this function'], - 'attribute': 'force', 'issueorigin': [{'state': self.__state.id, 'action': function}]}} - _issue = "Parameter 'force' not supported for this function" + 'attribute': ['force'], 'issueorigin': [{'state': self.__state.id, 'action': function}]}} self._log_warning("Attribute 'se_action_{0}': Parameter 'force' not supported " "for function '{1}'", name, function) elif value and function == "set": @@ -230,7 +229,7 @@ def __check_mode_setting(self, name, value, function, action): _issue = None # Parameter mode is supported only for type "remove" if "remove" not in function: - _issue = {name: {'issue': ['Parameter mode only supported for remove function'], 'attribute': 'mode', + _issue = {name: {'issue': ['Parameter mode only supported for remove function'], 'attribute': ['mode'], 'issueorigin': [{'state': self.__state.id, 'action': function}]}} self._log_warning("Attribute 'se_action_{0}': Parameter 'mode' not supported for function '{1}'", name, function) @@ -246,7 +245,7 @@ def __check_mode_setting(self, name, value, function, action): self._log_info("Attribute 'se_action_{0}': Function 'remove' changed to '{1}'", name, value) else: _issue = { - name: {'issue': ['Parameter {} not allowed for mode!'.format(value)], 'attribute': 'mode', + name: {'issue': ['Parameter {} not allowed for mode!'.format(value)], 'attribute': ['mode'], 'issueorigin': [{'state': self.__state.id, 'action': function}]}} self._log_warning( "Attribute 'se_action_{0}': Parameter '{1}' for 'mode' is wrong - can only be {2}", @@ -365,9 +364,9 @@ def __handle_combined_action_attribute(self, name, value_list): def remove_action(e): if name in self.__actions: del self.__actions[name] - i = {name: {'issue': [str(e)], 'attribute': 'unknown', 'issueorigin': [{'state': self.__state.id, 'action': parameter['function']}], 'ignore': True}} + i = {name: {'issue': [str(e)], 'attribute': [f'se_action_{name}'], 'issueorigin': [{'state': self.__state.id, 'action': parameter['function']}], 'ignore': True}} _issue_list.append(i) - self._log_warning("Ignoring action {0} because: {1}", name, e) + self._log_warning("Removed action {0} because: {1}.", name, e) parameter = {'function': None, 'force': None, 'repeat': None, 'delay': 0, 'order': None, 'nextconditionset': None, 'conditionset': None, 'previousconditionset': None, 'previousstate_conditionset': None, 'mode': None, 'instanteval': None, 'mindelta': None, 'minagedelta': None} @@ -395,18 +394,18 @@ def remove_action(e): else: parameter[key] = val except Exception as ex: - remove_action("Problem with entry {} for action {}: {}".format(entry, name, ex)) + remove_action("Problem with entry {}: {}".format(entry, ex)) if _issue_list: return _issue_list parameter['action'] = name # function given and valid? if parameter['function'] is None: - remove_action("Attribute 'se_action_{0}: Parameter 'function' must be set!".format(name)) + remove_action("Parameter 'function' must be set!") return _issue_list if parameter['function'] not in ('set', 'force', 'run', 'byattr', 'trigger', 'special', 'add', 'remove', 'removeall', 'removefirst', 'removelast'): - remove_action("Attribute 'se_action_{0}: Invalid value '{1}' for parameter 'function'!".format(name, parameter['function'])) + remove_action("Invalid value '{}' for parameter 'function'!".format(parameter['function'])) return _issue_list _issue, parameter['function'] = self.__check_force_setting(name, parameter['force'], parameter['function']) @@ -571,8 +570,7 @@ def remove_action(e): # noinspection PyMethodMayBeStatic def __raise_missing_parameter_error(self, parameter, param_name): if param_name not in parameter or parameter[param_name] is None: - raise ValueError("Attribute 'se_action_{0}: Parameter '{1}' must be set for " - "function '{2}'!".format(parameter['action'], param_name, parameter['function'])) + raise ValueError("Parameter '{0}' must be set for function '{1}'!".format(param_name, parameter['function'])) # Check the actions optimize and complete them # state: state (item) to read from diff --git a/stateengine/StateEngineState.py b/stateengine/StateEngineState.py index b2ab0221a..43f7fb626 100755 --- a/stateengine/StateEngineState.py +++ b/stateengine/StateEngineState.py @@ -592,19 +592,29 @@ def filter_issues(input_dict): return for itm, dct in action_status.items(): if itm not in self.__action_status[actn_type]: - self.__action_status[actn_type].update({itm: dct}) + self.__action_status[actn_type][itm] = dct + else: + for key, value in dct.items(): + if key not in self.__action_status[actn_type][itm]: + self.__action_status[actn_type][itm][key] = value for (itm, dct) in action_status.items(): issues = dct.get('issue') attributes = dct.get('attribute') + if not isinstance(attributes, list): + attributes = [attributes] + if not isinstance(self.__action_status[actn_type][itm].get('attribute'), list): + self.__action_status[actn_type][itm]['attribute'] = [self.__action_status[actn_type][itm].get('attribute')] if issues: if isinstance(issues, list): + attributes = (attributes + [None] * len(issues))[:len(issues)] for i, issue in enumerate(issues): if issue not in self.__action_status[actn_type][itm]['issue']: self.__action_status[actn_type][itm]['issue'].append(issue) self.__action_status[actn_type][itm]['attribute'].append(attributes[i]) flattened_dict = {} + for key, action_type_dict in self.__action_status.items(): # Iterate through the inner dictionaries for inner_key, nested_dict in action_type_dict.items(): @@ -616,6 +626,7 @@ def filter_issues(input_dict): flattened_dict[inner_key].update(nested_dict) self.__used_attributes = deepcopy(flattened_dict) + self.__action_status = filter_issues(self.__action_status) self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) From 2b12591a116e1d4b3f6c49eaa889d354f5c282ad Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Wed, 27 Nov 2024 20:58:22 +0100 Subject: [PATCH 06/14] stateengine: improve visu, now compressed and less white space, no overlaps hopefully --- stateengine/StateEngineWebif.py | 56 +++++++++++++++------------ stateengine/__init__.py | 6 +-- stateengine/webif/templates/visu.html | 10 +++-- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index 76b562e71..fc9830590 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -63,12 +63,12 @@ def __init__(self, abitem): self.__active_state = abitem.laststate self.__graph = pydotplus.Dot('StateEngine', graph_type='digraph', splines='false', - overlap='scale', compound='false', imagepath='{}'.format(self.__img_path)) + overlap='compress', compound='false', imagepath='{}'.format(self.__img_path)) self.__graph.set_node_defaults(color='lightgray', style='filled', shape='box', fontname='Helvetica', fontsize='10', pin='true') self.__graph.set_edge_defaults(color='darkgray', style='filled', shape='box', fontname='Helvetica', fontsize='10', len=0.1) - self.__graph.set_graph_defaults(sep=+0) + self.__graph.set_graph_defaults(sep=+0.15) self.__nodes = {} self.__heightfactor = 1 self.__widthfactor = 1 @@ -165,7 +165,7 @@ def _actionlabel(self, state, label_type, conditionset, active): if _success == 'True' and active \ else '' if not action2 == 'None': - actionlabel += '{} {} {} {}'.format(fontcolor, action1, action2, action3, additionaltext) + actionlabel += '{} {} {} {}'.format(fontcolor, action1, action2, action3, additionaltext) actionlabel += '{}'.format(success_info) actionlabel += '>' @@ -182,7 +182,7 @@ def _conditionlabel(self, state, conditionset): if _empty_set: return '', '', 0 conditionlist = '<' - conditionlist += ''.format(conditionset) + conditionlist += ''.format(conditionset) tooltip_count = 0 for k, condition in enumerate(self.__states[state]['conditionsets'].get(conditionset)): condition_dict = self.__states[state]['conditionsets'][conditionset].get(condition) @@ -211,11 +211,12 @@ def _conditionlabel(self, state, conditionset): current_clean = ", ".join(f"{k} = {v}" for k, v in current.items()) text = " Current {}".format(current_clean) if current is not None and len(current) > 0 else " Not evaluated." conditionlist += ('').format(condition.upper(), text) conditions_done.append(condition) - conditionlist += '", conditionlist)) * 0.004 - new_y -= (0.01 + nodeheight) * self.__heightfactor - position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) + nodeheight = 1 + len(re.findall(r'', conditionlist)) * 0.26 + nodeheight += len(re.findall(r'', conditionlist)) * 0.16 + nodeheight += len(re.findall(r'', conditionlist)) * 0.26 + nodeheight /= 2 + new_y -= nodeheight * self.__heightfactor + conditionset_positions.append(nodeheight) + if j > 0: + new_y -= conditionset_positions[j-1] * self.__heightfactor + else: + new_y -= 0.8 * self.__heightfactor + position = '{},{}!'.format(10 * self.__widthfactor, new_y) label = 'no condition' if conditionset == '' else conditionset self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( '{}_{}'.format(state, conditionset), style="filled", fillcolor=color, shape="diamond", label=label, pos=position) #self._log_debug('Node {} {} drawn. Conditionlist {}', state, conditionset, conditionlist) - position = '{},{}!'.format(0.06 * self.__widthfactor, new_y) + position = '{},{}!'.format(3 * self.__widthfactor, new_y) xlabel = '1 tooltip' if condition_tooltip_count == 1\ else '{} tooltips'.format(condition_tooltip_count)\ if condition_tooltip_count > 1 else '' @@ -508,7 +515,7 @@ def drawgraph(self, filename): self.__graph.add_edge(parenthesis_edge) self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) - new_x = 0.35 * self.__widthfactor + new_x = 17.5 * self.__widthfactor if not actionlist_enter == '': position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ @@ -523,8 +530,9 @@ def drawgraph(self, filename): self._add_actioncondition(state, conditionset, 'actions_enter', new_y, cond1, cond2) if not actionlist_stay == '': - new_y -= 0.03 * self.__heightfactor if not actionlist_enter == '' else 0 + new_y -= 1.5 * self.__heightfactor if not actionlist_enter == '' else 0 position = '{},{}!'.format(new_x, new_y) + xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ else '{} tooltips'.format(action_tooltip_count_stay)\ if action_tooltip_count_stay > 1 else '' @@ -536,7 +544,7 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) self._add_actioncondition(state, conditionset, 'actions_stay', new_y, cond1, cond2) - position = '{},{}!'.format(0.35 * self.__widthfactor, new_y) + position = '{},{}!'.format(17.5 * self.__widthfactor, new_y) cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None @@ -570,8 +578,8 @@ def drawgraph(self, filename): previous_conditionset = self.__nodes['{}_{}'.format(state, conditionset)] if len(actions_leave) > 0: - new_y -= 0.06 * self.__heightfactor if j == 0 else 0.08 * self.__heightfactor - position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) + new_y -= 1 * self.__heightfactor if j == 0 else 2 * self.__heightfactor + position = '{},{}!'.format(10 * self.__widthfactor, new_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) except Exception as ex: @@ -603,8 +611,8 @@ def drawgraph(self, filename): previous_conditionset = self.__nodes['{}_leave'.format(state)] if len(actions_pass) > 0: - new_y -= 0.06 * self.__heightfactor if j == 0 else 0.08 * self.__heightfactor - position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) + new_y -= 1 * self.__heightfactor if j == 0 else 2 * self.__heightfactor + position = '{},{}!'.format(10 * self.__widthfactor, new_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) except Exception: diff --git a/stateengine/__init__.py b/stateengine/__init__.py index eba18c4da..357ba6762 100755 --- a/stateengine/__init__.py +++ b/stateengine/__init__.py @@ -231,11 +231,9 @@ def get_graph(self, abitem, graphtype='link', width=1, height=1): except Exception: formatted_date = "Unbekannt" return f'
{self.translate("Letzte Aktualisierung:")} {formatted_date}
\ - \ + \ ' else: return '' diff --git a/stateengine/webif/templates/visu.html b/stateengine/webif/templates/visu.html index aabb441f9..975e1d0f6 100755 --- a/stateengine/webif/templates/visu.html +++ b/stateengine/webif/templates/visu.html @@ -35,6 +35,11 @@ visibility: hidden; pointer-events: all; } + #visu_object { + display: block; + width: 100%; + height: auto; + } #visu_parent { width:99%; padding-bottom: 20px; @@ -119,6 +124,8 @@ const urlObj = new URL(window.location.href); const abitem = urlObj.searchParams.get('abitem'); + const svgObject = document.getElementById('visu_object'); + const svgHeight = svgObject.offsetHeight; // Height of the SVG object let panZoomState = { zoom: null, @@ -171,9 +178,6 @@ adjustObjectHeight(panZoomState.zoom, svgHeight, panZoomState.pan); }, 200); - const svgObject = document.getElementById('visu_object'); - const svgHeight = svgObject.offsetHeight; // Height of the SVG object - svgObject.style.width = `100%`; const zoomInButton = document.getElementById('zoomInButton'); const zoomOutButton = document.getElementById('zoomOutButton'); const resetButton = document.getElementById('resetButton'); From 2d786eb7014a269f8001d631e4c9ee47086eb516 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 1 Dec 2024 19:38:40 +0100 Subject: [PATCH 07/14] stateengine: adjust conditionlist content in visu to avoid overlap --- stateengine/StateEngineWebif.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index fc9830590..767de4f85 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -210,7 +210,7 @@ def _conditionlabel(self, state, conditionset): if condition not in conditions_done: current_clean = ", ".join(f"{k} = {v}" for k, v in current.items()) text = " Current {}".format(current_clean) if current is not None and len(current) > 0 else " Not evaluated." - conditionlist += ('', conditionlist)) * 0.26 + nodeheight = 1 + len(re.findall(r'', conditionlist)) * 0.23 nodeheight += len(re.findall(r'', conditionlist)) * 0.16 - nodeheight += len(re.findall(r'', conditionlist)) * 0.26 + nodeheight += len(re.findall(r'', conditionlist)) * 0.23 nodeheight /= 2 new_y -= nodeheight * self.__heightfactor - conditionset_positions.append(nodeheight) + conditionset_nodeheights.append(nodeheight) if j > 0: - new_y -= conditionset_positions[j-1] * self.__heightfactor + new_y -= conditionset_nodeheights[j-1] * self.__heightfactor else: new_y -= 0.8 * self.__heightfactor position = '{},{}!'.format(10 * self.__widthfactor, new_y) @@ -641,7 +643,7 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_pass'.format(state)], self.__nodes['{}_actions_pass'.format(state)], style='bold', taillabel=" True", tooltip='run pass actions')) - + above_nodeheights.append(conditionset_nodeheights[-1]) previous_state = state result = self.__graph.write_svg(filename, prog='neato') From 5d8eb4baa83576fa64455a57c7824bc004a37cfa Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 1 Dec 2024 21:38:53 +0100 Subject: [PATCH 10/14] stateengine: improve visu overlap for actions --- stateengine/StateEngineWebif.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index d84b37cea..b07bea918 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -418,6 +418,7 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes[state]) self.__graph.add_node(self.__nodes['{}_right'.format(state)]) conditionset_nodeheights = [] + actions_nodeheights = [] actionlist_enter = '' actionlist_stay = '' actionlist_leave = '' @@ -519,6 +520,9 @@ def drawgraph(self, filename): new_x = 17.5 * self.__widthfactor if not actionlist_enter == '': + nodeheight = 0.2 + len(re.findall(r'', actionlist_stay)) * 0.26 + nodeheight /= 2 + actions_nodeheights.append(nodeheight) position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ else '{} tooltips'.format(action_tooltip_count_enter)\ @@ -532,7 +536,13 @@ def drawgraph(self, filename): self._add_actioncondition(state, conditionset, 'actions_enter', new_y, cond1, cond2) if not actionlist_stay == '': - new_y -= 1.5 * self.__heightfactor if not actionlist_enter == '' else 0 + nodeheight = 0.2 + max(2, len(re.findall(r'', actionlist_stay))) * 0.26 + nodeheight /= 2 + new_y -= nodeheight * self.__heightfactor + actions_nodeheights.append(nodeheight) + new_y -= actions_nodeheights[j - 1] * self.__heightfactor + + #new_y -= 1.5 * self.__heightfactor if not actionlist_enter == '' else 0 position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ From b24c88127f7a829902be4ca4fe7ed79a979d92e1 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 10 Dec 2024 22:37:44 +0100 Subject: [PATCH 11/14] stateengine: logging update --- stateengine/StateEngineAction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 9afa80236..43b704888 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -432,7 +432,7 @@ def _check_condition(condition: str): try: _matching = cond.fullmatch(_updated_current_condition) if _matching: - self._log_debug("Given {} '{}' matches current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) + self._log_debug("Given {} '{}' matching current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) _condition_met.append(_updated_current_condition) _conditions_met_count += 1 _conditions_met_type.append(condition) From 6cc401933adb838602e73583008970db641b700b Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 10 Dec 2024 22:38:12 +0100 Subject: [PATCH 12/14] stateengine: add empty icon for visu to make all entries the same height (nicer visu) --- .../static/img/visualisations/sign_empty.png | Bin 0 -> 1773 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 stateengine/webif/static/img/visualisations/sign_empty.png diff --git a/stateengine/webif/static/img/visualisations/sign_empty.png b/stateengine/webif/static/img/visualisations/sign_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..b6354e6c88b8502f940e78157d217c917c5b8855 GIT binary patch literal 1773 zcmbVNPjB2r6gO0=peRximnxT9uBuWM+cUOzy&lz#(rijr!XYG!NN>!}jCV&_|H1ZV zcXL9BOTPg^oVmdl;J^uq55V`}z=aD6Z@g=#sS**f(yZ;7-~8TtzxU?NtAqWIH|}iS z(KKyiu-89?=Up}4xefpK|2n7ecsJjBS!&vQAE@z`_Wcj{H0}0M4v(wj=o7-S#G*VK z32Tw$fY!99TZ^2s6H)0SF_vj){Q1kThAw$%e9?(el<$a%+&e48(b@ixot?0N8(Yux zrwalEiKwW)NTz8?7NJq|5_nhBwxQRM>LfI_l|lVDI?#8rLg;~oO@@%8`++s`S=VDM z4nEQyPAs(<4RwWq%~1iyQdJwT97Nz z<#3q``%skGtYBjI6x@8!L{rK5Le5LO)ygHm#6n&JRVyttCp(3pmFU9=4M?qPqQFGG zp@WIrBd+@dIRqi)Ku)5J%XryBJ;=X_(GVko0)o9K2wy;G?fj_ORbE z{n+cd-A+sck2m}K8Jnr}tNp9Pz%vFM7gl4`?Zv*wO*--e)9-qL891)u#j)>T<_O_6 zY@Zeq&Lf?^&Z@lP!01xSF&%YH-*<%Ru};Sv@eXJ#FlQsb>o6P}x=J1a8gPlKiVqFE zo>XNpovFFcQwb6|hOW}LQsoHx0muT_dhS3j#Pql?A+oY2aT|2K^78VEY7MG%$(}5$ ztXQs3?veOv{Wg_)U9E(&l}{x-6(HeiQeKgQn+3btKc5H)v#B@S8A1hlF7bLzy{%wn z7FTmxi0v_G(=@1q=A7z|XF2+Z5oIz}C--sV9h=Z}ETA>ohW)?8xmiWM5zcjI;%4D= zEqsG;Y}F7~iL&3!VZHWR^MvT@Dd@=RwY?4CqrFc=3Q84lTfF-H?H}Q$*&Ot@hrRdN s-{c|TQ4^5S#Wv%_R~N2A8me%)P*$I-S7YW{L63t1&d@T)c^nh literal 0 HcmV?d00001 From 5464d19379e81fea127538afe0868eb36d99d029 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 10 Dec 2024 22:52:03 +0100 Subject: [PATCH 13/14] stateengine: highly improve visu overlapping and white space, some additional visu fixes --- stateengine/StateEngineWebif.py | 147 ++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 47 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index b07bea918..3376af5d4 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -66,7 +66,7 @@ def __init__(self, abitem): overlap='compress', compound='false', imagepath='{}'.format(self.__img_path)) self.__graph.set_node_defaults(color='lightgray', style='filled', shape='box', fontname='Helvetica', fontsize='10', pin='true') - self.__graph.set_edge_defaults(color='darkgray', style='filled', shape='box', + self.__graph.set_edge_defaults(color='darkgray', style='filled', shape='box', labeldistance=2.5, fontname='Helvetica', fontsize='10', len=0.1) self.__graph.set_graph_defaults(sep=+0.15) self.__nodes = {} @@ -100,7 +100,7 @@ def _actionlabel(self, state, label_type, conditionset, active): # label_type: on_enter, on_stay, on_leave, on_pass # active: if action is currently run - actionlabel = actionstart = '<
{}
{}
' + 'cellborder="0">' + '' '' '
{}:{}
{}:{}
' + conditionlist += '
' info_status = str(condition_dict.get('status') or 'n/a') info_item = str(condition_dict.get('item') or 'n/a') info_eval = str(condition_dict.get('eval') or 'n/a') @@ -333,7 +334,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c label = 'first enter' if action_type in ['actions_enter', 'actions_enter_or_stay'] else 'staying at state' - position = '{},{}!'.format(0.25 * self.__widthfactor, new_y) + position = '{},{}!'.format(12.6 * self.__widthfactor, new_y) color = color_enter if label == 'first enter' else color_stay self.__nodes['{}_{}_state_{}'.format(state, conditionset, action_type)] = \ pydotplus.Node('{}_{}_state_{}'.format(state, conditionset, action_type), style="filled", fillcolor=color, @@ -379,7 +380,7 @@ def drawgraph(self, filename): color = "olivedrab" if state == self.__active_state \ else "gray" if i > list_index else "indianred2" - new_y -= 0.03 * self.__heightfactor + new_y -= 1.5 * self.__heightfactor position = '{},{}!'.format(0, new_y) if not i == 0: condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ @@ -389,7 +390,7 @@ def drawgraph(self, filename): self.__nodes['{}_above'.format(state)] = pydotplus.Node('{}_above'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above'.format(state)]) - position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) + position = '{},{}!'.format(10 * self.__widthfactor, new_y) self.__nodes['{}_above_right'.format(state)] = pydotplus.Node('{}_above_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above_right'.format(state)]) @@ -400,7 +401,7 @@ def drawgraph(self, filename): style='bold', color='black', label="False ", dir="none")) self.__graph.add_edge(pydotplus.Edge(state, self.__nodes['{}_above'.format(state)], style='bold', color='black', label="", dir="back")) - new_y -= 0.03 * self.__heightfactor + new_y -= 1.5 * self.__heightfactor position = '{},{}!'.format(0, new_y) #self._log_debug('state: {} {}',state, position) @@ -409,7 +410,7 @@ def drawgraph(self, filename): label='<
' '
{}
{}
>'.format( state, self.__states[state]['name'])) - position = '{},{}!'.format(0.2 * self.__widthfactor, new_y) + position = '{},{}!'.format(10 * self.__widthfactor, new_y) self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes[state]) @@ -483,18 +484,24 @@ def drawgraph(self, filename): actionlist_pass, action_tooltip_pass, action_tooltip_count_pass = \ self._actionlabel(state, 'actions_pass', conditionset, active) - - conditionset_positions.append(new_y) conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) - nodeheight = len(re.findall(r"
' '' '' @@ -277,7 +277,7 @@ def _conditionlabel(self, state, conditionset): else: info = "n/a" - conditionlist += '{}'.format(info) + conditionlist += '{}'.format(info) comparison = ">=" if not min_none and compare == "min"\ else "<=" if not max_none and compare == "max"\ else "older" if not agemin_none and compare == "agemin"\ @@ -300,7 +300,7 @@ def _conditionlabel(self, state, conditionset): match_info = match.get('value') if compare in ["min", "max", "value"]\ else match.get('age') if compare in ["agemin", "agemax", "age"]\ else match.get(compare) - conditionlist += ''.format(match_info) - conditionlist += '
{}:{}
{}'.format(comparison) + conditionlist += '      {}   '.format(comparison) conditionlist += '"{}"'.format(info) if not item_none and not status_none \ and not eval_none and not status_eval_none else '' @@ -319,7 +319,7 @@ def _conditionlabel(self, state, conditionset): else '' if match_info and len(match_info) > 0 \ else '' conditionlist += '{}
>' + conditionlist += '
>' return conditionlist, condition_tooltip, tooltip_count def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, cond2): From 8be6edae6a5b39d1ee4b9a1fe2690061d3f11811 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 1 Dec 2024 20:51:29 +0100 Subject: [PATCH 08/14] stateengine: use itemsApi approach for diverse methods (return_item, find_items) --- stateengine/StateEngineAction.py | 5 +++-- stateengine/StateEngineFunctions.py | 7 ++++--- stateengine/StateEngineItem.py | 9 +++++---- stateengine/StateEngineTools.py | 2 +- stateengine/__init__.py | 6 ++++-- stateengine/webif/__init__.py | 3 --- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index 462c96acb..9afa80236 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -22,10 +22,10 @@ from . import StateEngineEval from . import StateEngineValue from . import StateEngineDefaults -from . import StateEngineCurrent import datetime from lib.shtime import Shtime import re +from lib.item import Items # Base class from which all action classes are derived @@ -67,6 +67,7 @@ def __cast_seconds(value): def __init__(self, abitem, name: str): super().__init__(abitem) self._se_plugin = abitem.se_plugin + self.itemsApi = Items.get_instance() self._parent = self._abitem.id self._caller = StateEngineDefaults.plugin_identification self.shtime = Shtime.get_instance() @@ -964,7 +965,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s self._log_info("{0}: Setting values by attribute '{1}'.{2}", actionname, self.__byattr, repeat_text) self.update_webif_actionstatus(state, self._name, 'True') source = self.set_source(current_condition, previous_condition, previousstate_condition, next_condition) - for item in self._sh.find_items(self.__byattr): + for item in self.itemsApi.find_items(self.__byattr): self._log_info("\t{0} = {1}", item.property.path, item.conf[self.__byattr]) item(item.conf[self.__byattr], caller=self._caller, source=source) self._abitem.last_run = {self._name: datetime.datetime.now()} diff --git a/stateengine/StateEngineFunctions.py b/stateengine/StateEngineFunctions.py index e714a73c3..9e632cbce 100755 --- a/stateengine/StateEngineFunctions.py +++ b/stateengine/StateEngineFunctions.py @@ -19,13 +19,13 @@ # You should have received a copy of the GNU General Public License # along with this plugin. If not, see . ######################################################################### -import logging import threading import re from . import StateEngineLogger from . import StateEngineTools from . import StateEngineDefaults from ast import literal_eval +from lib.item import Items class SeFunctions: @@ -44,6 +44,7 @@ def __init__(self, smarthome=None, logger=None): self.__locks = {} self.__global_struct = {} self.__ab_alive = False + self.itemsApi = Items.get_instance() def __repr__(self): return "SeFunctions" @@ -97,7 +98,7 @@ def check_include_exclude(entry_type): elog.decrease_indent() return None - item = self.__sh.items.return_item(item_id) + item = self.itemsApi.return_item(item_id) if item is None: self.logger.error("manual_item_update_eval: item {0} not found!".format(item_id)) @@ -111,7 +112,7 @@ def check_include_exclude(entry_type): if "se_manual_logitem" in item.conf: elog_item_id = item.conf["se_manual_logitem"] - elog_item = self.__sh.items.return_item(elog_item_id) + elog_item = self.itemsApi.return_item(elog_item_id) if elog_item is None: self.logger.error("manual_item_update_item: se_manual_logitem {0} not found!".format(elog_item_id)) elog = StateEngineLogger.SeLoggerDummy() diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index d5c255c4b..a9609b371 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -34,7 +34,7 @@ from lib.shtime import Shtime from lib.item.item import Item -from lib.item.items import Items +from lib.item import Items import copy import threading import queue @@ -201,6 +201,7 @@ def ab_alive(self, value): # se_plugin: smartplugin instance def __init__(self, smarthome, item, se_plugin): self.__item = item + self.itemsApi = Items.get_instance() self.__logger = SeLogger.create(self.__item) self.__logging_off = False self.update_lock = threading.Lock() @@ -2162,7 +2163,7 @@ def return_item(self, item_id): if isinstance(item_id, (StateEngineStruct.SeStruct, self.__itemClass)): return item_id, None if isinstance(item_id, StateEngineState.SeState): - return self.__sh.items.return_item(item_id.id), None + return self.itemsApi.return_item(item_id.id), None if item_id is None: _issue = "item_id is None" return None, [_issue] @@ -2203,7 +2204,7 @@ def return_item(self, item_id): self.__logger.warning(_issue) return None, [_issue] else: - item = self.__sh.items.return_item(item_id) + item = self.itemsApi.return_item(item_id) if item is None: _issue = "Item '{0}' not found.".format(item_id) self.__logger.warning(_issue) @@ -2226,7 +2227,7 @@ def return_item(self, item_id): rel_item_id = item_id[parent_level:] if rel_item_id != "": result += "." + rel_item_id - item = self.__sh.items.return_item(result) + item = self.itemsApi.return_item(result) if item is None: _issue = "Determined item '{0}' does not exist.".format(item_id) self.__logger.warning(_issue) diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index 760d4ca5f..290fdaef3 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -23,7 +23,7 @@ import datetime from ast import literal_eval import re -from lib.item.items import Items +from lib.item import Items # General class for everything that is below the SeItem Class diff --git a/stateengine/__init__.py b/stateengine/__init__.py index 357ba6762..60134a690 100755 --- a/stateengine/__init__.py +++ b/stateengine/__init__.py @@ -33,6 +33,7 @@ import os import copy from lib.model.smartplugin import * +from lib.item import Items from .webif import WebInterface from datetime import datetime @@ -55,6 +56,7 @@ def __init__(self, sh): super().__init__() StateEngineDefaults.logger = self.logger self._items = self.abitems = {} + self.itemsApi = Items.get_instance() self.mod_http = None self.__sh = sh self.alive = False @@ -130,9 +132,9 @@ def parse_item(self, item): # Initialization of plugin def run(self): # Initialize - StateEngineStructs.global_struct = copy.deepcopy(self.__sh.items.return_struct_definitions()) + StateEngineStructs.global_struct = copy.deepcopy(self.itemsApi.return_struct_definitions()) self.logger.info("Init StateEngine items") - for item in self.__sh.find_items("se_plugin"): + for item in self.itemsApi.find_items("se_plugin"): if item.conf["se_plugin"] == "active": try: abitem = StateEngineItem.SeItem(self.__sh, item, self) diff --git a/stateengine/webif/__init__.py b/stateengine/webif/__init__.py index 7ed239b98..ee758ad5c 100755 --- a/stateengine/webif/__init__.py +++ b/stateengine/webif/__init__.py @@ -26,7 +26,6 @@ ######################################################################### import json - from lib.model.smartplugin import SmartPluginWebIf @@ -63,8 +62,6 @@ def index(self, action=None, item_id=None, item_path=None, reload=None, abitem=N :return: contents of the template after beeing rendered """ - item = self.plugin.get_sh().items.return_item(item_path) - tmpl = self.tplenv.get_template('{}.html'.format(page)) pagelength = self.plugin.get_parameter_value('webif_pagelength') if action == "get_graph" and abitem is not None: From d2644a3e8bd8566dc434d18261cc35ed32557366 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 1 Dec 2024 21:07:24 +0100 Subject: [PATCH 09/14] stateengine: avoid overlaps in visu when only one conditionset given --- stateengine/StateEngineWebif.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index 767de4f85..d84b37cea 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -367,6 +367,7 @@ def drawgraph(self, filename): new_y = 2 previous_state = '' previous_conditionset = '' + above_nodeheights = [] for i, state in enumerate(self.__states): #self._log_debug('Adding state for webif {}', self.__states[state]) if isinstance(self.__states[state], (OrderedDict, dict)): @@ -379,10 +380,11 @@ def drawgraph(self, filename): list_index = 0 color = "olivedrab" if state == self.__active_state \ else "gray" if i > list_index else "indianred2" - - new_y -= 1.5 * self.__heightfactor - position = '{},{}!'.format(0, new_y) + if not i == 0: + new_y -= above_nodeheights[i-1] * self.__heightfactor + position = '{},{}!'.format(0, new_y) + condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ else 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ else list(self.__states[previous_state]['conditionsets'].keys())[-1] @@ -415,7 +417,7 @@ def drawgraph(self, filename): shape="square", width="0", label="") self.__graph.add_node(self.__nodes[state]) self.__graph.add_node(self.__nodes['{}_right'.format(state)]) - conditionset_positions = [] + conditionset_nodeheights = [] actionlist_enter = '' actionlist_stay = '' actionlist_leave = '' @@ -485,14 +487,14 @@ def drawgraph(self, filename): self._actionlabel(state, 'actions_pass', conditionset, active) conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) - nodeheight = 1 + len(re.findall(r'
' + actionlabel = actionstart = '<
' action_tooltip = '' types = [label_type] if label_type in ['actions_leave', 'actions_pass'] else ['actions_enter_or_stay', label_type] tooltip_count = 0 @@ -210,8 +210,8 @@ def _conditionlabel(self, state, conditionset): if condition not in conditions_done: current_clean = ", ".join(f"{k} = {v}" for k, v in current.items()) text = " Current {}".format(current_clean) if current is not None and len(current) > 0 else " Not evaluated." - conditionlist += ('', conditionlist)) * 0.23 - nodeheight += len(re.findall(r'', conditionlist)) * 0.16 - nodeheight += len(re.findall(r'', conditionlist)) * 0.23 - nodeheight /= 2 - new_y -= nodeheight * self.__heightfactor + titles = len(re.findall(r'', conditionlist)) + entries = len(re.findall(r'', conditionlist)) + nodeheight = 0.958 + 0.5 + 0.479 # main header, last line, spacing on top and bottom + nodeheight += titles * 1.292 # each title + nodeheight += entries * 1 #each entry + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) conditionset_nodeheights.append(nodeheight) + new_y -= nodeheight * self.__heightfactor if j > 0: - new_y -= conditionset_nodeheights[j-1] * self.__heightfactor + new_y -= max(float(actions_nodeheights.get(j-1, 0.0) + 0.2), conditionset_nodeheights[j-1] + 0.2) * self.__heightfactor else: - new_y -= 0.8 * self.__heightfactor + new_y -= 0.46 * self.__heightfactor # half ellipse on top position = '{},{}!'.format(10 * self.__widthfactor, new_y) label = 'no condition' if conditionset == '' else conditionset self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( @@ -514,16 +523,25 @@ def drawgraph(self, filename): shape="rect", label=conditionlist, pos=position, tooltip=condition_tooltip, xlabel=xlabel) self.__graph.add_node(self.__nodes['{}_{}_conditions'.format(state, conditionset)]) # Create a dotted line between conditionlist and conditionset name - parenthesis_edge = pydotplus.Edge(self.__nodes['{}_{}_conditions'.format(state, conditionset)], self.__nodes['{}_{}'.format(state, conditionset)], arrowhead="none", color="black", style="dotted", constraint="false") + parenthesis_edge = pydotplus.Edge(self.__nodes['{}_{}_conditions'.format(state, conditionset)], + self.__nodes['{}_{}'.format(state, conditionset)], + arrowhead="none", color="black", style="dotted", constraint="false") self.__graph.add_edge(parenthesis_edge) self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) - + action_y = new_y new_x = 17.5 * self.__widthfactor + nodeheights = 0 + nodeheight = 0 + if not actionlist_enter == '': - nodeheight = 0.2 + len(re.findall(r'', actionlist_stay)) * 0.26 - nodeheight /= 2 - actions_nodeheights.append(nodeheight) - position = '{},{}!'.format(new_x, new_y) + nodeheight = 0.644 + nodeheight += len(re.findall(r'', actionlist_enter)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + nodeheight = max(0.4, nodeheight) + nodeheights += nodeheight + last_action_nodeheight = nodeheight + position = '{},{}!'.format(new_x, action_y) xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ else '{} tooltips'.format(action_tooltip_count_enter)\ if action_tooltip_count_enter > 1 else '' @@ -533,17 +551,26 @@ def drawgraph(self, filename): shape="rectangle", label=actionlist_enter, pos=position, tooltip=action_tooltip_enter, xlabel=xlabel) self.__graph.add_node(self.__nodes['{}_{}_actions_enter'.format(state, conditionset)]) - self._add_actioncondition(state, conditionset, 'actions_enter', new_y, cond1, cond2) + self._add_actioncondition(state, conditionset, 'actions_enter', action_y, cond1, cond2) if not actionlist_stay == '': - nodeheight = 0.2 + max(2, len(re.findall(r'', actionlist_stay))) * 0.26 - nodeheight /= 2 - new_y -= nodeheight * self.__heightfactor - actions_nodeheights.append(nodeheight) - new_y -= actions_nodeheights[j - 1] * self.__heightfactor - - #new_y -= 1.5 * self.__heightfactor if not actionlist_enter == '' else 0 - position = '{},{}!'.format(new_x, new_y) + add_nodeheight = nodeheight + action_y -= nodeheight * self.__heightfactor + nodeheight = 0.644 + nodeheight += len(re.findall(r'', actionlist_stay)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + nodeheight = max(0.4, nodeheight) + add_nodeheight += nodeheight + if action_y != new_y: + nodeheight += 0.2 + action_y -= nodeheight * self.__heightfactor + elif j <= len(self.__states[state]['conditionsets']) - 1: + add_nodeheight = 0 + nodeheights += nodeheight * 2 - 0.2 + last_action_nodeheight = nodeheight + add_nodeheight + + position = '{},{}!'.format(new_x, action_y) xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ else '{} tooltips'.format(action_tooltip_count_stay)\ @@ -554,21 +581,25 @@ def drawgraph(self, filename): shape="rectangle", label=actionlist_stay, pos=position, tooltip=action_tooltip_stay, xlabel=xlabel) self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) - self._add_actioncondition(state, conditionset, 'actions_stay', new_y, cond1, cond2) + self._add_actioncondition(state, conditionset, 'actions_stay', action_y, cond1, cond2) - position = '{},{}!'.format(17.5 * self.__widthfactor, new_y) + actions_nodeheights[j] = nodeheights + position = '{},{}!'.format(17.5 * self.__widthfactor, action_y) cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None cond4 = self.__nodes.get('{}_{}_actions_pass'.format(state, conditionset)) is None if cond1 and cond2 and cond3 and cond4: + actions_nodeheights[j] = 0.5 + last_action_nodeheight = 0.5 + nodeheight= 0.5 self.__nodes['{}_{}_right'.format(state, conditionset)] = pydotplus.Node('{}_{}_right'.format( state, conditionset), shape="circle", width="0.7", pos=position, label="", fillcolor="black", style="filled", tooltip="No Action") self.__graph.add_node(self.__nodes['{}_{}_right'.format(state, conditionset)]) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], self.__nodes['{}_{}_right'.format(state, conditionset)], - style='bold', taillabel=" True", tooltip='action on enter')) + style='bold', taillabel="True", tooltip='action on enter')) if self.__states[state].get('is_copy_for'): xlabel = "can currently release {}\n\r".format(self.__states[state].get('is_copy_for')) elif self.__states[state].get('releasedby'): @@ -589,12 +620,21 @@ def drawgraph(self, filename): style='bold', color='black', tooltip='check next conditionset')) previous_conditionset = self.__nodes['{}_{}'.format(state, conditionset)] + end_node_heights = 0 if len(actions_leave) > 0: - new_y -= 1 * self.__heightfactor if j == 0 else 2 * self.__heightfactor - position = '{},{}!'.format(10 * self.__widthfactor, new_y) + action_y -= nodeheight * self.__heightfactor + nodeheight = 0.8 + nodeheight += len(re.findall(r'', actionlist_leave)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + end_node_heights += max(0.5, nodeheight) + last_action_nodeheight + last_action_nodeheight = max(1.0, nodeheight) + action_y -= max(0.5, nodeheight) * self.__heightfactor + new_y = action_y + position = '{},{}!'.format(10 * self.__widthfactor, action_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception as ex: + except Exception: cond2 = True cond3 = True if self.__states[state].get('leave') is True else False color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" @@ -605,7 +645,7 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_leave'.format(state)], style='bold', color='black', tooltip='check leave')) - position = '{},{}!'.format(new_x, new_y) + position = '{},{}!'.format(new_x, action_y) xlabel = '1 tooltip' if action_tooltip_count_leave == 1\ else '{} tooltips'.format(action_tooltip_count_leave)\ if action_tooltip_count_leave > 1 else '' @@ -619,12 +659,23 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes['{}_actions_leave'.format(state)]) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_leave'.format(state)], self.__nodes['{}_actions_leave'.format(state)], style='bold', - taillabel=" True", tooltip='run leave actions')) + taillabel="True", tooltip='run leave actions')) previous_conditionset = self.__nodes['{}_leave'.format(state)] + nodeheight += 0.2 if len(actions_pass) > 0: - new_y -= 1 * self.__heightfactor if j == 0 else 2 * self.__heightfactor - position = '{},{}!'.format(10 * self.__widthfactor, new_y) + action_y -= nodeheight * self.__heightfactor + nodeheight = 0.8 + nodeheight += len(re.findall(r'', actionlist_pass)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + if end_node_heights == 0: + end_node_heights += last_action_nodeheight + last_action_nodeheight = max(1.0, nodeheight) + end_node_heights += max(0.5, nodeheight) + action_y -= max(0.5, nodeheight) * self.__heightfactor + new_y = action_y + position = '{},{}!'.format(10 * self.__widthfactor, action_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) except Exception: @@ -638,7 +689,7 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_pass'.format(state)], style='bold', color='black', tooltip='check pass')) - position = '{},{}!'.format(new_x, new_y) + position = '{},{}!'.format(new_x, action_y) xlabel = '1 tooltip' if action_tooltip_count_pass == 1\ else '{} tooltips'.format(action_tooltip_count_pass)\ if action_tooltip_count_pass > 1 else '' @@ -653,7 +704,9 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_pass'.format(state)], self.__nodes['{}_actions_pass'.format(state)], style='bold', taillabel=" True", tooltip='run pass actions')) - above_nodeheights.append(conditionset_nodeheights[-1]) + last_height = conditionset_nodeheights[-1] - end_node_heights + 0.1 + conditionset_nodeheights.append(last_height) + above_nodeheights = conditionset_nodeheights previous_state = state result = self.__graph.write_svg(filename, prog='neato') From 1afe7a4cdb9761666373977aa6fca47967048815 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 10 Dec 2024 22:52:45 +0100 Subject: [PATCH 14/14] stateengine: minor code improvement --- stateengine/StateEngineWebif.py | 643 ++++++++++++++++---------------- 1 file changed, 321 insertions(+), 322 deletions(-) diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index 3376af5d4..e745d12e7 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -371,343 +371,342 @@ def drawgraph(self, filename): last_action_nodeheight = 0 for i, state in enumerate(self.__states): #self._log_debug('Adding state for webif {}', self.__states[state]) - #if not isinstance(self.__states[state], (OrderedDict, dict)): - # continue - if isinstance(self.__states[state], (OrderedDict, dict)): - self.__conditionset_count = len(self.__states[state].get('conditionsets')) - if self.__conditionset_count == 0: - self.__states[state]['conditionsets'][''] = '' + if not isinstance(self.__states[state], (OrderedDict, dict)): + continue + self.__conditionset_count = len(self.__states[state].get('conditionsets')) + if self.__conditionset_count == 0: + self.__states[state]['conditionsets'][''] = '' + try: + list_index = list(self.__states.keys()).index(self.__active_state) + except Exception: + list_index = 0 + color = "olivedrab" if state == self.__active_state \ + else "gray" if i > list_index else "indianred2" + + if not i == 0: + new_y -= max(float(above_nodeheights[-1] + 0.3), 0.46, float(last_action_nodeheight)) * self.__heightfactor # half elipse added at end + position = '{},{}!'.format(0, new_y) + + condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ + else 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ + else list(self.__states[previous_state]['conditionsets'].keys())[-1] + lastnode = self.__nodes['{}_{}'.format(previous_state, condition_node)] + self.__nodes['{}_above'.format(state)] = pydotplus.Node('{}_above'.format(state), pos=position, + shape="square", width="0", label="") + self.__graph.add_node(self.__nodes['{}_above'.format(state)]) + position = '{},{}!'.format(10 * self.__widthfactor, new_y) + self.__nodes['{}_above_right'.format(state)] = pydotplus.Node('{}_above_right'.format(state), + pos=position, shape="square", width="0", label="") + self.__graph.add_node(self.__nodes['{}_above_right'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_above'.format(state)], + self.__nodes['{}_above_right'.format(state)], style='bold', + color='black', label="", dir="none")) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_above_right'.format(state)], lastnode, + style='bold', color='black', label="False ", dir="none")) + self.__graph.add_edge(pydotplus.Edge(state, self.__nodes['{}_above'.format(state)], style='bold', + color='black', label="", dir="back")) + new_y -= 1.5 * self.__heightfactor + position = '{},{}!'.format(0, new_y) + #self._log_debug('state: {} {}',state, position) + + self.__nodes[state] = pydotplus.Node(state, pos=position, pin=True, notranslate=True, style="filled", + fillcolor=color, shape="ellipse", + label='<
' + conditionlist += ('').format(condition.upper(), text) @@ -317,7 +317,7 @@ def _conditionlabel(self, state, conditionset): match_info = '' if match_info == 'yes'\ else '' if match_info == 'no'\ else '' if match_info and len(match_info) > 0 \ - else '' + else '' conditionlist += ''.format(match_info) conditionlist += '
{}:{}
' '' '' '
{}:{}
{}
>' return conditionlist, condition_tooltip, tooltip_count @@ -332,7 +332,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c color_stay = "gray" if (cond1 and cond2 and cond5) or \ (cond_stay and cond4 and cond5) else "olivedrab" if cond4 else "indianred2" - label = 'first enter' if action_type in ['actions_enter', 'actions_enter_or_stay'] else 'staying at state' + label = 'first enter' if action_type in ['actions_enter', 'actions_enter_or_stay'] else 'staying' position = '{},{}!'.format(12.6 * self.__widthfactor, new_y) color = color_enter if label == 'first enter' else color_stay @@ -343,7 +343,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c if self.__nodes.get('{}_{}_state_actions_enter_edge'.format(state, conditionset)) is None: self.__nodes['{}_{}_state_{}_edge'.format(state, conditionset, action_type)] = \ pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], self.__nodes['{}_{}_state_{}'.format( - state, conditionset, action_type)], style='bold', taillabel=" True", tooltip='first enter') + state, conditionset, action_type)], style='bold', taillabel="True", tooltip='first enter') self.__graph.add_edge(self.__nodes['{}_{}_state_{}_edge'.format(state, conditionset, action_type)]) else: self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}_state_actions_enter'.format(state, conditionset)], @@ -351,7 +351,7 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c style='bold', label="False ")) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}_state_{}'.format(state, conditionset, action_type)], self.__nodes['{}_{}_{}'.format(state, conditionset, action_type)], - style='bold', taillabel=" True")) + style='bold', taillabel="True")) try: if action_type == 'actions_enter': self.__nodes['{}_{}_actions_enter'.format(state, conditionset)].obj_dict['attributes']['fillcolor'] = color @@ -364,12 +364,15 @@ def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, c pass def drawgraph(self, filename): - new_y = 2 + new_y = 0 previous_state = '' previous_conditionset = '' above_nodeheights = [] + last_action_nodeheight = 0 for i, state in enumerate(self.__states): #self._log_debug('Adding state for webif {}', self.__states[state]) + #if not isinstance(self.__states[state], (OrderedDict, dict)): + # continue if isinstance(self.__states[state], (OrderedDict, dict)): self.__conditionset_count = len(self.__states[state].get('conditionsets')) if self.__conditionset_count == 0: @@ -380,11 +383,11 @@ def drawgraph(self, filename): list_index = 0 color = "olivedrab" if state == self.__active_state \ else "gray" if i > list_index else "indianred2" - + if not i == 0: - new_y -= above_nodeheights[i-1] * self.__heightfactor + new_y -= max(float(above_nodeheights[-1] + 0.3), 0.46, float(last_action_nodeheight)) * self.__heightfactor # half elipse added at end position = '{},{}!'.format(0, new_y) - + condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ else 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ else list(self.__states[previous_state]['conditionsets'].keys())[-1] @@ -418,7 +421,6 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes[state]) self.__graph.add_node(self.__nodes['{}_right'.format(state)]) conditionset_nodeheights = [] - actions_nodeheights = [] actionlist_enter = '' actionlist_stay = '' actionlist_leave = '' @@ -440,6 +442,10 @@ def drawgraph(self, filename): action_tooltip_stay = "" action_tooltip_leave = "" action_tooltip_pass = "" + action_y = new_y + actions_nodeheights = {} + nodeheight = 0 + for j, conditionset in enumerate(self.__states[state]['conditionsets']): cond3 = conditionset == '' try: @@ -488,16 +494,19 @@ def drawgraph(self, filename): self._actionlabel(state, 'actions_pass', conditionset, active) conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) - nodeheight = 1 + len(re.findall(r'

' + '
{}
{}
>'.format( + state, self.__states[state]['name'])) + position = '{},{}!'.format(10 * self.__widthfactor, new_y) + self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, + shape="square", width="0", label="") + self.__graph.add_node(self.__nodes[state]) + self.__graph.add_node(self.__nodes['{}_right'.format(state)]) + conditionset_nodeheights = [] + actionlist_enter = '' + actionlist_stay = '' + actionlist_leave = '' + actionlist_pass = '' + condition_tooltip = '' + action_tooltip = '' + j = 0 + new_x = 0.55 + actions_enter = self.__states[state].get('actions_enter') or [] + actions_enter_or_stay = self.__states[state].get('actions_enter_or_stay') or [] + actions_stay = self.__states[state].get('actions_stay') or [] + actions_leave = self.__states[state].get('actions_leave') or [] + actions_pass = self.__states[state].get('actions_pass') or [] + action_tooltip_count_enter = 0 + action_tooltip_count_stay = 0 + action_tooltip_count_leave = 0 + action_tooltip_count_pass = 0 + action_tooltip_enter = "" + action_tooltip_stay = "" + action_tooltip_leave = "" + action_tooltip_pass = "" + action_y = new_y + actions_nodeheights = {} + nodeheight = 0 + + for j, conditionset in enumerate(self.__states[state]['conditionsets']): + cond3 = conditionset == '' try: - list_index = list(self.__states.keys()).index(self.__active_state) + cond1 = i >= list(self.__states.keys()).index(self.__active_state) except Exception: - list_index = 0 - color = "olivedrab" if state == self.__active_state \ - else "gray" if i > list_index else "indianred2" - - if not i == 0: - new_y -= max(float(above_nodeheights[-1] + 0.3), 0.46, float(last_action_nodeheight)) * self.__heightfactor # half elipse added at end - position = '{},{}!'.format(0, new_y) - - condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ - else 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ - else list(self.__states[previous_state]['conditionsets'].keys())[-1] - lastnode = self.__nodes['{}_{}'.format(previous_state, condition_node)] - self.__nodes['{}_above'.format(state)] = pydotplus.Node('{}_above'.format(state), pos=position, - shape="square", width="0", label="") - self.__graph.add_node(self.__nodes['{}_above'.format(state)]) - position = '{},{}!'.format(10 * self.__widthfactor, new_y) - self.__nodes['{}_above_right'.format(state)] = pydotplus.Node('{}_above_right'.format(state), - pos=position, shape="square", width="0", label="") - self.__graph.add_node(self.__nodes['{}_above_right'.format(state)]) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_above'.format(state)], - self.__nodes['{}_above_right'.format(state)], style='bold', - color='black', label="", dir="none")) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_above_right'.format(state)], lastnode, - style='bold', color='black', label="False ", dir="none")) - self.__graph.add_edge(pydotplus.Edge(state, self.__nodes['{}_above'.format(state)], style='bold', - color='black', label="", dir="back")) - new_y -= 1.5 * self.__heightfactor - position = '{},{}!'.format(0, new_y) - #self._log_debug('state: {} {}',state, position) + cond1 = True + try: + cond4 = i == list(self.__states.keys()).index(self.__active_state) + except Exception: + cond4 = True + #self._log_debug('i {}, index of active state {}', i, list(self.__states.keys()).index(self.__active_state)) + try: + cond2 = (j > list(self.__states[state]['conditionsets'].keys()).index(self.__active_conditionset) + or i > list(self.__states.keys()).index(self.__active_state)) + except Exception: + cond2 = False if cond3 and cond4 else True + color = "gray" if cond1 and cond2 else "olivedrab" \ + if (conditionset == self.__active_conditionset or cond3) and state == self.__active_state else "indianred2" + try: + cond5 = i >= list(self.__states.keys()).index(self.__active_state) + except Exception: + cond5 = True + + cond6 = conditionset in ['', self.__active_conditionset] and state == self.__active_state + cond_enter = True if self.__states[state].get('enter') is True else False + cond_stay = True if self.__states[state].get('stay') is True else False + active = True if cond_enter and cond6 else False + + if len(actions_enter) > 0 or len(actions_enter_or_stay) > 0: + actionlist_enter, action_tooltip_enter, action_tooltip_count_enter = \ + self._actionlabel(state, 'actions_enter', conditionset, active) + active = True if cond_stay and cond6 else False + if len(actions_stay) > 0 or len(actions_enter_or_stay) > 0: + actionlist_stay, action_tooltip_stay, action_tooltip_count_stay = \ + self._actionlabel(state, 'actions_stay', conditionset, active) + cond_leave = True if self.__states[state].get('leave') is True else False + active = True if cond_leave else False - self.__nodes[state] = pydotplus.Node(state, pos=position, pin=True, notranslate=True, style="filled", - fillcolor=color, shape="ellipse", - label='<
' - '
{}
{}
>'.format( - state, self.__states[state]['name'])) + if len(actions_leave) > 0: + actionlist_leave, action_tooltip_leave, action_tooltip_count_leave = \ + self._actionlabel(state, 'actions_leave', conditionset, active) + cond_pass = True if self.__states[state].get('pass') is True else False + active = False if (cond5 and not cond_pass) or cond_leave else True + if len(actions_pass) > 0: + actionlist_pass, action_tooltip_pass, action_tooltip_count_pass = \ + self._actionlabel(state, 'actions_pass', conditionset, active) + + conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) + titles = len(re.findall(r'', conditionlist)) + entries = len(re.findall(r'', conditionlist)) + nodeheight = 0.958 + 0.5 + 0.479 # main header, last line, spacing on top and bottom + nodeheight += titles * 1.292 # each title + nodeheight += entries * 1 #each entry + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + conditionset_nodeheights.append(nodeheight) + new_y -= nodeheight * self.__heightfactor + if j > 0: + new_y -= max(float(actions_nodeheights.get(j-1, 0.0) + 0.2), conditionset_nodeheights[j-1] + 0.2) * self.__heightfactor + else: + new_y -= 0.46 * self.__heightfactor # half ellipse on top position = '{},{}!'.format(10 * self.__widthfactor, new_y) - self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, - shape="square", width="0", label="") - self.__graph.add_node(self.__nodes[state]) - self.__graph.add_node(self.__nodes['{}_right'.format(state)]) - conditionset_nodeheights = [] - actionlist_enter = '' - actionlist_stay = '' - actionlist_leave = '' - actionlist_pass = '' - condition_tooltip = '' - action_tooltip = '' - j = 0 - new_x = 0.55 - actions_enter = self.__states[state].get('actions_enter') or [] - actions_enter_or_stay = self.__states[state].get('actions_enter_or_stay') or [] - actions_stay = self.__states[state].get('actions_stay') or [] - actions_leave = self.__states[state].get('actions_leave') or [] - actions_pass = self.__states[state].get('actions_pass') or [] - action_tooltip_count_enter = 0 - action_tooltip_count_stay = 0 - action_tooltip_count_leave = 0 - action_tooltip_count_pass = 0 - action_tooltip_enter = "" - action_tooltip_stay = "" - action_tooltip_leave = "" - action_tooltip_pass = "" + label = 'no condition' if conditionset == '' else conditionset + self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( + '{}_{}'.format(state, conditionset), style="filled", fillcolor=color, shape="diamond", + label=label, pos=position) + #self._log_debug('Node {} {} drawn. Conditionlist {}', state, conditionset, conditionlist) + position = '{},{}!'.format(3 * self.__widthfactor, new_y) + xlabel = '1 tooltip' if condition_tooltip_count == 1\ + else '{} tooltips'.format(condition_tooltip_count)\ + if condition_tooltip_count > 1 else '' + if not conditionlist == '': + self.__nodes['{}_{}_conditions'.format(state, conditionset)] = pydotplus.Node( + '{}_{}_conditions'.format(state, conditionset), style="filled", fillcolor=color, + shape="rect", label=conditionlist, pos=position, tooltip=condition_tooltip, xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_{}_conditions'.format(state, conditionset)]) + # Create a dotted line between conditionlist and conditionset name + parenthesis_edge = pydotplus.Edge(self.__nodes['{}_{}_conditions'.format(state, conditionset)], + self.__nodes['{}_{}'.format(state, conditionset)], + arrowhead="none", color="black", style="dotted", constraint="false") + self.__graph.add_edge(parenthesis_edge) + self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) action_y = new_y - actions_nodeheights = {} + new_x = 17.5 * self.__widthfactor + nodeheights = 0 nodeheight = 0 - for j, conditionset in enumerate(self.__states[state]['conditionsets']): - cond3 = conditionset == '' - try: - cond1 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception: - cond1 = True - try: - cond4 = i == list(self.__states.keys()).index(self.__active_state) - except Exception: - cond4 = True - #self._log_debug('i {}, index of active state {}', i, list(self.__states.keys()).index(self.__active_state)) - try: - cond2 = (j > list(self.__states[state]['conditionsets'].keys()).index(self.__active_conditionset) - or i > list(self.__states.keys()).index(self.__active_state)) - except Exception: - cond2 = False if cond3 and cond4 else True - color = "gray" if cond1 and cond2 else "olivedrab" \ - if (conditionset == self.__active_conditionset or cond3) and state == self.__active_state else "indianred2" - try: - cond5 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception: - cond5 = True - - cond6 = conditionset in ['', self.__active_conditionset] and state == self.__active_state - cond_enter = True if self.__states[state].get('enter') is True else False - cond_stay = True if self.__states[state].get('stay') is True else False - active = True if cond_enter and cond6 else False - - if len(actions_enter) > 0 or len(actions_enter_or_stay) > 0: - actionlist_enter, action_tooltip_enter, action_tooltip_count_enter = \ - self._actionlabel(state, 'actions_enter', conditionset, active) - active = True if cond_stay and cond6 else False - if len(actions_stay) > 0 or len(actions_enter_or_stay) > 0: - actionlist_stay, action_tooltip_stay, action_tooltip_count_stay = \ - self._actionlabel(state, 'actions_stay', conditionset, active) - cond_leave = True if self.__states[state].get('leave') is True else False - active = True if cond_leave else False - - if len(actions_leave) > 0: - actionlist_leave, action_tooltip_leave, action_tooltip_count_leave = \ - self._actionlabel(state, 'actions_leave', conditionset, active) - cond_pass = True if self.__states[state].get('pass') is True else False - active = False if (cond5 and not cond_pass) or cond_leave else True - if len(actions_pass) > 0: - actionlist_pass, action_tooltip_pass, action_tooltip_count_pass = \ - self._actionlabel(state, 'actions_pass', conditionset, active) - - conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) - titles = len(re.findall(r'', conditionlist)) - entries = len(re.findall(r'', conditionlist)) - nodeheight = 0.958 + 0.5 + 0.479 # main header, last line, spacing on top and bottom - nodeheight += titles * 1.292 # each title - nodeheight += entries * 1 #each entry + if not actionlist_enter == '': + nodeheight = 0.644 + nodeheight += len(re.findall(r'', actionlist_enter)) * 0.67 nodeheight /= 5.7 nodeheight = round(nodeheight, 4) - conditionset_nodeheights.append(nodeheight) - new_y -= nodeheight * self.__heightfactor - if j > 0: - new_y -= max(float(actions_nodeheights.get(j-1, 0.0) + 0.2), conditionset_nodeheights[j-1] + 0.2) * self.__heightfactor - else: - new_y -= 0.46 * self.__heightfactor # half ellipse on top - position = '{},{}!'.format(10 * self.__widthfactor, new_y) - label = 'no condition' if conditionset == '' else conditionset - self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( - '{}_{}'.format(state, conditionset), style="filled", fillcolor=color, shape="diamond", - label=label, pos=position) - #self._log_debug('Node {} {} drawn. Conditionlist {}', state, conditionset, conditionlist) - position = '{},{}!'.format(3 * self.__widthfactor, new_y) - xlabel = '1 tooltip' if condition_tooltip_count == 1\ - else '{} tooltips'.format(condition_tooltip_count)\ - if condition_tooltip_count > 1 else '' - if not conditionlist == '': - self.__nodes['{}_{}_conditions'.format(state, conditionset)] = pydotplus.Node( - '{}_{}_conditions'.format(state, conditionset), style="filled", fillcolor=color, - shape="rect", label=conditionlist, pos=position, tooltip=condition_tooltip, xlabel=xlabel) - self.__graph.add_node(self.__nodes['{}_{}_conditions'.format(state, conditionset)]) - # Create a dotted line between conditionlist and conditionset name - parenthesis_edge = pydotplus.Edge(self.__nodes['{}_{}_conditions'.format(state, conditionset)], - self.__nodes['{}_{}'.format(state, conditionset)], - arrowhead="none", color="black", style="dotted", constraint="false") - self.__graph.add_edge(parenthesis_edge) - self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) - action_y = new_y - new_x = 17.5 * self.__widthfactor - nodeheights = 0 - nodeheight = 0 - - if not actionlist_enter == '': - nodeheight = 0.644 - nodeheight += len(re.findall(r'', actionlist_enter)) * 0.67 - nodeheight /= 5.7 - nodeheight = round(nodeheight, 4) - nodeheight = max(0.4, nodeheight) - nodeheights += nodeheight - last_action_nodeheight = nodeheight - position = '{},{}!'.format(new_x, action_y) - xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ - else '{} tooltips'.format(action_tooltip_count_enter)\ - if action_tooltip_count_enter > 1 else '' - #self._log_debug('action enter: {}', position) - self.__nodes['{}_{}_actions_enter'.format(state, conditionset)] = pydotplus.Node( - '{}_{}_actions_enter'.format(state, conditionset), style="filled", fillcolor=color, - shape="rectangle", label=actionlist_enter, pos=position, tooltip=action_tooltip_enter, - xlabel=xlabel) - self.__graph.add_node(self.__nodes['{}_{}_actions_enter'.format(state, conditionset)]) - self._add_actioncondition(state, conditionset, 'actions_enter', action_y, cond1, cond2) - - if not actionlist_stay == '': - add_nodeheight = nodeheight - action_y -= nodeheight * self.__heightfactor - nodeheight = 0.644 - nodeheight += len(re.findall(r'', actionlist_stay)) * 0.67 - nodeheight /= 5.7 - nodeheight = round(nodeheight, 4) - nodeheight = max(0.4, nodeheight) - add_nodeheight += nodeheight - if action_y != new_y: - nodeheight += 0.2 - action_y -= nodeheight * self.__heightfactor - elif j <= len(self.__states[state]['conditionsets']) - 1: - add_nodeheight = 0 - nodeheights += nodeheight * 2 - 0.2 - last_action_nodeheight = nodeheight + add_nodeheight - - position = '{},{}!'.format(new_x, action_y) - - xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ - else '{} tooltips'.format(action_tooltip_count_stay)\ - if action_tooltip_count_stay > 1 else '' - #self._log_debug('action stay: {}', position) - self.__nodes['{}_{}_actions_stay'.format(state, conditionset)] = pydotplus.Node( - '{}_{}_actions_stay'.format(state, conditionset), style="filled", fillcolor=color, - shape="rectangle", label=actionlist_stay, pos=position, tooltip=action_tooltip_stay, - xlabel=xlabel) - self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) - self._add_actioncondition(state, conditionset, 'actions_stay', action_y, cond1, cond2) - - actions_nodeheights[j] = nodeheights - position = '{},{}!'.format(17.5 * self.__widthfactor, action_y) - cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None - cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None - cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None - cond4 = self.__nodes.get('{}_{}_actions_pass'.format(state, conditionset)) is None - if cond1 and cond2 and cond3 and cond4: - actions_nodeheights[j] = 0.5 - last_action_nodeheight = 0.5 - nodeheight= 0.5 - self.__nodes['{}_{}_right'.format(state, conditionset)] = pydotplus.Node('{}_{}_right'.format( - state, conditionset), shape="circle", width="0.7", pos=position, label="", fillcolor="black", - style="filled", tooltip="No Action") - self.__graph.add_node(self.__nodes['{}_{}_right'.format(state, conditionset)]) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], - self.__nodes['{}_{}_right'.format(state, conditionset)], - style='bold', taillabel="True", tooltip='action on enter')) - if self.__states[state].get('is_copy_for'): - xlabel = "can currently release {}\n\r".format(self.__states[state].get('is_copy_for')) - elif self.__states[state].get('releasedby'): - xlabel = "can currently get released by {}\n\r".format(self.__states[state].get('releasedby')) - else: - xlabel = "" - if j == 0: - self.__graph.add_edge(pydotplus.Edge(self.__nodes[state], self.__nodes['{}_right'.format(state)], - style='bold', color='black', dir='none', - xlabel=xlabel, edgetooltip='check first conditionset')) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_right'.format(state)], - self.__nodes['{}_{}'.format(state, conditionset)], - style='bold', color='black', tooltip='check first conditionset')) - #self._log_debug('Drew line from state') - else: - self.__graph.add_edge(pydotplus.Edge(previous_conditionset, - self.__nodes['{}_{}'.format(state, conditionset)], - style='bold', color='black', tooltip='check next conditionset')) - previous_conditionset = self.__nodes['{}_{}'.format(state, conditionset)] - - end_node_heights = 0 - if len(actions_leave) > 0: - action_y -= nodeheight * self.__heightfactor - nodeheight = 0.8 - nodeheight += len(re.findall(r'', actionlist_leave)) * 0.67 - nodeheight /= 5.7 - nodeheight = round(nodeheight, 4) - end_node_heights += max(0.5, nodeheight) + last_action_nodeheight - last_action_nodeheight = max(1.0, nodeheight) - action_y -= max(0.5, nodeheight) * self.__heightfactor - new_y = action_y - position = '{},{}!'.format(10 * self.__widthfactor, action_y) - try: - cond2 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception: - cond2 = True - cond3 = True if self.__states[state].get('leave') is True else False - color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" - self.__nodes['{}_leave'.format(state)] = pydotplus.Node('{}_leave'.format(state), - style="filled", fillcolor=color, shape="diamond", - label='leave', pos=position) - self.__graph.add_node(self.__nodes['{}_leave'.format(state)]) - self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_leave'.format(state)], - style='bold', color='black', tooltip='check leave')) - + nodeheight = max(0.4, nodeheight) + nodeheights += nodeheight + last_action_nodeheight = nodeheight position = '{},{}!'.format(new_x, action_y) - xlabel = '1 tooltip' if action_tooltip_count_leave == 1\ - else '{} tooltips'.format(action_tooltip_count_leave)\ - if action_tooltip_count_leave > 1 else '' - #self._log_debug('action leave: {}', position) - self.__nodes['{}_actions_leave'.format(state)] = pydotplus.Node('{}_actions_leave'.format(state), - style="filled", fillcolor=color, - shape="rectangle", label=actionlist_leave, - pos=position, align="center", - tooltip=action_tooltip_leave, - xlabel=xlabel) - self.__graph.add_node(self.__nodes['{}_actions_leave'.format(state)]) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_leave'.format(state)], - self.__nodes['{}_actions_leave'.format(state)], style='bold', - taillabel="True", tooltip='run leave actions')) - previous_conditionset = self.__nodes['{}_leave'.format(state)] - nodeheight += 0.2 - - if len(actions_pass) > 0: + xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ + else '{} tooltips'.format(action_tooltip_count_enter)\ + if action_tooltip_count_enter > 1 else '' + #self._log_debug('action enter: {}', position) + self.__nodes['{}_{}_actions_enter'.format(state, conditionset)] = pydotplus.Node( + '{}_{}_actions_enter'.format(state, conditionset), style="filled", fillcolor=color, + shape="rectangle", label=actionlist_enter, pos=position, tooltip=action_tooltip_enter, + xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_{}_actions_enter'.format(state, conditionset)]) + self._add_actioncondition(state, conditionset, 'actions_enter', action_y, cond1, cond2) + + if not actionlist_stay == '': + add_nodeheight = nodeheight action_y -= nodeheight * self.__heightfactor - nodeheight = 0.8 - nodeheight += len(re.findall(r'', actionlist_pass)) * 0.67 + nodeheight = 0.644 + nodeheight += len(re.findall(r'', actionlist_stay)) * 0.67 nodeheight /= 5.7 nodeheight = round(nodeheight, 4) - if end_node_heights == 0: - end_node_heights += last_action_nodeheight - last_action_nodeheight = max(1.0, nodeheight) - end_node_heights += max(0.5, nodeheight) - action_y -= max(0.5, nodeheight) * self.__heightfactor - new_y = action_y - position = '{},{}!'.format(10 * self.__widthfactor, action_y) - try: - cond2 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception: - cond2 = True - cond3 = True if self.__states[state].get('pass') is True else False - color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" - self.__nodes['{}_pass'.format(state)] = pydotplus.Node('{}_pass'.format(state), - style="filled", fillcolor=color, shape="diamond", - label='pass', pos=position) - self.__graph.add_node(self.__nodes['{}_pass'.format(state)]) - self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_pass'.format(state)], - style='bold', color='black', tooltip='check pass')) + nodeheight = max(0.4, nodeheight) + add_nodeheight += nodeheight + if action_y != new_y: + nodeheight += 0.2 + action_y -= nodeheight * self.__heightfactor + elif j <= len(self.__states[state]['conditionsets']) - 1: + add_nodeheight = 0 + nodeheights += nodeheight * 2 - 0.2 + last_action_nodeheight = nodeheight + add_nodeheight position = '{},{}!'.format(new_x, action_y) - xlabel = '1 tooltip' if action_tooltip_count_pass == 1\ - else '{} tooltips'.format(action_tooltip_count_pass)\ - if action_tooltip_count_pass > 1 else '' - #self._log_debug('action pass: {}', position) - self.__nodes['{}_actions_pass'.format(state)] = pydotplus.Node('{}_actions_pass'.format(state), - style="filled", fillcolor=color, - shape="rectangle", label=actionlist_pass, - pos=position, align="center", - tooltip=action_tooltip_pass, - xlabel=xlabel) - self.__graph.add_node(self.__nodes['{}_actions_pass'.format(state)]) - self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_pass'.format(state)], - self.__nodes['{}_actions_pass'.format(state)], style='bold', - taillabel=" True", tooltip='run pass actions')) - last_height = conditionset_nodeheights[-1] - end_node_heights + 0.1 - conditionset_nodeheights.append(last_height) - above_nodeheights = conditionset_nodeheights - previous_state = state + + xlabel = '1 tooltip' if action_tooltip_count_stay == 1\ + else '{} tooltips'.format(action_tooltip_count_stay)\ + if action_tooltip_count_stay > 1 else '' + #self._log_debug('action stay: {}', position) + self.__nodes['{}_{}_actions_stay'.format(state, conditionset)] = pydotplus.Node( + '{}_{}_actions_stay'.format(state, conditionset), style="filled", fillcolor=color, + shape="rectangle", label=actionlist_stay, pos=position, tooltip=action_tooltip_stay, + xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) + self._add_actioncondition(state, conditionset, 'actions_stay', action_y, cond1, cond2) + + actions_nodeheights[j] = nodeheights + position = '{},{}!'.format(17.5 * self.__widthfactor, action_y) + cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None + cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None + cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None + cond4 = self.__nodes.get('{}_{}_actions_pass'.format(state, conditionset)) is None + if cond1 and cond2 and cond3 and cond4: + actions_nodeheights[j] = 0.5 + last_action_nodeheight = 0.5 + nodeheight= 0.5 + self.__nodes['{}_{}_right'.format(state, conditionset)] = pydotplus.Node('{}_{}_right'.format( + state, conditionset), shape="circle", width="0.7", pos=position, label="", fillcolor="black", + style="filled", tooltip="No Action") + self.__graph.add_node(self.__nodes['{}_{}_right'.format(state, conditionset)]) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_{}'.format(state, conditionset)], + self.__nodes['{}_{}_right'.format(state, conditionset)], + style='bold', taillabel="True", tooltip='action on enter')) + if self.__states[state].get('is_copy_for'): + xlabel = "can currently release {}\n\r".format(self.__states[state].get('is_copy_for')) + elif self.__states[state].get('releasedby'): + xlabel = "can currently get released by {}\n\r".format(self.__states[state].get('releasedby')) + else: + xlabel = "" + if j == 0: + self.__graph.add_edge(pydotplus.Edge(self.__nodes[state], self.__nodes['{}_right'.format(state)], + style='bold', color='black', dir='none', + xlabel=xlabel, edgetooltip='check first conditionset')) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_right'.format(state)], + self.__nodes['{}_{}'.format(state, conditionset)], + style='bold', color='black', tooltip='check first conditionset')) + #self._log_debug('Drew line from state') + else: + self.__graph.add_edge(pydotplus.Edge(previous_conditionset, + self.__nodes['{}_{}'.format(state, conditionset)], + style='bold', color='black', tooltip='check next conditionset')) + previous_conditionset = self.__nodes['{}_{}'.format(state, conditionset)] + + end_node_heights = 0 + if len(actions_leave) > 0: + action_y -= nodeheight * self.__heightfactor + nodeheight = 0.8 + nodeheight += len(re.findall(r'', actionlist_leave)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + end_node_heights += max(0.5, nodeheight) + last_action_nodeheight + last_action_nodeheight = max(1.0, nodeheight) + action_y -= max(0.5, nodeheight) * self.__heightfactor + new_y = action_y + position = '{},{}!'.format(10 * self.__widthfactor, action_y) + try: + cond2 = i >= list(self.__states.keys()).index(self.__active_state) + except Exception: + cond2 = True + cond3 = True if self.__states[state].get('leave') is True else False + color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" + self.__nodes['{}_leave'.format(state)] = pydotplus.Node('{}_leave'.format(state), + style="filled", fillcolor=color, shape="diamond", + label='leave', pos=position) + self.__graph.add_node(self.__nodes['{}_leave'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_leave'.format(state)], + style='bold', color='black', tooltip='check leave')) + + position = '{},{}!'.format(new_x, action_y) + xlabel = '1 tooltip' if action_tooltip_count_leave == 1\ + else '{} tooltips'.format(action_tooltip_count_leave)\ + if action_tooltip_count_leave > 1 else '' + #self._log_debug('action leave: {}', position) + self.__nodes['{}_actions_leave'.format(state)] = pydotplus.Node('{}_actions_leave'.format(state), + style="filled", fillcolor=color, + shape="rectangle", label=actionlist_leave, + pos=position, align="center", + tooltip=action_tooltip_leave, + xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_actions_leave'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_leave'.format(state)], + self.__nodes['{}_actions_leave'.format(state)], style='bold', + taillabel="True", tooltip='run leave actions')) + previous_conditionset = self.__nodes['{}_leave'.format(state)] + nodeheight += 0.2 + + if len(actions_pass) > 0: + action_y -= nodeheight * self.__heightfactor + nodeheight = 0.8 + nodeheight += len(re.findall(r'', actionlist_pass)) * 0.67 + nodeheight /= 5.7 + nodeheight = round(nodeheight, 4) + if end_node_heights == 0: + end_node_heights += last_action_nodeheight + last_action_nodeheight = max(1.0, nodeheight) + end_node_heights += max(0.5, nodeheight) + action_y -= max(0.5, nodeheight) * self.__heightfactor + new_y = action_y + position = '{},{}!'.format(10 * self.__widthfactor, action_y) + try: + cond2 = i >= list(self.__states.keys()).index(self.__active_state) + except Exception: + cond2 = True + cond3 = True if self.__states[state].get('pass') is True else False + color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" + self.__nodes['{}_pass'.format(state)] = pydotplus.Node('{}_pass'.format(state), + style="filled", fillcolor=color, shape="diamond", + label='pass', pos=position) + self.__graph.add_node(self.__nodes['{}_pass'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_pass'.format(state)], + style='bold', color='black', tooltip='check pass')) + + position = '{},{}!'.format(new_x, action_y) + xlabel = '1 tooltip' if action_tooltip_count_pass == 1\ + else '{} tooltips'.format(action_tooltip_count_pass)\ + if action_tooltip_count_pass > 1 else '' + #self._log_debug('action pass: {}', position) + self.__nodes['{}_actions_pass'.format(state)] = pydotplus.Node('{}_actions_pass'.format(state), + style="filled", fillcolor=color, + shape="rectangle", label=actionlist_pass, + pos=position, align="center", + tooltip=action_tooltip_pass, + xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_actions_pass'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_pass'.format(state)], + self.__nodes['{}_actions_pass'.format(state)], style='bold', + taillabel=" True", tooltip='run pass actions')) + last_height = conditionset_nodeheights[-1] - end_node_heights + 0.1 + conditionset_nodeheights.append(last_height) + above_nodeheights = conditionset_nodeheights + previous_state = state result = self.__graph.write_svg(filename, prog='neato') return result