--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/FBD_Objects.py Sun Sep 09 23:05:01 2012 +0200
@@ -0,0 +1,1028 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from GraphicCommons import *
+from plcopen.structures import *
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Block
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a function block
+"""
+
+def TestConnectorName(name, block_type):
+ return name in ["OUT", "MN", "MX"] or name.startswith("IN") and (block_type, name) != ("EXPT", "IN2")
+
+class FBD_Block(Graphic_Element):
+
+ # Create a new block
+ def __init__(self, parent, type, name, id = None, extension = 0, inputs = None, connectors = {}, executionControl = False, executionOrder = 0):
+ Graphic_Element.__init__(self, parent)
+ self.Type = None
+ self.Description = None
+ self.Extension = None
+ self.ExecutionControl = False
+ self.Id = id
+ self.SetName(name)
+ self.SetExecutionOrder(executionOrder)
+ self.Inputs = []
+ self.Outputs = []
+ self.Colour = wx.BLACK
+ self.Pen = MiterPen(wx.BLACK)
+ self.SetType(type, extension, inputs, connectors, executionControl)
+ self.Highlights = {}
+
+ # Make a clone of this FBD_Block
+ def Clone(self, parent, id = None, name = "", pos = None):
+ if self.Name != "" and name == "":
+ name = self.Name
+ block = FBD_Block(parent, self.Type, name, id, self.Extension)
+ block.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ block.SetPosition(pos.x, pos.y)
+ else:
+ block.SetPosition(self.Pos.x, self.Pos.y)
+ block.Inputs = [input.Clone(block) for input in self.Inputs]
+ block.Outputs = [output.Clone(block) for output in self.Outputs]
+ return block
+
+ def GetConnectorTranslation(self, element):
+ return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
+
+ def Flush(self):
+ for input in self.Inputs:
+ input.Flush()
+ self.Inputs = []
+ for output in self.Outputs:
+ output.Flush()
+ self.Outputs = []
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ for input in self.Inputs:
+ if input.IsConnected():
+ rect = rect.Union(input.GetConnectedRedrawRect(movex, movey))
+ for output in self.Outputs:
+ if output.IsConnected():
+ rect = rect.Union(output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Delete this block by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteBlock(self)
+
+ # Unconnect all inputs and outputs
+ def Clean(self):
+ for input in self.Inputs:
+ input.UnConnect(delete = True)
+ for output in self.Outputs:
+ output.UnConnect(delete = True)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the size of text for execution order
+ def RefreshExecutionOrderSize(self):
+ self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt, connectors=True):
+ if self.Name != "":
+ test_text = self.GetTextBoundingBox().InsideXY(pt.x, pt.y)
+ else:
+ test_text = False
+ test_block = self.GetBlockBoundingBox(connectors).InsideXY(pt.x, pt.y)
+ return test_text or test_block
+
+ # Returns the bounding box of the name outside the block
+ def GetTextBoundingBox(self):
+ # Calculate the size of the name outside the block
+ text_width, text_height = self.NameSize
+ return wx.Rect(self.Pos.x + (self.Size[0] - text_width) / 2,
+ self.Pos.y - (text_height + 2),
+ text_width,
+ text_height)
+
+ # Returns the bounding box of function block without name outside
+ def GetBlockBoundingBox(self, connectors=True):
+ bbx_x, bbx_y = self.Pos.x, self.Pos.y
+ bbx_width, bbx_height = self.Size
+ if connectors:
+ bbx_x -= min(1, len(self.Inputs)) * CONNECTOR_SIZE
+ bbx_width += (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE
+ if self.ExecutionOrder != 0:
+ bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
+ bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
+ bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
+ return wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
+
+ # Refresh the block bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = self.GetBlockBoundingBox()
+ if self.Name != "":
+ self.BoundingBox.Union(self.GetTextBoundingBox())
+
+ # Refresh the positions of the block connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ # Calculate the size for the connector lines
+ lines = max(len(self.Inputs), len(self.Outputs))
+ if lines > 0:
+ linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE)
+ # Update inputs and outputs positions
+ position = BLOCK_LINE_SIZE + linesize / 2
+ for i in xrange(lines):
+ if scaling is not None:
+ ypos = round_scaling(self.Pos.y + position, scaling[1]) - self.Pos.y
+ else:
+ ypos = position
+ if i < len(self.Inputs):
+ self.Inputs[i].SetPosition(wx.Point(0, ypos))
+ if i < len(self.Outputs):
+ self.Outputs[i].SetPosition(wx.Point(self.Size[0], ypos))
+ position += linesize
+ self.RefreshConnected()
+
+ # Refresh the positions of wires connected to inputs and outputs
+ def RefreshConnected(self, exclude = []):
+ for input in self.Inputs:
+ input.MoveConnected(exclude)
+ for output in self.Outputs:
+ output.MoveConnected(exclude)
+
+ # Returns the block connector that starts with the point given if it exists
+ def GetConnector(self, position, name = None):
+ # if a name is given
+ if name is not None:
+ # Test each input and output connector
+ #for input in self.Inputs:
+ # if name == input.GetName():
+ # return input
+ for output in self.Outputs:
+ if name == output.GetName():
+ return output
+ return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+
+ def GetInputTypes(self):
+ return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
+
+ def SetOutputValues(self, values):
+ for output in self.Outputs:
+ output.SetValue(values.get(ouput.getName(), None))
+
+ def GetConnectionResultType(self, connector, connectortype):
+ if not TestConnectorName(connector.GetName(), self.Type):
+ return connectortype
+ resulttype = connectortype
+ for input in self.Inputs:
+ if input != connector and input.GetType(True) == "ANY" and TestConnectorName(input.GetName(), self.Type):
+ inputtype = input.GetConnectedType()
+ if resulttype is None or inputtype is not None and self.IsOfType(inputtype, resulttype):
+ resulttype = inputtype
+ for output in self.Outputs:
+ if output != connector and output.GetType(True) == "ANY" and TestConnectorName(output.GetName(), self.Type):
+ outputtype = output.GetConnectedType()
+ if resulttype is None or outputtype is not None and self.IsOfType(outputtype, resulttype):
+ resulttype = outputtype
+ return resulttype
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ return {"inputs" : self.Inputs, "outputs" : self.Outputs}
+
+ # Test if point given is on one of the block connectors
+ def TestConnector(self, pt, direction = None, exclude = True):
+ # Test each input connector
+ for input in self.Inputs:
+ if input.TestPoint(pt, direction, exclude):
+ return input
+ # Test each output connector
+ for output in self.Outputs:
+ if output.TestPoint(pt, direction, exclude):
+ return output
+ return None
+
+ # Changes the block type
+ def SetType(self, type, extension, inputs = None, connectors = {}, executionControl = False):
+ if type != self.Type or self.Extension != extension or executionControl != self.ExecutionControl:
+ if type != self.Type:
+ self.Type = type
+ self.TypeSize = self.Parent.GetTextExtent(self.Type)
+ self.Extension = extension
+ self.ExecutionControl = executionControl
+ # Find the block definition from type given and create the corresponding
+ # inputs and outputs
+ blocktype = self.Parent.GetBlockType(type, inputs)
+ if blocktype:
+ self.Colour = wx.BLACK
+ inputs = [input for input in blocktype["inputs"]]
+ outputs = [output for output in blocktype["outputs"]]
+ if blocktype["extensible"]:
+ start = int(inputs[-1][0].replace("IN", ""))
+ for i in xrange(self.Extension - len(blocktype["inputs"])):
+ start += 1
+ inputs.append(("IN%d"%start, inputs[-1][1], inputs[-1][2]))
+ comment = blocktype["comment"]
+ self.Description = _(comment) + blocktype.get("usage", "")
+ else:
+ self.Colour = wx.RED
+ inputs = connectors.get("inputs", [])
+ outputs = connectors.get("outputs", [])
+ self.Description = None
+ if self.ExecutionControl:
+ inputs.insert(0, ("EN","BOOL","none"))
+ outputs.insert(0, ("ENO","BOOL","none"))
+ self.Pen = MiterPen(self.Colour)
+
+ # Extract the inputs properties and create or modify the corresponding connector
+ idx = 0
+ for idx, (input_name, input_type, input_modifier) in enumerate(inputs):
+ if idx < len(self.Inputs):
+ connector = self.Inputs[idx]
+ connector.SetName(input_name)
+ connector.SetType(input_type)
+ else:
+ connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
+ self.Inputs.append(connector)
+ if input_modifier == "negated":
+ connector.SetNegated(True)
+ elif input_modifier != "none":
+ connector.SetEdge(input_modifier)
+ for i in xrange(idx + 1, len(self.Inputs)):
+ self.Inputs[i].UnConnect(delete = True)
+ self.Inputs = self.Inputs[:idx + 1]
+
+ # Extract the outputs properties and create or modify the corresponding connector
+ idx = 0
+ for idx, (output_name, output_type, output_modifier) in enumerate(outputs):
+ if idx < len(self.Outputs):
+ connector = self.Outputs[idx]
+ connector.SetName(output_name)
+ connector.SetType(output_type)
+ else:
+ connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
+ self.Outputs.append(connector)
+ if output_modifier == "negated":
+ connector.SetNegated(True)
+ elif output_modifier != "none":
+ connector.SetEdge(output_modifier)
+ for i in xrange(idx + 1, len(self.Outputs)):
+ self.Outputs[i].UnConnect(delete = True)
+ self.Outputs = self.Outputs[:idx + 1]
+
+ self.RefreshMinSize()
+ self.RefreshConnectors()
+ self.RefreshBoundingBox()
+
+ # Returns the block type
+ def GetType(self):
+ return self.Type
+
+ # Changes the block name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returs the block name
+ def GetName(self):
+ return self.Name
+
+ # Changes the extension name
+ def SetExtension(self, extension):
+ self.Extension = extension
+
+ # Returs the extension name
+ def GetExtension(self):
+ return self.Extension
+
+ # Changes the execution order
+ def SetExecutionOrder(self, executionOrder):
+ self.ExecutionOrder = executionOrder
+ self.RefreshExecutionOrderSize()
+
+ # Returs the execution order
+ def GetExecutionOrder(self):
+ return self.ExecutionOrder
+
+ # Returs the execution order
+ def GetExecutionControl(self):
+ return self.ExecutionControl
+
+ # Refresh the block minimum size
+ def RefreshMinSize(self):
+ # Calculate the inputs maximum width
+ max_input = 0
+ for input in self.Inputs:
+ w, h = input.GetNameSize()
+ max_input = max(max_input, w)
+ # Calculate the outputs maximum width
+ max_output = 0
+ for output in self.Outputs:
+ w, h = output.GetNameSize()
+ max_output = max(max_output, w)
+ width = max(self.TypeSize[0] + 10, max_input + max_output + 15)
+ height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE
+ self.MinSize = width, height
+
+ # Returns the block minimum size
+ def GetMinSize(self):
+ return self.MinSize
+
+ # Changes the negated property of the connector handled
+ def SetConnectorNegated(self, negated):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetNegated(negated)
+ self.RefreshModel(False)
+
+ # Changes the edge property of the connector handled
+ def SetConnectorEdge(self, edge):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetEdge(edge)
+ self.RefreshModel(False)
+
+## # Method called when a Motion event have been generated
+## def OnMotion(self, event, dc, scaling):
+## if not event.Dragging():
+## pos = event.GetLogicalPosition(dc)
+## for input in self.Inputs:
+## rect = input.GetRedrawRect()
+## if rect.InsideXY(pos.x, pos.y):
+## print "Find input"
+## tip = wx.TipWindow(self.Parent, "Test")
+## tip.SetBoundingRect(rect)
+## return Graphic_Element.OnMotion(self, event, dc, scaling)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the block properties
+ self.Parent.EditBlockContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Popup the menu with special items for a block and a connector if one is handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ self.Parent.PopupBlockMenu(connector)
+ else:
+ self.Parent.PopupBlockMenu()
+
+ # Refreshes the block model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshBlockModel(self)
+ # If block has moved, refresh the model of wires connected to outputs
+ if move:
+ for output in self.Outputs:
+ output.RefreshWires()
+
+ def GetToolTipValue(self):
+ return self.Description
+
+ # Adds an highlight to the block
+ def AddHighlight(self, infos, start, end ,highlight_type):
+ if infos[0] in ["type", "name"] and start[0] == 0 and end[0] == 0:
+ highlights = self.Highlights.setdefault(infos[0], [])
+ AddHighlight(highlights, (start, end, highlight_type))
+ elif infos[0] == "input" and infos[1] < len(self.Inputs):
+ self.Inputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
+ elif infos[0] == "output" and infos[1] < len(self.Outputs):
+ self.Outputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
+
+ # Removes an highlight from the block
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] in ["type", "name"]:
+ highlights = self.Highlights.get(infos[0], [])
+ if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
+ self.Highlights.pop(infos[0])
+ elif infos[0] == "input" and infos[1] < len(self.Inputs):
+ self.Inputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
+ elif infos[0] == "output" and infos[1] < len(self.Outputs):
+ self.Outputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
+
+ # Removes all the highlights of one particular type from the block
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for name, highlights in highlight_items:
+ highlights = ClearHighlights(highlights, highlight_type)
+ if len(highlights) == 0:
+ self.Highlights.pop(name)
+ for input in self.Inputs:
+ input.ClearHighlights(highlight_type)
+ for output in self.Outputs:
+ output.ClearHighlights(highlight_type)
+
+ # Draws block
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(self.Pen)
+ dc.SetBrush(wx.WHITE_BRUSH)
+ dc.SetTextForeground(self.Colour)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ type_size = dc.GetTextExtent(self.Type)
+ executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
+ else:
+ name_size = self.NameSize
+ type_size = self.TypeSize
+ executionorder_size = self.ExecutionOrderSize
+
+ # Draw a rectangle with the block size
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw block name and block type
+ name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y - (name_size[1] + 2))
+ type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2,
+ self.Pos.y + 5)
+ dc.DrawText(self.Name, name_pos[0], name_pos[1])
+ dc.DrawText(self.Type, type_pos[0], type_pos[1])
+ # Draw inputs and outputs connectors
+ for input in self.Inputs:
+ input.Draw(dc)
+ for output in self.Outputs:
+ output.Draw(dc)
+ if self.ExecutionOrder != 0:
+ # Draw block execution order
+ dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
+ self.Pos.y + self.Size[1] + 2)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights.get("name", []), name_pos[0], name_pos[1])
+ DrawHighlightedText(dc, self.Type, self.Highlights.get("type", []), type_pos[0], type_pos[1])
+
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Variable
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a variable
+"""
+
+class FBD_Variable(Graphic_Element):
+
+ # Create a new variable
+ def __init__(self, parent, type, name, value_type, id = None, executionOrder = 0):
+ Graphic_Element.__init__(self, parent)
+ self.Type = None
+ self.ValueType = None
+ self.Id = id
+ self.SetName(name)
+ self.SetExecutionOrder(executionOrder)
+ self.Input = None
+ self.Output = None
+ self.SetType(type, value_type)
+ self.Highlights = []
+
+ # Make a clone of this FBD_Variable
+ def Clone(self, parent, id = None, pos = None):
+ variable = FBD_Variable(parent, self.Type, self.Name, self.ValueType, id)
+ variable.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ variable.SetPosition(pos.x, pos.y)
+ else:
+ variable.SetPosition(self.Pos.x, self.Pos.y)
+ if self.Input:
+ variable.Input = self.Input.Clone(variable)
+ if self.Output:
+ variable.Output = self.Output.Clone(variable)
+ return variable
+
+ def GetConnectorTranslation(self, element):
+ connectors = {}
+ if self.Input is not None:
+ connectors[self.Input] = element.Input
+ if self.Output is not None:
+ connectors[self.Output] = element.Output
+ return connectors
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ if self.Input and self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output and self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Unconnect connector
+ def Clean(self):
+ if self.Input:
+ self.Input.UnConnect(delete = True)
+ if self.Output:
+ self.Output.UnConnect(delete = True)
+
+ # Delete this variable by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteVariable(self)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the size of text for execution order
+ def RefreshExecutionOrderSize(self):
+ self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
+
+ # Refresh the variable bounding box
+ def RefreshBoundingBox(self):
+ if self.Type in (OUTPUT, INOUT):
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ else:
+ bbx_x = self.Pos.x
+ if self.Type == INOUT:
+ bbx_width = self.Size[0] + 2 * CONNECTOR_SIZE
+ else:
+ bbx_width = self.Size[0] + CONNECTOR_SIZE
+ bbx_x = min(bbx_x, self.Pos.x + (self.Size[0] - self.NameSize[0]) / 2)
+ bbx_width = max(bbx_width, self.NameSize[0])
+ bbx_height = self.Size[1]
+ if self.ExecutionOrder != 0:
+ bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
+ bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
+ bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
+ self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width + 1, bbx_height + 1)
+
+ # Refresh the position of the variable connector
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ if scaling is not None:
+ position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
+ else:
+ position = self.Size[1] / 2
+ if self.Input:
+ self.Input.SetPosition(wx.Point(0, position))
+ if self.Output:
+ self.Output.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected to connector
+ def RefreshConnected(self, exclude = []):
+ if self.Input:
+ self.Input.MoveConnected(exclude)
+ if self.Output:
+ self.Output.MoveConnected(exclude)
+
+ # Test if point given is on the variable connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ if self.Input and self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ if self.Output and self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ return None
+
+ # Returns the block connector that starts with the point given if it exists
+ def GetConnector(self, position, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input and output connector if they exists
+ #if self.Input and name == self.Input.GetName():
+ # return self.Input
+ if self.Output and name == self.Output.GetName():
+ return self.Output
+ connectors = []
+ # Test input connector if it exists
+ if self.Input:
+ connectors.append(self.Input)
+ # Test output connector if it exists
+ if self.Output:
+ connectors.append(self.Output)
+ return self.FindNearestConnector(position, connectors)
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ connectors = {"inputs": [], "outputs": []}
+ if self.Input:
+ connectors["inputs"].append(self.Input)
+ if self.Output:
+ connectors["outputs"].append(self.Output)
+ return connectors
+
+ # Changes the negated property of the variable connector if handled
+ def SetConnectorNegated(self, negated):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetNegated(negated)
+ self.RefreshModel(False)
+
+ # Changes the variable type
+ def SetType(self, type, value_type):
+ if type != self.Type:
+ self.Type = type
+ self.Clean()
+ self.Input = None
+ self.Output = None
+ # Create an input or output connector according to variable type
+ if self.Type != INPUT:
+ self.Input = Connector(self, "", value_type, wx.Point(0, 0), WEST, onlyone = True)
+ if self.Type != OUTPUT:
+ self.Output = Connector(self, "", value_type, wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+ elif value_type != self.ValueType:
+ if self.Input:
+ self.Input.SetType(value_type)
+ if self.Output:
+ self.Output.SetType(value_type)
+ self.RefreshConnectors()
+
+ # Returns the variable type
+ def GetType(self):
+ return self.Type
+
+ # Changes the variable name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the variable name
+ def GetName(self):
+ return self.Name
+
+ # Changes the execution order
+ def SetExecutionOrder(self, executionOrder):
+ self.ExecutionOrder = executionOrder
+ self.RefreshExecutionOrderSize()
+
+ # Returs the execution order
+ def GetExecutionOrder(self):
+ return self.ExecutionOrder
+
+ # Changes the element size
+ def SetSize(self, width, height):
+ scaling = self.Parent.GetScaling()
+ min_width, min_height = self.GetMinSize()
+ if width < min_width:
+ if self.Type == INPUT:
+ posx = max(0, self.Pos.x + width - min_width)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ elif self.Type == OUTPUT:
+ posx = max(0, self.Pos.x + (width - min_width) / 2)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ width = min_width
+ if scaling is not None:
+ width = round_scaling(width, scaling[0], 1)
+ if height < min_height:
+ posy = max(0, self.Pos.y + (height - min_height) / 2)
+ if scaling is not None:
+ posy = round_scaling(posy, scaling[1])
+ self.Pos.y = posy
+ height = min_height
+ if scaling is not None:
+ height = round_scaling(height, scaling[1], 1)
+ Graphic_Element.SetSize(self, width, height)
+
+ # Returns the variable minimum size
+ def GetMinSize(self):
+ return self.NameSize[0] + 10, self.NameSize[1] + 10
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the variable properties
+ self.Parent.EditVariableContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the variable model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshVariableModel(self)
+ # If variable has moved and variable is not of type OUTPUT, refresh the model
+ # of wires connected to output connector
+ if move and self.Type != OUTPUT:
+ if self.Output:
+ self.Output.RefreshWires()
+
+ # Adds an highlight to the variable
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "expression" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the variable
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "expression":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the variable
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws variable
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
+ else:
+ name_size = self.NameSize
+ executionorder_size = self.ExecutionOrderSize
+
+ text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y + (self.Size[1] - name_size[1]) / 2)
+ # Draw a rectangle with the variable size
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw variable name
+ dc.DrawText(self.Name, text_pos[0], text_pos[1])
+ # Draw connectors
+ if self.Input:
+ self.Input.Draw(dc)
+ if self.Output:
+ self.Output.Draw(dc)
+ if self.ExecutionOrder != 0:
+ # Draw variable execution order
+ dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
+ self.Pos.y + self.Size[1] + 2)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Connector
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a connection
+"""
+
+class FBD_Connector(Graphic_Element):
+
+ # Create a new connection
+ def __init__(self, parent, type, name, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Id = id
+ self.SetName(name)
+ self.Pos = wx.Point(0, 0)
+ self.Size = wx.Size(0, 0)
+ self.Highlights = []
+ # Create an input or output connector according to connection type
+ if self.Type == CONNECTOR:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
+ else:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+ self.RefreshNameSize()
+
+ def Flush(self):
+ if self.Connector:
+ self.Connector.Flush()
+ self.Connector = None
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ if self.Connector and self.Connector.IsConnected():
+ rect = rect.Union(self.Connector.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Make a clone of this FBD_Connector
+ def Clone(self, parent, id = None, pos = None):
+ connection = FBD_Connector(parent, self.Type, self.Name, id)
+ connection.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ connection.SetPosition(pos.x, pos.y)
+ else:
+ connection.SetPosition(self.Pos.x, self.Pos.y)
+ connection.Connector = self.Connector.Clone(connection)
+ return connection
+
+ def GetConnectorTranslation(self, element):
+ return {self.Connector : element.Connector}
+
+ # Unconnect connector
+ def Clean(self):
+ if self.Connector:
+ self.Connector.UnConnect(delete = True)
+
+ # Delete this connection by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteConnection(self)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the connection bounding box
+ def RefreshBoundingBox(self):
+ if self.Type == CONNECTOR:
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ else:
+ bbx_x = self.Pos.x
+ bbx_width = self.Size[0] + CONNECTOR_SIZE
+ self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width, self.Size[1])
+
+ # Refresh the position of the connection connector
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ if scaling is not None:
+ position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
+ else:
+ position = self.Size[1] / 2
+ if self.Type == CONNECTOR:
+ self.Connector.SetPosition(wx.Point(0, position))
+ else:
+ self.Connector.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected to connector
+ def RefreshConnected(self, exclude = []):
+ if self.Connector:
+ self.Connector.MoveConnected(exclude)
+
+ # Test if point given is on the connection connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ if self.Connector and self.Connector.TestPoint(pt, direction, exclude):
+ return self.Connector
+ return None
+
+ # Returns the connection connector
+ def GetConnector(self, position = None, name = None):
+ return self.Connector
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ connectors = {"inputs": [], "outputs": []}
+ if self.Type == CONNECTOR:
+ connectors["inputs"].append(self.Connector)
+ else:
+ connectors["outputs"].append(self.Connector)
+ return connectors
+
+ # Changes the variable type
+ def SetType(self, type):
+ if type != self.Type:
+ self.Type = type
+ self.Clean()
+ # Create an input or output connector according to connection type
+ if self.Type == CONNECTOR:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
+ else:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+
+ # Returns the connection type
+ def GetType(self):
+ return self.Type
+
+ def GetConnectionResultType(self, connector, connectortype):
+ if self.Type == CONTINUATION:
+ connector = self.Parent.GetConnectorByName(self.Name)
+ if connector is not None:
+ return connector.Connector.GetConnectedType()
+ return connectortype
+
+ # Changes the connection name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the connection name
+ def GetName(self):
+ return self.Name
+
+ # Changes the element size
+ def SetSize(self, width, height):
+ scaling = self.Parent.GetScaling()
+ min_width, min_height = self.GetMinSize()
+ if width < min_width:
+ if self.Type == CONTINUATION:
+ posx = max(0, self.Pos.x + width - min_width)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ width = min_width
+ if scaling is not None:
+ width = round_scaling(width, scaling[0], 1)
+ if height < min_height:
+ posy = max(0, self.Pos.y + (height - min_height) / 2)
+ if scaling is not None:
+ posy = round_scaling(posy, scaling[1])
+ self.Pos.y = posy
+ height = min_height
+ if scaling is not None:
+ height = round_scaling(height, scaling[1], 1)
+ Graphic_Element.SetSize(self, width, height)
+
+ # Returns the connection minimum size
+ def GetMinSize(self):
+ text_width, text_height = self.NameSize
+ if text_height % 2 == 1:
+ text_height += 1
+ return text_width + text_height + 20, text_height + 10
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the connection properties
+ self.Parent.EditConnectionContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the connection model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshConnectionModel(self)
+ # If connection has moved and connection is of type CONTINUATION, refresh
+ # the model of wires connected to connector
+ if move and self.Type == CONTINUATION:
+ if self.Connector:
+ self.Connector.RefreshWires()
+
+ # Adds an highlight to the connection
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the connection
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the connection
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws connection
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ else:
+ name_size = self.NameSize
+
+ # Draw a rectangle with the connection size with arrows inside
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ arrowsize = min(self.Size[1] / 2, (self.Size[0] - name_size[0] - 10) / 2)
+ dc.DrawLine(self.Pos.x, self.Pos.y, self.Pos.x + arrowsize,
+ self.Pos.y + self.Size[1] / 2)
+ dc.DrawLine(self.Pos.x + arrowsize, self.Pos.y + self.Size[1] / 2,
+ self.Pos.x, self.Pos.y + self.Size[1])
+ dc.DrawLine(self.Pos.x + self.Size[0] - arrowsize, self.Pos.y,
+ self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2)
+ dc.DrawLine(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2,
+ self.Pos.x + self.Size[0] - arrowsize, self.Pos.y + self.Size[1])
+ # Draw connection name
+ text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y + (self.Size[1] - name_size[1]) / 2)
+ dc.DrawText(self.Name, text_pos[0], text_pos[1])
+ # Draw connector
+ if self.Connector:
+ self.Connector.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
+