graphics/FBD_Objects.py
changeset 0 b622defdfd98
child 2 93bc4c2cf376
equal deleted inserted replaced
-1:000000000000 0:b622defdfd98
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C): Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU Lesser General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #Lesser General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU Lesser General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from wxPython.wx import *
       
    26 import wx
       
    27 
       
    28 from GraphicCommons import *
       
    29 from plcopen.structures import *
       
    30 
       
    31 
       
    32 #-------------------------------------------------------------------------------
       
    33 #                         Function Block Diagram Block
       
    34 #-------------------------------------------------------------------------------
       
    35 
       
    36 """
       
    37 Class that implements the graphic representation of a function block
       
    38 """
       
    39 
       
    40 class FBD_Block(Graphic_Element):
       
    41     
       
    42     # Create a new block
       
    43     def __init__(self, parent, type, name, id = None, extension = 0, inputs = [], outputs = []):
       
    44         Graphic_Element.__init__(self, parent)
       
    45         self.Type = type
       
    46         self.Name = name
       
    47         self.Id = id
       
    48         # Find the block definition from type given and create the corresponding
       
    49         # inputs and outputs
       
    50         blocktype = GetBlockType(type)
       
    51         if blocktype:
       
    52             inputs = [input for input in blocktype["inputs"]]
       
    53             outputs = [output for output in blocktype["outputs"]]
       
    54             if blocktype["extensible"]:
       
    55                 start = int(inputs[-1][0].replace("IN", ""))
       
    56                 for i in xrange(extension - len(blocktype["inputs"])):
       
    57                     start += 1
       
    58                     inputs.append(("IN%d"%start, inputs[-1][1], input[-1][2]))
       
    59         self.SetConnectors(inputs, outputs)
       
    60     
       
    61     # Destructor
       
    62     def __del__(self):
       
    63         self.Inputs = []
       
    64         self.Outputs = []
       
    65     
       
    66     # Delete this block by calling the appropriate method
       
    67     def Delete(self):
       
    68         self.Parent.DeleteBlock(self)
       
    69     
       
    70     # Unconnect all inputs and outputs
       
    71     def Clean(self):
       
    72         for input in self.Inputs:
       
    73             input.UnConnect()
       
    74         for output in self.Outputs:
       
    75             output.UnConnect()
       
    76     
       
    77     # Refresh the block bounding box
       
    78     def RefreshBoundingBox(self):
       
    79         dc = wxClientDC(self.Parent)
       
    80         # Calculate the size of the name outside the block
       
    81         text_width, text_height = dc.GetTextExtent(self.Name)
       
    82         # Calculate the bounding box size
       
    83         bbx_x = self.Pos.x - max(min(1, len(self.Inputs)) * CONNECTOR_SIZE, (text_width - self.Size[0]) / 2)
       
    84         bbx_width = self.Size[0] + 1 + (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE
       
    85         if self.Name != "":
       
    86             bbx_y = self.Pos.y - (text_height + 2)
       
    87             bbx_height = self.Size[1] + (text_height + 2)
       
    88         else:
       
    89             bbx_y = self.Pos.y
       
    90             bbx_height = self.Size[1]
       
    91         self.BoundingBox = wxRect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
       
    92     
       
    93     # Refresh the positions of the block connectors
       
    94     def RefreshConnectors(self):
       
    95         # Calculate the size for the connector lines
       
    96         lines = max(len(self.Inputs), len(self.Outputs))
       
    97         linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE)
       
    98         # Update inputs positions
       
    99         position = BLOCK_LINE_SIZE + linesize / 2
       
   100         for input in self.Inputs:
       
   101             input.SetPosition(wxPoint(0, position))
       
   102             position += linesize
       
   103         # Update outputs positions
       
   104         position = BLOCK_LINE_SIZE + linesize / 2
       
   105         for output in self.Outputs:
       
   106             output.SetPosition(wxPoint(self.Size[0], position))
       
   107             position += linesize
       
   108         self.RefreshConnected()
       
   109     
       
   110     # Refresh the positions of wires connected to inputs and outputs
       
   111     def RefreshConnected(self, exclude = []):
       
   112         for input in self.Inputs:
       
   113             input.MoveConnected(exclude)
       
   114         for output in self.Outputs:
       
   115             output.MoveConnected(exclude)
       
   116     
       
   117     # Returns the block connector that starts with the point given if it exists 
       
   118     def GetConnector(self, position):
       
   119         # Test each input connector
       
   120         for input in self.Inputs:
       
   121             input_pos = input.GetRelPosition()
       
   122             if position.x == self.Pos.x + input_pos.x and position.y == self.Pos.y + input_pos.y:
       
   123                 return input
       
   124         # Test each output connector
       
   125         for output in self.Outputs:
       
   126             output_pos = output.GetRelPosition()
       
   127             if position.x == self.Pos.x + output_pos.x and position.y == self.Pos.y + output_pos.y:
       
   128                 return output
       
   129         return None
       
   130     
       
   131     # Returns all the block connectors 
       
   132     def GetConnectors(self):
       
   133         return {"inputs" : self.Inputs, "outputs" : self.Outputs}
       
   134     
       
   135     # Test if point given is on one of the block connectors
       
   136     def TestConnector(self, pt, exclude=True):
       
   137         # Test each input connector
       
   138         for input in self.Inputs:
       
   139             if input.TestPoint(pt, exclude):
       
   140                 return input
       
   141         # Test each output connector
       
   142         for output in self.Outputs:
       
   143             if output.TestPoint(pt, exclude):
       
   144                 return output
       
   145         return None
       
   146     
       
   147     # Returns the block type
       
   148     def GetType(self):
       
   149         return self.Type
       
   150     
       
   151     # Changes the block name
       
   152     def SetName(self, name):
       
   153         self.Name = name
       
   154     
       
   155     # Returs the block name
       
   156     def GetName(self):
       
   157         return self.Name
       
   158     
       
   159     # Returns the block minimum size
       
   160     def GetMinSize(self):
       
   161         dc = wxClientDC(self.Parent)
       
   162         text_width, text_height = dc.GetTextExtent(self.Type)
       
   163         # Calculate the inputs maximum width
       
   164         max_input = 0
       
   165         for input in self.Inputs:
       
   166             w, h = dc.GetTextExtent(input.GetName())
       
   167             max_input = max(max_input, w)
       
   168         # Calculate the outputs maximum width
       
   169         max_output = 0
       
   170         for output in self.Outputs:
       
   171             w, h = dc.GetTextExtent(output.GetName())
       
   172             max_output = max(max_output, w)
       
   173         width = max(text_width + 10, max_input + max_output + 15)
       
   174         height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE
       
   175         return width, height
       
   176     
       
   177     # Changes the block connectors
       
   178     def SetConnectors(self, inputs, outputs):
       
   179         # Extract the inputs properties and create the corresponding connector
       
   180         self.Inputs = []
       
   181         for input_name, input_type, input_modifier in inputs:
       
   182             connector = Connector(self, input_name, input_type, wxPoint(0, 0), WEST)
       
   183             if input_modifier == "negated":
       
   184                 connector.SetNegated(True)
       
   185             elif input_modifier != "none":
       
   186                 connector.SetEdge(input_modifier)
       
   187             self.Inputs.append(connector)
       
   188         # Extract the outputs properties and create the corresponding connector
       
   189         self.Outputs = []
       
   190         for output_name, output_type, output_modifier in outputs:
       
   191             connector = Connector(self, output_name, output_type, wxPoint(0, 0), EAST)
       
   192             if output_modifier == "negated":
       
   193                 connector.SetNegated(True)
       
   194             elif output_modifier != "none":
       
   195                 connector.SetEdge(output_modifier)
       
   196             self.Outputs.append(connector)
       
   197         self.RefreshBoundingBox()
       
   198     
       
   199     # Changes the negated property of the connector handled
       
   200     def SetConnectorNegated(self, negated):
       
   201         handle_type, handle = self.Handle
       
   202         if handle_type == HANDLE_CONNECTOR:
       
   203             handle.SetNegated(negated)
       
   204             self.RefreshModel(False)
       
   205     
       
   206     # Changes the edge property of the connector handled
       
   207     def SetConnectorEdge(self, edge):
       
   208         handle_type, handle = self.Handle
       
   209         if handle_type == HANDLE_CONNECTOR:
       
   210             handle.SetEdge(edge)
       
   211             self.RefreshModel(False)
       
   212     
       
   213     # Method called when a RightUp event have been generated
       
   214     def OnRightUp(self, event, scaling):
       
   215         pos = GetScaledEventPosition(event, scaling)
       
   216         # Popup the menu with special items for a block and a connector if one is handled
       
   217         connector = self.TestConnector(pos, False)
       
   218         if connector:
       
   219             self.Handle = (HANDLE_CONNECTOR, connector)
       
   220             self.Parent.PopupBlockMenu(connector)
       
   221         else:
       
   222             self.Parent.PopupBlockMenu()
       
   223     
       
   224     # Refreshes the block model
       
   225     def RefreshModel(self, move=True):
       
   226         self.Parent.RefreshBlockModel(self)
       
   227         # If block has moved, refresh the model of wires connected to outputs
       
   228         if move:
       
   229             for output in self.Outputs:
       
   230                 output.RefreshWires()
       
   231     
       
   232     # Draws block
       
   233     def Draw(self, dc):
       
   234         dc.SetPen(wxBLACK_PEN)
       
   235         dc.SetBrush(wxWHITE_BRUSH)
       
   236         # Draw a rectangle with the block size
       
   237         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
   238         # Draw block name and block type
       
   239         namewidth, nameheight = dc.GetTextExtent(self.Name)
       
   240         dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2,
       
   241                 self.Pos.y - (nameheight + 2))
       
   242         typewidth, typeheight = dc.GetTextExtent(self.Type)
       
   243         dc.DrawText(self.Type, self.Pos.x + (self.Size[0] - typewidth) / 2,
       
   244                 self.Pos.y + 5)
       
   245         # Draw inputs and outputs connectors
       
   246         for input in self.Inputs:
       
   247             input.Draw(dc)
       
   248         for output in self.Outputs:
       
   249             output.Draw(dc)
       
   250         Graphic_Element.Draw(self, dc)
       
   251 
       
   252 
       
   253 #-------------------------------------------------------------------------------
       
   254 #                        Function Block Diagram Variable
       
   255 #-------------------------------------------------------------------------------
       
   256 
       
   257 """
       
   258 Class that implements the graphic representation of a variable
       
   259 """
       
   260 
       
   261 class FBD_Variable(Graphic_Element):
       
   262 
       
   263     # Create a new variable
       
   264     def __init__(self, parent, type, name, value_type, id = None):
       
   265         Graphic_Element.__init__(self, parent)
       
   266         self.Type = type
       
   267         self.Name = name
       
   268         self.Id = id
       
   269         self.Input = None
       
   270         self.Output = None
       
   271         # Create an input or output connector according to variable type
       
   272         if self.Type != INPUT:
       
   273             self.Input = Connector(self, "", value_type, wxPoint(0, 0), WEST)
       
   274         if self.Type != OUTPUT:
       
   275             self.Output = Connector(self, "", value_type, wxPoint(0, 0), EAST)
       
   276         self.RefreshConnectors()
       
   277     
       
   278     # Destructor
       
   279     def __del__(self):
       
   280         self.Input = None
       
   281         self.Output = None
       
   282     
       
   283     # Unconnect connector
       
   284     def Clean(self):
       
   285         if self.Input:
       
   286             self.Input.UnConnect()
       
   287         if self.Output:
       
   288             self.Output.UnConnect()
       
   289     
       
   290     # Delete this variable by calling the appropriate method
       
   291     def Delete(self):
       
   292         self.Parent.DeleteVariable(self)
       
   293     
       
   294     # Refresh the variable bounding box
       
   295     def RefreshBoundingBox(self):
       
   296         dc = wxClientDC(self.Parent)
       
   297         if self.Type in (OUTPUT, INOUT):
       
   298             bbx_x = self.Pos.x - CONNECTOR_SIZE
       
   299         else:
       
   300             bbx_x = self.Pos.x
       
   301         if self.Type == INOUT:
       
   302             bbx_width = self.Size[0] + 2 * CONNECTOR_SIZE
       
   303         else:
       
   304             bbx_width = self.Size[0] + CONNECTOR_SIZE
       
   305         self.BoundingBox = wxRect(bbx_x, self.Pos.y, bbx_width + 1, self.Size[1] + 1)
       
   306     
       
   307     # Refresh the position of the variable connector
       
   308     def RefreshConnectors(self):
       
   309         if self.Input:
       
   310             self.Input.SetPosition(wxPoint(0, self.Size[1] / 2))
       
   311         if self.Output:
       
   312             self.Output.SetPosition(wxPoint(self.Size[0], self.Size[1] / 2))
       
   313         self.RefreshConnected(self)
       
   314     
       
   315     # Refresh the position of wires connected to connector
       
   316     def RefreshConnected(self, exclude = []):
       
   317         if self.Input:
       
   318             self.Input.MoveConnected(exclude)
       
   319         if self.Output:
       
   320             self.Output.MoveConnected(exclude)
       
   321         
       
   322     # Test if point given is on the variable connector
       
   323     def TestConnector(self, pt, exclude=True):
       
   324         if self.Input and self.Input.TestPoint(pt, exclude):
       
   325             return self.Input
       
   326         if self.Output and self.Output.TestPoint(pt, exclude):
       
   327             return self.Output
       
   328         return None
       
   329     
       
   330     # Returns the block connector that starts with the point given if it exists 
       
   331     def GetConnector(self, position):
       
   332         # Test input connector if it exists
       
   333         if self.Input:
       
   334             input_pos = self.Input.GetRelPosition()
       
   335             if position.x == self.Pos.x + input_pos.x and position.y == self.Pos.y + input_pos.y:
       
   336                 return self.Input
       
   337         # Test output connector if it exists
       
   338         if self.Output:
       
   339             output_pos = self.Output.GetRelPosition()
       
   340             if position.x == self.Pos.x + output_pos.x and position.y == self.Pos.y + output_pos.y:
       
   341                 return self.Output
       
   342         return None
       
   343     
       
   344     # Returns all the block connectors 
       
   345     def GetConnectors(self):
       
   346         return {"input" : self.Input, "output" : self.Output}
       
   347     
       
   348     # Changes the negated property of the variable connector if handled
       
   349     def SetConnectorNegated(self, negated):
       
   350         handle_type, handle = self.Handle
       
   351         if handle_type == HANDLE_CONNECTOR:
       
   352             handle.SetNegated(negated)
       
   353             self.RefreshModel(False)
       
   354     
       
   355     # Returns the variable type
       
   356     def GetType(self):
       
   357         return self.Type
       
   358     
       
   359     # Changes the variable name
       
   360     def SetName(self, name):
       
   361         self.Name = name
       
   362     
       
   363     # Returns the variable name
       
   364     def GetName(self):
       
   365         return self.Name
       
   366     
       
   367     # Returns the variable minimum size
       
   368     def GetMinSize(self):
       
   369         dc = wxClientDC(self.Parent)
       
   370         text_width, text_height = dc.GetTextExtent(self.Name)
       
   371         return text_width + 10, text_height + 10
       
   372     
       
   373     # Method called when a RightUp event have been generated
       
   374     def OnRightUp(self, event, scaling):
       
   375         pos = GetScaledEventPosition(event, scaling)
       
   376         # Popup the menu with special items for a variable and a connector if it's handled
       
   377         connector = self.TestConnectors(pos, False)
       
   378         if connector:
       
   379             self.Handle = (HANDLE_CONNECTOR, connector)
       
   380             self.Parent.PopupVariableMenu(connector)
       
   381         else:
       
   382             self.Parent.PopupVariableMenu()
       
   383     
       
   384     # Refreshes the variable model
       
   385     def RefreshModel(self, move=True):
       
   386         self.Parent.RefreshVariableModel(self)
       
   387         # If variable has moved and variable is not of type OUTPUT, refresh the model
       
   388         # of wires connected to output connector
       
   389         if move and self.Type != OUTPUT:
       
   390             if self.Output:
       
   391                 self.Output.RefreshWires()
       
   392     
       
   393     # Draws variable
       
   394     def Draw(self, dc):
       
   395         dc.SetPen(wxBLACK_PEN)
       
   396         dc.SetBrush(wxWHITE_BRUSH)
       
   397         # Draw a rectangle with the variable size
       
   398         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
   399         # Draw variable name
       
   400         namewidth, nameheight = dc.GetTextExtent(self.Name)
       
   401         dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2,
       
   402                 self.Pos.y + (self.Size[1] - nameheight) / 2)
       
   403         # Draw connectors
       
   404         if self.Input:
       
   405             self.Input.Draw(dc)
       
   406         if self.Output:    
       
   407             self.Output.Draw(dc)
       
   408         Graphic_Element.Draw(self, dc)
       
   409 
       
   410 
       
   411 #-------------------------------------------------------------------------------
       
   412 #                        Function Block Diagram Connector
       
   413 #-------------------------------------------------------------------------------
       
   414 
       
   415 """
       
   416 Class that implements the graphic representation of a connection
       
   417 """
       
   418 
       
   419 class FBD_Connector(Graphic_Element):
       
   420 
       
   421     # Create a new connection
       
   422     def __init__(self, parent, type, name, id = None):
       
   423         Graphic_Element.__init__(self, parent)
       
   424         self.Type = type
       
   425         self.Name = name
       
   426         self.Id = id
       
   427         self.Pos = wxPoint(0, 0)
       
   428         self.Size = wxSize(0, 0)
       
   429         # Create an input or output connector according to connection type
       
   430         if self.Type == CONNECTOR:
       
   431             self.Connector = Connector(self, "", "ANY", wxPoint(0, 0), WEST)
       
   432         else:
       
   433             self.Connector = Connector(self, "", "ANY", wxPoint(0, 0), EAST)
       
   434         self.RefreshConnectors()
       
   435     
       
   436     # Destructor
       
   437     def __del__(self):
       
   438         self.Connector = None
       
   439     
       
   440     # Unconnect connector
       
   441     def Clean(self):
       
   442         if self.Connector:
       
   443             self.Connector.UnConnect()
       
   444     
       
   445     # Delete this connection by calling the appropriate method
       
   446     def Delete(self):
       
   447         self.Parent.DeleteConnection(self)
       
   448     
       
   449     # Refresh the connection bounding box
       
   450     def RefreshBoundingBox(self):
       
   451         dc = wxClientDC(self.Parent)
       
   452         if self.Type == CONNECTOR:
       
   453             bbx_x = self.Pos.x - CONNECTOR_SIZE
       
   454         else:
       
   455             bbx_x = self.Pos.x
       
   456         bbx_width = self.Size[0] + CONNECTOR_SIZE
       
   457         self.BoundingBox = wxRect(bbx_x, self.Pos.y, bbx_width, self.Size[1])
       
   458     
       
   459     # Refresh the position of the connection connector
       
   460     def RefreshConnectors(self):
       
   461         if self.Type == CONNECTOR:
       
   462             self.Connector.SetPosition(wxPoint(0, self.Size[1] / 2))
       
   463         else:
       
   464             self.Connector.SetPosition(wxPoint(self.Size[0], self.Size[1] / 2))
       
   465         self.RefreshConnected(self)
       
   466     
       
   467     # Refresh the position of wires connected to connector
       
   468     def RefreshConnected(self, exclude = []):
       
   469         if self.Connector:
       
   470             self.Connector.MoveConnected(exclude)
       
   471     
       
   472     # Test if point given is on the connection connector
       
   473     def TestConnector(self, pt, exclude=True):
       
   474         if self.Connector and self.Connector.TestPoint(pt, exclude):
       
   475             return self.Connector
       
   476         return None
       
   477     
       
   478     # Returns the connection connector
       
   479     def GetConnector(self, position = None):
       
   480         return self.Connector
       
   481     
       
   482     # Returns the connection type
       
   483     def GetType(self):
       
   484         return self.Type
       
   485     
       
   486     # Changes the connection name
       
   487     def SetName(self, name):
       
   488         self.Name = name
       
   489         
       
   490     # Returns the connection name
       
   491     def GetName(self):
       
   492         return self.Name
       
   493     
       
   494     # Returns the connection minimum size
       
   495     def GetMinSize(self):
       
   496         dc = wxClientDC(self.Parent)
       
   497         text_width, text_height = dc.GetTextExtent(self.Name)
       
   498         if text_height % 2 == 1:
       
   499             text_height += 1
       
   500         return text_width + text_height + 20, text_height + 10
       
   501     
       
   502     # Method called when a RightUp event have been generated
       
   503     def OnRightUp(self, event, scaling):
       
   504         # Popup the default menu
       
   505         self.Parent.PopupDefaultMenu()
       
   506     
       
   507     # Refreshes the connection model
       
   508     def RefreshModel(self, move=True):
       
   509         self.Parent.RefreshConnectionModel(self)
       
   510         # If connection has moved and connection is of type CONTINUATION, refresh
       
   511         # the model of wires connected to connector
       
   512         if move and self.Type == CONTINUATION:
       
   513             if self.Connector:
       
   514                 self.Connector.RefreshWires()
       
   515     
       
   516     # Draws connection
       
   517     def Draw(self, dc):
       
   518         dc.SetPen(wxBLACK_PEN)
       
   519         dc.SetBrush(wxWHITE_BRUSH)
       
   520         # Draw a rectangle with the connection size with arrows in 
       
   521         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
   522         namewidth, nameheight = dc.GetTextExtent(self.Name)
       
   523         arrowsize = min(self.Size[1] / 2, (self.Size[0] - namewidth - 10) / 2)
       
   524         dc.DrawLine(self.Pos.x, self.Pos.y, self.Pos.x + arrowsize, 
       
   525                 self.Pos.y + self.Size[1] / 2)
       
   526         dc.DrawLine(self.Pos.x + arrowsize, self.Pos.y + self.Size[1] / 2, 
       
   527                 self.Pos.x, self.Pos.y + self.Size[1])
       
   528         dc.DrawLine(self.Pos.x + self.Size[0] - arrowsize, self.Pos.y, 
       
   529                 self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2)
       
   530         dc.DrawLine(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2, 
       
   531                 self.Pos.x + self.Size[0] - arrowsize, self.Pos.y + self.Size[1])
       
   532         # Draw variable name
       
   533         dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2,
       
   534                 self.Pos.y + (self.Size[1] - nameheight) / 2)
       
   535         # Draw connector
       
   536         if self.Connector:
       
   537             self.Connector.Draw(dc)
       
   538         Graphic_Element.Draw(self, dc)