diff -r 000000000000 -r b622defdfd98 graphics/FBD_Objects.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graphics/FBD_Objects.py Wed Jan 31 16:31:39 2007 +0100 @@ -0,0 +1,538 @@ +#!/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): 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 Lesser 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 +#Lesser General Public License for more details. +# +#You should have received a copy of the GNU Lesser 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 + +from wxPython.wx import * +import wx + +from GraphicCommons import * +from plcopen.structures import * + + +#------------------------------------------------------------------------------- +# Function Block Diagram Block +#------------------------------------------------------------------------------- + +""" +Class that implements the graphic representation of a function block +""" + +class FBD_Block(Graphic_Element): + + # Create a new block + def __init__(self, parent, type, name, id = None, extension = 0, inputs = [], outputs = []): + Graphic_Element.__init__(self, parent) + self.Type = type + self.Name = name + self.Id = id + # Find the block definition from type given and create the corresponding + # inputs and outputs + blocktype = GetBlockType(type) + if blocktype: + 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(extension - len(blocktype["inputs"])): + start += 1 + inputs.append(("IN%d"%start, inputs[-1][1], input[-1][2])) + self.SetConnectors(inputs, outputs) + + # Destructor + def __del__(self): + self.Inputs = [] + self.Outputs = [] + + # 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() + for output in self.Outputs: + output.UnConnect() + + # Refresh the block bounding box + def RefreshBoundingBox(self): + dc = wxClientDC(self.Parent) + # Calculate the size of the name outside the block + text_width, text_height = dc.GetTextExtent(self.Name) + # Calculate the bounding box size + bbx_x = self.Pos.x - max(min(1, len(self.Inputs)) * CONNECTOR_SIZE, (text_width - self.Size[0]) / 2) + bbx_width = self.Size[0] + 1 + (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE + if self.Name != "": + bbx_y = self.Pos.y - (text_height + 2) + bbx_height = self.Size[1] + (text_height + 2) + else: + bbx_y = self.Pos.y + bbx_height = self.Size[1] + self.BoundingBox = wxRect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1) + + # Refresh the positions of the block connectors + def RefreshConnectors(self): + # Calculate the size for the connector lines + lines = max(len(self.Inputs), len(self.Outputs)) + linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE) + # Update inputs positions + position = BLOCK_LINE_SIZE + linesize / 2 + for input in self.Inputs: + input.SetPosition(wxPoint(0, position)) + position += linesize + # Update outputs positions + position = BLOCK_LINE_SIZE + linesize / 2 + for output in self.Outputs: + output.SetPosition(wxPoint(self.Size[0], position)) + 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): + # Test each input connector + for input in self.Inputs: + input_pos = input.GetRelPosition() + if position.x == self.Pos.x + input_pos.x and position.y == self.Pos.y + input_pos.y: + return input + # Test each output connector + for output in self.Outputs: + output_pos = output.GetRelPosition() + if position.x == self.Pos.x + output_pos.x and position.y == self.Pos.y + output_pos.y: + return output + return None + + # 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, exclude=True): + # Test each input connector + for input in self.Inputs: + if input.TestPoint(pt, exclude): + return input + # Test each output connector + for output in self.Outputs: + if output.TestPoint(pt, exclude): + return output + return None + + # Returns the block type + def GetType(self): + return self.Type + + # Changes the block name + def SetName(self, name): + self.Name = name + + # Returs the block name + def GetName(self): + return self.Name + + # Returns the block minimum size + def GetMinSize(self): + dc = wxClientDC(self.Parent) + text_width, text_height = dc.GetTextExtent(self.Type) + # Calculate the inputs maximum width + max_input = 0 + for input in self.Inputs: + w, h = dc.GetTextExtent(input.GetName()) + max_input = max(max_input, w) + # Calculate the outputs maximum width + max_output = 0 + for output in self.Outputs: + w, h = dc.GetTextExtent(output.GetName()) + max_output = max(max_output, w) + width = max(text_width + 10, max_input + max_output + 15) + height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE + return width, height + + # Changes the block connectors + def SetConnectors(self, inputs, outputs): + # Extract the inputs properties and create the corresponding connector + self.Inputs = [] + for input_name, input_type, input_modifier in inputs: + connector = Connector(self, input_name, input_type, wxPoint(0, 0), WEST) + if input_modifier == "negated": + connector.SetNegated(True) + elif input_modifier != "none": + connector.SetEdge(input_modifier) + self.Inputs.append(connector) + # Extract the outputs properties and create the corresponding connector + self.Outputs = [] + for output_name, output_type, output_modifier in outputs: + connector = Connector(self, output_name, output_type, wxPoint(0, 0), EAST) + if output_modifier == "negated": + connector.SetNegated(True) + elif output_modifier != "none": + connector.SetEdge(output_modifier) + self.Outputs.append(connector) + self.RefreshBoundingBox() + + # 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 RightUp event have been generated + def OnRightUp(self, event, scaling): + pos = GetScaledEventPosition(event, scaling) + # Popup the menu with special items for a block and a connector if one is handled + connector = self.TestConnector(pos, 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() + + # Draws block + def Draw(self, dc): + dc.SetPen(wxBLACK_PEN) + dc.SetBrush(wxWHITE_BRUSH) + # 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 + namewidth, nameheight = dc.GetTextExtent(self.Name) + dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2, + self.Pos.y - (nameheight + 2)) + typewidth, typeheight = dc.GetTextExtent(self.Type) + dc.DrawText(self.Type, self.Pos.x + (self.Size[0] - typewidth) / 2, + self.Pos.y + 5) + # Draw inputs and outputs connectors + for input in self.Inputs: + input.Draw(dc) + for output in self.Outputs: + output.Draw(dc) + Graphic_Element.Draw(self, dc) + + +#------------------------------------------------------------------------------- +# 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): + Graphic_Element.__init__(self, parent) + self.Type = type + self.Name = name + self.Id = id + 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, wxPoint(0, 0), WEST) + if self.Type != OUTPUT: + self.Output = Connector(self, "", value_type, wxPoint(0, 0), EAST) + self.RefreshConnectors() + + # Destructor + def __del__(self): + self.Input = None + self.Output = None + + # Unconnect connector + def Clean(self): + if self.Input: + self.Input.UnConnect() + if self.Output: + self.Output.UnConnect() + + # Delete this variable by calling the appropriate method + def Delete(self): + self.Parent.DeleteVariable(self) + + # Refresh the variable bounding box + def RefreshBoundingBox(self): + dc = wxClientDC(self.Parent) + 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 + self.BoundingBox = wxRect(bbx_x, self.Pos.y, bbx_width + 1, self.Size[1] + 1) + + # Refresh the position of the variable connector + def RefreshConnectors(self): + if self.Input: + self.Input.SetPosition(wxPoint(0, self.Size[1] / 2)) + if self.Output: + self.Output.SetPosition(wxPoint(self.Size[0], self.Size[1] / 2)) + self.RefreshConnected(self) + + # 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, exclude=True): + if self.Input and self.Input.TestPoint(pt, exclude): + return self.Input + if self.Output and self.Output.TestPoint(pt, exclude): + return self.Output + return None + + # Returns the block connector that starts with the point given if it exists + def GetConnector(self, position): + # Test input connector if it exists + if self.Input: + input_pos = self.Input.GetRelPosition() + if position.x == self.Pos.x + input_pos.x and position.y == self.Pos.y + input_pos.y: + return self.Input + # Test output connector if it exists + if self.Output: + output_pos = self.Output.GetRelPosition() + if position.x == self.Pos.x + output_pos.x and position.y == self.Pos.y + output_pos.y: + return self.Output + return None + + # Returns all the block connectors + def GetConnectors(self): + return {"input" : self.Input, "output" : self.Output} + + # 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) + + # Returns the variable type + def GetType(self): + return self.Type + + # Changes the variable name + def SetName(self, name): + self.Name = name + + # Returns the variable name + def GetName(self): + return self.Name + + # Returns the variable minimum size + def GetMinSize(self): + dc = wxClientDC(self.Parent) + text_width, text_height = dc.GetTextExtent(self.Name) + return text_width + 10, text_height + 10 + + # Method called when a RightUp event have been generated + def OnRightUp(self, event, scaling): + pos = GetScaledEventPosition(event, scaling) + # Popup the menu with special items for a variable and a connector if it's handled + connector = self.TestConnectors(pos, False) + if connector: + self.Handle = (HANDLE_CONNECTOR, connector) + self.Parent.PopupVariableMenu(connector) + else: + self.Parent.PopupVariableMenu() + + # 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() + + # Draws variable + def Draw(self, dc): + dc.SetPen(wxBLACK_PEN) + dc.SetBrush(wxWHITE_BRUSH) + # 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 + namewidth, nameheight = dc.GetTextExtent(self.Name) + dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2, + self.Pos.y + (self.Size[1] - nameheight) / 2) + # Draw connectors + if self.Input: + self.Input.Draw(dc) + if self.Output: + self.Output.Draw(dc) + Graphic_Element.Draw(self, dc) + + +#------------------------------------------------------------------------------- +# 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.Name = name + self.Id = id + self.Pos = wxPoint(0, 0) + self.Size = wxSize(0, 0) + # Create an input or output connector according to connection type + if self.Type == CONNECTOR: + self.Connector = Connector(self, "", "ANY", wxPoint(0, 0), WEST) + else: + self.Connector = Connector(self, "", "ANY", wxPoint(0, 0), EAST) + self.RefreshConnectors() + + # Destructor + def __del__(self): + self.Connector = None + + # Unconnect connector + def Clean(self): + if self.Connector: + self.Connector.UnConnect() + + # Delete this connection by calling the appropriate method + def Delete(self): + self.Parent.DeleteConnection(self) + + # Refresh the connection bounding box + def RefreshBoundingBox(self): + dc = wxClientDC(self.Parent) + 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 = wxRect(bbx_x, self.Pos.y, bbx_width, self.Size[1]) + + # Refresh the position of the connection connector + def RefreshConnectors(self): + if self.Type == CONNECTOR: + self.Connector.SetPosition(wxPoint(0, self.Size[1] / 2)) + else: + self.Connector.SetPosition(wxPoint(self.Size[0], self.Size[1] / 2)) + self.RefreshConnected(self) + + # 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, exclude=True): + if self.Connector and self.Connector.TestPoint(pt, exclude): + return self.Connector + return None + + # Returns the connection connector + def GetConnector(self, position = None): + return self.Connector + + # Returns the connection type + def GetType(self): + return self.Type + + # Changes the connection name + def SetName(self, name): + self.Name = name + + # Returns the connection name + def GetName(self): + return self.Name + + # Returns the connection minimum size + def GetMinSize(self): + dc = wxClientDC(self.Parent) + text_width, text_height = dc.GetTextExtent(self.Name) + if text_height % 2 == 1: + text_height += 1 + return text_width + text_height + 20, text_height + 10 + + # Method called when a RightUp event have been generated + def OnRightUp(self, event, 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() + + # Draws connection + def Draw(self, dc): + dc.SetPen(wxBLACK_PEN) + dc.SetBrush(wxWHITE_BRUSH) + # Draw a rectangle with the connection size with arrows in + dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1) + namewidth, nameheight = dc.GetTextExtent(self.Name) + arrowsize = min(self.Size[1] / 2, (self.Size[0] - namewidth - 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 variable name + dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2, + self.Pos.y + (self.Size[1] - nameheight) / 2) + # Draw connector + if self.Connector: + self.Connector.Draw(dc) + Graphic_Element.Draw(self, dc)