# HG changeset patch
# User etisserant
# Date 1170257499 -3600
# Node ID b622defdfd98b0cf0391e9004750f92cd89d7a48
PLCOpenEditor initial commit. 31/01/07.
diff -r 000000000000 -r b622defdfd98 .project
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.project Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,11 @@
+
+
+ plcopeneditor
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 FBDViewer.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/FBDViewer.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,793 @@
+#!/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 plcopen.structures import *
+from graphics.GraphicCommons import *
+from graphics.FBD_Objects import *
+from Viewer import *
+
+class FBD_Viewer(Viewer):
+
+ def __init__(self, parent, window, controler):
+ Viewer.__init__(self, parent, window, controler)
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerLeftDown(self, event):
+ if self.Mode == MODE_SELECTION:
+ pos = event.GetPosition()
+ if event.ControlDown() and self.SelectedElement:
+ element = self.FindElement(pos, True)
+ if element:
+ if isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement.SelectElement(element)
+ elif self.SelectedElement:
+ group = Graphic_Group(self)
+ group.SelectElement(self.SelectedElement)
+ group.SelectElement(element)
+ self.SelectedElement = group
+ elements = self.SelectedElement.GetElements()
+ if len(elements) == 0:
+ self.SelectedElement = element
+ elif len(elements) == 1:
+ self.SelectedElement = elements[0]
+ self.SelectedElement.SetSelected(True)
+ else:
+ element = self.FindElement(pos)
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ self.Refresh()
+ if element:
+ self.SelectedElement = element
+ self.SelectedElement.OnLeftDown(event, self.Scaling)
+ self.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.Scaling)
+ elif self.Mode in [MODE_BLOCK,MODE_VARIABLE,MODE_CONNECTION,MODE_COMMENT]:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.Scaling)
+ elif self.Mode == MODE_WIRE:
+ pos = GetScaledEventPosition(event, self.Scaling)
+ wire = Wire(self, [wxPoint(pos.x, pos.y), EAST], [wxPoint(pos.x, pos.y), WEST])
+ wire.oldPos = pos
+ wire.Handle = (HANDLE_POINT, 0)
+ wire.ProcessDragging(0, 0)
+ wire.Handle = (HANDLE_POINT, 1)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ if self.SelectedElement:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = wire
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ if self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ if len(elements) > 0:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(elements)
+ self.SelectedElement.SetSelected(True)
+ self.Refresh()
+ elif self.Mode == MODE_BLOCK:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.AddNewBlock, bbox)
+ elif self.Mode == MODE_VARIABLE:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.AddNewVariable, bbox)
+ elif self.Mode == MODE_CONNECTION:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.AddNewConnection, bbox)
+ elif self.Mode == MODE_COMMENT:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.AddNewComment, bbox)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.RefreshModel()
+ self.SelectedElement.SetSelected(True)
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ pos = event.GetPosition()
+ element = self.FindElement(pos)
+ if element:
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = element
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.OnRightUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ if self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnLeftDClick(event, self.Scaling)
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ if self.rubberBand.IsShown():
+ self.rubberBand.OnMotion(event, self.Scaling)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.Refresh()
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.Refresh()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ keycode = event.GetKeyCode()
+ if self.Scaling:
+ scaling = self.Scaling
+ else:
+ scaling = (8, 8)
+ if keycode == WXK_DELETE and self.SelectedElement:
+ self.SelectedElement.Clean()
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ elif keycode == WXK_LEFT and self.SelectedElement:
+ self.SelectedElement.Move(-scaling[0], 0)
+ elif keycode == WXK_RIGHT and self.SelectedElement:
+ self.SelectedElement.Move(scaling[0], 0)
+ elif keycode == WXK_UP and self.SelectedElement:
+ self.SelectedElement.Move(0, -scaling[1])
+ elif keycode == WXK_DOWN and self.SelectedElement:
+ self.SelectedElement.Move(0, scaling[1])
+ self.Refresh()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Adding element functions
+#-------------------------------------------------------------------------------
+
+ def AddNewBlock(self, bbox):
+ dialog = BlockPropertiesDialog(self.Parent)
+ dialog.SetBlockList(self.Controler.GetBlockTypes())
+ dialog.SetMinBlockSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wxID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ if "name" in values:
+ block = FBD_Block(self, values["type"], values["name"], id, values["extension"])
+ else:
+ block = FBD_Block(self, values["type"], "", id, values["extension"])
+ block.SetPosition(bbox.x, bbox.y)
+ block.SetSize(values["width"], values["height"])
+ self.Blocks.append(block)
+ self.Elements.append(block)
+ self.Controler.AddCurrentElementEditingBlock(id)
+ self.RefreshBlockModel(block)
+ self.Parent.RefreshProjectTree()
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddNewVariable(self, bbox):
+ dialog = VariablePropertiesDialog(self.Parent)
+ dialog.SetMinVariableSize((bbox.width, bbox.height))
+ varlist = []
+ vars = self.Controler.GetCurrentElementEditingInterfaceVars()
+ if vars:
+ for var in vars:
+ varlist.append((var["Name"], var["Class"], var["Type"]))
+ returntype = self.Controler.GetCurrentElementEditingInterfaceReturnType()
+ if returntype:
+ varlist.append((self.Controler.GetCurrentElementEditingName(), "Output", returntype))
+ dialog.SetVariables(varlist)
+ if dialog.ShowModal() == wxID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ variable = FBD_Variable(self, values["type"], values["name"], values["value_type"], id)
+ variable.SetPosition(bbox.x, bbox.y)
+ variable.SetSize(values["width"], values["height"])
+ self.Blocks.append(variable)
+ self.Elements.append(variable)
+ self.Controler.AddCurrentElementEditingVariable(id, values["type"])
+ self.RefreshVariableModel(variable)
+ self.Parent.RefreshProjectTree()
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddNewConnection(self, bbox):
+ dialog = ConnectionPropertiesDialog(self.Parent)
+ dialog.SetMinConnectionSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wxID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ connection = FBD_Connection(self, values["type"], values["name"], id)
+ connection.SetPosition(bbox.x, bbox.y)
+ connection.SetSize(values["width"], values["height"])
+ self.Blocks.append(connection)
+ self.Elements.append(connection)
+ self.Controler.AddCurrentElementEditingConnection(id, values["type"])
+ self.RefreshConnectionModel(connection)
+ self.Parent.RefreshProjectTree()
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddNewComment(self, bbox):
+ dialog = wxTextEntryDialog(self.Parent, "Add a new comment", "Please enter comment text", "", wxOK|wxCANCEL|wxTE_MULTILINE)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ id = self.GetNewId()
+ comment = Comment(self, value, id)
+ comment.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = comment.GetMinSize()
+ comment.SetSize(max(min_width,bbox.width),max(min_height,bbox.height))
+ self.Elements.append(comment)
+ self.Controler.AddCurrentElementEditingComment(id)
+ self.RefreshCommentModel(comment)
+ self.Refresh()
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Delete element functions
+#-------------------------------------------------------------------------------
+
+ def DeleteBlock(self, block):
+ wires = []
+ for output in block.GetConnectors()["outputs"]:
+ wires.extend([wire[0] for wire in output.GetWires()])
+ block.Clean()
+ self.Blocks.remove(block)
+ self.Elements.remove(block)
+ self.Controler.RemoveCurrentElementEditingInstance(block.GetId())
+ for wire in wires:
+ wire.RefreshModel()
+ self.Parent.RefreshProjectTree()
+
+ def DeleteVariable(self, variable):
+ wires = []
+ if self.SelectedElement.GetType() == INPUT:
+ connector = variable.GetConnector()
+ wires.extend([wire[0] for wire in connector.GetWires()])
+ variable.Clean()
+ self.Blocks.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ self.Controler.RemoveCurrentElementEditingInstance(variable.GetId())
+ for wire in wires:
+ wire.RefreshModel()
+ self.Parent.RefreshProjectTree()
+
+ def DeleteConnection(self, connection):
+ wires = []
+ if self.SelectedElement.GetType() == CONTINUATION:
+ connector = connection.GetConnector()
+ wires.extend([wire[0] for wire in connector.GetWires()])
+ connection.Clean()
+ self.Blocks.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ self.Controler.RemoveCurrentElementEditingInstance(connection.GetId())
+ for wire in wires:
+ wire.RefreshModel()
+ self.Parent.RefreshProjectTree()
+
+ def DeleteComment(self, comment):
+ self.Elements.remove(self.SelectedElement)
+ self.Controler.RemoveCurrentElementEditingInstance(comment.GetId())
+
+ def DeleteWire(self, wire):
+ connected = wire.GetConnected()
+ self.SelectedElement.Clean()
+ self.Wires.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ for connector in connected:
+ connector.RefreshParentBlock()
+
+#-------------------------------------------------------------------------------
+# Create New Block Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_BLOCKPROPERTIESDIALOG, wxID_BLOCKPROPERTIESDIALOGMAINPANEL,
+ wxID_BLOCKPROPERTIESDIALOGNAME, wxID_BLOCKPROPERTIESDIALOGTYPETREE,
+ wxID_BLOCKPROPERTIESDIALOGTYPEDESC, wxID_BLOCKPROPERTIESDIALOGINPUTS,
+ wxID_BLOCKPROPERTIESDIALOGPREVIEW, wxID_BLOCKPROPERTIESDIALOGSTATICTEXT1,
+ wxID_BLOCKPROPERTIESDIALOGSTATICTEXT2, wxID_BLOCKPROPERTIESDIALOGSTATICTEXT3,
+ wxID_BLOCKPROPERTIESDIALOGSTATICTEXT4,
+] = [wx.NewId() for _init_ctrls in range(11)]
+
+class BlockPropertiesDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_BLOCKPROPERTIESDIALOG,
+ name='BlockPropertiesDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(600, 360), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Block Properties')
+ self.SetClientSize(wx.Size(600, 360))
+
+ self.MainPanel = wx.Panel(id=wxID_BLOCKPROPERTIESDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(600, 320), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticbox1 = wx.StaticBox(id=wxID_BLOCKPROPERTIESDIALOGSTATICTEXT1,
+ label='Type:', name='staticBox1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(245, 280), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_BLOCKPROPERTIESDIALOGSTATICTEXT2,
+ label='Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(274, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_BLOCKPROPERTIESDIALOGSTATICTEXT2,
+ label='Inputs:', name='staticText4', parent=self.MainPanel,
+ pos=wx.Point(424, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText4 = wx.StaticText(id=wxID_BLOCKPROPERTIESDIALOGSTATICTEXT4,
+ label='Preview:', name='staticText4', parent=self.MainPanel,
+ pos=wx.Point(274, 80), size=wx.Size(100, 17), style=0)
+
+ self.TypeTree = wx.TreeCtrl(id=wxID_BLOCKPROPERTIESDIALOGTYPETREE,
+ name='TypeTree', parent=self.MainPanel, pos=wx.Point(34, 44),
+ size=wx.Size(225, 180), style=wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_SINGLE|wx.SUNKEN_BORDER)
+ EVT_TREE_SEL_CHANGED(self, wxID_BLOCKPROPERTIESDIALOGTYPETREE, self.OnTypeTreeItemSelected)
+
+ self.TypeDesc = wx.TextCtrl(id=wxID_BLOCKPROPERTIESDIALOGTYPEDESC,
+ name='TypeDesc', parent=self.MainPanel, pos=wx.Point(34, 230),
+ size=wx.Size(225, 65), style=wx.TE_READONLY|wx.TE_MULTILINE)
+
+ self.Name = wx.TextCtrl(id=wxID_BLOCKPROPERTIESDIALOGNAME, value='',
+ name='Name', parent=self.MainPanel, pos=wx.Point(274, 48),
+ size=wx.Size(145, 24), style=0)
+ EVT_TEXT(self, wxID_BLOCKPROPERTIESDIALOGNAME, self.OnNameChanged)
+
+ self.Inputs = wx.SpinCtrl(id=wxID_BLOCKPROPERTIESDIALOGINPUTS,
+ name='Inputs', parent=self.MainPanel, pos=wx.Point(424, 48),
+ size=wx.Size(145, 24), style=0, min=2, max=20)
+ EVT_SPINCTRL(self, wxID_BLOCKPROPERTIESDIALOGINPUTS, self.OnInputsChanged)
+
+ self.Preview = wx.Panel(id=wxID_BLOCKPROPERTIESDIALOGPREVIEW,
+ name='Preview', parent=self.MainPanel, pos=wx.Point(274, 104),
+ size=wx.Size(300, 200), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wxColour(255,255,255))
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+ self.Name.SetValue("")
+ self.Name.Enable(False)
+ self.Inputs.Enable(False)
+ self.Block = None
+ self.MinBlockSize = None
+
+ EVT_PAINT(self, self.OnPaint)
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ selected = self.TypeTree.GetSelection()
+ if not selected.IsOk() or self.TypeTree.GetItemParent(selected) == self.TypeTree.GetRootItem() or selected == self.TypeTree.GetRootItem():
+ message = wxMessageDialog(self, "Form isn't complete. Valid block type must be selected!", "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ elif self.Name.IsEnabled() and self.Name.GetValue() == "":
+ message = wxMessageDialog(self, "Form isn't complete. Name must be filled!", "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def SetBlockList(self, blocktypes):
+ root = self.TypeTree.AddRoot("")
+ for category in blocktypes:
+ category_item = self.TypeTree.AppendItem(root, category["name"])
+ for blocktype in category["list"]:
+ blocktype_item = self.TypeTree.AppendItem(category_item, blocktype["name"])
+
+ def SetMinBlockSize(self, size):
+ self.MinBlockSize = size
+
+ def GetValues(self):
+ values = {}
+ values["type"] = self.TypeTree.GetItemText(self.TypeTree.GetSelection())
+ if self.Name.GetValue() != "":
+ values["name"] = self.Name.GetValue()
+ values["width"], values["height"] = self.Block.GetSize()
+ values["extension"] = self.Inputs.GetValue()
+ return values
+
+ def OnTypeTreeItemSelected(self, event):
+ self.Name.SetValue("")
+ selected = event.GetItem()
+ if self.TypeTree.GetItemParent(selected) != self.TypeTree.GetRootItem():
+ blocktype = GetBlockType(self.TypeTree.GetItemText(selected))
+ if blocktype:
+ self.Inputs.SetValue(len(blocktype["inputs"]))
+ self.Inputs.Enable(blocktype["extensible"])
+ self.Name.Enable(blocktype["type"] != "function")
+ self.TypeDesc.SetValue(blocktype["comment"])
+ wxCallAfter(self.RefreshPreview)
+ else:
+ self.Name.Enable(False)
+ self.Inputs.Enable(False)
+ self.Inputs.SetValue(2)
+ self.TypeDesc.SetValue("")
+ wxCallAfter(self.ErasePreview)
+ else:
+ self.Name.Enable(False)
+ self.Inputs.Enable(False)
+ self.Inputs.SetValue(2)
+ self.TypeDesc.SetValue("")
+ wxCallAfter(self.ErasePreview)
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ if self.Name.IsEnabled():
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnInputsChanged(self, event):
+ if self.Inputs.IsEnabled():
+ self.RefreshPreview()
+ event.Skip()
+
+ def ErasePreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ self.Block = None
+
+ def RefreshPreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ blocktype = self.TypeTree.GetItemText(self.TypeTree.GetSelection())
+ self.Block = FBD_Block(self.Preview, blocktype, self.Name.GetValue(), extension = self.Inputs.GetValue())
+ width, height = self.MinBlockSize
+ min_width, min_height = self.Block.GetMinSize()
+ width, height = max(min_width, width), max(min_height, height)
+ self.Block.SetSize(width, height)
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - width) / 2
+ y = (clientsize.height - height) / 2
+ self.Block.SetPosition(x, y)
+ self.Block.Draw(dc)
+
+ def OnPaint(self, event):
+ if self.Block:
+ self.RefreshPreview()
+ else:
+ self.ErasePreview()
+
+
+#-------------------------------------------------------------------------------
+# Create New Variable Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_VARIABLEPROPERTIESDIALOG, wxID_VARIABLEPROPERTIESDIALOGMAINPANEL,
+ wxID_VARIABLEPROPERTIESDIALOGNAME, wxID_VARIABLEPROPERTIESDIALOGCLASS,
+ wxID_VARIABLEPROPERTIESDIALOGPREVIEW, wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT1,
+ wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT2, wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+class VariablePropertiesDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_VARIABLEPROPERTIESDIALOG,
+ name='VariablePropertiesDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(400, 320), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Variable Properties')
+ self.SetClientSize(wx.Size(400, 320))
+
+ self.MainPanel = wx.Panel(id=wxID_VARIABLEPROPERTIESDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(400, 280), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT1,
+ label='Class:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT2,
+ label='Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(204, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_VARIABLEPROPERTIESDIALOGSTATICTEXT3,
+ label='Preview:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 72), size=wx.Size(100, 17), style=0)
+
+ self.Class = wx.Choice(id=wxID_VARIABLEPROPERTIESDIALOGCLASS,
+ name='Class', parent=self.MainPanel, pos=wx.Point(24, 48),
+ size=wx.Size(145, 24), style=0)
+ EVT_CHOICE(self, wxID_VARIABLEPROPERTIESDIALOGCLASS, self.OnClassChanged)
+
+ self.Name = wx.Choice(id=wxID_VARIABLEPROPERTIESDIALOGNAME,
+ name='Name', parent=self.MainPanel, pos=wx.Point(204, 48),
+ size=wx.Size(145, 24), style=0)
+ EVT_CHOICE(self, wxID_VARIABLEPROPERTIESDIALOGNAME, self.OnNameChanged)
+
+ self.Preview = wx.Panel(id=wxID_VARIABLEPROPERTIESDIALOGPREVIEW,
+ name='Preview', parent=self.MainPanel, pos=wx.Point(24, 104),
+ size=wx.Size(350, 150), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wxColour(255,255,255))
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+ self.Variable = None
+ self.VarList = []
+ self.MinVariableSize = None
+ self.RefreshNameList()
+
+ for choice in ["Input", "Output", "InOut"]:
+ self.Class.Append(choice)
+ self.Class.SetStringSelection("Input")
+
+ EVT_PAINT(self, self.OnPaint)
+
+ def RefreshNameList(self):
+ selected = self.Name.GetStringSelection()
+ self.Name.Clear()
+ for name, var_type, value_type in self.VarList:
+ if var_type in ["Local","Temp","Output","InOut"]:
+ self.Name.Append(name)
+ elif var_type == "Input" and self.Class.GetStringSelection() == "Input":
+ self.Name.Append(name)
+ if self.Name.FindString(selected) != wxNOT_FOUND:
+ self.Name.SetStringSelection(selected)
+ self.Name.Enable(self.Name.GetCount() > 0)
+
+ def SetMinVariableSize(self, size):
+ self.MinVariableSize = size
+
+ def SetVariables(self, vars):
+ self.VarList = vars
+ self.RefreshNameList()
+
+ def GetValues(self):
+ values = {}
+ classtype = self.Class.GetStringSelection()
+ if classtype == "Input":
+ values["type"] = INPUT
+ elif classtype == "Output":
+ values["type"] = OUTPUT
+ elif classtype == "InOut":
+ values["type"] = INOUT
+ values["name"] = self.Name.GetStringSelection()
+ values["value_type"] = ""
+ for var_name, var_type, value_type in self.VarList:
+ if var_name == values["name"]:
+ values["value_type"] = value_type
+ values["width"], values["height"] = self.Variable.GetSize()
+ return values
+
+ def OnClassChanged(self, event):
+ self.RefreshNameList()
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ name = self.Name.GetStringSelection()
+ type = ""
+ for var_name, var_type, value_type in self.VarList:
+ if var_name == name:
+ type = value_type
+ classtype = self.Class.GetStringSelection()
+ if classtype == "Input":
+ self.Variable = FBD_Variable(self.Preview, INPUT, name, type)
+ elif classtype == "Output":
+ self.Variable = FBD_Variable(self.Preview, OUTPUT, name, type)
+ elif classtype == "InOut":
+ self.Variable = FBD_Variable(self.Preview, INOUT, name, type)
+ width, height = self.MinVariableSize
+ min_width, min_height = self.Variable.GetMinSize()
+ width, height = max(min_width, width), max(min_height, height)
+ self.Variable.SetSize(width, height)
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - width) / 2
+ y = (clientsize.height - height) / 2
+ self.Variable.SetPosition(x, y)
+ self.Variable.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+
+#-------------------------------------------------------------------------------
+# Create New Connection Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_CONNECTIONPROPERTIESDIALOG, wxID_CONNECTIONPROPERTIESDIALOGMAINPANEL,
+ wxID_CONNECTIONPROPERTIESDIALOGNAME, wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON1,
+ wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON2, wxID_CONNECTIONPROPERTIESDIALOGPREVIEW,
+ wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT1, wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT2,
+ wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(9)]
+
+class ConnectionPropertiesDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_CONNECTIONPROPERTIESDIALOG,
+ name='ConnectionPropertiesDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(350, 220), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Connection Properties')
+ self.SetClientSize(wx.Size(350, 220))
+
+ self.MainPanel = wx.Panel(id=wxID_CONNECTIONPROPERTIESDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(340, 360), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT1,
+ label='Type:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT2,
+ label='Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 104), size=wx.Size(70, 17), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_CONNECTIONPROPERTIESDIALOGSTATICTEXT3,
+ label='Preview:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(174, 24), size=wx.Size(100, 17), style=0)
+
+ self.radioButton1 = wx.RadioButton(id=wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON1,
+ label='Connector', name='radioButton1', parent=self.MainPanel,
+ pos=wx.Point(24, 48), size=wx.Size(114, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON1, self.OnTypeChanged)
+ self.radioButton1.SetValue(True)
+
+ self.radioButton2 = wx.RadioButton(id=wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON2,
+ label='Continuation', name='radioButton2', parent=self.MainPanel,
+ pos=wx.Point(24, 72), size=wx.Size(128, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_CONNECTIONPROPERTIESDIALOGRADIOBUTTON2, self.OnTypeChanged)
+ self.radioButton2.SetValue(False)
+
+ self.Name = wx.TextCtrl(id=wxID_CONNECTIONPROPERTIESDIALOGNAME,
+ name='Name', parent=self.MainPanel, pos=wx.Point(24, 130),
+ size=wx.Size(145, 24), style=0)
+ EVT_TEXT(self, wxID_CONNECTIONPROPERTIESDIALOGNAME, self.OnNameChanged)
+
+ self.Preview = wx.Panel(id=wxID_CONNECTIONPROPERTIESDIALOGPREVIEW,
+ name='Preview', parent=self.MainPanel, pos=wx.Point(174, 48),
+ size=wx.Size(150, 100), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wxColour(255,255,255))
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+ self.Connection = None
+ self.MinConnectionSize = None
+
+ EVT_PAINT(self, self.OnPaint)
+
+ def SetMinConnectionSize(self, size):
+ self.MinConnectionSize = size
+
+ def GetValues(self):
+ values = {}
+ if self.radioButton1.GetValue():
+ values["type"] = CONNECTOR
+ else:
+ values["type"] = CONTINUATION
+ values["name"] = self.Name.GetValue()
+ values["width"], values["height"] = self.Connection.GetSize()
+ return values
+
+ def OnTypeChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ if self.radioButton1.GetValue():
+ self.Connection = FBD_Connector(self.Preview, CONNECTOR, self.Name.GetValue())
+ else:
+ self.Connection = FBD_Connector(self.Preview, CONTINUATION, self.Name.GetValue())
+ width, height = self.MinConnectionSize
+ min_width, min_height = self.Connection.GetMinSize()
+ width, height = max(min_width, width), max(min_height, height)
+ self.Connection.SetSize(width, height)
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - width) / 2
+ y = (clientsize.height - height) / 2
+ self.Connection.SetPosition(x, y)
+ self.Connection.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
diff -r 000000000000 -r b622defdfd98 GenerateProgram.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/GenerateProgram.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,35 @@
+#!/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
+
+
+"""
+Class implementing a generator for PLC program code
+"""
+
+class PLCGenerator:
+
+ # Create a rubberband by indicated on which window it must be drawn
+ def __init__(self, controler):
+ self.drawingSurface = drawingSurface
+ self.Reset()
diff -r 000000000000 -r b622defdfd98 Images/action.png
Binary file Images/action.png has changed
diff -r 000000000000 -r b622defdfd98 Images/action.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/action.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,113 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/block.png
Binary file Images/block.png has changed
diff -r 000000000000 -r b622defdfd98 Images/block.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/block.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,86 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/branch.png
Binary file Images/branch.png has changed
diff -r 000000000000 -r b622defdfd98 Images/branch.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/branch.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,95 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/coil.png
Binary file Images/coil.png has changed
diff -r 000000000000 -r b622defdfd98 Images/coil.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/coil.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,111 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/comment.png
Binary file Images/comment.png has changed
diff -r 000000000000 -r b622defdfd98 Images/comment.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/comment.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,107 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/connection.png
Binary file Images/connection.png has changed
diff -r 000000000000 -r b622defdfd98 Images/connection.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/connection.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,95 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/contact.png
Binary file Images/contact.png has changed
diff -r 000000000000 -r b622defdfd98 Images/contact.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/contact.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,85 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/divergence.png
Binary file Images/divergence.png has changed
diff -r 000000000000 -r b622defdfd98 Images/divergence.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/divergence.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,81 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/initial_step.png
Binary file Images/initial_step.png has changed
diff -r 000000000000 -r b622defdfd98 Images/initial_step.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/initial_step.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,93 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/jump.png
Binary file Images/jump.png has changed
diff -r 000000000000 -r b622defdfd98 Images/jump.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/jump.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,77 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/select.png
Binary file Images/select.png has changed
diff -r 000000000000 -r b622defdfd98 Images/select.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/select.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,89 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/step.png
Binary file Images/step.png has changed
diff -r 000000000000 -r b622defdfd98 Images/step.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/step.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,86 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/transition.png
Binary file Images/transition.png has changed
diff -r 000000000000 -r b622defdfd98 Images/transition.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/transition.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,79 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/variable.png
Binary file Images/variable.png has changed
diff -r 000000000000 -r b622defdfd98 Images/variable.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/variable.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,85 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 Images/wire.png
Binary file Images/wire.png has changed
diff -r 000000000000 -r b622defdfd98 Images/wire.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/wire.svg Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,85 @@
+
+
+
diff -r 000000000000 -r b622defdfd98 LDViewer.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LDViewer.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1184 @@
+#!/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 types import *
+
+from plcopen.structures import *
+from graphics.GraphicCommons import *
+from graphics.FBD_Objects import *
+from Viewer import *
+
+def ExtractNextBlocks(block, block_list):
+ current_list = [block]
+ while len(current_list) > 0:
+ next_list = []
+ for current in current_list:
+ connectors = current.GetConnectors()
+ input_connectors = []
+ if isinstance(current, LD_PowerRail) and current.GetType() == RIGHTRAIL:
+ input_connectors = connectors
+ else:
+ if "inputs" in connectors:
+ input_connectors = connectors["inputs"]
+ if "input" in connectors:
+ input_connectors = [connectors["input"]]
+ for connector in input_connectors:
+ for wire, handle in connector.GetWires():
+ next = wire.EndConnected.GetParentBlock()
+ if not isinstance(next, LD_PowerRail) and next not in block_list:
+ block_list.append(next)
+ next_list.append(next)
+ current_list = next_list
+
+def CalcBranchSize(elements, stop):
+ branch_size = 0
+ stop_list = [stop]
+ ExtractNextBlocks(stop, stop_list)
+ element_tree = {}
+ for element in elements:
+ if element not in element_tree:
+ element_tree[element] = {"parents":["start"], "children":[], "weight":None}
+ GenerateTree(element, element_tree, stop_list)
+ elif element_tree[element]:
+ element_tree[element]["parents"].append("start")
+ for element, values in element_tree.items():
+ if values and values["children"] == ["stop"]:
+ CalcWeight(element, element_tree)
+ if values["weight"]:
+ branch_size += values["weight"]
+ else:
+ return 1
+ return branch_size
+
+def RemoveElement(remove, element_tree):
+ if remove in element_tree and element_tree[remove]:
+ for child in element_tree[remove]["children"]:
+ if child != "stop":
+ RemoveElement(child, element_tree)
+ element_tree[remove] = None
+
+def GenerateTree(element, element_tree, stop_list):
+ if element in element_tree:
+ connectors = element.GetConnectors()
+ input_connectors = []
+ if isinstance(element, LD_PowerRail) and element.GetType() == RIGHTRAIL:
+ input_connectors = connectors
+ else:
+ if "inputs" in connectors:
+ input_connectors = connectors["inputs"]
+ if "input" in connectors:
+ input_connectors = [connectors["input"]]
+ for connector in input_connectors:
+ for wire, handle in connector.GetWires():
+ next = wire.EndConnected.GetParentBlock()
+ if isinstance(next, LD_PowerRail) and next.GetType() == LEFTRAIL or next in stop_list:
+ for remove in element_tree[element]["children"]:
+ RemoveElement(remove, element_tree)
+ element_tree[element]["children"] = ["stop"]
+ elif element_tree[element]["children"] == ["stop"]:
+ element_tree[next] = None
+ elif next not in element_tree or element_tree[next]:
+ element_tree[element]["children"].append(next)
+ if next in element_tree:
+ element_tree[next]["parents"].append(element)
+ else:
+ element_tree[next] = {"parents":[element], "children":[], "weight":None}
+ GenerateTree(next, element_tree, stop_list)
+
+def CalcWeight(element, element_tree):
+ weight = 0
+ parts = None
+ if element in element_tree:
+ for parent in element_tree[element]["parents"]:
+ if parent == "start":
+ weight += 1
+ elif parent in element_tree:
+ if not parts:
+ parts = len(element_tree[parent]["children"])
+ else:
+ parts = min(parts, len(element_tree[parent]["children"]))
+ if not element_tree[parent]["weight"]:
+ CalcWeight(parent, element_tree)
+ if element_tree[parent]["weight"]:
+ weight += element_tree[parent]["weight"]
+ else:
+ element_tree[element]["weight"] = None
+ return
+ else:
+ element_tree[element]["weight"] = None
+ return
+ if not parts:
+ parts = 1
+ element_tree[element]["weight"] = max(1, weight / parts)
+
+
+#-------------------------------------------------------------------------------
+# Ladder Diagram Graphic elements Viewer class
+#-------------------------------------------------------------------------------
+
+"""
+Class derived from Viewer class that implements a Viewer of Ladder Diagram
+"""
+
+class LD_Viewer(Viewer):
+
+ def __init__(self, parent, window, controler):
+ Viewer.__init__(self, parent, window, controler)
+ self.Rungs = []
+ self.Comments = []
+
+#-------------------------------------------------------------------------------
+# Refresh functions
+#-------------------------------------------------------------------------------
+
+ def RefreshView(self):
+ Viewer.RefreshView(self)
+ for i, rung in enumerate(self.Rungs):
+ bbox = rung.GetBoundingBox()
+ if i < len(self.Comments):
+ pos = self.Comments[i].GetPosition()
+ if pos[1] > bbox.y:
+ self.Comment.insert(i, None)
+ else:
+ self.Comment.insert(i, None)
+
+ def loadInstance(self, instance, ids):
+ Viewer.loadInstance(self, instance, ids)
+ if instance["type"] == "leftPowerRail":
+ element = self.FindElementById(instance["id"])
+ rung = Graphic_Group(self)
+ rung.SelectElement(element)
+ self.Rungs.append(rung)
+ elif instance["type"] == "rightPowerRail":
+ rungs = []
+ for connector in instance["connectors"]:
+ for link in connector["links"]:
+ connected = self.FindElementById(link["refLocalId"])
+ rung = self.FindRung(connected)
+ if rung not in rungs:
+ rungs.append(rung)
+ if len(rungs) > 1:
+ raise "ValueError", "Ladder element with id %d is on more than one rung."%instance["id"]
+ element = self.FindElementById(instance["id"])
+ self.Rungs[rungs[0]].SelectElement(element)
+ for connector in element.GetConnectors():
+ for wire, num in connector.GetWires():
+ self.Rungs[rungs[0]].SelectElement(wire)
+ self.RefreshPosition(element)
+ elif instance["type"] in ["contact", "coil"]:
+ rungs = []
+ for link in instance["connectors"]["input"]["links"]:
+ connected = self.FindElementById(link["refLocalId"])
+ rung = self.FindRung(connected)
+ if rung not in rungs:
+ rungs.append(rung)
+ if len(rungs) > 1:
+ raise "ValueError", "Ladder element with id %d is on more than one rung."%instance["id"]
+ element = self.FindElementById(instance["id"])
+ self.Rungs[rungs[0]].SelectElement(element)
+ for wire, num in element.GetConnectors()["input"].GetWires():
+ self.Rungs[rungs[0]].SelectElement(wire)
+ self.RefreshPosition(element)
+ elif instance["type"] == "comment":
+ element = self.FindElementById(instance["id"])
+ pos = element.GetPosition()
+ i = 0
+ inserted = False
+ while i < len(self.Comments) and not inserted:
+ ipos = self.Comments[i].GetPosition()
+ if pos[1] < ipos[1]:
+ self.Comments.insert(i, element)
+ inserted = True
+ i += 1
+ if not inserted:
+ self.Comments.append(element)
+
+#-------------------------------------------------------------------------------
+# Search Element functions
+#-------------------------------------------------------------------------------
+
+ def FindRung(self, element):
+ for i, rung in enumerate(self.Rungs):
+ if rung.IsElementIn(element):
+ return i
+ return None
+
+ def FindElement(self, pos):
+ elements = []
+ for element in self.Elements:
+ if element.HitTest(pos) or element.TestHandle(pos) != (0, 0):
+ elements.append(element)
+ if len(elements) == 1:
+ return elements[0]
+ elif len(elements) > 1:
+ group = Graphic_Group(self)
+ for element in elements:
+ if element in self.Blocks:
+ return element
+ group.SelectElement(element)
+ return group
+ return None
+
+ def SearchElements(self, bbox):
+ elements = []
+ for element in self.Blocks:
+ element_bbox = element.GetBoundingBox()
+ if element_bbox.x >= bbox.x and element_bbox.y >= bbox.y and element_bbox.x + element_bbox.width <= bbox.x + bbox.width and element_bbox.y + element_bbox.height <= bbox.y + bbox.height:
+ elements.append(element)
+ return elements
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerLeftDown(self, event):
+ if self.Mode == MODE_SELECTION:
+ pos = event.GetPosition()
+ element = self.FindElement(pos)
+ if self.SelectedElement:
+ if self.SelectedElement in self.Elements:
+ if self.SelectedElement != element:
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(None)
+ else:
+ self.SelectedElement.SetSelected(False)
+ else:
+ self.SelectedElement = None
+ elif element and element not in self.Elements:
+ if self.SelectedElement.GetElements() != element.GetElements():
+ for elt in self.SelectedElement.GetElements():
+ if elt in self.Wires:
+ elt.SetSelectedSegment(None)
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ else:
+ for elt in self.SelectedElement.GetElements():
+ if elt in self.Wires:
+ elt.SetSelectedSegment(None)
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ self.Refresh()
+ if element:
+ self.SelectedElement = element
+ self.SelectedElement.OnLeftDown(event, self.Scaling)
+ self.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.Scaling)
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ if self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ if len(elements) > 0:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(elements)
+ self.SelectedElement.SetSelected(True)
+ self.Refresh()
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ if self.SelectedElement in self.Elements:
+ if self.SelectedElement in self.Wires:
+ result = self.SelectedElement.TestSegment(event.GetPosition(), True)
+ if result and result[1] in [EAST, WEST]:
+ self.SelectedElement.SetSelectedSegment(result[0])
+ else:
+ self.SelectedElement.OnLeftUp(event, self.Scaling)
+ else:
+ for element in self.SelectedElement.GetElements():
+ if element in self.Wires:
+ result = element.TestSegment(event.GetPosition(), True)
+ if result and result[1] in [EAST, WEST]:
+ element.SetSelectedSegment(result[0])
+ else:
+ element.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ pos = event.GetPosition()
+ element = self.FindElement(pos)
+ if element:
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = element
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.OnRightUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ if self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnLeftDClick(event, self.Scaling)
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ if self.rubberBand.IsShown():
+ self.rubberBand.OnMotion(event, self.Scaling)
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ keycode = event.GetKeyCode()
+ if keycode == WXK_DELETE and self.SelectedElement:
+ if self.SelectedElement in self.Blocks:
+ self.SelectedElement.Delete()
+ elif self.SelectedElement in self.Wires:
+ self.DeleteWire(self.SelectedElement)
+ elif self.SelectedElement not in self.Elements:
+ all_wires = True
+ for element in self.SelectedElement.GetElements():
+ all_wires &= element in self.Wires
+ if all_wires:
+ self.DeleteWire(self.SelectedElement)
+ else:
+ self.SelectedElement.Delete()
+ self.Refresh()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Adding element functions
+#-------------------------------------------------------------------------------
+
+ def AddRung(self):
+ dialog = LDElementDialog(self.Parent, "coil")
+ varlist = []
+ vars = self.Controler.GetCurrentElementEditingInterfaceVars()
+ if vars:
+ for var in vars:
+ if var["Type"] != "Input" and var["Value"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetCurrentElementEditingInterfaceReturnType()
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetCurrentElementEditingName())
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":COIL_NORMAL})
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ startx, starty = LD_OFFSET[0], 0
+ if len(self.Rungs) > 0:
+ bbox = self.Rungs[-1].GetBoundingBox()
+ starty = bbox.y + bbox.height
+ starty += LD_OFFSET[1]
+ rung = Graphic_Group(self)
+ # Create comment
+ id = self.GetNewId()
+ comment = Comment(self, "Commentaire", id)
+ comment.SetPosition(startx, starty)
+ comment.SetSize(LD_COMMENT_DEFAULTSIZE[0], LD_COMMENT_DEFAULTSIZE[1])
+ self.Elements.append(comment)
+ self.Comments.append(comment)
+ self.Controler.AddCurrentElementEditingComment(id)
+ self.RefreshCommentModel(comment)
+ starty += LD_COMMENT_DEFAULTSIZE[1] + LD_OFFSET[1]
+ # Create LeftPowerRail
+ id = self.GetNewId()
+ leftpowerrail = LD_PowerRail(self, LEFTRAIL, id)
+ leftpowerrail.SetPosition(startx, starty)
+ self.Elements.append(leftpowerrail)
+ self.Blocks.append(leftpowerrail)
+ rung.SelectElement(leftpowerrail)
+ self.Controler.AddCurrentElementEditingPowerRail(id, LEFTRAIL)
+ self.RefreshPowerRailModel(leftpowerrail)
+ # Create Coil
+ id = self.GetNewId()
+ coil = LD_Coil(self, values["type"], values["name"], id)
+ coil.SetPosition(startx, starty + (LD_LINE_SIZE - LD_ELEMENT_SIZE[1]) / 2)
+ self.Elements.append(coil)
+ self.Blocks.append(coil)
+ rung.SelectElement(coil)
+ self.Controler.AddCurrentElementEditingCoil(id)
+ # Create Wire between LeftPowerRail and Coil
+ wire = Wire(self)
+ start_connector = coil.GetConnectors()["input"]
+ end_connector = leftpowerrail.GetConnectors()[0]
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ rung.SelectElement(wire)
+ # Create RightPowerRail
+ id = self.GetNewId()
+ rightpowerrail = LD_PowerRail(self, RIGHTRAIL, id)
+ rightpowerrail.SetPosition(startx, starty)
+ self.Elements.append(rightpowerrail)
+ self.Blocks.append(rightpowerrail)
+ rung.SelectElement(rightpowerrail)
+ self.Controler.AddCurrentElementEditingPowerRail(id, RIGHTRAIL)
+ # Create Wire between LeftPowerRail and Coil
+ wire = Wire(self)
+ start_connector = rightpowerrail.GetConnectors()[0]
+ end_connector = coil.GetConnectors()["output"]
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ rung.SelectElement(wire)
+ self.RefreshPosition(coil)
+ self.Rungs.append(rung)
+ self.Refresh()
+
+ def AddContact(self):
+ wires = []
+ if self.SelectedElement in self.Wires:
+ left_element = self.SelectedElement.EndConnected
+ if not isinstance(left_element.GetParentBlock(), LD_Coil):
+ wires.append(self.SelectedElement)
+ elif self.SelectedElement and self.SelectedElement not in self.Elements:
+ if False not in [element in self.Wires for element in self.SelectedElement.GetElements()]:
+ for element in self.SelectedElement.GetElements():
+ wires.append(element)
+ if len(wires) > 0:
+ dialog = LDElementDialog(self.Parent, "contact")
+ varlist = []
+ vars = self.Controler.GetCurrentElementEditingInterfaceVars()
+ if vars:
+ for var in vars:
+ if var["Type"] != "Output" and var["Value"] == "BOOL":
+ varlist.append(var["Name"])
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":CONTACT_NORMAL})
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ points = wires[0].GetSelectedSegmentPoints()
+ id = self.GetNewId()
+ contact = LD_Contact(self, values["type"], values["name"], id)
+ contact.SetPosition(0, points[0].y - (LD_ELEMENT_SIZE[1] + 1) / 2)
+ self.Elements.append(contact)
+ self.Blocks.append(contact)
+ self.Controler.AddCurrentElementEditingContact(id)
+ rungindex = self.FindRung(wires[0])
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ rung.SelectElement(contact)
+ connectors = contact.GetConnectors()
+ left_elements = []
+ right_elements = []
+ left_index = []
+ right_index = []
+ for wire in wires:
+ if wire.EndConnected not in left_elements:
+ left_elements.append(wire.EndConnected)
+ left_index.append(wire.EndConnected.GetWireIndex(wire))
+ else:
+ idx = left_elements.index(wire.EndConnected)
+ left_index[idx] = min(left_index[idx], wire.EndConnected.GetWireIndex(wire))
+ if wire.StartConnected not in right_elements:
+ right_elements.append(wire.StartConnected)
+ right_index.append(wire.StartConnected.GetWireIndex(wire))
+ else:
+ idx = right_elements.index(wire.StartConnected)
+ right_index[idx] = min(right_index[idx], wire.StartConnected.GetWireIndex(wire))
+ wire.SetSelectedSegment(None)
+ wire.Clean()
+ rung.SelectElement(wire)
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ wires = []
+ right_wires = []
+ for i, left_element in enumerate(left_elements):
+ wire = Wire(self)
+ wires.append(wire)
+ connectors["input"].Connect((wire, 0), False)
+ left_element.InsertConnect(left_index[i], (wire, -1), False)
+ wire.ConnectStartPoint(None, connectors["input"])
+ wire.ConnectEndPoint(None, left_element)
+ for i, right_element in enumerate(right_elements):
+ wire = Wire(self)
+ wires.append(wire)
+ right_wires.append(wire)
+ right_element.InsertConnect(right_index[i], (wire, 0), False)
+ connectors["output"].Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, right_element)
+ wire.ConnectEndPoint(None, connectors["output"])
+ right_wires.reverse()
+ for wire in wires:
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ rung.SelectElement(wire)
+ self.RefreshPosition(contact)
+ if len(right_wires) > 1:
+ group = Graphic_Group(self)
+ group.SetSelected(False)
+ for wire in right_wires:
+ wire.SetSelectedSegment(-1)
+ group.SelectElement(wire)
+ self.SelectedElement = group
+ else:
+ right_wires[0].SetSelectedSegment(-1)
+ self.SelectedElement = right_wires[0]
+ rung.RefreshBoundingBox()
+ new_bbox = rung.GetBoundingBox()
+ self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+ self.Refresh()
+
+ def AddBranch(self):
+ blocks = []
+ if self.SelectedElement in self.Blocks:
+ blocks = [self.SelectedElement]
+ elif self.SelectedElement not in self.Elements:
+ elements = self.SelectedElement.GetElements()
+ for element in elements:
+ if isinstance(element, (LD_PowerRail, LD_Coil)):
+ return
+ blocks.append(element)
+ if len(blocks) > 0:
+ blocks_infos = []
+ left_elements = []
+ left_index = []
+ right_elements = []
+ right_index = []
+ for block in blocks:
+ connectors = block.GetConnectors()
+ block_infos = {"inputs":[],"outputs":[],"lefts":[],"rights":[]}
+ if "inputs" in connectors:
+ block_infos["inputs"] = connectors["inputs"]
+ if "outputs" in connectors:
+ block_infos["outputs"] = connectors["outputs"]
+ if "input" in connectors:
+ block_infos["inputs"] = [connectors["input"]]
+ if "output" in connectors:
+ block_infos["outputs"] = [connectors["output"]]
+ for connector in block_infos["inputs"]:
+ for wire, handle in connector.GetWires():
+ found = False
+ for infos in blocks_infos:
+ if wire.EndConnected in infos["outputs"]:
+ for left_element in infos["lefts"]:
+ if left_element not in block_infos["lefts"]:
+ block_infos["lefts"].append(left_element)
+ found = True
+ if not found and wire.EndConnected not in block_infos["lefts"]:
+ block_infos["lefts"].append(wire.EndConnected)
+ if wire.EndConnected not in left_elements:
+ left_elements.append(wire.EndConnected)
+ left_index.append(wire.EndConnected.GetWireIndex(wire))
+ else:
+ index = left_elements.index(wire.EndConnected)
+ left_index[index] = max(left_index[index], wire.EndConnected.GetWireIndex(wire))
+ for connector in block_infos["outputs"]:
+ for wire, handle in connector.GetWires():
+ found = False
+ for infos in blocks_infos:
+ if wire.StartConnected in infos["inputs"]:
+ for right_element in infos["rights"]:
+ if right_element not in block_infos["rights"]:
+ block_infos["rights"].append(right_element)
+ found = True
+ if not found and wire.StartConnected not in block_infos["rights"]:
+ block_infos["rights"].append(wire.StartConnected)
+ if wire.StartConnected not in right_elements:
+ right_elements.append(wire.StartConnected)
+ right_index.append(wire.StartConnected.GetWireIndex(wire))
+ else:
+ index = right_elements.index(wire.StartConnected)
+ right_index[index] = max(right_index[index], wire.StartConnected.GetWireIndex(wire))
+ for connector in block_infos["inputs"]:
+ for infos in blocks_infos:
+ if connector in infos["rights"]:
+ infos["rights"].remove(connector)
+ if connector in right_elements:
+ index = right_elements.index(connector)
+ right_elements.pop(index)
+ right_index.pop(index)
+ for right_element in block_infos["rights"]:
+ if right_element not in infos["rights"]:
+ infos["rights"].append(right_element)
+ for connector in block_infos["outputs"]:
+ for infos in blocks_infos:
+ if connector in infos["lefts"]:
+ infos["lefts"].remove(connector)
+ if connector in left_elements:
+ index = left_elements.index(connector)
+ left_elements.pop(index)
+ left_index.pop(index)
+ for left_element in block_infos["lefts"]:
+ if left_element not in infos["lefts"]:
+ infos["lefts"].append(left_element)
+ blocks_infos.append(block_infos)
+ for infos in blocks_infos:
+ left_elements = [element for element in infos["lefts"]]
+ for left_element in left_elements:
+ if isinstance(left_element.GetParentBlock(), LD_PowerRail):
+ infos["lefts"].remove(left_element)
+ if "LD_PowerRail" not in infos["lefts"]:
+ infos["lefts"].append("LD_PowerRail")
+ right_elements = [element for element in infos["rights"]]
+ for right_element in right_elements:
+ if isinstance(right_element.GetParentBlock(), LD_PowerRail):
+ infos["rights"].remove(tight_element)
+ if "LD_PowerRail" not in infos["rights"]:
+ infos["rights"].append("LD_PowerRail")
+ infos["lefts"].sort()
+ infos["rights"].sort()
+ lefts = blocks_infos[0]["lefts"]
+ rights = blocks_infos[0]["rights"]
+ good = True
+ for infos in blocks_infos[1:]:
+ good &= infos["lefts"] == lefts
+ good &= infos["rights"] == rights
+ if good:
+ rungindex = self.FindRung(blocks[0])
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ left_powerrail = True
+ right_powerrail = True
+ for element in left_elements:
+ left_powerrail &= isinstance(element.GetParentBlock(), LD_PowerRail)
+ for element in right_elements:
+ right_powerrail &= isinstance(element.GetParentBlock(), LD_PowerRail)
+ if not left_powerrail or not right_powerrail:
+ if left_powerrail:
+ powerrail = left_elements[0].GetParentBlock()
+ index = 0
+ for left_element in left_elements:
+ index = max(index, powerrail.GetConnectorIndex(left_element))
+ if powerrail.IsNullConnector(index + 1):
+ powerrail.DeleteConnector(index + 1)
+ powerrail.InsertConnector(index + 1)
+ powerrail.RefreshModel()
+ connectors = powerrail.GetConnectors()
+ for i, right_element in enumerate(right_elements):
+ new_wire = Wire(self)
+ right_element.InsertConnect(right_index[i] + 1, (new_wire, 0), False)
+ connectors[index + 1].Connect((new_wire, -1), False)
+ new_wire.ConnectStartPoint(None, right_element)
+ new_wire.ConnectEndPoint(None, connectors[index + 1])
+ self.Wires.append(new_wire)
+ self.Elements.append(new_wire)
+ rung.SelectElement(new_wire)
+ right_elements.reverse()
+ elif right_powerrail:
+ pass
+ else:
+ left_elements.reverse()
+ right_elements.reverse()
+ wires = []
+ for i, left_element in enumerate(left_elements):
+ for j, right_element in enumerate(right_elements):
+ exist = False
+ for wire, handle in right_element.GetWires():
+ exist |= wire.EndConnected == left_element
+ if not exist:
+ new_wire = Wire(self)
+ wires.append(new_wire)
+ right_element.InsertConnect(right_index[j] + 1, (new_wire, 0), False)
+ left_element.InsertConnect(left_index[i] + 1, (new_wire, -1), False)
+ new_wire.ConnectStartPoint(None, right_element)
+ new_wire.ConnectEndPoint(None, left_element)
+ wires.reverse()
+ for wire in wires:
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ rung.SelectElement(wire)
+ right_elements.reverse()
+ for block in blocks:
+ self.RefreshPosition(block)
+ for right_element in right_elements:
+ self.RefreshPosition(right_element.GetParentBlock())
+ self.SelectedElement.RefreshBoundingBox()
+ rung.RefreshBoundingBox()
+ new_bbox = rung.GetBoundingBox()
+ self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+ self.Refresh()
+
+#-------------------------------------------------------------------------------
+# Delete element functions
+#-------------------------------------------------------------------------------
+
+ def DeleteContact(self, contact):
+ rungindex = self.FindRung(contact)
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ connectors = contact.GetConnectors()
+ input_wires = [wire for wire, handle in connectors["input"].GetWires()]
+ output_wires = [wire for wire, handle in connectors["output"].GetWires()]
+ left_elements = [(wire.EndConnected, wire.EndConnected.GetWireIndex(wire)) for wire in input_wires]
+ right_elements = [(wire.StartConnected, wire.StartConnected.GetWireIndex(wire)) for wire in output_wires]
+ for wire in input_wires:
+ wire.Clean()
+ rung.SelectElement(wire)
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ for wire in output_wires:
+ wire.Clean()
+ rung.SelectElement(wire)
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ rung.SelectElement(contact)
+ contact.Clean()
+ left_elements.reverse()
+ right_elements.reverse()
+ powerrail = len(left_elements) == 1 and isinstance(left_elements[0][0].GetParentBlock(), LD_PowerRail)
+ for left_element, left_index in left_elements:
+ for right_element, right_index in right_elements:
+ wire_removed = []
+ for wire, handle in right_element.GetWires():
+ if wire.EndConnected == left_element:
+ wire_removed.append(wire)
+ elif isinstance(wire.EndConnected.GetParentBlock(), LD_PowerRail) and powerrail:
+ left_powerrail = wire.EndConnected.GetParentBlock()
+ index = left_powerrail.GetConnectorIndex(wire.EndConnected)
+ left_powerrail.DeleteConnector(index)
+ wire_removed.append(wire)
+ for wire in wire_removed:
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ rung.SelectElement(wire)
+ wires = []
+ for left_element, left_index in left_elements:
+ for right_element, right_index in right_elements:
+ wire = Wire(self)
+ wires.append(wire)
+ right_element.InsertConnect(right_index, (wire, 0), False)
+ left_element.InsertConnect(left_index, (wire, -1), False)
+ wire.ConnectStartPoint(None, right_element)
+ wire.ConnectEndPoint(None, left_element)
+ wires.reverse()
+ for wire in wires:
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ rung.SelectElement(wire)
+ right_elements.reverse()
+ for right_element, right_index in right_elements:
+ self.RefreshPosition(right_element.GetParentBlock())
+ self.Blocks.remove(contact)
+ self.Elements.remove(contact)
+ self.Controler.RemoveCurrentElementEditingInstance(contact.GetId())
+ rung.RefreshBoundingBox()
+ new_bbox = rung.GetBoundingBox()
+ self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+ self.SelectedElement = None
+
+ def DeleteCoil(self, coil):
+ rungindex = self.FindRung(coil)
+ rung = self.Rungs[rungindex]
+ bbox = rung.GetBoundingBox()
+ for element in rung.GetElements():
+ if element in self.Wires:
+ element.Clean()
+ self.Wires.remove(element)
+ self.Elements.remove(element)
+ for element in rung.GetElements():
+ if element in self.Blocks:
+ self.Controler.RemoveCurrentElementEditingInstance(element.GetId())
+ self.Blocks.remove(element)
+ self.Elements.remove(element)
+ self.Controler.RemoveCurrentElementEditingInstance(self.Comments[rungindex].GetId())
+ self.Elements.remove(self.Comments[rungindex])
+ self.Comments.pop(rungindex)
+ self.Rungs.pop(rungindex)
+ if rungindex < len(self.Rungs):
+ next_bbox = self.Rungs[rungindex].GetBoundingBox()
+ self.RefreshRungs(bbox.y - next_bbox.y, rungindex)
+ self.SelectedElement = None
+
+ def DeleteWire(self, wire):
+ wires = []
+ left_elements = []
+ right_elements = []
+ if wire in self.Wires:
+ wires = [wire]
+ elif wire not in self.Elements:
+ for element in wire.GetElements():
+ if element in self.Wires:
+ wires.append(element)
+ else:
+ wires = []
+ break
+ if len(wires) > 0:
+ rungindex = self.FindRung(wires[0])
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ for wire in wires:
+ connections = wire.GetSelectedSegmentConnections()
+ left_block = wire.EndConnected.GetParentBlock()
+ if wire.EndConnected not in left_elements:
+ left_elements.append(wire.EndConnected)
+ if wire.StartConnected not in right_elements:
+ right_elements.append(wire.StartConnected)
+ if connections == (False, False) or connections == (False, True) and isinstance(left_block, LD_PowerRail):
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ rung.SelectElement(wire)
+ for left_element in left_elements:
+ left_block = left_element.GetParentBlock()
+ if isinstance(left_block, LD_PowerRail):
+ if len(left_element.GetWires()) == 0:
+ index = left_block.GetConnectorIndex(left_element)
+ left_block.DeleteConnector(index)
+ else:
+ connectors = left_block.GetConnectors()
+ output_connectors = []
+ if "outputs" in connectors:
+ output_connectors = connectors["outputs"]
+ if "output" in connectors:
+ output_connectors = [connectors["output"]]
+ for connector in output_connectors:
+ for wire, handle in connector.GetWires():
+ self.RefreshPosition(wire.StartConnected.GetParentBlock())
+ for right_element in right_elements:
+ self.RefreshPosition(right_element.GetParentBlock())
+ rung.RefreshBoundingBox()
+ new_bbox = rung.GetBoundingBox()
+ self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+ self.SelectedElement = None
+
+#-------------------------------------------------------------------------------
+# Refresh element position functions
+#-------------------------------------------------------------------------------
+
+ def RefreshPosition(self, element):
+ if isinstance(element, LD_PowerRail) and element.GetType() == LEFTRAIL:
+ element.RefreshModel()
+ return
+ connectors = element.GetConnectors()
+ input_connectors = []
+ output_connectors = []
+ if isinstance(element, LD_PowerRail) and element.GetType() == RIGHTRAIL:
+ input_connectors = connectors
+ else:
+ if "inputs" in connectors:
+ input_connectors = connectors["inputs"]
+ if "outputs" in connectors:
+ output_connectors = connectors["outputs"]
+ if "input" in connectors:
+ input_connectors = [connectors["input"]]
+ if "output" in connectors:
+ output_connectors = [connectors["output"]]
+ position = element.GetPosition()
+ minx = 0
+ onlyone = []
+ for connector in input_connectors:
+ onlyone.append(len(connector.GetWires()) == 1)
+ for wire, handle in connector.GetWires():
+ onlyone[-1] &= len(wire.EndConnected.GetWires()) == 1
+ leftblock = wire.EndConnected.GetParentBlock()
+ pos = leftblock.GetPosition()
+ size = leftblock.GetSize()
+ minx = max(minx, pos[0] + size[0])
+ if isinstance(element, LD_Coil):
+ interval = LD_WIRECOIL_SIZE
+ else:
+ interval = LD_WIRE_SIZE
+ if False in onlyone:
+ interval += LD_WIRE_SIZE
+ movex = minx + interval - position[0]
+ element.Move(movex, 0)
+ for i, connector in enumerate(input_connectors):
+ startpoint = connector.GetPosition(False)
+ previous_blocks = []
+ block_list = []
+ start_offset = 0
+ if not onlyone[i]:
+ middlepoint = minx + LD_WIRE_SIZE
+ for j, (wire, handle) in enumerate(connector.GetWires()):
+ block = wire.EndConnected.GetParentBlock()
+ endpoint = wire.EndConnected.GetPosition(False)
+ if j == 0:
+ if not onlyone[i] and wire.EndConnected.GetWireIndex(wire) > 0:
+ start_offset = endpoint.y - startpoint.y
+ offset = start_offset
+ else:
+ offset = start_offset + LD_LINE_SIZE * CalcBranchSize(previous_blocks, block)
+ if block in block_list:
+ wires = wire.EndConnected.GetWires()
+ endmiddlepoint = wires[0][0].StartConnected.GetPosition(False)[0] - LD_WIRE_SIZE
+ points = [startpoint, wxPoint(middlepoint, startpoint.y),
+ wxPoint(middlepoint, startpoint.y + offset),
+ wxPoint(endmiddlepoint, startpoint.y + offset),
+ wxPoint(endmiddlepoint, endpoint.y), endpoint]
+ else:
+ if startpoint.y + offset != endpoint.y:
+ if isinstance(block, LD_PowerRail):
+ index = block.GetConnectorIndex(wire.EndConnected)
+ if index:
+ diff = (startpoint.y - endpoint.y) / LD_LINE_SIZE
+ for k in xrange(abs(diff)):
+ if diff < 0:
+ block.DeleteConnector(index - 1 - k)
+ else:
+ block.InsertConnector(index + k, False)
+ else:
+ block.Move(0, startpoint.y + offset - endpoint.y)
+ self.RefreshPosition(block)
+ endpoint = wire.EndConnected.GetPosition(False)
+ if not onlyone[i]:
+ points = [startpoint, wxPoint(middlepoint, startpoint.y),
+ wxPoint(middlepoint, endpoint.y), endpoint]
+ else:
+ points = [startpoint, endpoint]
+ wire.SetPoints(points)
+ previous_blocks.append(block)
+ ExtractNextBlocks(block, block_list)
+ element.RefreshModel(False)
+ for connector in output_connectors:
+ for wire, handle in connector.GetWires():
+ self.RefreshPosition(wire.StartConnected.GetParentBlock())
+
+ def RefreshRungs(self, movey, fromidx):
+ if movey != 0:
+ for i in xrange(fromidx, len(self.Rungs)):
+ self.Comments[i].Move(0, movey)
+ self.Comments[i].RefreshModel()
+ self.Rungs[i].Move(0, movey)
+ for element in self.Rungs[i].GetElements():
+ if element in self.Blocks:
+ self.RefreshPosition(element)
+
+#-------------------------------------------------------------------------------
+# Edit element content functions
+#-------------------------------------------------------------------------------
+
+ def EditContactContent(self, contact):
+ dialog = LDElementDialog(self.Parent, "contact")
+ varlist = []
+ vars = self.Controler.GetCurrentElementEditingInterfaceVars()
+ if vars:
+ for var in vars:
+ if var["Type"] != "Output" and var["Value"] == "BOOL":
+ varlist.append(var["Name"])
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":contact.GetName(),"type":contact.GetType()})
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ contact.SetName(values["name"])
+ contact.SetType(values["type"])
+ contact.RefreshModel(False)
+ self.Refresh()
+ dialog.Destroy()
+
+ def EditCoilContent(self, coil):
+ dialog = LDElementDialog(self.Parent, "coil")
+ varlist = []
+ vars = self.Controler.GetCurrentElementEditingInterfaceVars()
+ if vars:
+ for var in vars:
+ if var["Type"] != "Input" and var["Value"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetCurrentElementEditingInterfaceReturnType()
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetCurrentElementEditingName())
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":coil.GetName(),"type":coil.GetType()})
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ coil.SetName(values["name"])
+ coil.SetType(values["type"])
+ coil.RefreshModel(False)
+ self.Refresh()
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Edit Ladder Element Properties Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_LDELEMENTDIALOG, wxID_LDELEMENTDIALOGMAINPANEL,
+ wxID_LDELEMENTDIALOGNAME, wxID_LDELEMENTDIALOGRADIOBUTTON1,
+ wxID_LDELEMENTDIALOGRADIOBUTTON2, wxID_LDELEMENTDIALOGRADIOBUTTON3,
+ wxID_LDELEMENTDIALOGRADIOBUTTON4, wxID_LDELEMENTDIALOGPREVIEW,
+ wxID_LDELEMENTDIALOGSTATICTEXT1, wxID_LDELEMENTDIALOGSTATICTEXT2,
+ wxID_LDELEMENTDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(11)]
+
+class LDElementDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt, title, labels):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_LDELEMENTDIALOG,
+ name='VariablePropertiesDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(350, 260), style=wx.DEFAULT_DIALOG_STYLE,
+ title=title)
+ self.SetClientSize(wx.Size(350, 260))
+
+ self.MainPanel = wx.Panel(id=wxID_LDELEMENTDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(340, 200), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_LDELEMENTDIALOGSTATICTEXT1,
+ label='Type:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(70, 17), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_LDELEMENTDIALOGSTATICTEXT2,
+ label='Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 150), size=wx.Size(70, 17), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_LDELEMENTDIALOGSTATICTEXT3,
+ label='Preview:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(174, 24), size=wx.Size(100, 17), style=0)
+
+ self.radioButton1 = wx.RadioButton(id=wxID_LDELEMENTDIALOGRADIOBUTTON1,
+ label=labels[0], name='radioButton1', parent=self.MainPanel,
+ pos=wx.Point(24, 48), size=wx.Size(114, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_LDELEMENTDIALOGRADIOBUTTON1, self.OnTypeChanged)
+ self.radioButton1.SetValue(True)
+
+ self.radioButton2 = wx.RadioButton(id=wxID_LDELEMENTDIALOGRADIOBUTTON2,
+ label=labels[1], name='radioButton2', parent=self.MainPanel,
+ pos=wx.Point(24, 72), size=wx.Size(128, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_LDELEMENTDIALOGRADIOBUTTON2, self.OnTypeChanged)
+
+ self.radioButton3 = wx.RadioButton(id=wxID_LDELEMENTDIALOGRADIOBUTTON3,
+ label=labels[2], name='radioButton3', parent=self.MainPanel,
+ pos=wx.Point(24, 96), size=wx.Size(114, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_LDELEMENTDIALOGRADIOBUTTON3, self.OnTypeChanged)
+
+ self.radioButton4 = wx.RadioButton(id=wxID_LDELEMENTDIALOGRADIOBUTTON4,
+ label=labels[3], name='radioButton4', parent=self.MainPanel,
+ pos=wx.Point(24, 120), size=wx.Size(128, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_LDELEMENTDIALOGRADIOBUTTON4, self.OnTypeChanged)
+
+ self.Name = wx.Choice(id=wxID_LDELEMENTDIALOGNAME,
+ name='Name', parent=self.MainPanel, pos=wx.Point(24, 174),
+ size=wx.Size(145, 24), style=0)
+ EVT_CHOICE(self, wxID_LDELEMENTDIALOGNAME, self.OnNameChanged)
+
+ self.Preview = wx.Panel(id=wxID_LDELEMENTDIALOGPREVIEW,
+ name='Preview', parent=self.MainPanel, pos=wx.Point(174, 48),
+ size=wx.Size(150, 150), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wxColour(255,255,255))
+
+ self._init_sizers()
+
+ def __init__(self, parent, type):
+ self.Type = type
+ if type == "contact":
+ self._init_ctrls(parent, "Edit Contact Values", ['Normal','Reverse','Rising Edge','Falling Edge'])
+ self.Element = LD_Contact(self.Preview, CONTACT_NORMAL, "")
+ elif type == "coil":
+ self._init_ctrls(parent, "Edit Coil Values", ['Normal','Reverse','Set','Reset'])
+ self.Element = LD_Coil(self.Preview, COIL_NORMAL, "")
+ self.Element.SetPosition((150 - LD_ELEMENT_SIZE[0]) / 2, (150 - LD_ELEMENT_SIZE[1]) / 2)
+
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ EVT_PAINT(self, self.OnPaint)
+
+ def SetVariables(self, vars):
+ self.Name.Clear()
+ for name in vars:
+ self.Name.Append(name)
+ self.Name.Enable(self.Name.GetCount() > 0)
+
+ def SetValues(self, values):
+ for name, value in values.items():
+ if name == "name":
+ self.Element.SetName(value)
+ self.Name.SetStringSelection(value)
+ elif name == "type":
+ self.Element.SetType(value)
+ if self.Type == "contact":
+ if value == CONTACT_NORMAL:
+ self.radioButton1.SetValue(True)
+ elif value == CONTACT_REVERSE:
+ self.radioButton2.SetValue(True)
+ elif value == CONTACT_RISING:
+ self.radioButton3.SetValue(True)
+ elif value == CONTACT_FALLING:
+ self.radioButton4.SetValue(True)
+ elif self.Type == "coil":
+ if value == COIL_NORMAL:
+ self.radioButton1.SetValue(True)
+ elif value == COIL_REVERSE:
+ self.radioButton2.SetValue(True)
+ elif value == COIL_SET:
+ self.radioButton3.SetValue(True)
+ elif value == COIL_RESET:
+ self.radioButton4.SetValue(True)
+
+ def GetValues(self):
+ values = {}
+ values["name"] = self.Element.GetName()
+ values["type"] = self.Element.GetType()
+ return values
+
+ def OnTypeChanged(self, event):
+ if self.Type == "contact":
+ if self.radioButton1.GetValue():
+ self.Element.SetType(CONTACT_NORMAL)
+ elif self.radioButton2.GetValue():
+ self.Element.SetType(CONTACT_REVERSE)
+ elif self.radioButton3.GetValue():
+ self.Element.SetType(CONTACT_RISING)
+ elif self.radioButton4.GetValue():
+ self.Element.SetType(CONTACT_FALLING)
+ elif self.Type == "coil":
+ if self.radioButton1.GetValue():
+ self.Element.SetType(COIL_NORMAL)
+ elif self.radioButton2.GetValue():
+ self.Element.SetType(COIL_REVERSE)
+ elif self.radioButton3.GetValue():
+ self.Element.SetType(COIL_SET)
+ elif self.radioButton4.GetValue():
+ self.Element.SetType(COIL_RESET)
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.Element.SetName(self.Name.GetStringSelection())
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ self.Element.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
diff -r 000000000000 -r b622defdfd98 PLCControler.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCControler.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1827 @@
+#!/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 minixsv import pyxsval
+import cPickle
+import os,sys,re
+
+from plcopen import plcopen
+from plcopen.structures import *
+from graphics.GraphicCommons import *
+from PLCGenerator import *
+
+[ITEM_UNEDITABLE, ITEM_PROJECT, ITEM_POU, ITEM_CLASS, ITEM_VARIABLE,
+ ITEM_TRANSITION, ITEM_ACTION, ITEM_CONFIGURATION, ITEM_RESOURCE] = range(9)
+
+#-------------------------------------------------------------------------------
+# Undo Buffer for PLCOpenEditor
+#-------------------------------------------------------------------------------
+
+# Length of the buffer
+UNDO_BUFFER_LENGTH = 20
+
+"""
+Class implementing a buffer of changes made on the current editing Object Dictionary
+"""
+class UndoBuffer:
+
+ # Constructor initialising buffer
+ def __init__(self, currentstate, issaved = False):
+ self.Buffer = []
+ self.CurrentIndex = -1
+ self.MinIndex = -1
+ self.MaxIndex = -1
+ # if current state is defined
+ if currentstate:
+ self.CurrentIndex = 0
+ self.MinIndex = 0
+ self.MaxIndex = 0
+ # Initialising buffer with currentstate at the first place
+ for i in xrange(UNDO_BUFFER_LENGTH):
+ if i == 0:
+ self.Buffer.append(currentstate)
+ else:
+ self.Buffer.append(None)
+ # Initialising index of state saved
+ if issaved:
+ self.LastSave = 0
+ else:
+ self.LastSave = -1
+
+ # Add a new state in buffer
+ def Buffering(self, currentstate):
+ self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH
+ self.Buffer[self.CurrentIndex] = currentstate
+ # Actualising buffer limits
+ self.MaxIndex = self.CurrentIndex
+ if self.MinIndex == self.CurrentIndex:
+ # If the removed state was the state saved, there is no state saved in the buffer
+ if self.LastSave == self.MinIndex:
+ self.LastSave = -1
+ self.MinIndex = (self.MinIndex + 1) % UNDO_BUFFER_LENGTH
+ self.MinIndex = max(self.MinIndex, 0)
+
+ # Return current state of buffer
+ def Current(self):
+ return self.Buffer[self.CurrentIndex]
+
+ # Change current state to previous in buffer and return new current state
+ def Previous(self):
+ if self.CurrentIndex != self.MinIndex:
+ self.CurrentIndex = (self.CurrentIndex - 1) % UNDO_BUFFER_LENGTH
+ return self.Buffer[self.CurrentIndex]
+ return None
+
+ # Change current state to next in buffer and return new current state
+ def Next(self):
+ if self.CurrentIndex != self.MaxIndex:
+ self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH
+ return self.Buffer[self.CurrentIndex]
+ return None
+
+ # Return True if current state is the first in buffer
+ def IsFirst(self):
+ return self.CurrentIndex == self.MinIndex
+
+ # Return True if current state is the last in buffer
+ def IsLast(self):
+ return self.CurrentIndex == self.MaxIndex
+
+ # Note that current state is saved
+ def CurrentSaved(self):
+ self.LastSave = self.CurrentIndex
+
+ # Return True if current state is saved
+ def IsCurrentSaved(self):
+ return self.LastSave == self.CurrentIndex
+
+
+#-------------------------------------------------------------------------------
+# Controler for PLCOpenEditor
+#-------------------------------------------------------------------------------
+
+"""
+Class which controls the operations made on the plcopen model and answers to view requests
+"""
+class PLCControler:
+
+ # Create a new PLCControler
+ def __init__(self):
+ self.LastNewIndex = 0
+ self.Reset()
+
+ # Reset PLCControler internal variables
+ def Reset(self):
+ self.Project = None
+ self.ProjectBuffer = None
+ self.FilePath = ""
+ self.FileName = ""
+ self.ElementsOpened = []
+ self.CurrentElementEditing = None
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ def GetQualifierTypes(self):
+ return plcopen.QualifierList
+
+
+#-------------------------------------------------------------------------------
+# Project management functions
+#-------------------------------------------------------------------------------
+
+ # Return if a project is opened
+ def HasOpenedProject(self):
+ return self.Project != None
+
+ # Create a new project by replacing the current one
+ def CreateNewProject(self, name):
+ # Create the project
+ self.Project = plcopen.project()
+ self.Project.setName(name)
+ # Initialize the project buffer
+ self.ProjectBuffer = UndoBuffer(self.Copy(self.Project))
+
+ # Change project name
+ def SetProjectName(self, name):
+ self.Project.setName(name)
+
+ # Return project name
+ def GetProjectName(self):
+ return self.Project.getName()
+
+ # Return project pou names
+ def GetProjectPouNames(self):
+ return [pou.getName() for pou in self.Project.getPous()]
+
+ # Return project pou names
+ def GetProjectConfigNames(self):
+ return [config.getName() for config in self.Project.getConfigurations()]
+
+ # Return file path if project is an open file
+ def GetFilePath(self):
+ return self.FilePath
+
+ # Return file name and point out if file is up to date
+ def GetFilename(self):
+ if self.ProjectBuffer.IsCurrentSaved():
+ return self.FileName
+ else:
+ return "~%s~"%self.FileName
+
+ # Change file path and save file name or create a default one if file path not defined
+ def SetFilePath(self, filepath):
+ self.FilePath = filepath
+ if filepath == "":
+ self.LastNewIndex += 1
+ self.FileName = "Unnamed%d"%self.LastNewIndex
+ else:
+ self.FileName = os.path.splitext(os.path.basename(filepath))[0]
+
+ # Change project properties
+ def SetProjectProperties(self, values):
+ self.Project.setFileHeader(values)
+
+ # Return project properties
+ def GetProjectProperties(self):
+ return self.Project.getFileHeader()
+
+ # Return project informations
+ def GetProjectInfos(self):
+ if self.Project:
+ infos = {"name": self.Project.getName(), "type": ITEM_PROJECT}
+ pou_types = {"function": {"name": "Functions", "type": ITEM_UNEDITABLE, "values":[]},
+ "functionBlock": {"name": "Function Blocks", "type": ITEM_UNEDITABLE, "values":[]},
+ "program": {"name": "Programs", "type": ITEM_UNEDITABLE, "values":[]}}
+ for pou in self.Project.getPous():
+ pou_type = pou.getPouType().getValue()
+ pou_infos = {"name": pou.getName(), "type": ITEM_POU}
+ var_types = {"Input": {"name": "Input", "type": ITEM_CLASS, "values": []},
+ "Output": {"name": "Output", "type": ITEM_CLASS, "values": []},
+ "InOut": {"name": "InOut", "type": ITEM_CLASS, "values": []},
+ "External": {"name": "External", "type": ITEM_CLASS, "values": []},
+ "Local": {"name": "Local", "type": ITEM_CLASS, "values": []},
+ "Temp": {"name": "Temp", "type": ITEM_CLASS, "values": []},
+ "Global": {"name": "Global", "type": ITEM_CLASS, "values": []}}
+ for var in self.GetPouInterfaceVars(pou):
+ var_values = {"name": var["Name"], "type": ITEM_VARIABLE, "values": []}
+ if var["Class"] in var_types.keys():
+ var_types[var["Class"]]["values"].append(var_values)
+ pou_values = []
+ pou_values.append({"name": "Interface", "type": ITEM_CLASS,
+ "values": [var_types["Input"], var_types["Output"], var_types["InOut"], var_types["External"]]})
+ pou_values.append({"name": "Variables", "type": ITEM_CLASS,
+ "values": [var_types["Local"], var_types["Temp"]]})
+ if pou_type == "program":
+ pou_values.append(var_types["Global"])
+ if pou.getBodyType() == "SFC":
+ transitions = []
+ for transition in pou.getTransitionList():
+ transitions.append({"name": transition.getName(), "type": ITEM_TRANSITION, "values": []})
+ pou_values.append({"name": "Transitions", "type": ITEM_UNEDITABLE, "values": transitions})
+ actions = []
+ for action in pou.getActionList():
+ actions.append({"name": action.getName(), "type": ITEM_ACTION, "values": []})
+ pou_values.append({"name": "Actions", "type": ITEM_UNEDITABLE, "values": actions})
+ if pou_type in pou_types:
+ pou_infos["values"] = pou_values
+ pou_types[pou_type]["values"].append(pou_infos)
+ configurations = {"name": "Configurations", "type": ITEM_UNEDITABLE, "values": []}
+ for config in self.Project.getConfigurations():
+ config_name = config.getName()
+ config_infos = {"name": config_name, "type": ITEM_CONFIGURATION, "values": []}
+ config_vars = {"name": "Global", "type": ITEM_CLASS, "values": []}
+ for var in self.GetConfigurationGlobalVars(config_name):
+ var_values = {"name": var["Name"], "type": ITEM_VARIABLE, "values": []}
+ config_vars["values"].append(var_values)
+ resources = {"name": "Resources", "type": ITEM_UNEDITABLE, "values": []}
+ for resource in config.getResource():
+ resource_name = resource.getName()
+ resource_infos = {"name": resource_name, "type": ITEM_RESOURCE, "values": []}
+ resource_vars = {"name": "Global", "type": ITEM_CLASS, "values": []}
+ for var in self.GetConfigurationResourceGlobalVars(config_name, resource_name):
+ var_values = {"name": var["Name"], "type": ITEM_VARIABLE, "values": []}
+ resource_vars["values"].append(var_values)
+ resource_infos["values"].append(resource_vars)
+ resources["values"].append(resource_infos)
+ config_infos["values"] = [config_vars, resources]
+ configurations["values"].append(config_infos)
+ infos["values"] = [{"name": "Properties", "type": ITEM_UNEDITABLE, "values": []},
+ pou_types["function"], pou_types["functionBlock"],
+ pou_types["program"], configurations]
+ return infos
+ return None
+
+ # Refresh the tree of user-defined pou cross-use
+ def RefreshPouUsingTree(self):
+ # Reset the tree of user-defined pou cross-use
+ self.PouUsingTree = {}
+ if self.Project:
+ pous = self.Project.getPous()
+ # Reference all the user-defined pou names and initialize the tree of
+ # user-defined pou cross-use
+ pounames = [pou.getName() for pou in pous]
+ for name in pounames:
+ self.PouUsingTree[name] = []
+ # Analyze each pou
+ for pou in pous:
+ name = pou.getName()
+ bodytype = pou.getBodyType()
+ # If pou is written in a graphical language
+ if bodytype in ["FBD","LD","SFC"]:
+ # Analyze each instance of the pou
+ for instance in pou.getInstances():
+ if isinstance(instance, plcopen.block):
+ typename = instance.getTypeName()
+ # Update tree if there is a cross-use
+ if typename in pounames and name not in self.PouUsingTree[typename]:
+ self.PouUsingTree[typename].append(name)
+ # If pou is written in a textual language
+ elif bodytype in ["IL", "ST"]:
+ text = pou.getText()
+ # Search if each pou is mentioned in the pou text
+ for typename in pounames:
+ typename_model = re.compile("[ \t\n]%s[ \t\n]"%typename)
+ # Update tree if there is a cross-use
+ if typename != name and typename_model.search(text):
+ self.PouUsingTree[typename].append(name)
+
+ # Return if pou given by name is used by another pou
+ def PouIsUsed(self, name):
+ if name in self.PouUsingTree:
+ return len(self.PouUsingTree[name]) > 0
+ return False
+
+ # Return if pou given by name is directly or undirectly used by the reference pou
+ def PouIsUsedBy(self, name, reference):
+ if name in self.PouUsingTree:
+ list = self.PouUsingTree[name]
+ # Test if pou is directly used by reference
+ if reference in list:
+ return True
+ else:
+ # Test if pou is undirectly used by reference, by testing if pous
+ # that directly use pou is directly or undirectly used by reference
+ used = False
+ for element in list:
+ used |= self.PouIsUsedBy(element, reference)
+ return used
+ return False
+
+ def GenerateProgram(self):
+ if self.Project:
+ program = GenerateCurrentProgram(self.Project)
+ programfile = open("test.st", "w")
+ programfile.write(program)
+ programfile.close()
+
+#-------------------------------------------------------------------------------
+# Project Pous management functions
+#-------------------------------------------------------------------------------
+
+ # Add a Pou to Project
+ def ProjectAddPou(self, name, pou_type, body_type):
+ # Add the pou to project
+ self.Project.appendPou(name, pou_type, body_type)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Remove a pou from project
+ def ProjectRemovePou(self, name):
+ removed = None
+ # Search if the pou removed is currently opened
+ for i, pou in enumerate(self.ElementsOpened):
+ if pou == name:
+ removed = i
+ # If found, remove pou from list of opened pous and actualize current edited
+ if removed != None:
+ self.ElementsOpened.pop(removed)
+ if self.CurrentElementEditing > removed:
+ self.CurrentElementEditing -= 1
+ if len(self.ElementsOpened) > 0:
+ self.CurrentElementEditing = max(0, min(self.CurrentElementEditing, len(self.ElementsOpened) - 1))
+ else:
+ self.CurrentElementEditing = None
+ # Remove pou from project
+ self.Project.removePou(name)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Add a configuration to Project
+ def ProjectAddConfiguration(self, name):
+ self.Project.addConfiguration(name)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Remove a configuration from project
+ def ProjectRemoveConfiguration(self, name):
+ self.Project.removeConfiguration(name)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Add a resource to a configuration of the Project
+ def ProjectAddConfigurationResource(self, config, name):
+ self.Project.addConfigurationResource(config, name)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Remove a resource from a configuration of the project
+ def ProjectRemoveConfigurationResource(self, config, name):
+ self.Project.removeConfigurationResource(config, name)
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Add a Transition to a Project Pou
+ def ProjectAddPouTransition(self, pou_name, transition_name, transition_type):
+ pou = self.Project.getPou(pou_name)
+ pou.addTransition(transition_name, transition_type)
+
+ # Add a Transition to a Project Pou
+ def ProjectAddPouAction(self, pou_name, action_name, action_type):
+ pou = self.Project.getPou(pou_name)
+ pou.addAction(action_name, action_type)
+
+ # Change the name of a pou
+ def ChangePouName(self, old_name, new_name):
+ # Found the pou corresponding to old name and change its name to new name
+ pou = self.Project.getPou(old_name)
+ pou.setName(new_name)
+ # If pou is currently opened, change its name in the list of opened pous
+ if old_name in self.ElementsOpened:
+ idx = self.ElementsOpened.index(old_name)
+ self.ElementsOpened[idx] = new_name
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ # Change the name of a pou transition
+ def ChangePouTransitionName(self, pou_name, old_name, new_name):
+ # Found the pou transition corresponding to old name and change its name to new name
+ pou = self.Project.getPou(pou_name)
+ transition = pou.getTransition(old_name)
+ transition.setName(new_name)
+ # If pou transition is currently opened, change its name in the list of opened elements
+ old_computedname = self.ComputePouTransitionName(pou_name, old_name)
+ new_computedname = self.ComputePouTransitionName(pou_name, new_name)
+ if old_computedname in self.ElementsOpened:
+ idx = self.ElementsOpened.index(old_computedname)
+ self.ElementsOpened[idx] = new_computedname
+
+ # Change the name of a pou action
+ def ChangePouActionName(self, pou_name, old_name, new_name):
+ # Found the pou action corresponding to old name and change its name to new name
+ pou = self.Project.getPou(pou_name)
+ action = pou.getAction(old_name)
+ action.setName(new_name)
+ # If pou action is currently opened, change its name in the list of opened elements
+ old_computedname = self.ComputePouActionName(pou_name, old_name)
+ new_computedname = self.ComputePouActionName(pou_name, new_name)
+ if old_computedname in self.ElementsOpened:
+ idx = self.ElementsOpened.index(old_computedname)
+ self.ElementsOpened[idx] = new_computedname
+
+ # Change the name of a configuration
+ def ChangeConfigurationName(self, old_name, new_name):
+ # Found the configuration corresponding to old name and change its name to new name
+ configuration = self.Project.getConfiguration(old_name)
+ configuration.setName(new_name)
+ # If configuration is currently opened, change its name in the list of opened elements
+ for idx, element in enumerate(self.ElementsOpened):
+ self.ElementsOpened[idx] = element.replace(old_name, new_name)
+
+ # Change the name of a configuration resource
+ def ChangeConfigurationResourceName(self, config_name, old_name, new_name):
+ # Found the resource corresponding to old name and change its name to new name
+ resource = self.Project.getConfigurationResource(config_name)
+ resource.setName(new_name)
+ # If resource is currently opened, change its name in the list of opened elements
+ old_computedname = self.ComputeConfigurationResourceName(config_name, old_name)
+ new_computedname = self.ComputeConfigurationResourceName(config_name, new_name)
+ if old_computedname in self.ElementsOpened:
+ idx = self.ElementsOpened.index(old_computedname)
+ self.ElementsOpened[idx] = new_computedname
+
+ # Return the type of the pou given by its name
+ def GetPouType(self, name):
+ # Found the pou correponding to name and return its type
+ pou = self.Project.getPou(name)
+ return pou.pouType.getValue()
+
+ # Return pous with SFC language
+ def GetSFCPous(self):
+ list = []
+ if self.Project:
+ for pou in self.Project.getPous():
+ if pou.getBodyType() == "SFC":
+ list.append(pou.getName())
+ return list
+
+ # Return the body language of the pou given by its name
+ def GetPouBodyType(self, name):
+ # Found the pou correponding to name and return its body language
+ pou = self.Project.getPou(name)
+ return pou.getBodyType()
+
+ # Return the body language of the transition given by its name
+ def GetTransitionBodyType(self, pou_name, pou_transition):
+ # Found the pou correponding to name and return its body language
+ pou = self.Project.getPou(pou_name)
+ transition = pou.getTransition(pou_transition)
+ return transition.getBodyType()
+
+ # Return the body language of the pou given by its name
+ def GetActionBodyType(self, pou_name, pou_action):
+ # Found the pou correponding to name and return its body language
+ pou = self.Project.getPou(pou_name)
+ action = pou.getAction(pou_action)
+ return action.getBodyType()
+
+ # Extract varlists from a list of vars
+ def ExtractVarLists(self, vars):
+ varlist_list = []
+ current_varlist = None
+ current_type = None
+ for var in vars:
+ if current_type != (var["Class"], var["Retain"], var["Constant"]):
+ current_type = (var["Class"], var["Retain"], var["Constant"])
+ current_varlist = plcopen.varList()
+ varlist_list.append((var["Class"], current_varlist))
+ if var["Retain"] == "Yes":
+ varlist.setRetain(True)
+ if var["Constant"] == "Yes":
+ varlist.setConstant(True)
+ # Create variable and change its properties
+ tempvar = plcopen.varListPlain_variable()
+ tempvar.setName(var["Name"])
+ var_type = plcopen.dataType()
+ var_type.setValue(var["Type"])
+ tempvar.setType(var_type)
+ if var["Initial Value"] != "":
+ value = plcopen.value()
+ value.setValue(var["Initial Value"])
+ tempvar.setInitialValue(value)
+ if var["Location"] != "":
+ tempvar.setAddress(var["Location"])
+ # Add variable to varList
+ current_varlist.appendVariable(tempvar)
+ return varlist_list
+
+ # Replace the configuration globalvars by those given
+ def SetConfigurationGlobalVars(self, name, vars):
+ # Found the configuration corresponding to name
+ configuration = self.Project.getConfiguration(name)
+ if configuration:
+ # Set configuration global vars
+ configuration.setGlobalVars([])
+ for vartype, varlist in self.ExtractVarLists(vars):
+ configuration.globalVars.append(varlist)
+ self.RefreshBlockTypes()
+
+ # Return the configuration globalvars
+ def GetConfigurationGlobalVars(self, name):
+ vars = []
+ # Found the configuration corresponding to name
+ configuration = self.Project.getConfiguration(name)
+ if configuration:
+ # Extract variables from every varLists
+ for varlist in configuration.getGlobalVars():
+ for var in varlist.getVariable():
+ tempvar = {"Name":var.getName(),"Class":"Global","Type":var.getType().getValue(),
+ "Location":var.getAddress()}
+ initial = var.getInitialValue()
+ if initial:
+ tempvar["Initial Value"] = initial.getValue()
+ else:
+ tempvar["Initial Value"] = ""
+ if varlist.getRetain():
+ tempvar["Retain"] = "Yes"
+ else:
+ tempvar["Retain"] = "No"
+ if varlist.getConstant():
+ tempvar["Constant"] = "Yes"
+ else:
+ tempvar["Constant"] = "No"
+ vars.append(tempvar)
+ return vars
+
+ # Replace the resource globalvars by those given
+ def SetConfigurationResourceGlobalVars(self, config_name, name, vars):
+ # Found the resource corresponding to name
+ resource = self.Project.getConfigurationResource(config_name, name)
+ # Set resource global vars
+ if resource:
+ resource.setGlobalVars([])
+ for vartype, varlist in self.ExtractVarLists(vars):
+ resource.globalVars.append(varlist)
+ self.RefreshBlockTypes()
+
+ # Return the resource globalvars
+ def GetConfigurationResourceGlobalVars(self, config_name, name):
+ vars = []
+ # Found the resource corresponding to name
+ resource = self.Project.getConfigurationResource(config_name, name)
+ if resource:
+ # Extract variables from every varLists
+ for varlist in resource.getGlobalVars():
+ for var in varlist.getVariable():
+ tempvar = {"Name":var.getName(),"Class":"Global","Type":var.getType().getValue(),
+ "Location":var.getAddress()}
+ initial = var.getInitialValue()
+ if initial:
+ tempvar["Initial Value"] = initial.getValue()
+ else:
+ tempvar["Initial Value"] = ""
+ if varlist.getRetain():
+ tempvar["Retain"] = "Yes"
+ else:
+ tempvar["Retain"] = "No"
+ if varlist.getConstant():
+ tempvar["Constant"] = "Yes"
+ else:
+ tempvar["Constant"] = "No"
+ vars.append(tempvar)
+ return vars
+
+ # Return the interface of the pou given by its name
+ def GetPouInterfaceVarsByName(self, name):
+ # Found the pou correponding to name and return the interface
+ return self.GetPouInterfaceVars(self.Project.getPou(name))
+
+ # Return the interface for the given pou
+ def GetPouInterfaceVars(self, pou):
+ vars = []
+ # Verify that the pou has an interface
+ if pou.interface:
+ # Extract variables from every varLists
+ for type, varlist in pou.getVars():
+ for var in varlist.getVariable():
+ tempvar = {"Name":var.getName(),"Class":type,"Type":var.getType().getValue(),
+ "Location":var.getAddress()}
+ initial = var.getInitialValue()
+ if initial:
+ tempvar["Initial Value"] = initial.getValue()
+ else:
+ tempvar["Initial Value"] = ""
+ if varlist.getRetain():
+ tempvar["Retain"] = "Yes"
+ else:
+ tempvar["Retain"] = "No"
+ if varlist.getConstant():
+ tempvar["Constant"] = "Yes"
+ else:
+ tempvar["Constant"] = "No"
+ vars.append(tempvar)
+ return vars
+
+ # Replace the Pou interface by the one given
+ def SetPouInterfaceVars(self, name, vars):
+ # Found the pou corresponding to name and add interface if there isn't one yet
+ pou = self.Project.getPou(name)
+ if not pou.interface:
+ pou.interface = plcopen.pou_interface()
+ # Set Pou interface
+ pou.setVars(self.ExtractVarLists(vars))
+ self.RefreshBlockTypes()
+
+ # Replace the return type of the pou given by its name (only for functions)
+ def SetPouInterfaceReturnType(self, name, type):
+ pou = self.Project.getPou(name)
+ if not pou.interface:
+ pou.interface = plcopen.pou_interface()
+ # If there isn't any return type yet, add it
+ return_type = pou.interface.getReturnType()
+ if not return_type:
+ return_type = plcopen.dataType()
+ pou.interface.setReturnType(return_type)
+ # Change return type
+ return_type.setValue(type)
+ self.RefreshBlockTypes()
+
+ # Return the return type of the pou given by its name
+ def GetPouInterfaceReturnTypeByName(self, name):
+ # Found the pou correponding to name and return the return type
+ return self.GetPouInterfaceReturnType(self.Project.getPou(name))
+
+ # Return the return type of the given pou
+ def GetPouInterfaceReturnType(self, pou):
+ # Verify that the pou has an interface
+ if pou.interface:
+ # Return the return type if there is one
+ return_type = pou.interface.getReturnType()
+ if return_type:
+ return return_type.getValue()
+ return None
+
+ # Update Block types with user-defined pou added
+ def RefreshBlockTypes(self):
+ if BlockTypes[-1]["name"] == "User-defined POUs":
+ BlockTypes[-1]["list"] = []
+ else:
+ BlockTypes.append({"name" : "User-defined POUs", "list": []})
+ if self.Project:
+ for pou in self.Project.getPous():
+ pou_name = pou.getName()
+ pou_type = pou.pouType.getValue()
+ if pou_type != "program":
+ block_infos = {"name" : pou_name, "type" : pou_type, "extensible" : False,
+ "inputs" : [], "outputs" : [], "comment" : ""}
+ if pou.getInterface():
+ for type, varlist in pou.getVars():
+ if type == "InOut":
+ for var in varlist.getVariable():
+ block_infos["inputs"].append((var.getName(),var.getType().getValue(),"none"))
+ block_infos["outputs"].append((var.getName(),var.getType().getValue(),"none"))
+ elif type == "Input":
+ for var in varlist.getVariable():
+ block_infos["inputs"].append((var.getName(),var.getType().getValue(),"none"))
+ elif type == "Output":
+ for var in varlist.getVariable():
+ block_infos["outputs"].append((var.getName(),var.getType().getValue(),"none"))
+ return_type = pou.interface.getReturnType()
+ if return_type:
+ block_infos["outputs"].append(("",return_type.getValue(),"none"))
+ if pou.getBodyType() in ["FBD","LD","SFC"]:
+ for instance in pou.getInstances():
+ if isinstance(instance, plcopen.comment):
+ block_infos["comment"] = instance.getContentText()
+ BlockTypes[-1]["list"].append(block_infos)
+
+ # Return Block types checking for recursion
+ def GetBlockTypes(self):
+ if self.CurrentElementEditing != None:
+ current_name = self.ElementsOpened[self.CurrentElementEditing]
+ words = current_name.split("::")
+ if len(words) == 1:
+ name = current_name
+ else:
+ name = words[1]
+ blocktypes = [category for category in BlockTypes[:-1]]
+ blocktypes.append({"name" : "User-defined POUs", "list": []})
+ if self.Project:
+ pou = self.Project.getPou(name)
+ name = pou.getName()
+ type = pou.pouType.getValue()
+ for blocktype in BlockTypes[-1]["list"]:
+ if blocktype["name"] != name and not self.PouIsUsedBy(name, blocktype["name"]) and not (type == "function" and blocktype["type"] == "functionBlock"):
+ blocktypes[-1]["list"].append(blocktype)
+ return blocktypes
+ return []
+
+ # Return Block types checking for recursion
+ def GetBlockResource(self):
+ blocktypes = []
+ for category in BlockTypes[:-1]:
+ for blocktype in category["list"]:
+ if blocktype["type"] != "function":
+ blocktypes.append(blocktype["name"])
+ if self.Project:
+ for pou in self.Project.getPous():
+ if pou.pouType.getValue() != "function":
+ blocktypes.append(pou.getName())
+ return blocktypes
+
+#-------------------------------------------------------------------------------
+# Project opened Pous management functions
+#-------------------------------------------------------------------------------
+
+ # Return the list of pou names
+ def GetElementsOpenedNames(self):
+ names = []
+ for pou_name in self.ElementsOpened:
+ words = pou_name.split("::")
+ if len(words) == 1:
+ names.append(pou_name)
+ else:
+ names.append("%s-%s"%(words[1],words[2]))
+ return names
+
+ # Compute a pou transition name
+ def ComputePouTransitionName(self, pou, transition):
+ return "T::%s::%s" % (pou, transition)
+
+ # Compute a pou action name
+ def ComputePouActionName(self, pou, action):
+ return "A::%s::%s" % (pou, action)
+
+ # Compute a pou name
+ def ComputeConfigurationResourceName(self, config, resource):
+ return "R::%s::%s" % (config, resource)
+
+ # Open a pou by giving its name
+ def OpenElementEditing(self, name):
+ # If pou not opened yet
+ if name not in self.ElementsOpened:
+ # Add pou name to list of pou opened and make it current editing
+ self.ElementsOpened.append(name)
+ self.CurrentElementEditing = len(self.ElementsOpened) - 1
+ return self.CurrentElementEditing
+ return None
+
+ # Open a pou transition by giving pou and transition names
+ def OpenPouTransitionEditing(self, pou, transition):
+ return self.OpenElementEditing(self.ComputePouTransitionName(pou, transition))
+
+ # Open a pou action by giving pou and action names
+ def OpenPouActionEditing(self, pou, action):
+ return self.OpenElementEditing(self.ComputePouActionName(pou, action))
+
+ # Open a configuration resource by giving configuration and resource names
+ def OpenConfigurationResourceEditing(self, config, resource):
+ return self.OpenElementEditing(self.ComputeConfigurationResourceName(config, resource))
+
+ # Return if pou given by name is opened
+ def IsElementEditing(self, name):
+ return name in self.ElementsOpened
+
+ # Return if pou transition given by pou and transition names is opened
+ def IsPouTransitionEditing(self, pou, transition):
+ return self.ComputePouTransitionName(pou, transition) in self.ElementsOpened
+
+ # Return if pou action given by pou and action names is opened
+ def IsPouActionEditing(self, pou, action):
+ return self.ComputePouActionName(pou, action) in self.ElementsOpened
+
+ # Return if pou action given by pou and action names is opened
+ def IsConfigurationResourceEditing(self, pou, action):
+ return self.ComputeConfigurationResourceName(config, resource) in self.ElementsOpened
+
+ # Close current pou editing
+ def CloseElementEditing(self):
+ # Remove pou from list of pou opened
+ self.ElementsOpened.pop(self.CurrentElementEditing)
+ # Update index of current pou editing
+ if len(self.ElementsOpened) > 0:
+ self.CurrentElementEditing = min(self.CurrentElementEditing, len(self.ElementsOpened) - 1)
+ else:
+ self.CurrentElementEditing = None
+
+ # Change current pou editing for pou given by name
+ def ChangeElementEditing(self, name):
+ # Verify that pou is opened
+ if name in self.ElementsOpened:
+ # Change current pou editing
+ self.CurrentElementEditing = self.ElementsOpened.index(name)
+ return self.CurrentElementEditing
+ return None
+
+ # Change current pou editing for transition given by pou and transition names
+ def ChangePouTransitionEditing(self, pou, transition):
+ return self.ChangeElementEditing(self.ComputePouTransitionName(pou, transition))
+
+ # Change current pou editing for action given by pou and action names
+ def ChangePouActionEditing(self, pou, action):
+ return self.ChangeElementEditing(self.ComputePouActionName(pou, action))
+
+ # Change current pou editing for action given by configuration and resource names
+ def ChangeConfigurationResourceEditing(self, config, resource):
+ return self.ChangeElementEditing(self.ComputeConfigurationResourceName(config, resource))
+
+#-------------------------------------------------------------------------------
+# Project opened Pous management functions
+#-------------------------------------------------------------------------------
+
+ # Return current pou editing
+ def GetCurrentElementEditing(self):
+ # Verify that there's one editing and return it
+ if self.CurrentElementEditing != None:
+ name = self.ElementsOpened[self.CurrentElementEditing]
+ words = name.split("::")
+ if len(words) == 1:
+ return self.Project.getPou(name)
+ else:
+ if words[0] in ['T', 'A']:
+ pou = self.Project.getPou(words[1])
+ if words[0] == 'T':
+ return pou.getTransition(words[2])
+ elif words[0] == 'A':
+ return pou.getAction(words[2])
+ elif words[0] == 'R':
+ result = self.Project.getConfigurationResource(words[1], words[2])
+ return result
+ return None
+
+ # Return current pou editing name
+ def GetCurrentElementEditingName(self):
+ # Verify that there's one editing and return its name
+ if self.CurrentElementEditing != None:
+ name = self.ElementsOpened[self.CurrentElementEditing]
+ words = name.split("::")
+ if len(words) == 1:
+ return name
+ else:
+ return words[2]
+ return None
+
+ # Replace the index of current pou editing by the one given
+ def RefreshCurrentElementEditing(self, index):
+ self.CurrentElementEditing = index
+
+ # Return language in which current pou editing is written
+ def GetCurrentElementEditingBodyType(self):
+ if self.CurrentElementEditing != None:
+ name = self.ElementsOpened[self.CurrentElementEditing]
+ words = name.split("::")
+ if len(words) == 1:
+ return self.GetPouBodyType(name)
+ else:
+ if words[0] == 'T':
+ return self.GetTransitionBodyType(words[1], words[2])
+ elif words[0] == 'A':
+ return self.GetActionBodyType(words[1], words[2])
+ return None
+
+ # Return the variables of the current pou editing
+ def GetCurrentElementEditingInterfaceVars(self):
+ if self.CurrentElementEditing != None:
+ current_name = self.ElementsOpened[self.CurrentElementEditing]
+ words = current_name.split("::")
+ if len(words) == 1:
+ pou = self.Project.getPou(current_name)
+ return self.GetPouInterfaceVars(pou)
+ else:
+ pou = self.Project.getPou(words[1])
+ return self.GetPouInterfaceVars(pou)
+ return []
+
+ # Return the return type of the current pou editing
+ def GetCurrentElementEditingInterfaceReturnType(self):
+ if self.CurrentElementEditing != None:
+ current_name = self.ElementsOpened[self.CurrentElementEditing]
+ words = current_name.split("::")
+ if len(words) == 1:
+ pou = self.Project.getPou(current_name)
+ return self.GetPouInterfaceReturnType(pou)
+ elif words[0] == 'T':
+ return "BOOL"
+ return None
+
+ # Change the text of the current pou editing
+ def SetCurrentElementEditingText(self, text):
+ if self.CurrentElementEditing != None:
+ self.GetCurrentElementEditing().setText(text)
+ self.RefreshPouUsingTree()
+
+ # Return the current pou editing text
+ def GetCurrentElementEditingText(self):
+ if self.CurrentElementEditing != None:
+ return self.GetCurrentElementEditing().getText()
+ return ""
+
+ # Return the current pou editing transitions
+ def GetCurrentElementEditingTransitions(self):
+ if self.CurrentElementEditing != None:
+ pou = self.GetCurrentElementEditing()
+ if pou.getBodyType() == "SFC":
+ transitions = []
+ for transition in pou.getTransitionList():
+ transitions.append(transition.getName())
+ return transitions
+ return []
+
+ # Return the current pou editing transitions
+ def GetCurrentElementEditingActions(self):
+ if self.CurrentElementEditing != None:
+ pou = self.GetCurrentElementEditing()
+ if pou.getBodyType() == "SFC":
+ actions = []
+ for action in pou.getActionList():
+ actions.append(action.getName())
+ return actions
+ return []
+
+ # Return the current pou editing informations
+ def GetCurrentElementEditingInstanceInfos(self, id = None, exclude = []):
+ infos = {}
+ # if id is defined
+ if id != None:
+ instance = self.GetCurrentElementEditing().getInstance(id)
+ else:
+ instance = self.GetCurrentElementEditing().getRandomInstance(exclude)
+ if instance:
+ if id != None:
+ infos["id"] = id
+ else:
+ infos["id"] = instance.getLocalId()
+ infos["x"] = instance.getX()
+ infos["y"] = instance.getY()
+ infos["height"] = instance.getHeight()
+ infos["width"] = instance.getWidth()
+ if isinstance(instance, plcopen.block):
+ infos["name"] = instance.getInstanceName()
+ infos["type"] = instance.getTypeName()
+ infos["connectors"] = {"inputs":[],"outputs":[]}
+ for variable in instance.inputVariables.getVariable():
+ connector = {}
+ connector["position"] = variable.connectionPointIn.getRelPosition()
+ connector["negated"] = variable.getNegated()
+ connector["edge"] = variable.getConnectorEdge()
+ connector["links"] = []
+ connections = variable.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ connector["links"].append(dic)
+ infos["connectors"]["inputs"].append(connector)
+ for variable in instance.outputVariables.getVariable():
+ connector = {}
+ connector["position"] = variable.connectionPointOut.getRelPosition()
+ connector["negated"] = variable.getNegated()
+ connector["edge"] = variable.getConnectorEdge()
+ infos["connectors"]["outputs"].append(connector)
+ elif isinstance(instance, plcopen.inVariable):
+ infos["name"] = instance.getExpression()
+ infos["value_type"] = self.GetPouVarValueType(self.GetCurrentElementEditing(), infos["name"])
+ infos["type"] = "input"
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointOut.getRelPosition()
+ infos["connector"]["negated"] = instance.getNegated()
+ infos["connector"]["edge"] = instance.getConnectorEdge()
+ elif isinstance(instance, plcopen.outVariable):
+ infos["name"] = instance.getExpression()
+ infos["value_type"] = self.GetPouVarValueType(self.GetCurrentElementEditing(), infos["name"])
+ infos["type"] = "output"
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connector"]["negated"] = instance.getNegated()
+ infos["connector"]["edge"] = instance.getConnectorEdge()
+ infos["connector"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connector"]["links"].append(dic)
+ elif isinstance(instance, plcopen.inOutVariable):
+ infos["name"] = instance.getExpression()
+ infos["value_type"] = self.GetPouVarValueType(self.GetCurrentElementEditing(), infos["name"])
+ infos["type"] = "inout"
+ infos["connectors"] = {"input":{},"output":{}}
+ infos["connectors"]["output"]["position"] = instance.connectionPointOut.getRelPosition()
+ infos["connectors"]["output"]["negated"] = instance.getNegatedOut()
+ infos["connectors"]["output"]["edge"] = instance.getOutputEdge()
+ infos["connectors"]["input"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connectors"]["input"]["negated"] = instance.getNegatedIn()
+ infos["connectors"]["input"]["edge"] = instance.getInputEdge()
+ infos["connectors"]["input"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connectors"]["input"]["links"].append(dic)
+ elif isinstance(instance, plcopen.continuation):
+ infos["name"] = instance.getName()
+ infos["value_type"] = self.GetPouVarValueType(self.GetCurrentElementEditing(), infos["name"])
+ infos["type"] = "continuation"
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointOut.getRelPosition()
+ elif isinstance(instance, plcopen.connector):
+ infos["name"] = instance.getName()
+ infos["value_type"] = self.GetPouVarValueType(self.GetCurrentElementEditing(), infos["name"])
+ infos["type"] = "connection"
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connector"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connector"]["links"].append(dic)
+ elif isinstance(instance, plcopen.comment):
+ infos["type"] = "comment"
+ infos["content"] = instance.getContentText()
+ elif isinstance(instance, plcopen.leftPowerRail):
+ infos["type"] = "leftPowerRail"
+ infos["connectors"] = []
+ for connection in instance.getConnectionPointOut():
+ connector = {}
+ connector["position"] = connection.getRelPosition()
+ infos["connectors"].append(connector)
+ elif isinstance(instance, plcopen.rightPowerRail):
+ infos["type"] = "rightPowerRail"
+ infos["connectors"] = []
+ for connection in instance.getConnectionPointIn():
+ connector = {}
+ connector["position"] = connection.getRelPosition()
+ connector["links"] = []
+ for link in connection.getConnections():
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ connector["links"].append(dic)
+ infos["connectors"].append(connector)
+ elif isinstance(instance, plcopen.contact):
+ infos["type"] = "contact"
+ infos["name"] = instance.getVariable()
+ infos["negated"] = instance.getNegated()
+ infos["edge"] = instance.getContactEdge()
+ infos["connectors"] = {"input":{},"output":{}}
+ infos["connectors"]["input"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connectors"]["input"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connectors"]["input"]["links"].append(dic)
+ infos["connectors"]["output"]["position"] = instance.connectionPointOut.getRelPosition()
+ elif isinstance(instance, plcopen.coil):
+ infos["type"] = "coil"
+ infos["name"] = instance.getVariable()
+ infos["negated"] = instance.getNegated()
+ infos["storage"] = instance.getCoilStorage()
+ infos["connectors"] = {"input":{},"output":{}}
+ infos["connectors"]["input"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connectors"]["input"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connectors"]["input"]["links"].append(dic)
+ infos["connectors"]["output"]["position"] = instance.connectionPointOut.getRelPosition()
+ elif isinstance(instance, plcopen.step):
+ infos["type"] = "step"
+ infos["name"] = instance.getName()
+ infos["initial"] = instance.getInitialStep()
+ infos["connectors"] = {}
+ if instance.connectionPointIn:
+ infos["connectors"]["input"] = {}
+ infos["connectors"]["input"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connectors"]["input"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connectors"]["input"]["links"].append(dic)
+ if instance.connectionPointOut:
+ infos["connectors"]["output"] = {"position" : instance.connectionPointOut.getRelPosition()}
+ if instance.connectionPointOutAction:
+ infos["connectors"]["action"] = {"position" : instance.connectionPointOutAction.getRelPosition()}
+ elif isinstance(instance, plcopen.transition):
+ infos["type"] = "transition"
+ condition = instance.getConditionContent()
+ infos["condition_type"] = condition["type"]
+ infos["condition"] = condition["value"]
+ infos["connectors"] = {"input":{},"output":{}}
+ infos["connectors"]["input"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connectors"]["input"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connectors"]["input"]["links"].append(dic)
+ infos["connectors"]["output"]["position"] = instance.connectionPointOut.getRelPosition()
+ elif isinstance(instance, (plcopen.selectionDivergence, plcopen.simultaneousDivergence)):
+ if isinstance(instance, plcopen.selectionDivergence):
+ infos["type"] = "selectionDivergence"
+ else:
+ infos["type"] = "simultaneousDivergence"
+ infos["connectors"] = {"inputs":[],"outputs":[]}
+ connector = {}
+ connector["position"] = instance.connectionPointIn.getRelPosition()
+ connector["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ connector["links"].append(dic)
+ infos["connectors"]["inputs"].append(connector)
+ for sequence in instance.getConnectionPointOut():
+ connector = {}
+ connector["position"] = sequence.getRelPosition()
+ infos["connectors"]["outputs"].append(connector)
+ elif isinstance(instance, (plcopen.selectionConvergence, plcopen.simultaneousConvergence)):
+ if isinstance(instance, plcopen.selectionConvergence):
+ infos["type"] = "selectionConvergence"
+ else:
+ infos["type"] = "simultaneousConvergence"
+ infos["connectors"] = {"inputs":[],"outputs":[]}
+ for sequence in instance.getConnectionPointIn():
+ connector = {}
+ connector["position"] = sequence.getRelPosition()
+ connector["links"] = []
+ connections = sequence.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ connector["links"].append(dic)
+ infos["connectors"]["inputs"].append(connector)
+ connector = {}
+ connector["position"] = instance.connectionPointOut.getRelPosition()
+ infos["connectors"]["outputs"].append(connector)
+ elif isinstance(instance, plcopen.jumpStep):
+ infos["type"] = "jump"
+ infos["target"] = instance.getTargetName()
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connector"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connector"]["links"].append(dic)
+ elif isinstance(instance, plcopen.actionBlock):
+ infos["type"] = "actionBlock"
+ infos["actions"] = instance.getActions()
+ infos["connector"] = {}
+ infos["connector"]["position"] = instance.connectionPointIn.getRelPosition()
+ infos["connector"]["links"] = []
+ connections = instance.connectionPointIn.getConnections()
+ if connections:
+ for link in connections:
+ dic = {"refLocalId":link.getRefLocalId(),"points":link.getPoints()}
+ infos["connector"]["links"].append(dic)
+ return infos
+ return False
+
+ # Return the variable type of the given pou
+ def GetPouVarValueType(self, pou, varname):
+ for type, varlist in pou.getVars():
+ for var in varlist.getVariable():
+ if var.getName() == varname:
+ return var.getType()
+ return ""
+
+ def SetConnectionWires(self, connection, connector):
+ wires = connector.GetWires()
+ idx = 0
+ for wire, handle in wires:
+ points = wire.GetPoints(handle != 0)
+ if handle == 0:
+ refLocalId = wire.GetConnectedId(-1)
+ else:
+ refLocalId = wire.GetConnectedId(0)
+ if refLocalId != None:
+ connection.addConnection()
+ connection.setConnectionId(idx, refLocalId)
+ connection.setConnectionPoints(idx, points)
+ idx += 1
+
+ def AddCurrentElementEditingBlock(self, id):
+ block = plcopen.block()
+ block.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("block", block)
+ self.RefreshPouUsingTree()
+
+ def SetCurrentElementEditingBlockInfos(self, id, infos):
+ block = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ block.setInstanceName(value)
+ elif param == "type":
+ block.setTypeName(value)
+ elif param == "height":
+ block.setHeight(value)
+ elif param == "width":
+ block.setWidth(value)
+ elif param == "x":
+ block.setX(value)
+ elif param == "y":
+ block.setY(value)
+ elif param == "connectors":
+ block.inputVariables.setVariable([])
+ block.outputVariables.setVariable([])
+ for connector in value["inputs"]:
+ variable = plcopen.inputVariables_variable()
+ variable.setFormalParameter(connector.GetName())
+ if connector.IsNegated():
+ variable.setNegated(True)
+ if connector.GetEdge() != "none":
+ variable.setConnectorEdge(connector.GetEdge())
+ position = connector.GetRelPosition()
+ variable.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(variable.connectionPointIn, connector)
+ block.inputVariables.appendVariable(variable)
+ for connector in value["outputs"]:
+ variable = plcopen.outputVariables_variable()
+ variable.setFormalParameter(connector.GetName())
+ if connector.IsNegated():
+ variable.setNegated(True)
+ if connector.GetEdge() != "none":
+ variable.setConnectorEdge(connector.GetEdge())
+ position = connector.GetRelPosition()
+ variable.addConnectionPointOut()
+ variable.connectionPointOut.setRelPosition(position.x, position.y)
+ block.outputVariables.appendVariable(variable)
+ self.RefreshPouUsingTree()
+
+ def AddCurrentElementEditingVariable(self, id, type):
+ if type == INPUT:
+ name = "inVariable"
+ variable = plcopen.inVariable()
+ elif type == OUTPUT:
+ name = "outVariable"
+ variable = plcopen.outVariable()
+ elif type == INOUT:
+ name = "inOutVariable"
+ variable = plcopen.inOutVariable()
+ variable.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance(name, variable)
+
+ def SetCurrentElementEditingVariableInfos(self, id, infos):
+ variable = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ variable.setExpression(value)
+ elif param == "height":
+ variable.setHeight(value)
+ elif param == "width":
+ variable.setWidth(value)
+ elif param == "x":
+ variable.setX(value)
+ elif param == "y":
+ variable.setY(value)
+ elif param == "connectors":
+ if isinstance(variable, plcopen.inVariable):
+ if value["output"].IsNegated():
+ variable.setNegated(True)
+ if value["output"].GetEdge() != "none":
+ variable.setConnectorEdge(value["output"].GetEdge())
+ position = value["output"].GetRelPosition()
+ variable.addConnectionPointOut()
+ variable.connectionPointOut.setRelPosition(position.x, position.y)
+ elif isinstance(variable, plcopen.outVariable):
+ if value["input"].IsNegated():
+ variable.setNegated(True)
+ if value["input"].GetEdge() != "none":
+ variable.setConnectorEdge(value["input"].GetEdge())
+ position = value["input"].GetRelPosition()
+ variable.addConnectionPointIn()
+ variable.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(variable.connectionPointIn, value["input"])
+ elif isinstance(variable, plcopen.inOutVariable):
+ if value["input"].IsNegated():
+ variable.setNegatedIn(True)
+ if value["input"].GetEdge() != "none":
+ variable.setInputEdge(value["input"].GetEdge())
+ if value["output"].IsNegated():
+ variable.setNegatedOut(True)
+ if value["output"].GetEdge() != "none":
+ variable.setOutputEdge(value["output"].GetEdge())
+ position = value["output"].GetRelPosition()
+ variable.addConnectionPointOut()
+ variable.connectionPointOut.setRelPosition(position.x, position.y)
+ position = value["input"].GetRelPosition()
+ variable.addConnectionPointIn()
+ variable.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(variable.connectionPointIn, value["input"])
+
+
+ def AddCurrentElementEditingConnection(self, id, type):
+ if type == CONNECTOR:
+ name = "connector"
+ connection = plcopen.connector()
+ elif type == CONTINUATION:
+ name = "continuation"
+ connection = plcopen.continuation()
+ connection.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance(name, connection)
+
+ def SetCurrentElementEditingConnectionInfos(self, id, infos):
+ connection = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ connection.setName(value)
+ elif param == "height":
+ connection.setHeight(value)
+ elif param == "width":
+ connection.setWidth(value)
+ elif param == "x":
+ connection.setX(value)
+ elif param == "y":
+ connection.setY(value)
+ elif param == "connector":
+ position = value.GetRelPosition()
+ if isinstance(connection, plcopen.continuation):
+ connection.addConnectionPointOut()
+ connection.connectionPointOut.setRelPosition(position.x, position.y)
+ elif isinstance(connection, plcopen.connector):
+ connection.addConnectionPointIn()
+ connection.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(connection.connectionPointIn, value)
+
+ def AddCurrentElementEditingComment(self, id):
+ comment = plcopen.comment()
+ comment.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("comment", comment)
+
+ def SetCurrentElementEditingCommentInfos(self, id, infos):
+ comment = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "content":
+ comment.setContentText(value)
+ elif param == "height":
+ comment.setHeight(value)
+ elif param == "width":
+ comment.setWidth(value)
+ elif param == "x":
+ comment.setX(value)
+ elif param == "y":
+ comment.setY(value)
+
+ def AddCurrentElementEditingPowerRail(self, id, type):
+ if type == LEFTRAIL:
+ name = "leftPowerRail"
+ powerrail = plcopen.leftPowerRail()
+ elif type == RIGHTRAIL:
+ name = "rightPowerRail"
+ powerrail = plcopen.rightPowerRail()
+ powerrail.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance(name, powerrail)
+
+ def SetCurrentElementEditingPowerRailInfos(self, id, infos):
+ powerrail = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "height":
+ powerrail.setHeight(value)
+ elif param == "width":
+ powerrail.setWidth(value)
+ elif param == "x":
+ powerrail.setX(value)
+ elif param == "y":
+ powerrail.setY(value)
+ elif param == "connectors":
+ if isinstance(powerrail, plcopen.leftPowerRail):
+ powerrail.setConnectionPointOut([])
+ for connector in value:
+ position = connector.GetRelPosition()
+ connection = plcopen.leftPowerRail_connectionPointOut()
+ connection.setRelPosition(position.x, position.y)
+ powerrail.connectionPointOut.append(connection)
+ elif isinstance(powerrail, plcopen.rightPowerRail):
+ powerrail.setConnectionPointIn([])
+ for connector in value:
+ position = connector.GetRelPosition()
+ connection = plcopen.connectionPointIn()
+ connection.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(connection, connector)
+ powerrail.connectionPointIn.append(connection)
+
+ def AddCurrentElementEditingContact(self, id):
+ contact = plcopen.contact()
+ contact.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("contact", contact)
+
+ def SetCurrentElementEditingContactInfos(self, id, infos):
+ contact = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ contact.setVariable(value)
+ elif param == "type":
+ if value == CONTACT_NORMAL:
+ contact.setNegated(False)
+ contact.setContactEdge("none")
+ elif value == CONTACT_REVERSE:
+ contact.setNegated(True)
+ contact.setContactEdge("none")
+ elif value == CONTACT_RISING:
+ contact.setNegated(False)
+ contact.setContactEdge("rising")
+ elif value == CONTACT_FALLING:
+ contact.setNegated(False)
+ contact.setContactEdge("falling")
+ elif param == "height":
+ contact.setHeight(value)
+ elif param == "width":
+ contact.setWidth(value)
+ elif param == "x":
+ contact.setX(value)
+ elif param == "y":
+ contact.setY(value)
+ elif param == "connectors":
+ input_connector = value["input"]
+ position = input_connector.GetRelPosition()
+ contact.addConnectionPointIn()
+ contact.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(contact.connectionPointIn, input_connector)
+ output_connector = value["output"]
+ position = output_connector.GetRelPosition()
+ contact.addConnectionPointOut()
+ contact.connectionPointOut.setRelPosition(position.x, position.y)
+
+ def AddCurrentElementEditingCoil(self, id):
+ coil = plcopen.coil()
+ coil.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("coil", coil)
+
+ def SetCurrentElementEditingCoilInfos(self, id, infos):
+ coil = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ coil.setVariable(value)
+ elif param == "type":
+ if value == COIL_NORMAL:
+ coil.setNegated(False)
+ coil.setCoilStorage("none")
+ elif value == COIL_REVERSE:
+ coil.setNegated(True)
+ coil.setCoilStorage("none")
+ elif value == COIL_SET:
+ coil.setNegated(False)
+ coil.setCoilStorage("set")
+ elif value == COIL_RESET:
+ coil.setNegated(False)
+ coil.setCoilStorage("reset")
+ elif param == "height":
+ coil.setHeight(value)
+ elif param == "width":
+ coil.setWidth(value)
+ elif param == "x":
+ coil.setX(value)
+ elif param == "y":
+ coil.setY(value)
+ elif param == "connectors":
+ input_connector = value["input"]
+ position = input_connector.GetRelPosition()
+ coil.addConnectionPointIn()
+ coil.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(coil.connectionPointIn, input_connector)
+ output_connector = value["output"]
+ position = output_connector.GetRelPosition()
+ coil.addConnectionPointOut()
+ coil.connectionPointOut.setRelPosition(position.x, position.y)
+
+ def AddCurrentElementEditingStep(self, id):
+ step = plcopen.step()
+ step.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("step", step)
+
+ def SetCurrentElementEditingStepInfos(self, id, infos):
+ step = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "name":
+ step.setName(value)
+ elif param == "initial":
+ step.setInitialStep(value)
+ elif param == "height":
+ step.setHeight(value)
+ elif param == "width":
+ step.setWidth(value)
+ elif param == "x":
+ step.setX(value)
+ elif param == "y":
+ step.setY(value)
+ elif param == "connectors":
+ input_connector = value["input"]
+ if input_connector:
+ position = input_connector.GetRelPosition()
+ step.addConnectionPointIn()
+ step.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(step.connectionPointIn, input_connector)
+ else:
+ step.deleteConnectionPointIn()
+ output_connector = value["output"]
+ if output_connector:
+ position = output_connector.GetRelPosition()
+ step.addConnectionPointOut()
+ step.connectionPointOut.setRelPosition(position.x, position.y)
+ else:
+ step.deleteConnectionPointOut()
+ action_connector = value["action"]
+ if action_connector:
+ position = action_connector.GetRelPosition()
+ step.addConnectionPointOutAction()
+ step.connectionPointOutAction.setRelPosition(position.x, position.y)
+ else:
+ step.deleteConnectionPointOutAction()
+
+ def AddCurrentElementEditingTransition(self, id):
+ transition = plcopen.transition()
+ transition.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("transition", transition)
+
+ def SetCurrentElementEditingTransitionInfos(self, id, infos):
+ transition = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "type" and "condition" in infos:
+ transition.setConditionContent(value, infos["condition"])
+ elif param == "height":
+ transition.setHeight(value)
+ elif param == "width":
+ transition.setWidth(value)
+ elif param == "x":
+ transition.setX(value)
+ elif param == "y":
+ transition.setY(value)
+ elif param == "connectors":
+ input_connector = value["input"]
+ position = input_connector.GetRelPosition()
+ transition.addConnectionPointIn()
+ transition.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(transition.connectionPointIn, input_connector)
+ output_connector = value["output"]
+ position = output_connector.GetRelPosition()
+ transition.addConnectionPointOut()
+ transition.connectionPointOut.setRelPosition(position.x, position.y)
+
+ def AddCurrentElementEditingDivergence(self, id, type):
+ if type == SELECTION_DIVERGENCE:
+ name = "selectionDivergence"
+ divergence = plcopen.selectionDivergence()
+ elif type == SELECTION_CONVERGENCE:
+ name = "selectionConvergence"
+ divergence = plcopen.selectionConvergence()
+ elif type == SIMULTANEOUS_DIVERGENCE:
+ name = "simultaneousDivergence"
+ divergence = plcopen.simultaneousDivergence()
+ elif type == SIMULTANEOUS_CONVERGENCE:
+ name = "simultaneousConvergence"
+ divergence = plcopen.simultaneousConvergence()
+ divergence.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance(name, divergence)
+
+ def SetCurrentElementEditingDivergenceInfos(self, id, infos):
+ divergence = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "height":
+ divergence.setHeight(value)
+ elif param == "width":
+ divergence.setWidth(value)
+ elif param == "x":
+ divergence.setX(value)
+ elif param == "y":
+ divergence.setY(value)
+ elif param == "connectors":
+ input_connectors = value["inputs"]
+ if isinstance(divergence, (plcopen.selectionDivergence, plcopen.simultaneousDivergence)):
+ position = input_connectors[0].GetRelPosition()
+ divergence.addConnectionPointIn()
+ divergence.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(divergence.connectionPointIn, input_connectors[0])
+ else:
+ divergence.setConnectionPointIn([])
+ for input_connector in input_connectors:
+ position = input_connector.GetRelPosition()
+ if isinstance(divergence, plcopen.selectionConvergence):
+ connection = plcopen.selectionConvergence_connectionPointIn()
+ else:
+ connection = plcopen.connectionPointIn()
+ connection.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(connection, input_connector)
+ divergence.appendConnectionPointIn(connection)
+ output_connectors = value["outputs"]
+ if isinstance(divergence, (plcopen.selectionConvergence, plcopen.simultaneousConvergence)):
+ position = output_connectors[0].GetRelPosition()
+ divergence.addConnectionPointOut()
+ divergence.connectionPointOut.setRelPosition(position.x, position.y)
+ else:
+ divergence.setConnectionPointOut([])
+ for output_connector in output_connectors:
+ position = output_connector.GetRelPosition()
+ if isinstance(divergence, plcopen.selectionDivergence):
+ connection = plcopen.selectionDivergence_connectionPointOut()
+ else:
+ connection = plcopen.simultaneousDivergence_connectionPointOut()
+ connection.setRelPosition(position.x, position.y)
+ divergence.appendConnectionPointOut(connection)
+
+ def AddCurrentElementEditingJump(self, id):
+ jump = plcopen.jumpStep()
+ jump.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("jumpStep", jump)
+
+ def SetCurrentElementEditingJumpInfos(self, id, infos):
+ jump = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "target":
+ jump.setTargetName(value)
+ elif param == "height":
+ jump.setHeight(value)
+ elif param == "width":
+ jump.setWidth(value)
+ elif param == "x":
+ jump.setX(value)
+ elif param == "y":
+ jump.setY(value)
+ elif param == "connector":
+ position = value.GetRelPosition()
+ jump.addConnectionPointIn()
+ jump.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(jump.connectionPointIn, value)
+
+ def AddCurrentElementEditingActionBlock(self, id):
+ actionBlock = plcopen.actionBlock()
+ actionBlock.setLocalId(id)
+ self.GetCurrentElementEditing().addInstance("actionBlock", actionBlock)
+
+ def SetCurrentElementEditingActionBlockInfos(self, id, infos):
+ actionBlock = self.GetCurrentElementEditing().getInstance(id)
+ for param, value in infos.items():
+ if param == "actions":
+ actionBlock.setActions(value)
+ elif param == "height":
+ actionBlock.setHeight(value)
+ elif param == "width":
+ actionBlock.setWidth(value)
+ elif param == "x":
+ actionBlock.setX(value)
+ elif param == "y":
+ actionBlock.setY(value)
+ elif param == "connector":
+ position = value.GetRelPosition()
+ actionBlock.addConnectionPointIn()
+ actionBlock.connectionPointIn.setRelPosition(position.x, position.y)
+ self.SetConnectionWires(actionBlock.connectionPointIn, value)
+
+ def RemoveCurrentElementEditingInstance(self, id):
+ self.GetCurrentElementEditing().removeInstance(id)
+ self.RefreshPouUsingTree()
+
+ def GetCurrentResourceEditingVariables(self):
+ varlist = []
+ name = self.ElementsOpened[self.CurrentElementEditing]
+ words = name.split("::")
+ for var in self.GetConfigurationGlobalVars(words[1]):
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ for var in self.GetConfigurationResourceGlobalVars(words[1], words[2]):
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ return varlist
+
+ def SetCurrentResourceEditingInfos(self, tasks, instances):
+ resource = self.GetCurrentElementEditing()
+ resource.setTask([])
+ resource.setPouInstance([])
+ task_list = {}
+ for task in tasks:
+ new_task = plcopen.resource_task()
+ new_task.setName(task["Name"])
+ if task["Single"] != "":
+ new_task.setSingle(task["Single"])
+ if task["Interval"] != "":
+ new_task.setInterval(task["Interval"])
+ new_task.priority.setValue(int(task["Priority"]))
+ if task["Name"] != "":
+ task_list[task["Name"]] = new_task
+ resource.appendTask(new_task)
+ for instance in instances:
+ new_instance = plcopen.pouInstance()
+ new_instance.setName(instance["Name"])
+ new_instance.setType(instance["Type"])
+ if instance["Task"] != "":
+ task_list[instance["Task"]].appendPouInstance(new_instance)
+ else:
+ resource.appendPouInstance(new_instance)
+
+ def GetCurrentResourceEditingInfos(self):
+ resource = self.GetCurrentElementEditing()
+ tasks = resource.getTask()
+ instances = resource.getPouInstance()
+ tasks_data = []
+ instances_data = []
+ for task in tasks:
+ new_task = {}
+ new_task["Name"] = task.getName()
+ single = task.getSingle()
+ if single:
+ new_task["Single"] = single
+ else:
+ new_task["Single"] = ""
+ interval = task.getInterval()
+ if interval:
+ new_task["Interval"] = interval
+ else:
+ new_task["Interval"] = ""
+ new_task["Priority"] = str(task.priority.getValue())
+ tasks_data.append(new_task)
+ for instance in task.getPouInstance():
+ new_instance = {}
+ new_instance["Name"] = instance.getName()
+ new_instance["Type"] = instance.getType()
+ new_instance["Task"] = task.getName()
+ instances_data.append(new_instance)
+ for instance in instances:
+ new_instance = {}
+ new_instance["Name"] = instance.getName()
+ new_instance["Type"] = instance.getType()
+ new_instance["Task"] = ""
+ instances_data.append(new_instance)
+ return tasks_data, instances_data
+
+ def OpenXMLFile(self, filepath):
+ if sys:
+ sys.stdout = plcopen.HolePseudoFile()
+ tree = pyxsval.parseAndValidate(filepath, "plcopen/TC6_XML_V10_B.xsd")
+ if sys:
+ sys.stdout = sys.__stdout__
+
+ self.Project = plcopen.project()
+ self.Project.loadXMLTree(tree.getTree().childNodes[0])
+ self.UndoBuffer = UndoBuffer(self.Copy(self.Project), True)
+ self.SetFilePath(filepath)
+ self.ElementsOpened = []
+ self.CurrentElementEditing = None
+ self.RefreshPouUsingTree()
+ self.RefreshBlockTypes()
+
+ def SaveXMLFile(self, filepath = None):
+ if not filepath and self.FilePath == "":
+ return False
+ else:
+ text = "\n"
+ extras = {"xmlns" : "http://www.plcopen.org/xml/tc6.xsd",
+ "xmlns:xhtml" : "http://www.w3.org/1999/xhtml",
+ "xmlns:xsi" : "http://www.w3.org/2001/XMLSchema-instance",
+ "xsi:schemaLocation" : "http://www.plcopen.org/xml/tc6.xsd http://www.plcopen.org/xml/tc6.xsd"}
+ text += self.Project.generateXMLText("project", 0, extras)
+
+ if sys:
+ sys.stdout = plcopen.HolePseudoFile()
+ pyxsval.parseAndValidateString(text, open("plcopen/TC6_XML_V10_B.xsd","r").read())
+ if sys:
+ sys.stdout = sys.__stdout__
+
+ if filepath:
+ xmlfile = open(filepath,"w")
+ else:
+ xmlfile = open(self.FilePath,"w")
+ xmlfile.write(text)
+ xmlfile.close()
+ self.UndoBuffer.CurrentSaved()
+ if filepath:
+ self.SetFilePath(filepath)
+ return True
+
+#-------------------------------------------------------------------------------
+# Current Buffering Management Functions
+#-------------------------------------------------------------------------------
+
+ """
+ Return a copy of the project
+ """
+ def Copy(self, model):
+ return cPickle.loads(cPickle.dumps(model))
+
+ def BufferProject(self):
+ self.UndoBuffer.Buffering(self.Copy(self))
+
+ def ProjectIsSaved(self):
+ return self.UndoBuffer.IsCurrentSaved()
+
+ def LoadPrevious(self):
+ self.Project = self.Copy(self.UndoBuffer.Previous())
+ self.RefreshElementsOpened()
+
+ def LoadNext(self):
+ self.Project = self.Copy(self.UndoBuffer.Next())
+ self.RefreshElementsOpened()
+
+ def GetBufferState(self):
+ first = self.UndoBuffer.IsFirst()
+ last = self.UndoBuffer.IsLast()
+ return not first, not last
diff -r 000000000000 -r b622defdfd98 PLCGenerator.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCGenerator.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,422 @@
+#!/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 plcopen import plcopen
+from plcopen.structures import *
+from types import *
+
+varTypeNames = {"localVars" : "VAR", "tempVars" : "VAR_TEMP", "inputVars" : "VAR_INPUT",
+ "outputVars" : "VAR_OUTPUT", "inOutVars" : "VAR_IN_OUT", "externalVars" : "VAR_EXTERNAL",
+ "globalVars" : "VAR_GLOBAL", "accessVars" : "VAR_ACCESS"}
+"""
+Module implementing methods for generating PLC programs in ST or IL
+"""
+
+class PouProgram:
+
+ def __init__(self, name, type):
+ self.Name = name
+ self.Type = type
+ self.Interface = {}
+ self.Steps = {}
+ self.Transitions = {}
+ self.Order = []
+ self.Program = ""
+
+ def GenerateInterface(self, interface):
+ if self.Type == "FUNCTION":
+ self.Interface["returnType"] = interface.getReturnType().getValue()
+ for varlist in interface.getContent():
+ variables = {}
+ for var in varlist["value"].getVariable():
+ type = var.getType().getValue()
+ if type not in variables:
+ variables[type] = []
+ variables[type].append(var.getName())
+ self.Interface[(varTypeNames[varlist["name"]], varlist["value"].getRetain(),
+ varlist["value"].getConstant())] = variables
+
+ def GenerateProgram(self, pou):
+ body = pou.getBody()
+ body_content = body.getContent()
+ body_type = body_content["name"]
+ if body_type in ["IL","ST"]:
+ self.Program = "%s\n"%body_content["value"].getText()
+ elif body_type == "FBD":
+ for instance in body.getContentInstances():
+ if isinstance(instance, plcopen.outVariable):
+ var = instance.getExpression()
+ connections = instance.connectionPointIn.getConnections()
+ if connections and len(connections) == 1:
+ expression = self.ComputeFBDExpression(body, connections[0])
+ self.Program += " %s := %s;\n"%(var, expression)
+ elif body_type == "LD":
+ for instance in body.getContentInstances():
+ if isinstance(instance, plcopen.coil):
+ paths = self.GenerateLDPaths(instance, body)
+ variable = self.ExtractModifier(instance, instance.getVariable())
+ expression = self.ComputeLDExpression(paths, True)
+ self.Program += " %s := %s;\n"%(variable, expression)
+ elif body_type == "SFC":
+ for instance in body.getContentInstances():
+ if isinstance(instance, plcopen.step):
+ self.GenerateSFCSteps(instance, pou)
+ elif isinstance(instance, plcopen.actionBlock):
+ self.GenerateSFCActions(instance, pou)
+ elif isinstance(instance, plcopen.transition):
+ self.GenerateSFCTransitions(instance, pou)
+ elif isinstance(instance, plcopen.jumpStep):
+ self.GenerateSFCJump(instance, pou)
+ for name, values in self.Steps.items():
+ if values['initial']:
+ self.GenerateSFCStepOrder(name, [])
+ steps_type = "ARRAY [1..%d] OF BOOL"%len(self.Order)
+ if ("VAR", False, False) not in self.Interface:
+ self.Interface[("VAR", False, False)] = {}
+ if steps_type not in self.Interface[("VAR", False, False)]:
+ self.Interface[("VAR", False, False)][steps_type] = ["Steps"]
+ else:
+ self.Interface[("VAR", False, False)][steps_type].append("Steps")
+ for index, name in enumerate(self.Order):
+ values = self.Steps[name]
+ self.Program += " IF Steps[%d] THEN\n"%index
+ for action in values["actions"]:
+ if action["qualifier"] == "N":
+ for line in action["content"].splitlines():
+ self.Program += " %s\n"%line
+ elif action["qualifier"] == "S":
+ if "R_TRIG" not in self.Interface[("VAR", False, False)]:
+ self.Interface[("VAR", False, False)]["R_TRIG"] = []
+ i = 1
+ name = "R_TRIG%d"%i
+ while name in self.Interface[("VAR", False, False)]["R_TRIG"]:
+ i += 1
+ name = "R_TRIG%d"%i
+ self.Interface[("VAR", False, False)]["R_TRIG"].append(name)
+ self.Program += " IF %s(CLK := Steps[%d]) THEN\n"%(name, index)
+ self.Program += " %s := TRUE;\n"%action["content"]
+ for transition in values["transitions"]:
+ if transition["compute"] != '':
+ self.Program += " %s %s"%(transition["condition"], transition["compute"])
+ self.Program += " IF %s THEN\n"%transition["condition"]
+ for target in transition["targets"]:
+ self.Program += " Steps[%d] := TRUE;\n"%self.Order.index(target)
+ self.Program += " Steps[%d] := FALSE;\n"%index
+
+ def ComputeFBDExpression(self, body, link):
+ localid = link.getRefLocalId()
+ instance = body.getContentInstance(localid)
+ if isinstance(instance, plcopen.inVariable):
+ return instance.getExpression()
+ elif isinstance(instance, plcopen.block):
+ name = instance.getInstanceName()
+ type = instance.getTypeName()
+ block_infos = GetBlockType(type)
+ if block_infos["type"] == "function":
+ vars = []
+ for variable in instance.inputVariables.getVariable():
+ connections = variable.connectionPointIn.getConnections()
+ if connections and len(connections) == 1:
+ value = self.ComputeFBDExpression(body, connections[0])
+ vars.append(self.ExtractModifier(variable, value))
+ variable = instance.outputVariables.getVariable()[0]
+ return self.ExtractModifier(variable, "%s(%s)"%(type, ", ".join(vars)))
+ elif block_infos["type"] == "functionBlock":
+ if ("VAR", False, False) not in self.Interface:
+ self.Interface[("VAR", False, False)] = {}
+ if type not in self.Interface[("VAR", False, False)]:
+ self.Interface[("VAR", False, False)][type] = []
+ if name not in self.Interface[("VAR", False, False)][type]:
+ self.Interface[("VAR", False, False)][type].append(name)
+ vars = []
+ for variable in instance.inputVariables.getVariable():
+ connections = variable.connectionPointIn.getConnections()
+ if connections and len(connections) == 1:
+ parameter = variable.getFormalParameter()
+ value = self.ComputeFBDExpression(body, connections[0])
+ vars.append(self.ExtractModifier(variable, "%s := %s"%(parameter, value)))
+ self.Program += " %s(%s);\n"%(name, ", ".join(vars))
+ connectionPoint = link.getPosition()[-1]
+ for variable in instance.outputVariables.getVariable():
+ blockPointx, blockPointy = variable.connectionPointOut.getRelPosition()
+ if instance.getX() + blockPointx == connectionPoint.getX() and instance.getY() + blockPointy == connectionPoint.getY():
+ return self.ExtractModifier(variable, "%s.%s"%(name, variable.getFormalParameter()))
+ raise ValueError, "No output variable found"
+
+ def GenerateLDPaths(self, instance, body):
+ paths = []
+ variable = self.ExtractModifier(instance, instance.getVariable())
+ connections = instance.connectionPointIn.getConnections()
+ for connection in connections:
+ localId = connection.getRefLocalId()
+ next = body.getContentInstance(localId)
+ if isinstance(next, plcopen.leftPowerRail):
+ paths.append(None)
+ else:
+ paths.append(self.GenerateLDPaths(next, body))
+ if isinstance(instance, plcopen.coil):
+ if len(paths) > 1:
+ return tuple(paths)
+ else:
+ return paths
+ else:
+ if len(paths) > 1:
+ return [variable, tuple(paths)]
+ elif type(paths[0]) == ListType:
+ return [variable] + paths[0]
+ elif paths[0]:
+ return [variable, paths[0]]
+ else:
+ return variable
+
+ def GenerateSFCSteps(self, step, pou):
+ step_name = step.getName()
+ if step_name not in self.Steps:
+ step_infos = {"initial" : step.getInitialStep(), "transitions" : [], "actions" : []}
+ self.Steps[step_name] = step_infos
+ if step.connectionPointIn:
+ instances = []
+ connections = step.connectionPointIn.getConnections()
+ if len(connections) == 1:
+ instanceLocalId = connections[0].getRefLocalId()
+ instance = pou.body.getContentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.transition):
+ self.GenerateSFCTransitions(instance, pou)
+ instances.append(instance)
+ elif isinstance(instance, plcopen.selectionConvergence):
+ for connectionPointIn in instance.getConnectionPointIn():
+ divergence_connections = connectionPointIn.getConnections()
+ if len(divergence_connections) == 1:
+ transitionLocalId = connections[0].getRefLocalId()
+ transition = pou.body.getContentInstance(transitionLocalId)
+ self.GenerateSFCTransitions(transition, pou)
+ instances.append(transition)
+ for instance in instances:
+ self.Transitions[instance]["targets"].append(step_name)
+
+ def GenerateSFCJump(self, jump, pou):
+ jump_target = jump.getTargetName()
+ if jump.connectionPointIn:
+ instances = []
+ connections = jump.connectionPointIn.getConnections()
+ if len(connections) == 1:
+ instanceLocalId = connections[0].getRefLocalId()
+ instance = pou.body.getContentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.transition):
+ self.GenerateSFCTransitions(instance, pou)
+ instances.append(instance)
+ elif isinstance(instance, plcopen.selectionConvergence):
+ for connectionPointIn in instance.getConnectionPointIn():
+ divergence_connections = connectionPointIn.getConnections()
+ if len(divergence_connections) == 1:
+ transitionLocalId = divergence_connections[0].getRefLocalId()
+ transition = pou.body.getContentInstance(transitionLocalId)
+ self.GenerateSFCTransitions(transition, pou)
+ instances.append(transition)
+ for instance in instances:
+ self.Transitions[instance]["targets"].append(jump_target)
+
+ def GenerateSFCActions(self, actionBlock, pou):
+ connections = actionBlock.connectionPointIn.getConnections()
+ if len(connections) == 1:
+ stepLocalId = connections[0].getRefLocalId()
+ step = pou.body.getContentInstance(stepLocalId)
+ step_name = step.getName()
+ if step_name not in self.Steps:
+ self.GenerateSFCSteps(step, pou)
+ if step_name in self.Steps:
+ actions = actionBlock.getActions()
+ for action in actions:
+ action_infos = {"qualifier" : action["qualifier"], "content" : ""}
+ if action["type"] == "inline":
+ action_infos["content"] = action["value"]
+ elif action["type"] == "reference":
+ actionContent = pou.getAction(action["value"])
+ if actionContent:
+ actionType = actionContent.getBodyType()
+ actionBody = actionContent.getBody()
+ if actionType in ["ST", "IL"]:
+ action_infos["content"] = actionContent.getText()
+ elif actionType == "FBD":
+ for instance in actionBody.getContentInstances():
+ if isinstance(instance, plcopen.outVariable):
+ var = instance.getExpression()
+ connections = instance.connectionPointIn.getConnections()
+ if connections and len(connections) == 1:
+ expression = self.ComputeFBDExpression(actionBody, connections[0])
+ action_infos["content"] = self.Program + " %s := %s;"%(var, expression)
+ self.Program = ""
+ elif actionType == "LD":
+ for instance in actionbody.getContentInstances():
+ if isinstance(instance, plcopen.coil):
+ paths = self.GenerateLDPaths(instance, actionBody)
+ variable = self.ExtractModifier(instance, instance.getVariable())
+ expression = self.ComputeLDExpression(paths, True)
+ action_infos["content"] = self.Program + " %s := %s;"%(variable, expression)
+ self.Program = ""
+ else:
+ action_infos["content"] = action["value"]
+ self.Steps[step_name]["actions"].append(action_infos)
+
+ def GenerateSFCTransitions(self, transition, pou):
+ if transition not in self.Transitions:
+ connections = transition.connectionPointIn.getConnections()
+ if len(connections) == 1:
+ instanceLocalId = connections[0].getRefLocalId()
+ instance = pou.body.getContentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.step):
+ step_name = instance.getName()
+ if step_name not in self.Steps:
+ self.GenerateSFCSteps(instance, pou)
+ elif isinstance(instance, plcopen.selectionDivergence):
+ divergence_connections = instance.connectionPointIn.getConnections()
+ if len(divergence_connections) == 1:
+ stepLocalId = divergence_connections[0].getRefLocalId()
+ divergence_instance = pou.body.getContentInstance(stepLocalId)
+ if isinstance(divergence_instance, plcopen.step):
+ step_name = divergence_instance.getName()
+ if step_name not in self.Steps:
+ self.GenerateSFCSteps(divergence_instance, pou)
+ if step_name in self.Steps:
+ transition_infos = {"targets" : []}
+ transitionValues = transition.getConditionContent()
+ transition_infos["condition"] = transitionValues["value"]
+ if transitionValues["type"] == "inline":
+ transition_infos["compute"] = ""
+ else:
+ transitionContent = pou.getTransition(transitionValues["value"])
+ transitionType = transitionContent.getBodyType()
+ transitionBody = transitionContent.getBody()
+ if transitionType in ["ST", "IL"]:
+ transition_infos["compute"] = "%s\n"%transitionContent.getText()
+ elif conditionType == "FBD":
+ for instance in conditionBody.getContentInstances():
+ if isinstance(instance, plcopen.outVariable):
+ var = instance.getExpression()
+ connections = instance.connectionPointIn.getConnections()
+ if connections and len(connections) == 1:
+ expression = self.ComputeFBDExpression(actionBody, connections[0])
+ transition_infos["compute"] = self.Program + ":= %s;\n"%(var, expression)
+ self.Program = ""
+ elif actionType == "LD":
+ for instance in conditionbody.getContentInstances():
+ if isinstance(instance, plcopen.coil):
+ paths = self.GenerateLDPaths(instance, conditionBody)
+ variable = self.ExtractModifier(instance, instance.getVariable())
+ expression = self.ComputeLDExpression(paths, True)
+ transition_infos["compute"] = self.Program + ":= %s;\n"%(variable, expression)
+ self.Program = ""
+ self.Steps[step_name]["transitions"].append(transition_infos)
+ self.Transitions[transition] = transition_infos
+
+ def GenerateSFCStepOrder(self, name, path):
+ self.Order.append(name)
+ for transition in self.Steps[name]["transitions"]:
+ for target in transition["targets"]:
+ if target not in self.Order or target not in path:
+ if target in self.Order:
+ self.Order.remove(target)
+ self.GenerateSFCStepOrder(target, path + [name])
+
+ def ComputeLDExpression(self, paths, first = False):
+ if type(paths) == TupleType:
+ if None in paths:
+ return "TRUE"
+ else:
+ var = [self.ComputeLDExpression(path) for path in paths]
+ if first:
+ return " OR ".join(var)
+ else:
+ return "(%s)"%" OR ".join(var)
+ elif type(paths) == ListType:
+ var = [self.ComputeLDExpression(path) for path in paths]
+ return " AND ".join(var)
+ else:
+ return paths
+
+ def ExtractModifier(self, variable, text):
+ if variable.getNegated():
+ return "NOT(%s)"%text
+ else:
+ edge = variable.getEdge()
+ if edge:
+ if edge.getValue() == "rising":
+ return self.AddTrigger("R_TRIG", text)
+ elif edge.getValue() == "falling":
+ return self.AddTrigger("F_TRIG", text)
+ return text
+
+ def AddTrigger(self, edge, text):
+ if ("VAR", False, False) not in self.Interface:
+ self.Interface[("VAR", False, False)] = {}
+ if type not in self.Interface[("VAR", False, False)]:
+ self.Interface[("VAR", False, False)][edge] = []
+ i = 1
+ name = "%s%d"%(edge, i)
+ while name in self.Interface[("VAR", False, False)][edge]:
+ i += 1
+ name = "%s%d"%(edge, i)
+ self.Interface[("VAR", False, False)][edge].append(name)
+ self.Program += " %s(CLK := %s);\n"%(name, text)
+ return "%s.Q"%name
+
+ def GenerateSTProgram(self):
+ program = ""
+ if "returnType" in self.Interface:
+ program += "%s %s : %s\n"%(self.Type, self.Name, self.Interface["returnType"])
+ else:
+ program += "%s %s\n"%(self.Type, self.Name)
+ for values, variables in self.Interface.items():
+ if values != "returnType":
+ program += " %s"%values[0]
+ if values[1]:
+ program += " RETAIN"
+ if values[2]:
+ program += " CONSTANT"
+ program += "\n"
+ for vartype, list in variables.items():
+ program += " %s : %s;\n"%(", ".join(list), vartype)
+ program += " END_%s\n"%values[0]
+ program += "\n"
+ program += self.Program
+ program += "END_%s\n\n"%self.Type
+ return program
+
+def GenerateCurrentProgram(project):
+ program = ""
+ for pou in project.getPous():
+ pou_type = pou.getPouType().getValue()
+ if pou_type == "function":
+ pou_program = PouProgram(pou.getName(), "FUNCTION")
+ elif pou_type == "functionBlock":
+ pou_program = PouProgram(pou.getName(), "FUNCTION_BLOCK")
+ elif pou_type == "program":
+ pou_program = PouProgram(pou.getName(), "PROGRAM")
+ else:
+ raise ValueError, "Undefined pou type"
+ pou_program.GenerateInterface(pou.getInterface())
+ pou_program.GenerateProgram(pou)
+ program += pou_program.GenerateSTProgram()
+ return program
+
diff -r 000000000000 -r b622defdfd98 PLCOpenEditor.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCOpenEditor.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,2216 @@
+#Boa:Frame:PLCOpenEditor
+#!/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 *
+from wxPython.grid import *
+from time import localtime
+from datetime import datetime
+import wx
+
+from SFCViewer import *
+from FBDViewer import *
+from LDViewer import *
+from Viewer import *
+from PLCControler import *
+from plcopen import OpenPDFDoc
+from plcopen.structures import *
+
+import os, re, platform, sys, time, traceback, getopt
+
+__version__ = "$Revision$"
+
+def create(parent):
+ return PLCOpenEditor(parent)
+
+def usage():
+ print "\nUsage of PLCOpenEditor.py :"
+ print "\n %s [Filepath]\n"%sys.argv[0]
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
+except getopt.GetoptError:
+ # print help information and exit:
+ usage()
+ sys.exit(2)
+
+for o, a in opts:
+ if o in ("-h", "--help"):
+ usage()
+ sys.exit()
+
+fileOpen = None
+if len(args) > 1:
+ usage()
+ sys.exit()
+elif len(args) == 1:
+ fileOpen = args[0]
+
+
+# Test if identifier is valid
+def TestIdentifier(identifier):
+ if identifier[0].isdigit():
+ return False
+ words = identifier.split('_')
+ for i, word in enumerate(words):
+ if len(word) == 0 and i != 0:
+ return False
+ if len(word) != 0 and not word.isalnum():
+ return False
+ return True
+
+[wxID_PLCOPENEDITOR, wxID_PLCOPENEDITORPROJECTTREE,
+ wxID_PLCOPENEDITORSPLITTERWINDOW1, wxID_PLCOPENEDITORTABSOPENED,
+ wxID_PLCOPENEDITORDEFAULTTOOLBAR, wxID_PLCOPENEDITORSFCTOOLBAR,
+ wxID_PLCOPENEDITORFBDTOOLBAR, wxID_PLCOPENEDITORLDTOOLBAR,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+[wxID_PLCOPENEDITORDEFAULTTOOLBARITEMS0,
+] = [wx.NewId() for _init_coll_DefaultToolBar_Items in range(1)]
+
+[wxID_PLCOPENEDITORSFCTOOLBARITEMS0, wxID_PLCOPENEDITORSFCTOOLBARITEMS1,
+ wxID_PLCOPENEDITORSFCTOOLBARITEMS2, wxID_PLCOPENEDITORSFCTOOLBARITEMS3,
+ wxID_PLCOPENEDITORSFCTOOLBARITEMS4, wxID_PLCOPENEDITORSFCTOOLBARITEMS5,
+ wxID_PLCOPENEDITORSFCTOOLBARITEMS6,
+] = [wx.NewId() for _init_coll_SFCToolBar_Items in range(7)]
+
+[wxID_PLCOPENEDITORFBDTOOLBARITEMS0, wxID_PLCOPENEDITORFBDTOOLBARITEMS1,
+ wxID_PLCOPENEDITORFBDTOOLBARITEMS2, wxID_PLCOPENEDITORFBDTOOLBARITEMS3,
+ wxID_PLCOPENEDITORFBDTOOLBARITEMS4, wxID_PLCOPENEDITORFBDTOOLBARITEMS5,
+] = [wx.NewId() for _init_coll_FBDToolBar_Items in range(6)]
+
+[wxID_PLCOPENEDITORLDTOOLBARITEMS0, wxID_PLCOPENEDITORLDTOOLBARITEMS1,
+ wxID_PLCOPENEDITORLDTOOLBARITEMS2, wxID_PLCOPENEDITORLDTOOLBARITEMS3,
+ wxID_PLCOPENEDITORLDTOOLBARITEMS4,
+] = [wx.NewId() for _init_coll_LDToolBar_Items in range(5)]
+
+[wxID_PLCOPENEDITORFILEMENUITEMS0, wxID_PLCOPENEDITORFILEMENUITEMS1,
+ wxID_PLCOPENEDITORFILEMENUITEMS2, wxID_PLCOPENEDITORFILEMENUITEMS3,
+ wxID_PLCOPENEDITORFILEMENUITEMS5, wxID_PLCOPENEDITORFILEMENUITEMS6,
+ wxID_PLCOPENEDITORFILEMENUITEMS7, wxID_PLCOPENEDITORFILEMENUITEMS9,
+ wxID_PLCOPENEDITORFILEMENUITEMS11,
+] = [wx.NewId() for _init_coll_FileMenu_Items in range(9)]
+
+[wxID_PLCOPENEDITORHELPMENUITEMS0, wxID_PLCOPENEDITORHELPMENUITEMS1,
+ wxID_PLCOPENEDITORHELPMENUITEMS2, wxID_PLCOPENEDITORHELPMENUITEMS3,
+] = [wx.NewId() for _init_coll_HelpMenu_Items in range(4)]
+
+[wxID_PLCOPENEDITORSFCMENUITEMS0, wxID_PLCOPENEDITORSFCMENUITEMS1,
+ wxID_PLCOPENEDITORSFCMENUITEMS2, wxID_PLCOPENEDITORSFCMENUITEMS3,
+] = [wx.NewId() for _init_coll_HelpMenu_Items in range(4)]
+
+[wxID_PLCOPENEDITORCONFIGMENUITEMS0, wxID_PLCOPENEDITORCONFIGMENUITEMS1,
+] = [wx.NewId() for _init_coll_HelpMenu_Items in range(2)]
+
+[wxID_PLCOPENEDITOREDITMENUITEMS0, wxID_PLCOPENEDITOREDITMENUITEMS1,
+ wxID_PLCOPENEDITOREDITMENUITEMS11, wxID_PLCOPENEDITOREDITMENUITEMS12,
+ wxID_PLCOPENEDITOREDITMENUITEMS2, wxID_PLCOPENEDITOREDITMENUITEMS4,
+ wxID_PLCOPENEDITOREDITMENUITEMS5, wxID_PLCOPENEDITOREDITMENUITEMS6,
+ wxID_PLCOPENEDITOREDITMENUITEMS8, wxID_PLCOPENEDITOREDITMENUITEMS9,
+] = [wx.NewId() for _init_coll_EditMenu_Items in range(10)]
+
+[wxID_PLCOPENEDITORSFCMENUITEMS0, wxID_PLCOPENEDITORSFCMENUITEMS1,
+ wxID_PLCOPENEDITORSFCMENUITEMS2, wxID_PLCOPENEDITORSFCMENUITEMS3,
+] = [wx.NewId() for _init_coll_SFCMenu_Items in range(4)]
+
+[wxID_PLCOPENEDITORCONFIGMENUITEMS0, wxID_PLCOPENEDITORCONFIGMENUITEMS1,
+] = [wx.NewId() for _init_coll_ConfigMenu_Items in range(2)]
+
+class PLCOpenEditor(wx.Frame):
+ _custom_classes = {'wx.SashWindow' : ['Viewer']}
+
+ def _init_coll_EditMenu_Items(self, parent):
+ # generated method, don't edit
+
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS0,
+ kind=wx.ITEM_NORMAL, text=u'Refresh\tCTRL+R')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS1,
+ kind=wx.ITEM_NORMAL, text=u'Undo\tCTRL+Z')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS2,
+ kind=wx.ITEM_NORMAL, text=u'Redo\tCTRL+Y')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS4,
+ kind=wx.ITEM_NORMAL, text=u'Cut\tCTRL+X')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS5,
+ kind=wx.ITEM_NORMAL, text=u'Copy\tCTRL+C')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS6,
+ kind=wx.ITEM_NORMAL, text=u'Paste\tCTRL+V')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS8,
+ kind=wx.ITEM_NORMAL, text=u'Add POU')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS9,
+ kind=wx.ITEM_NORMAL, text=u'Remove POU')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS11,
+ kind=wx.ITEM_NORMAL, text=u'Add Configuration')
+ parent.Append(help='', id=wxID_PLCOPENEDITOREDITMENUITEMS12,
+ kind=wx.ITEM_NORMAL, text=u'Remove Configuration')
+ self.Bind(wx.EVT_MENU, self.OnRefreshMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS0)
+ self.Bind(wx.EVT_MENU, self.OnCutMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS4)
+ self.Bind(wx.EVT_MENU, self.OnCopyMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS5)
+ self.Bind(wx.EVT_MENU, self.OnPasteMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS6)
+ self.Bind(wx.EVT_MENU, self.OnAddPouMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS8)
+ self.Bind(wx.EVT_MENU, self.OnRemovePouMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS9)
+ self.Bind(wx.EVT_MENU, self.OnAddConfigurationMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS11)
+ self.Bind(wx.EVT_MENU, self.OnRemoveConfigurationMenu,
+ id=wxID_PLCOPENEDITOREDITMENUITEMS12)
+
+ def _init_coll_menuBar1_Menus(self, parent):
+ # generated method, don't edit
+
+ parent.Append(menu=self.FileMenu, title=u'File')
+ parent.Append(menu=self.EditMenu, title=u'Edit')
+ parent.Append(menu=self.HelpMenu, title=u'Help')
+
+ def _init_coll_ConfigMenu_Items(self, parent):
+ # generated method, don't edit
+
+ parent.Append(help='', id=wxID_PLCOPENEDITORCONFIGMENUITEMS0,
+ kind=wx.ITEM_NORMAL, text=u'Add Resource')
+ parent.Append(help='', id=wxID_PLCOPENEDITORCONFIGMENUITEMS1,
+ kind=wx.ITEM_NORMAL, text=u'Remove Resource')
+ self.Bind(wx.EVT_MENU, self.OnAddResourceMenu,
+ id=wxID_PLCOPENEDITORCONFIGMENUITEMS0)
+ self.Bind(wx.EVT_MENU, self.OnRemoveResourceMenu,
+ id=wxID_PLCOPENEDITORCONFIGMENUITEMS1)
+
+ def _init_coll_HelpMenu_Items(self, parent):
+ # generated method, don't edit
+
+ parent.Append(help='', id=wxID_PLCOPENEDITORHELPMENUITEMS0,
+ kind=wx.ITEM_NORMAL, text=u'PLCOpenEditor\tF1')
+ parent.Append(help='', id=wxID_PLCOPENEDITORHELPMENUITEMS1,
+ kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
+ parent.Append(help='', id=wxID_PLCOPENEDITORHELPMENUITEMS2,
+ kind=wx.ITEM_NORMAL, text=u'IEC 61131-3\tF3')
+ parent.Append(help='', id=wxID_PLCOPENEDITORHELPMENUITEMS3,
+ kind=wx.ITEM_NORMAL, text=u'About')
+ self.Bind(wx.EVT_MENU, self.OnPLCOpenMenu,
+ id=wxID_PLCOPENEDITORHELPMENUITEMS1)
+
+ def _init_coll_FileMenu_Items(self, parent):
+ # generated method, don't edit
+
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS0,
+ kind=wx.ITEM_NORMAL, text=u'New\tCTRL+N')
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS1,
+ kind=wx.ITEM_NORMAL, text=u'Open\tCTRL+O')
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS2,
+ kind=wx.ITEM_NORMAL, text=u'Close Tab\tCTRL+W')
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS3,
+ kind=wx.ITEM_NORMAL, text=u'Close Project')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS5,
+ kind=wx.ITEM_NORMAL, text=u'Save\tCTRL+S')
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS6,
+ kind=wx.ITEM_NORMAL, text=u'Save As...\tCTRL+SHIFT+S')
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS7,
+ kind=wx.ITEM_NORMAL, text=u'Generate Program\tCTRL+G')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS9,
+ kind=wx.ITEM_NORMAL, text=u'Properties')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_PLCOPENEDITORFILEMENUITEMS11,
+ kind=wx.ITEM_NORMAL, text=u'Quit\tCTRL+Q')
+ self.Bind(wx.EVT_MENU, self.OnNewProjectMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS0)
+ self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS1)
+ self.Bind(wx.EVT_MENU, self.OnCloseTabMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS2)
+ self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS3)
+ self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS5)
+ self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS6)
+ self.Bind(wx.EVT_MENU, self.OnGenerateProgramMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS7)
+ self.Bind(wx.EVT_MENU, self.OnQuitMenu,
+ id=wxID_PLCOPENEDITORFILEMENUITEMS11)
+
+ def _init_coll_SFCMenu_Items(self, parent):
+ # generated method, don't edit
+
+ parent.Append(help='', id=wxID_PLCOPENEDITORSFCMENUITEMS0,
+ kind=wx.ITEM_NORMAL, text=u'Add Transition')
+ parent.Append(help='', id=wxID_PLCOPENEDITORSFCMENUITEMS1,
+ kind=wx.ITEM_NORMAL, text=u'Add Action')
+ parent.Append(help='', id=wxID_PLCOPENEDITORSFCMENUITEMS2,
+ kind=wx.ITEM_NORMAL, text=u'Remove Transition')
+ parent.Append(help='', id=wxID_PLCOPENEDITORSFCMENUITEMS3,
+ kind=wx.ITEM_NORMAL, text=u'Remove Action')
+ self.Bind(wx.EVT_MENU, self.OnAddPouTransitionMenu,
+ id=wxID_PLCOPENEDITORSFCMENUITEMS0)
+ self.Bind(wx.EVT_MENU, self.OnAddPouActionMenu,
+ id=wxID_PLCOPENEDITORSFCMENUITEMS1)
+ self.Bind(wx.EVT_MENU, self.OnRemovePouTransitionMenu,
+ id=wxID_PLCOPENEDITORSFCMENUITEMS2)
+ self.Bind(wx.EVT_MENU, self.OnRemovePouActionMenu,
+ id=wxID_PLCOPENEDITORSFCMENUITEMS3)
+
+ def _init_utils(self):
+ # generated method, don't edit
+ self.menuBar1 = wx.MenuBar()
+
+ self.FileMenu = wx.Menu(title=u'')
+
+ self.EditMenu = wx.Menu(title=u'')
+
+ self.HelpMenu = wx.Menu(title='')
+
+ self.SFCMenu = wx.Menu(title='')
+
+ self.ConfigMenu = wx.Menu(title='')
+
+ self._init_coll_menuBar1_Menus(self.menuBar1)
+ self._init_coll_FileMenu_Items(self.FileMenu)
+ self._init_coll_EditMenu_Items(self.EditMenu)
+ self._init_coll_HelpMenu_Items(self.HelpMenu)
+ self._init_coll_SFCMenu_Items(self.SFCMenu)
+ self._init_coll_ConfigMenu_Items(self.ConfigMenu)
+
+ def _init_coll_MainGridSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.splitterWindow1, 0, border=0, flag=wxGROW)
+
+ def _init_coll_MainGridSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.MainGridSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=0)
+
+ self._init_coll_MainGridSizer_Growables(self.MainGridSizer)
+ self._init_coll_MainGridSizer_Items(self.MainGridSizer)
+
+ self.SetSizer(self.MainGridSizer)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Frame.__init__(self, id=wxID_PLCOPENEDITOR, name=u'PLCOpenEditor',
+ parent=prnt, pos=wx.Point(235, 287), size=wx.Size(1000, 600),
+ style=wx.DEFAULT_FRAME_STYLE, title=u'PLCOpenEditor')
+ self._init_utils()
+ self.SetClientSize(wx.Size(1000, 600))
+ self.SetMenuBar(self.menuBar1)
+ self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnProjectTreeItemBeginEdit,
+ id=wxID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnProjectTreeItemEndEdit,
+ id=wxID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnProjectTreeItemActivated,
+ id=wxID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnProjectTreeItemSelected,
+ id=wxID_PLCOPENEDITORPROJECTTREE)
+
+ self.splitterWindow1 = wx.SplitterWindow(id=wxID_PLCOPENEDITORSPLITTERWINDOW1,
+ name='splitterWindow1', parent=self, point=wx.Point(0, 0),
+ size=wx.Size(-1, -1), style=wx.SP_3D)
+ self.splitterWindow1.SetNeedUpdating(True)
+ self.splitterWindow1.SetMinimumPaneSize(1)
+
+ self.TabsOpened = wx.Notebook(id=wxID_PLCOPENEDITORTABSOPENED,
+ name='TabsOpened', parent=self.splitterWindow1, pos=wx.Point(0,
+ 0), size=wx.Size(-1, -1), style=0)
+ self.TabsOpened.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED,
+ self.OnPouSelectedChanged, id=wxID_PLCOPENEDITORTABSOPENED)
+
+ self.ProjectTree = wx.TreeCtrl(id=wxID_PLCOPENEDITORPROJECTTREE,
+ name='treeCtrl1', parent=self.splitterWindow1, pos=wx.Point(0, 0),
+ size=wx.Size(-1, -1),
+ style=wx.TR_HAS_BUTTONS|wx.TR_EDIT_LABELS|wx.TR_SINGLE|wx.SUNKEN_BORDER)
+ self.ProjectTree.Bind(wx.EVT_RIGHT_UP, self.OnProjectTreeRightUp)
+ self.splitterWindow1.SplitVertically(self.ProjectTree, self.TabsOpened,
+ 200)
+
+ self._init_sizers()
+
+ def init_coll_DefaultToolBar_Items(self, parent):
+ parent.AddRadioTool(wxID_PLCOPENEDITORDEFAULTTOOLBARITEMS0,
+ wxBitmap('Images/select.png'), wxNullBitmap, "Select an object")
+ self.Bind(wx.EVT_TOOL, self.OnSelectionTool,
+ id=wxID_PLCOPENEDITORDEFAULTTOOLBARITEMS0)
+
+ def init_coll_SFCToolBar_Items(self, parent):
+ parent.AddRadioTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS0,
+ wxBitmap('Images/select.png'), wxNullBitmap, "Select an object")
+ parent.AddRadioTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS1,
+ wxBitmap('Images/comment.png'), wxNullBitmap, "Create a new comment")
+ parent.AddRadioTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS2,
+ wxBitmap('Images/initial_step.png'), wxNullBitmap, "Create a new initial step")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS3,
+ wxBitmap('Images/step.png'), "Create a new step")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS4,
+ wxBitmap('Images/action.png'), "Add action block to step")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS5,
+ wxBitmap('Images/divergence.png'), "Create a new divergence")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORSFCTOOLBARITEMS6,
+ wxBitmap('Images/jump.png'), "Create a new jump")
+ self.Bind(wx.EVT_TOOL, self.OnSelectionTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS0)
+ self.Bind(wx.EVT_TOOL, self.OnCommentTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS1)
+ self.Bind(wx.EVT_TOOL, self.OnSFCInitialStepTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS2)
+ self.Bind(wx.EVT_TOOL, self.OnSFCStepTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS3)
+ self.Bind(wx.EVT_TOOL, self.OnSFCActionBlockTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS4)
+ self.Bind(wx.EVT_TOOL, self.OnSFCDivergenceTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS5)
+ self.Bind(wx.EVT_TOOL, self.OnSFCJumpTool,
+ id=wxID_PLCOPENEDITORSFCTOOLBARITEMS6)
+
+ def init_coll_FBDToolBar_Items(self, parent):
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS0,
+ wxBitmap('Images/select.png'), wxNullBitmap, "Select an object")
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS1,
+ wxBitmap('Images/comment.png'), wxNullBitmap, "Create a new comment")
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS2,
+ wxBitmap('Images/variable.png'), wxNullBitmap, "Create a new variable")
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS3,
+ wxBitmap('Images/block.png'), wxNullBitmap, "Create a new block")
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS4,
+ wxBitmap('Images/connection.png'), wxNullBitmap, "Create a new connection")
+ parent.AddRadioTool(wxID_PLCOPENEDITORFBDTOOLBARITEMS5,
+ wxBitmap('Images/wire.png'), wxNullBitmap, "Create a new wire")
+ self.Bind(wx.EVT_TOOL, self.OnSelectionTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS0)
+ self.Bind(wx.EVT_TOOL, self.OnCommentTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS1)
+ self.Bind(wx.EVT_TOOL, self.OnFBDVariableTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS2)
+ self.Bind(wx.EVT_TOOL, self.OnFBDBlockTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS3)
+ self.Bind(wx.EVT_TOOL, self.OnFBDConnectionTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS4)
+ self.Bind(wx.EVT_TOOL, self.OnWireTool,
+ id=wxID_PLCOPENEDITORFBDTOOLBARITEMS5)
+
+ def init_coll_LDToolBar_Items(self, parent):
+ parent.AddRadioTool(wxID_PLCOPENEDITORLDTOOLBARITEMS0,
+ wxBitmap('Images/select.png'), wxNullBitmap, "Select an object")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORLDTOOLBARITEMS1,
+ wxBitmap('Images/coil.png'), "Create a new rung")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORLDTOOLBARITEMS2,
+ wxBitmap('Images/contact.png'), "Create a new contact")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORLDTOOLBARITEMS3,
+ wxBitmap('Images/block.png'), "Create a new block")
+ parent.AddSimpleTool(wxID_PLCOPENEDITORLDTOOLBARITEMS4,
+ wxBitmap('Images/branch.png'), "Create a new branch")
+ self.Bind(wx.EVT_TOOL, self.OnSelectionTool,
+ id=wxID_PLCOPENEDITORLDTOOLBARITEMS0)
+ self.Bind(wx.EVT_TOOL, self.OnLDCoilTool,
+ id=wxID_PLCOPENEDITORLDTOOLBARITEMS1)
+ self.Bind(wx.EVT_TOOL, self.OnLDContactTool,
+ id=wxID_PLCOPENEDITORLDTOOLBARITEMS2)
+ self.Bind(wx.EVT_TOOL, self.OnLDBlockTool,
+ id=wxID_PLCOPENEDITORLDTOOLBARITEMS3)
+ self.Bind(wx.EVT_TOOL, self.OnLDBranchTool,
+ id=wxID_PLCOPENEDITORLDTOOLBARITEMS4)
+
+ def init_toolbars(self, parent):
+ self.DefaultToolBar = wxToolBar(id=wxID_PLCOPENEDITORDEFAULTTOOLBAR, name='DefaultToolBar',
+ parent=parent, pos=wx.Point(0, 27), size=wx.Size(0, 0),
+ style=wxTB_HORIZONTAL | wxNO_BORDER)
+
+ self.SFCToolBar = wxToolBar(id=wxID_PLCOPENEDITORSFCTOOLBAR, name='SFCToolBar',
+ parent=parent, pos=wx.Point(0, 27), size=wx.Size(0, 0),
+ style=wxTB_HORIZONTAL | wxNO_BORDER)
+
+ self.FBDToolBar = wxToolBar(id=wxID_PLCOPENEDITORFBDTOOLBAR, name='FBDToolBar',
+ parent=parent, pos=wx.Point(0, 27), size=wx.Size(0, 0),
+ style=wxTB_HORIZONTAL | wxNO_BORDER)
+
+ self.LDToolBar = wxToolBar(id=wxID_PLCOPENEDITORLDTOOLBAR, name='LDToolBar',
+ parent=parent, pos=wx.Point(0, 27), size=wx.Size(0, 0),
+ style=wxTB_HORIZONTAL | wxNO_BORDER)
+
+ self.init_coll_DefaultToolBar_Items(self.DefaultToolBar)
+ self.init_coll_SFCToolBar_Items(self.SFCToolBar)
+ self.init_coll_FBDToolBar_Items(self.FBDToolBar)
+ self.init_coll_LDToolBar_Items(self.LDToolBar)
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.init_toolbars(self)
+
+ self.Controler = PLCControler()
+
+ if fileOpen:
+ self.Controler.OpenXMLFile(fileOpen)
+ self.RefreshProjectTree()
+
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+
+ def RefreshFileMenu(self):
+ if self.Controler.HasOpenedProject():
+ if self.TabsOpened.GetPageCount() > 0:
+ self.FileMenu.FindItemByPosition(2).Enable(True)
+ else:
+ self.FileMenu.FindItemByPosition(2).Enable(False)
+ self.FileMenu.FindItemByPosition(3).Enable(True)
+ self.FileMenu.FindItemByPosition(5).Enable(True)
+ self.FileMenu.FindItemByPosition(6).Enable(True)
+ self.FileMenu.FindItemByPosition(7).Enable(True)
+ self.FileMenu.FindItemByPosition(9).Enable(True)
+ else:
+ self.FileMenu.FindItemByPosition(2).Enable(False)
+ self.FileMenu.FindItemByPosition(3).Enable(False)
+ self.FileMenu.FindItemByPosition(5).Enable(False)
+ self.FileMenu.FindItemByPosition(6).Enable(False)
+ self.FileMenu.FindItemByPosition(7).Enable(False)
+ self.FileMenu.FindItemByPosition(9).Enable(False)
+
+ def RefreshEditMenu(self):
+ self.EditMenu.FindItemByPosition(1).Enable(False)
+ self.EditMenu.FindItemByPosition(2).Enable(False)
+ if self.Controler.HasOpenedProject():
+ if self.TabsOpened.GetPageCount() > 0:
+ self.EditMenu.FindItemByPosition(0).Enable(True)
+ else:
+ self.EditMenu.FindItemByPosition(0).Enable(False)
+ self.EditMenu.FindItemByPosition(8).Enable(True)
+ self.EditMenu.FindItemByPosition(9).Enable(True)
+ else:
+ self.EditMenu.FindItemByPosition(0).Enable(False)
+ self.EditMenu.FindItemByPosition(8).Enable(False)
+ self.EditMenu.FindItemByPosition(9).Enable(False)
+ bodytype = self.Controler.GetCurrentElementEditingBodyType()
+ if bodytype in ["LD","ST"]:
+ self.EditMenu.FindItemByPosition(4).Enable(True)
+ self.EditMenu.FindItemByPosition(5).Enable(True)
+ self.EditMenu.FindItemByPosition(6).Enable(True)
+ else:
+ self.EditMenu.FindItemByPosition(4).Enable(False)
+ self.EditMenu.FindItemByPosition(5).Enable(False)
+ self.EditMenu.FindItemByPosition(6).Enable(False)
+
+ def OnNewProjectMenu(self, event):
+ dialog = ProjectDialog(self)
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ projectname = values.pop("projectName")
+ values["creationDateTime"] = datetime(*localtime()[:6])
+ self.Controler.CreateNewProject(projectname)
+ self.Controler.SetProjectProperties(values)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshProjectTree()
+ event.Skip()
+
+ def OnOpenProjectMenu(self, event):
+ filepath = self.Controler.GetFilePath()
+ if filepath != "":
+ directory = os.path.dirname(filepath)
+ else:
+ directory = os.getcwd()
+ dialog = wxFileDialog(self, "Choose a file", directory, "", "PLCOpen files (*.xml)|*.xml|All files|*.*", wxOPEN)
+ if dialog.ShowModal() == wxID_OK:
+ filepath = dialog.GetPath()
+ if os.path.isfile(filepath):
+ self.Controler.OpenXMLFile(filepath)
+ self.TabsOpened.DeleteAllPages()
+ self.RefreshProjectTree()
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnCloseTabMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ self.Controler.CloseElementEditing()
+ self.TabsOpened.DeletePage(selected)
+ if self.TabsOpened.GetPageCount() > 0:
+ self.TabsOpened.SetSelection(min(selected, self.TabsOpened.GetPageCount() - 1))
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ event.Skip()
+
+ def OnCloseProjectMenu(self, event):
+ self.Controler.Reset()
+ self.TabsOpened.DeleteAllPages()
+ self.ProjectTree.DeleteAllItems()
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ event.Skip()
+
+ def OnSaveProjectMenu(self, event):
+ self.SaveProject()
+ event.Skip()
+
+ def OnSaveProjectAsMenu(self, event):
+ self.SaveProjectAs()
+ event.Skip()
+
+ def OnGenerateProgramMenu(self, event):
+ self.Controler.GenerateProgram()
+ event.Skip()
+
+ def SaveProject(self):
+ result = self.Controler.SaveXMLFile()
+ if not result:
+ self.SaveProjectAs()
+
+ def SaveProjectAs(self):
+ filepath = self.Controler.GetFilePath()
+ if filepath != "":
+ directory, filename = os.path.split(filepath)
+ else:
+ directory, filename = os.getcwd(), "%s.od"%self.Controler.GetProjectName()
+ dialog = wxFileDialog(self, "Choose a file", directory, filename, "PLCOpen files (*.xml)|*.xml|All files|*.*", wxSAVE|wxOVERWRITE_PROMPT)
+ if dialog.ShowModal() == wxID_OK:
+ filepath = dialog.GetPath()
+ if os.path.isdir(os.path.dirname(filepath)):
+ result = self.Controler.SaveXMLFile(filepath)
+ if not result:
+ message = wxMessageDialog(self, "Can't save project to file %s!"%filepath, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ message = wxMessageDialog(self, "%s is not a valid folder!"%os.path.dirname(filepath), "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ dialog.Destroy()
+
+ def OnQuitMenu(self, event):
+ self.Close()
+ event.Skip()
+
+ def OnSelectionTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_SELECTION)
+ event.Skip()
+
+ def OnCommentTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_COMMENT)
+ event.Skip()
+
+ def OnWireTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_WIRE)
+ event.Skip()
+
+ def OnSFCInitialStepTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_INITIAL_STEP)
+ event.Skip()
+
+ def OnSFCStepTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddStep()
+ event.Skip()
+
+ def OnSFCActionBlockTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddStepAction()
+ event.Skip()
+
+ def OnSFCDivergenceTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddDivergence()
+ event.Skip()
+
+ def OnSFCJumpTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddJump()
+ event.Skip()
+
+ def OnFBDVariableTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_VARIABLE)
+ event.Skip()
+
+ def OnFBDBlockTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_BLOCK)
+ event.Skip()
+
+ def OnFBDConnectionTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_CONNECTION)
+ event.Skip()
+
+ def OnLDCoilTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddRung()
+ event.Skip()
+
+ def OnLDContactTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddContact()
+ event.Skip()
+
+ def OnLDBlockTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ pass
+ event.Skip()
+
+ def OnLDBranchTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddBranch()
+ event.Skip()
+
+ def OnPouSelectedChanged(self, event):
+ selected = event.GetSelection()
+ if selected >= 0:
+ self.Controler.RefreshCurrentElementEditing(selected)
+ found = False
+ name = self.TabsOpened.GetPageText(selected)
+ root = self.ProjectTree.GetRootItem()
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
+ while item.IsOk() and not found:
+ if self.ProjectTree.GetItemText(item) == name:
+ self.ProjectTree.SelectItem(item)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ event.Skip()
+
+ def OnProjectTreeItemEndEdit(self, event):
+ new_name = event.GetLabel()
+ if new_name != "":
+ if TestIdentifier(new_name):
+ item = event.GetItem()
+ itemtype = self.ProjectTree.GetPyData(item)
+ if itemtype == ITEM_PROJECT:
+ self.Controler.SetProjectName(new_name)
+ elif itemtype == ITEM_POU:
+ old_name = self.ProjectTree.GetItemText(item)
+ self.Controler.ChangePouName(old_name, new_name)
+ self.RefreshTabsOpenedTitles()
+ elif itemtype == ITEM_TRANSITION:
+ old_name = self.ProjectTree.GetItemText(item)
+ parent = self.ProjectTree.GetItemParent(item)
+ grandparent = self.ProjectTree.GetItemParent(parent)
+ grandparent_name = self.ProjectTree.GetItemText(grandparent)
+ self.Controler.ChangePouTransitionName(grandparent_name, old_name, new_name)
+ self.RefreshTabsOpenedTitles()
+ elif itemtype == ITEM_ACTION:
+ old_name = self.ProjectTree.GetItemText(item)
+ parent = self.ProjectTree.GetItemParent(item)
+ grandparent = self.ProjectTree.GetItemParent(parent)
+ grandparent_name = self.ProjectTree.GetItemText(grandparent)
+ self.Controler.ChangePouActionName(grandparent_name, old_name, new_name)
+ self.RefreshTabsOpenedTitles()
+ wxCallAfter(self.RefreshProjectTree)
+ event.Skip()
+ else:
+ message = wxMessageDialog(self, "\"%s\" is not a valid identifier!"%new_name, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ item = event.GetItem()
+ wxCallAfter(self.ProjectTree.EditLabel, item)
+ event.Veto()
+
+ def OnProjectTreeItemBeginEdit(self, event):
+ selected = event.GetItem()
+ if self.ProjectTree.GetPyData(selected) == ITEM_UNEDITABLE:
+ event.Veto()
+ else:
+ event.Skip()
+
+ def OnProjectTreeItemActivated(self, event):
+ selected = event.GetItem()
+ if self.ProjectTree.IsExpanded(selected):
+ self.ProjectTree.Collapse(selected)
+ else:
+ self.ProjectTree.Expand(selected)
+ name = self.ProjectTree.GetItemText(selected)
+ data = self.ProjectTree.GetPyData(selected)
+ if name == "Properties":
+ old_values = self.Controler.GetProjectProperties()
+ old_values["projectName"] = self.Controler.GetProjectName()
+ dialog = ProjectDialog(self)
+ dialog.SetValues(old_values)
+ if dialog.ShowModal() == wxID_OK:
+ new_values = dialog.GetValues()
+ projectname = new_values.pop("projectName")
+ new_values["creationDateTime"] = old_values["creationDateTime"]
+ self.Controler.SetProjectName(projectname)
+ self.Controler.SetProjectProperties(new_values)
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ elif data == ITEM_CLASS:
+ item = self.ProjectTree.GetItemParent(selected)
+ item_type = self.ProjectTree.GetPyData(item)
+ while item_type != ITEM_POU:
+ item = self.ProjectTree.GetItemParent(item)
+ item_type = self.ProjectTree.GetPyData(item)
+ pou_name = self.ProjectTree.GetItemText(item)
+ dialog = EditVariableDialog(self, pou_name, self.Controler.GetPouType(pou_name), name)
+ values = {}
+ values["returnType"] = self.Controler.GetPouInterfaceReturnTypeByName(pou_name)
+ values["data"] = self.Controler.GetPouInterfaceVarsByName(pou_name)
+ dialog.SetValues(values)
+ if dialog.ShowModal() == wxID_OK:
+ if not self.Controler.PouIsUsed(pou_name):
+ new_values = dialog.GetValues()
+ if "returnType" in new_values:
+ self.Controler.SetPouInterfaceReturnType(pou_name, new_values["returnType"])
+ self.Controler.SetPouInterfaceVars(pou_name, new_values["data"])
+ pou_names = self.Controler.GetElementsOpenedNames()
+ if pou_name in pou_names:
+ window = self.TabsOpened.GetPage(pou_names.index(pou_name))
+ if isinstance(window, TextViewer):
+ varlist = []
+ if "returnType" in new_values:
+ varlist.append(name)
+ for var in new_values["data"]:
+ varlist.append(var["Name"])
+ window.SetVariables(varlist)
+ else:
+ message = wxMessageDialog(self, "\"%s\" is used by one or more POUs. Its interface can't be changed!"%pou_name, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ dialog.Destroy()
+ elif name == "Global Vars":
+ parent = self.ProjectTree.GetItemParent(selected)
+ parent_name = self.ProjectTree.GetItemText(parent)
+ dialog = EditVariableDialog(self, parent_name, None)
+ if self.ProjectTree.GetPyData(parent) == ITEM_CONFIGURATION:
+ values = {"data" : self.Controler.GetConfigurationGlobalVars(parent_name)}
+ dialog.SetValues(values)
+ if dialog.ShowModal() == wxID_OK:
+ new_values = dialog.GetValues()
+ self.Controler.SetConfigurationGlobalVars(parent_name, new_values["data"])
+ else:
+ config = self.ProjectTree.GetItemParent(parent)
+ config_name = self.ProjectTree.GetItemText(config)
+ values = {"data" : self.Controler.GetConfigurationResourceGlobalVars(config_name, parent_name)}
+ dialog.SetValues(values)
+ if dialog.ShowModal() == wxID_OK:
+ new_values = dialog.GetValues()
+ self.Controler.SetConfigurationResourceGlobalVars(config_name, parent_name, new_values["data"])
+ elif data in [ITEM_POU, ITEM_TRANSITION, ITEM_ACTION]:
+ if data == ITEM_POU:
+ idx = self.Controler.OpenElementEditing(name)
+ language = self.Controler.GetPouBodyType(name)
+ varlist = []
+ returnType = self.Controler.GetPouInterfaceReturnTypeByName(name)
+ if returnType:
+ varlist.append(name)
+ vars = self.Controler.GetPouInterfaceVarsByName(name)
+ if vars:
+ for var in vars:
+ varlist.append(var["Name"])
+ else:
+ parent = self.ProjectTree.GetItemParent(selected)
+ parent_name = self.ProjectTree.GetItemText(parent)
+ grandparent = self.ProjectTree.GetItemParent(parent)
+ grandparent_name = self.ProjectTree.GetItemText(grandparent)
+ if data == ITEM_TRANSITION:
+ idx = self.Controler.OpenPouTransitionEditing(grandparent_name, name)
+ language = self.Controler.GetTransitionBodyType(grandparent_name, name)
+ elif data == ITEM_ACTION:
+ idx = self.Controler.OpenPouActionEditing(grandparent_name, name)
+ language = self.Controler.GetActionBodyType(grandparent_name, name)
+ varlist = [name]
+ vars = self.Controler.GetPouInterfaceVarsByName(grandparent_name)
+ if vars:
+ for var in vars:
+ varlist.append(var["Name"])
+ if idx != None:
+ if language == "FBD":
+ new_window = FBD_Viewer(self.TabsOpened, self, self.Controler)
+ elif language == "LD":
+ new_window = LD_Viewer(self.TabsOpened, self, self.Controler)
+ elif language == "SFC":
+ new_window = SFC_Viewer(self.TabsOpened, self, self.Controler)
+ elif language in ["IL", "ST"]:
+ new_window = TextViewer(self.TabsOpened, self, self.Controler)
+ if language == "IL":
+ new_window.SetKeywords(IL_KEYWORDS)
+ else:
+ new_window.SetKeywords(ST_KEYWORDS)
+ new_window.SetVariables(varlist)
+ new_window.SetFunctions(self.Controler.GetBlockTypes())
+ else:
+ return
+ new_window.RefreshView()
+ self.TabsOpened.AddPage(new_window, "")
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshTabsOpenedTitles()
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ else:
+ if data == ITEM_POU:
+ idx = self.Controler.ChangeElementEditing(name)
+ elif data == ITEM_TRANSITION:
+ idx = self.Controler.ChangePouTransitionEditing(grandparent_name, name)
+ elif data == ITEM_ACTION:
+ idx = self.Controler.ChangePouActionEditing(grandparent_name, name)
+ if idx != None:
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ elif data == ITEM_RESOURCE:
+ item = self.ProjectTree.GetItemParent(selected)
+ item_type = self.ProjectTree.GetPyData(item)
+ while item_type != ITEM_CONFIGURATION:
+ item = self.ProjectTree.GetItemParent(item)
+ item_type = self.ProjectTree.GetPyData(item)
+ config_name = self.ProjectTree.GetItemText(item)
+ idx = self.Controler.OpenConfigurationResourceEditing(config_name, name)
+ if idx != None:
+ new_window = ResourceEditor(self.TabsOpened, self, self.Controler)
+ new_window.RefreshView()
+ self.TabsOpened.AddPage(new_window, "")
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshTabsOpenedTitles()
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ else:
+ idx = self.Controler.ChangeConfigurationResourceEditing(parent_name, name)
+ if idx != None:
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+
+ def OnProjectTreeRightUp(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected) == ITEM_POU:
+ name = self.ProjectTree.GetItemText(selected)
+ if self.Controler.GetPouBodyType(name) == "SFC":
+ self.PopupMenu(self.SFCMenu)
+ elif self.ProjectTree.GetPyData(selected) == ITEM_CONFIGURATION:
+ self.PopupMenu(self.ConfigMenu)
+ event.Skip()
+
+ def OnProjectTreeItemSelected(self, event):
+ selected = event.GetItem()
+ name = self.ProjectTree.GetItemText(selected)
+ if self.ProjectTree.GetItemParent(selected) == self.ProjectTree.GetRootItem() and name != "Properties":
+ if self.Controler.IsElementEditing(name):
+ idx = self.Controler.ChangeElementEditing(name)
+ if idx != None:
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ else:
+ name = self.ProjectTree.GetItemText(selected)
+ parent = self.ProjectTree.GetItemParent(selected)
+ parent_name = self.ProjectTree.GetItemText(parent)
+ grandparent = self.ProjectTree.GetItemParent(parent)
+ grandparent_name = self.ProjectTree.GetItemText(grandparent)
+ if parent_name == "Transitions":
+ idx = self.Controler.ChangePouTransitionEditing(grandparent_name, name)
+ if idx != None:
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ elif parent_name == "Action":
+ idx = self.Controler.ChangePouActionEditing(grandparent_name, name)
+ if idx != None:
+ self.TabsOpened.SetSelection(idx)
+ self.RefreshFileMenu()
+ self.RefreshEditMenu()
+ self.RefreshToolBar()
+ event.Skip()
+
+ def RefreshProjectTree(self):
+ infos = self.Controler.GetProjectInfos()
+ root = self.ProjectTree.GetRootItem()
+ self.GenerateTreeBranch(root, infos)
+ self.ProjectTree.Expand(self.ProjectTree.GetRootItem())
+
+ def GenerateTreeBranch(self, root, infos):
+ to_delete = []
+ if root.IsOk():
+ self.ProjectTree.SetItemText(root, infos["name"])
+ else:
+ root = self.ProjectTree.AddRoot(infos["name"])
+ self.ProjectTree.SetPyData(root, infos["type"])
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
+ if len(infos["values"]) > 0:
+ for values in infos["values"]:
+ if not item.IsOk():
+ item = self.ProjectTree.AppendItem(root, "")
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ self.GenerateTreeBranch(item, values)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ while item.IsOk():
+ to_delete.append(item)
+ item, root_cookie = self.ProjectTree.GetNextChild(item, root_cookie)
+ for item in to_delete:
+ self.ProjectTree.Delete(item)
+
+ def RefreshToolBar(self):
+ language = self.Controler.GetCurrentElementEditingBodyType()
+ if language == "SFC":
+ self.SFCToolBar.ToggleTool(0, True)
+ self.SFCToolBar.Show()
+ self.SetToolBar(self.SFCToolBar)
+ self.DefaultToolBar.Hide()
+ self.FBDToolBar.Hide()
+ self.LDToolBar.Hide()
+ elif language == "FBD":
+ self.FBDToolBar.ToggleTool(0, True)
+ self.FBDToolBar.Show()
+ self.SetToolBar(self.FBDToolBar)
+ self.DefaultToolBar.Hide()
+ self.SFCToolBar.Hide()
+ self.LDToolBar.Hide()
+ elif language == "LD":
+ self.LDToolBar.ToggleTool(0, True)
+ self.LDToolBar.Show()
+ self.SetToolBar(self.LDToolBar)
+ self.DefaultToolBar.Hide()
+ self.SFCToolBar.Hide()
+ self.FBDToolBar.Hide()
+ else:
+ self.DefaultToolBar.ToggleTool(0, True)
+ self.DefaultToolBar.Show()
+ self.SetToolBar(self.DefaultToolBar)
+ self.SFCToolBar.Hide()
+ self.FBDToolBar.Hide()
+ self.LDToolBar.Hide()
+
+ def RefreshTabsOpenedTitles(self):
+ pous = self.Controler.GetElementsOpenedNames()
+ for i, pou in enumerate(pous):
+ self.TabsOpened.SetPageText(i, pou)
+
+ def OnRefreshMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).Refresh()
+ event.Skip()
+
+ def OnCutMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).Cut()
+ event.Skip()
+
+ def OnCopyMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).Copy()
+ event.Skip()
+
+ def OnPasteMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).Paste()
+ event.Skip()
+
+ def OnAddPouMenu(self, event):
+ dialog = PouDialog(self)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames())
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ self.Controler.ProjectAddPou(values["pouName"], values["pouType"], values["language"])
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnRemovePouMenu(self, event):
+ pous = self.Controler.GetProjectPouNames()
+ dialog = wxSingleChoiceDialog(self, "Select POU to remove:", "POU Remove", pous, wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ selected = dialog.GetStringSelection()
+ if not self.Controler.PouIsUsed(selected):
+ self.Controler.ProjectRemovePou(selected)
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ if self.TabsOpened.GetPageText(i) == selected:
+ self.TabsOpened.DeletePage(i)
+ self.RefreshProjectTree()
+ self.RefreshToolBar()
+ else:
+ message = wxMessageDialog(self, "%s is used by one or more POUs. It can't be removed!"%selected, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Skip()
+
+ def OnAddConfigurationMenu(self, event):
+ dialog = wxTextEntryDialog(self, "Enter configuration name:", "Create new configuration", "", wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ self.Controler.ProjectAddConfiguration(value)
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnRemoveConfigurationMenu(self, event):
+ configs = self.Controler.GetProjectConfigNames()
+ dialog = wxSingleChoiceDialog(self, "Select Configuration to remove:", "Configuration Remove", configs, wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ selected = dialog.GetStringSelection()
+ self.Controler.ProjectRemoveConfiguration(selected)
+ self.RefreshProjectTree()
+ event.Skip()
+
+ def OnAddPouTransitionMenu(self, event):
+ dialog = PouTransitionDialog(self)
+ dialog.SetPous(self.Controler.GetSFCPous())
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ self.Controler.ProjectAddPouTransition(values["pouName"], values["transitionName"], values["language"])
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnRemovePouTransitionMenu(self, event):
+ event.Skip()
+
+ def OnAddPouActionMenu(self, event):
+ dialog = PouActionDialog(self)
+ dialog.SetPous(self.Controler.GetSFCPous())
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ self.Controler.ProjectAddPouAction(values["pouName"], values["actionName"], values["language"])
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnRemovePouActionMenu(self, event):
+ event.Skip()
+
+ def OnAddResourceMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected) == ITEM_CONFIGURATION:
+ config_name = self.ProjectTree.GetItemText(selected)
+ dialog = wxTextEntryDialog(self, "Enter Resource name:", "Create new Resource", "", wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ self.Controler.ProjectAddConfigurationResource(config_name, value)
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnRemoveResourceMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected) == ITEM_CONFIGURATION:
+ config_name = self.ProjectTree.GetItemText(selected)
+ infos = self.Controler.GetProjectInfos()
+ resources = []
+ for config in infos["configs"]:
+ if config["name"] == config_name:
+ resources = config["resources"]
+ dialog = wxSingleChoiceDialog(self, "Select Resource to remove:", "Resource Remove", resources, wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ resource = dialog.GetStringSelection()
+ self.Controler.ProjectRemoveConfigurationResource(config_name, resource)
+ self.RefreshProjectTree()
+ dialog.Destroy()
+ event.Skip()
+
+ def OnPLCOpenMenu(self, event):
+ result = OpenPDFDoc()
+ if type(result) == StringType:
+ message = wxMessageDialog(self, result, "ERROR", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Skip()
+
+current_num = 0
+def GetNewNum():
+ global current_num
+ current_num += 1
+ return current_num
+
+#-------------------------------------------------------------------------------
+# Create Project Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_PROJECTDIALOG, wxID_PROJECTDIALOGMAINPANEL,
+ wxID_PROJECTDIALOGPROJECTNAME, wxID_PROJECTDIALOGCOMPANYNAME,
+ wxID_PROJECTDIALOGCOMPANYURL, wxID_PROJECTDIALOGPRODUCTNAME,
+ wxID_PROJECTDIALOGPRODUCTVERSION, wxID_PROJECTDIALOGPRODUCTRELEASE,
+ wxID_PROJECTDIALOGCONTENTDESCRIPTION, wxID_PROJECTDIALOGSTATICTEXT1,
+ wxID_PROJECTDIALOGSTATICTEXT2, wxID_PROJECTDIALOGSTATICTEXT3,
+ wxID_PROJECTDIALOGSTATICTEXT4, wxID_PROJECTDIALOGSTATICTEXT5,
+ wxID_PROJECTDIALOGSTATICTEXT6, wxID_PROJECTDIALOGSTATICTEXT7,
+] = [wx.NewId() for _init_ctrls in range(16)]
+
+class ProjectDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_PROJECTDIALOG,
+ name='ProjectDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(550, 450), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Create a new project')
+ self.SetClientSize(wx.Size(550, 450))
+
+ self.MainPanel = wx.Panel(id=wxID_PROJECTDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(450, 400), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT1,
+ label='Project Name (required):', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(215, 17), style=0)
+
+ self.ProjectName = wx.TextCtrl(id=wxID_PROJECTDIALOGPROJECTNAME,
+ name='ProjectName', parent=self.MainPanel, pos=wx.Point(224, 24),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT2,
+ label='Company Name (required):', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 64), size=wx.Size(215, 17), style=0)
+
+ self.CompanyName = wx.TextCtrl(id=wxID_PROJECTDIALOGCOMPANYNAME,
+ name='CompanyName', parent=self.MainPanel, pos=wx.Point(224, 64),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT3,
+ label='Company URL (optional):', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 104), size=wx.Size(215, 17), style=0)
+
+ self.CompanyURL = wx.TextCtrl(id=wxID_PROJECTDIALOGCOMPANYURL,
+ name='CompanyURL', parent=self.MainPanel, pos=wx.Point(224, 104),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText4 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT4,
+ label='Product Name (required):', name='staticText4', parent=self.MainPanel,
+ pos=wx.Point(24, 144), size=wx.Size(215, 17), style=0)
+
+ self.ProductName = wx.TextCtrl(id=wxID_PROJECTDIALOGPRODUCTNAME,
+ name='ProductName', parent=self.MainPanel, pos=wx.Point(224, 144),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText5 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT5,
+ label='Product Version (required):', name='staticText5', parent=self.MainPanel,
+ pos=wx.Point(24, 184), size=wx.Size(215, 17), style=0)
+
+ self.ProductVersion = wx.TextCtrl(id=wxID_PROJECTDIALOGPRODUCTVERSION,
+ name='ProductVersion', parent=self.MainPanel, pos=wx.Point(224, 184),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText6 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT6,
+ label='Product Release (optional):', name='staticText6', parent=self.MainPanel,
+ pos=wx.Point(24, 224), size=wx.Size(215, 17), style=0)
+
+ self.ProductRelease = wx.TextCtrl(id=wxID_PROJECTDIALOGPRODUCTRELEASE,
+ name='ProductRelease', parent=self.MainPanel, pos=wx.Point(224, 224),
+ size=wx.Size(295, 24), style=0)
+
+ self.staticText7 = wx.StaticText(id=wxID_PROJECTDIALOGSTATICTEXT7,
+ label='Content Description (optional):', name='staticText7', parent=self.MainPanel,
+ pos=wx.Point(24, 264), size=wx.Size(215, 17), style=0)
+
+ self.ContentDescription = wx.TextCtrl(id=wxID_PROJECTDIALOGCONTENTDESCRIPTION,
+ name='ProductRelease', parent=self.MainPanel, pos=wx.Point(224, 264),
+ size=wx.Size(295, 120), style=wxTE_MULTILINE)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ if self.ProjectName.GetValue() == "":
+ error.append("Project Name")
+ if self.CompanyName.GetValue() == "":
+ error.append("Company Name")
+ if self.ProductName.GetValue() == "":
+ error.append("Product Name")
+ if self.ProductVersion.GetValue() == "":
+ error.append("Product Version")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "projectName":
+ self.ProjectName.SetValue(value)
+ elif item == "companyName":
+ self.CompanyName.SetValue(value)
+ elif item == "companyURL":
+ self.CompanyURL.SetValue(value)
+ elif item == "productName":
+ self.ProductName.SetValue(value)
+ elif item == "productVersion":
+ self.ProductVersion.SetValue(value)
+ elif item == "productRelease":
+ self.ProductRelease.SetValue(value)
+ elif item == "contentDescription":
+ self.ContentDescription.SetValue(value)
+
+ def GetValues(self):
+ values = {}
+ values["projectName"] = self.ProjectName.GetValue()
+ values["companyName"] = self.CompanyName.GetValue()
+ if self.CompanyURL.GetValue() != None:
+ values["companyURL"] = self.CompanyURL.GetValue()
+ values["productName"] = self.ProductName.GetValue()
+ values["productVersion"] = self.ProductVersion.GetValue()
+ if self.ProductRelease.GetValue() != None:
+ values["productRelease"] = self.ProductRelease.GetValue()
+ if self.ProductRelease.GetValue() != None:
+ values["contentDescription"] = self.ContentDescription.GetValue()
+ return values
+
+#-------------------------------------------------------------------------------
+# Create Pou Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_POUDIALOG, wxID_POUDIALOGMAINPANEL, wxID_POUDIALOGPOUNAME,
+ wxID_POUDIALOGPOUTYPE, wxID_POUDIALOGLANGUAGE, wxID_POUDIALOGSTATICTEXT1,
+ wxID_POUDIALOGSTATICTEXT2, wxID_POUDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+class PouDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_POUDIALOG,
+ name='ProjectDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(300, 200), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Create a new project')
+ self.SetClientSize(wx.Size(300, 200))
+
+ self.MainPanel = wx.Panel(id=wxID_POUDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(300, 200), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_POUDIALOGSTATICTEXT1,
+ label='POU Name:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(95, 17), style=0)
+
+ self.PouName = wx.TextCtrl(id=wxID_POUDIALOGPOUNAME,
+ name='POUName', parent=self.MainPanel, pos=wx.Point(104, 24),
+ size=wx.Size(150, 24), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_POUDIALOGSTATICTEXT2,
+ label='POU Type:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 64), size=wx.Size(95, 17), style=0)
+
+ self.PouType = wx.Choice(id=wxID_POUDIALOGPOUTYPE,
+ name='POUType', parent=self.MainPanel, pos=wx.Point(104, 64),
+ size=wx.Size(150, 24), style=0)
+ EVT_CHOICE(self, wxID_POUDIALOGPOUTYPE, self.OnTypeChanged)
+
+ self.staticText3 = wx.StaticText(id=wxID_POUDIALOGSTATICTEXT3,
+ label='Language:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 104), size=wx.Size(95, 17), style=0)
+
+ self.Language = wx.Choice(id=wxID_POUDIALOGLANGUAGE,
+ name='Language', parent=self.MainPanel, pos=wx.Point(104, 104),
+ size=wx.Size(150, 24), style=0)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ for option in ["function","functionBlock","program"]:
+ self.PouType.Append(option)
+ self.RefreshLanguage()
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ pou_name = self.PouName.GetValue()
+ if pou_name == "":
+ error.append("POU Name")
+ if self.PouType.GetStringSelection() == "":
+ error.append("POU Type")
+ if self.Language.GetStringSelection() == "":
+ error.append("Language")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ elif not TestIdentifier(pou_name):
+ message = wxMessageDialog(self, "\"%s\" is not a valid identifier!"%pou_name, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ elif pou_name.upper() in IEC_KEYWORDS:
+ message = wxMessageDialog(self, "\"%s\" is a keyword. It can't be used!"%pou_name, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ elif pou_name.upper() in self.PouNames:
+ message = wxMessageDialog(self, "\"%s\" pou already exists!"%pou_name, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def RefreshLanguage(self):
+ selection = self.Language.GetStringSelection()
+ self.Language.Clear()
+ for option in ["IL","ST","LD","FBD","SFC"]:
+ if option != "SFC" or self.PouType.GetStringSelection() == "program":
+ self.Language.Append(option)
+ if self.Language.FindString(selection) != wxNOT_FOUND:
+ self.Language.SetStringSelection(selection)
+
+ def OnTypeChanged(self, event):
+ self.RefreshLanguage()
+ event.Skip()
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "pouName":
+ self.PouName.SetValue(value)
+ elif item == "pouType":
+ self.PouType.SetStringSelection(value)
+ elif item == "language":
+ self.Language.SetStringSelection(value)
+
+ def GetValues(self):
+ values = {}
+ values["pouName"] = self.PouName.GetValue()
+ values["pouType"] = self.PouType.GetStringSelection()
+ values["language"] = self.Language.GetStringSelection()
+ return values
+
+
+#-------------------------------------------------------------------------------
+# Create Pou Transition Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_POUTRANSITIONDIALOG, wxID_POUTRANSITIONDIALOGMAINPANEL,
+ wxID_POUTRANSITIONDIALOGPOUNAME, wxID_POUTRANSITIONDIALOGTRANSITIONNAME,
+ wxID_POUTRANSITIONDIALOGLANGUAGE, wxID_POUTRANSITIONDIALOGSTATICTEXT1,
+ wxID_POUTRANSITIONDIALOGSTATICTEXT2, wxID_POUTRANSITIONDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+class PouTransitionDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_POUTRANSITIONDIALOG,
+ name='ProjectDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(350, 200), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Create a new project')
+ self.SetClientSize(wx.Size(350, 200))
+
+ self.MainPanel = wx.Panel(id=wxID_POUTRANSITIONDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(350, 200), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_POUTRANSITIONDIALOGSTATICTEXT1,
+ label='POU Name:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(145, 17), style=0)
+
+ self.PouName = wx.Choice(id=wxID_POUTRANSITIONDIALOGPOUNAME,
+ name='POUName', parent=self.MainPanel, pos=wx.Point(154, 24),
+ size=wx.Size(150, 24), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_POUTRANSITIONDIALOGSTATICTEXT2,
+ label='Transition Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 64), size=wx.Size(145, 17), style=0)
+
+ self.TransitionName = wx.TextCtrl(id=wxID_POUTRANSITIONDIALOGTRANSITIONNAME,
+ name='TransitionName', parent=self.MainPanel, pos=wx.Point(154, 64),
+ size=wx.Size(150, 24), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_POUTRANSITIONDIALOGSTATICTEXT3,
+ label='Language:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 104), size=wx.Size(145, 17), style=0)
+
+ self.Language = wx.Choice(id=wxID_POUTRANSITIONDIALOGLANGUAGE,
+ name='Language', parent=self.MainPanel, pos=wx.Point(154, 104),
+ size=wx.Size(150, 24), style=0)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ for option in ["IL","ST","LD","FBD"]:
+ self.Language.Append(option)
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ if self.PouName.GetStringSelection() == "":
+ error.append("POU Name")
+ if self.TransitionName.GetValue() == "":
+ error.append("Transition Name")
+ if self.Language.GetStringSelection() == "":
+ error.append("Language")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def SetPous(self, pous):
+ for pou in pous:
+ self.PouName.Append(pou)
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "pouName":
+ self.PouName.SetStringSelection(value)
+ elif item == "transitionName":
+ self.TransitionName.SetValue(value)
+ elif item == "language":
+ self.Language.SetStringSelection(value)
+
+ def GetValues(self):
+ values = {}
+ values["pouName"] = self.PouName.GetStringSelection()
+ values["transitionName"] = self.TransitionName.GetValue()
+ values["language"] = self.Language.GetStringSelection()
+ return values
+
+#-------------------------------------------------------------------------------
+# Create Pou Action Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_POUACTIONDIALOG, wxID_POUACTIONDIALOGMAINPANEL,
+ wxID_POUACTIONDIALOGPOUNAME, wxID_POUACTIONDIALOGACTIONNAME,
+ wxID_POUACTIONDIALOGLANGUAGE, wxID_POUACTIONDIALOGSTATICTEXT1,
+ wxID_POUACTIONDIALOGSTATICTEXT2, wxID_POUACTIONDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+class PouActionDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_POUACTIONDIALOG,
+ name='ProjectDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(320, 200), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Create a new project')
+ self.SetClientSize(wx.Size(320, 200))
+
+ self.MainPanel = wx.Panel(id=wxID_POUACTIONDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(350, 200), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_POUACTIONDIALOGSTATICTEXT1,
+ label='POU Name:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(145, 17), style=0)
+
+ self.PouName = wx.Choice(id=wxID_POUACTIONDIALOGPOUNAME,
+ name='POUName', parent=self.MainPanel, pos=wx.Point(124, 24),
+ size=wx.Size(150, 24), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_POUACTIONDIALOGSTATICTEXT2,
+ label='Action Name:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 64), size=wx.Size(145, 17), style=0)
+
+ self.ActionName = wx.TextCtrl(id=wxID_POUACTIONDIALOGACTIONNAME,
+ name='ActionName', parent=self.MainPanel, pos=wx.Point(124, 64),
+ size=wx.Size(150, 24), style=0)
+
+ self.staticText3 = wx.StaticText(id=wxID_POUACTIONDIALOGSTATICTEXT3,
+ label='Language:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 104), size=wx.Size(145, 17), style=0)
+
+ self.Language = wx.Choice(id=wxID_POUACTIONDIALOGLANGUAGE,
+ name='Language', parent=self.MainPanel, pos=wx.Point(124, 104),
+ size=wx.Size(150, 24), style=0)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ for option in ["IL","ST","LD","FBD"]:
+ self.Language.Append(option)
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ if self.PouName.GetStringSelection() == "":
+ error.append("POU Name")
+ if self.ActionName.GetValue() == "":
+ error.append("Action Name")
+ if self.Language.GetStringSelection() == "":
+ error.append("Language")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def SetPous(self, pous):
+ for pou in pous:
+ self.PouName.Append(pou)
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "pouName":
+ self.PouName.SetStringSelection(value)
+ elif item == "actionName":
+ self.ActionName.SetValue(value)
+ elif item == "language":
+ self.Language.SetStringSelection(value)
+
+ def GetValues(self):
+ values = {}
+ values["pouName"] = self.PouName.GetStringSelection()
+ values["actionName"] = self.ActionName.GetValue()
+ values["language"] = self.Language.GetStringSelection()
+ return values
+
+#-------------------------------------------------------------------------------
+# Pou Interface Dialog
+#-------------------------------------------------------------------------------
+
+class VariableTable(wxPyGridTableBase):
+
+ """
+ A custom wxGrid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ wxPyGridTableBase.__init__(self)
+ self.data = data
+ self.colnames = colnames
+ self.Parent = parent
+ # XXX
+ # we need to store the row length and collength to
+ # see if the table has changed size
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+
+ def GetNumberCols(self):
+ return len(self.colnames)
+
+ def GetNumberRows(self):
+ return len(self.data)
+
+ def GetColLabelValue(self, col):
+ if col < len(self.colnames):
+ return self.colnames[col]
+
+ def GetRowLabelValues(self, row):
+ return row
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ name = str(self.data[row].get(self.GetColLabelValue(col), ""))
+ return name
+
+ def GetValueByName(self, row, colname):
+ return self.data[row].get(colname)
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.data[row][self.GetColLabelValue(col)] = value
+
+ def ResetView(self, grid):
+ """
+ (wxGrid) -> Reset the grid view. Call this to
+ update the grid if rows and columns have been added or deleted
+ """
+ grid.BeginBatch()
+ for current, new, delmsg, addmsg in [
+ (self._rows, self.GetNumberRows(), wxGRIDTABLE_NOTIFY_ROWS_DELETED, wxGRIDTABLE_NOTIFY_ROWS_APPENDED),
+ (self._cols, self.GetNumberCols(), wxGRIDTABLE_NOTIFY_COLS_DELETED, wxGRIDTABLE_NOTIFY_COLS_APPENDED),
+ ]:
+ if new < current:
+ msg = wxGridTableMessage(self,delmsg,new,current-new)
+ grid.ProcessTableMessage(msg)
+ elif new > current:
+ msg = wxGridTableMessage(self,addmsg,new-current)
+ grid.ProcessTableMessage(msg)
+ self.UpdateValues(grid)
+ grid.EndBatch()
+
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+ # update the column rendering scheme
+ self._updateColAttrs(grid)
+
+ # update the scrollbars and the displayed part of the grid
+ grid.AdjustScrollbars()
+ grid.ForceRefresh()
+
+ def UpdateValues(self, grid):
+ """Update all displayed values"""
+ # This sends an event to the grid table to update all of the values
+ msg = wxGridTableMessage(self, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES)
+ grid.ProcessTableMessage(msg)
+
+ def _updateColAttrs(self, grid):
+ """
+ wxGrid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for col in range(self.GetNumberCols()):
+ attr = wxGridCellAttr()
+ attr.SetAlignment(self.Parent.ColAlignements[col], wxALIGN_CENTRE)
+ grid.SetColAttr(col, attr)
+ grid.SetColSize(col, self.Parent.ColSizes[col])
+
+ typelist = None
+ accesslist = None
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col)
+ grid.SetReadOnly(row, col, False)
+ if colname in ["Name","Initial Value","Location"]:
+ editor = wxGridCellTextEditor()
+ renderer = wxGridCellStringRenderer()
+ elif colname == "Class":
+ if len(self.Parent.ClassList) == 1:
+ grid.SetReadOnly(row, col, True)
+ else:
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(",".join(self.Parent.ClassList))
+ elif colname == "Type":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TypeList)
+ elif colname in ["Retain", "Constant"]:
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.OptionList)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ grid.SetCellBackgroundColour(row, col, wxWHITE)
+
+ def SetData(self, data):
+ self.data = data
+
+ def GetData(self):
+ return self.data
+
+ def GetCurrentIndex(self):
+ return self.CurrentIndex
+
+ def SetCurrentIndex(self, index):
+ self.CurrentIndex = index
+
+ def AppendRow(self, row_content):
+ self.data.append(row_content)
+
+ def RemoveRow(self, row_index):
+ self.data.pop(row_index)
+
+ def MoveRow(self, row_index, move, grid):
+ new_index = max(0, min(row_index + move, len(self.data) - 1))
+ if new_index != row_index:
+ self.data.insert(new_index, self.data.pop(row_index))
+ grid.SetGridCursor(new_index, grid.GetGridCursorCol())
+
+ def Empty(self):
+ self.data = []
+ self.editors = []
+
+[wxID_EDITVARIABLEDIALOG, wxID_EDITVARIABLEDIALOGMAINPANEL,
+ wxID_EDITVARIABLEDIALOGVARIABLESGRID, wxID_EDITVARIABLEDIALOGRETURNTYPE,
+ wxID_EDITVARIABLEDIALOGCLASSFILTER, wxID_EDITVARIABLEDIALOGADDBUTTON,
+ wxID_EDITVARIABLEDIALOGDELETEBUTTON, wxID_EDITVARIABLEDIALOGUPBUTTON,
+ wxID_EDITVARIABLEDIALOGDOWNBUTTON, wxID_EDITVARIABLEDIALOGSTATICTEXT1,
+ wxID_EDITVARIABLEDIALOGSTATICTEXT2, wxID_EDITVARIABLEDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(12)]
+
+class EditVariableDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt, name):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_EDITVARIABLEDIALOG,
+ name='EditVariableDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(600, 440), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Edit variables of %s'%name)
+ self.SetClientSize(wx.Size(600, 440))
+
+ self.MainPanel = wx.Panel(id=wxID_EDITVARIABLEDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(600, 440), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_EDITVARIABLEDIALOGSTATICTEXT1,
+ label='Return Type:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 29), size=wx.Size(95, 17), style=0)
+
+ self.ReturnType = wx.Choice(id=wxID_EDITVARIABLEDIALOGRETURNTYPE,
+ name='ReturnType', parent=self.MainPanel, pos=wx.Point(124, 24),
+ size=wx.Size(145, 24), style=0)
+
+ self.staticText2 = wx.StaticText(id=wxID_EDITVARIABLEDIALOGSTATICTEXT2,
+ label='Class Filter:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(324, 29), size=wx.Size(95, 17), style=0)
+
+ self.ClassFilter = wx.Choice(id=wxID_EDITVARIABLEDIALOGCLASSFILTER,
+ name='ClassFilter', parent=self.MainPanel, pos=wx.Point(424, 24),
+ size=wx.Size(145, 24), style=0)
+ EVT_CHOICE(self, wxID_EDITVARIABLEDIALOGCLASSFILTER, self.OnClassFilter)
+
+ self.staticText3 = wx.StaticText(id=wxID_EDITVARIABLEDIALOGSTATICTEXT3,
+ label='Variables:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(24, 60), size=wx.Size(95, 17), style=0)
+
+ self.VariablesGrid = wx.grid.Grid(id=wxID_EDITVARIABLEDIALOGVARIABLESGRID,
+ name='VariablesGrid', parent=self.MainPanel, pos=wx.Point(24, 80),
+ size=wx.Size(550, 250), style=wxVSCROLL)
+ self.VariablesGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False,
+ 'Sans'))
+ self.VariablesGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL,
+ False, 'Sans'))
+ self.VariablesGrid.DisableDragGridSize()
+ self.VariablesGrid.EnableScrolling(False, True)
+
+ self.AddButton = wx.Button(id=wxID_EDITVARIABLEDIALOGADDBUTTON, label='Add',
+ name='AddButton', parent=self.MainPanel, pos=wx.Point(345, 340),
+ size=wx.Size(72, 32), style=0)
+ EVT_BUTTON(self, wxID_EDITVARIABLEDIALOGADDBUTTON, self.OnAddButton)
+
+ self.DeleteButton = wx.Button(id=wxID_EDITVARIABLEDIALOGDELETEBUTTON, label='Delete',
+ name='DeleteButton', parent=self.MainPanel, pos=wx.Point(425, 340),
+ size=wx.Size(72, 32), style=0)
+ EVT_BUTTON(self, wxID_EDITVARIABLEDIALOGDELETEBUTTON, self.OnDeleteButton)
+
+ self.UpButton = wx.Button(id=wxID_EDITVARIABLEDIALOGUPBUTTON, label='^',
+ name='UpButton', parent=self.MainPanel, pos=wx.Point(505, 340),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_EDITVARIABLEDIALOGUPBUTTON, self.OnUpButton)
+
+ self.DownButton = wx.Button(id=wxID_EDITVARIABLEDIALOGDOWNBUTTON, label='v',
+ name='DownButton', parent=self.MainPanel, pos=wx.Point(545, 340),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_EDITVARIABLEDIALOGDOWNBUTTON, self.OnDownButton)
+
+ self._init_sizers()
+
+ def __init__(self, parent, name, pou_type, filter = "All"):
+ self._init_ctrls(parent, name)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+ self.Filter = filter
+ self.FilterChoices = []
+ self.FilterChoiceTransfer = {"All" : "All", "Interface" : "Interface",
+ "Input" : " Input", "Output" : " Output", "InOut" : " InOut",
+ "External" : " External", "Variables" : "Variables", "Local" : " Local",
+ "Temp" : " Temp", "Global" : "Global", "Access" : "Access"}
+
+ if pou_type:
+ self.DefaultValue = {"Name" : "", "Class" : "Input", "Type" : "INT", "Location" : "", "Initial Value" : "", "Retain" : "No", "Constant" : "No"}
+ else:
+ self.DefaultValue = {"Name" : "", "Class" : "Global", "Type" : "INT", "Location" : "", "Initial Value" : "", "Retain" : "No", "Constant" : "No"}
+ if not pou_type or pou_type == "program":
+ self.Table = VariableTable(self, [], ["Name", "Class", "Type", "Location", "Initial Value", "Retain", "Constant"])
+ if pou_type:
+ self.FilterChoices = ["All","Interface"," Input"," Output"," InOut"," External","Variables"," Local"," Temp","Global","Access"]
+ else:
+ self.FilterChoices = ["All","Global","Access"]
+ self.ColSizes = [80, 70, 80, 80, 80, 60, 70]
+ self.ColAlignements = [wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_CENTER, wxALIGN_CENTER]
+ else:
+ self.Table = VariableTable(self, [], ["Name", "Class", "Type", "Initial Value", "Retain", "Constant"])
+ self.FilterChoices = ["All","Interface"," Input"," Output"," InOut"," External","Variables"," Local"," Temp"]
+ self.ColSizes = [120, 70, 80, 120, 60, 70]
+ self.ColAlignements = [wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_CENTER, wxALIGN_CENTER]
+ for choice in self.FilterChoices:
+ self.ClassFilter.Append(choice)
+ self.ClassFilter.SetStringSelection(self.FilterChoiceTransfer[self.Filter])
+ self.RefreshTypeList()
+ self.RefreshUpDownButtons()
+
+ self.OptionList = "Yes,No"
+ self.TypeList = ""
+ for value in TypeHierarchy.keys():
+ if not value.startswith("ANY"):
+ self.TypeList += "%s,"%value
+ self.TypeList = self.TypeList[:-1]
+
+ if pou_type == "function":
+ for value in TypeHierarchy.keys():
+ if not value.startswith("ANY"):
+ self.ReturnType.Append(value)
+ self.ReturnType.Enable(True)
+ else:
+ self.ReturnType.Enable(False)
+ self.staticText2.Hide()
+ self.ReturnType.Hide()
+
+ self.VariablesGrid.SetTable(self.Table)
+ self.VariablesGrid.SetRowLabelSize(0)
+
+ self.Table.ResetView(self.VariablesGrid)
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ self.VariablesGrid.SetGridCursor(0, 0)
+ error = []
+ if self.ReturnType.IsEnabled() and self.ReturnType.GetStringSelection() == "":
+ error.append("Return Type")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def OnClassFilter(self, event):
+ reverse_transfer = {}
+ for filter, choice in self.FilterChoiceTransfer.items():
+ reverse_transfer[choice] = filter
+ self.Filter = reverse_transfer[self.ClassFilter.GetStringSelection()]
+ self.RefreshTypeList()
+ self.RefreshValues()
+ self.RefreshUpDownButtons()
+ event.Skip()
+
+ def RefreshTypeList(self):
+ if self.Filter == "All":
+ self.ClassList = [choice for choice in self.FilterChoices if choice not in ["All","Interface","Variables"]]
+ elif self.Filter == "Interface":
+ self.ClassList = ["Input","Output","InOut","External"]
+ elif self.Filter == "Variables":
+ self.ClassList = ["Local","Temp"]
+ else:
+ self.ClassList = [self.Filter]
+
+ def RefreshUpDownButtons(self):
+ if self.Filter == "All":
+ self.UpButton.Enable(True)
+ self.DownButton.Enable(True)
+ else:
+ self.UpButton.Enable(False)
+ self.DownButton.Enable(False)
+
+ def OnAddButton(self, event):
+ self.Table.AppendRow(self.DefaultValue.copy())
+ self.Table.ResetView(self.VariablesGrid)
+ event.Skip()
+
+ def OnDeleteButton(self, event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ self.Table.RemoveRow(row)
+ self.Table.ResetView(self.VariablesGrid)
+ event.Skip()
+
+ def OnUpButton(self, event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ self.Table.MoveRow(row, -1, self.VariablesGrid)
+ self.Table.ResetView(self.VariablesGrid)
+ event.Skip()
+
+ def OnDownButton(self, event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ self.Table.MoveRow(row, 1, self.VariablesGrid)
+ self.Table.ResetView(self.VariablesGrid)
+ event.Skip()
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "returnType" and value and self.ReturnType.IsEnabled():
+ self.ReturnType.SetStringSelection(value)
+ if item == "data":
+ self.Values = value
+ self.RefreshValues()
+
+ def RefreshValues(self):
+ data = []
+ for variable in self.Values:
+ if variable["Class"] in self.ClassList:
+ data.append(variable)
+ self.Table.SetData(data)
+ self.Table.ResetView(self.VariablesGrid)
+
+ def GetValues(self):
+ values = {}
+ if self.ReturnType.IsEnabled():
+ values["returnType"] = self.ReturnType.GetStringSelection()
+ values["data"] = self.Table.GetData()
+ return values
+
+#-------------------------------------------------------------------------------
+# Exception Handler
+#-------------------------------------------------------------------------------
+
+Max_Traceback_List_Size = 20
+
+def Display_Exception_Dialog(e_type,e_value,e_tb):
+ trcbck_lst = []
+ for i,line in enumerate(traceback.extract_tb(e_tb)):
+ trcbck = " " + str(i+1) + ". "
+ if line[0].find(os.getcwd()) == -1:
+ trcbck += "file : " + str(line[0]) + ", "
+ else:
+ trcbck += "file : " + str(line[0][len(os.getcwd()):]) + ", "
+ trcbck += "line : " + str(line[1]) + ", " + "function : " + str(line[2])
+ trcbck_lst.append(trcbck)
+
+ # Allow clicking....
+ cap = wx.Window_GetCapture()
+ if cap:
+ cap.ReleaseMouse()
+
+ dlg = wx.SingleChoiceDialog(None,
+ """
+An error happens.
+
+Click on OK for saving an error report.
+
+Please contact LOLITech at:
++33 (0)3 29 52 95 67
+bugs_PLCOpenEditor@lolitech.fr
+
+
+Error:
+""" +
+ str(e_type) + " : " + str(e_value),
+ "Error",
+ trcbck_lst)
+ try:
+ res = (dlg.ShowModal() == wx.ID_OK)
+ finally:
+ dlg.Destroy()
+
+ return res
+
+def Display_Error_Dialog(e_value):
+ message = wxMessageDialog(None, str(e_value), "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+def get_last_traceback(tb):
+ while tb.tb_next:
+ tb = tb.tb_next
+ return tb
+
+
+def format_namespace(d, indent=' '):
+ return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])
+
+
+ignored_exceptions = [] # a problem with a line in a module is only reported once per session
+
+def wxAddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):
+
+ def handle_exception(e_type, e_value, e_traceback):
+ traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
+ last_tb = get_last_traceback(e_traceback)
+ ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
+ if str(e_value).startswith("!!!"):
+ Display_Error_Dialog(e_value)
+ elif ex not in ignored_exceptions:
+ result = Display_Exception_Dialog(e_type,e_value,e_traceback)
+ if result:
+ ignored_exceptions.append(ex)
+ info = {
+ 'app-title' : wx.GetApp().GetAppName(), # app_title
+ 'app-version' : app_version,
+ 'wx-version' : wx.VERSION_STRING,
+ 'wx-platform' : wx.Platform,
+ 'python-version' : platform.python_version(), #sys.version.split()[0],
+ 'platform' : platform.platform(),
+ 'e-type' : e_type,
+ 'e-value' : e_value,
+ 'date' : time.ctime(),
+ 'cwd' : os.getcwd(),
+ }
+ if e_traceback:
+ info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
+ last_tb = get_last_traceback(e_traceback)
+ exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred
+ info['locals'] = format_namespace(exception_locals)
+ if 'self' in exception_locals:
+ info['self'] = format_namespace(exception_locals['self'].__dict__)
+
+ output = open(path+os.sep+"bug_report_"+info['date'].replace(':','-').replace(' ','_')+".txt",'w')
+ lst = info.keys()
+ lst.sort()
+ for a in lst:
+ output.write(a+":\n"+str(info[a])+"\n\n")
+
+ #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
+ sys.excepthook = handle_exception
+
+if __name__ == '__main__':
+ app = wxPySimpleApp()
+ wxInitAllImageHandlers()
+
+ # Install a exception handle for bug reports
+ wxAddExceptHook(os.getcwd(),__version__)
+
+ frame = PLCOpenEditor(None)
+
+ frame.Show()
+ app.MainLoop()
+
diff -r 000000000000 -r b622defdfd98 SFCViewer.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/SFCViewer.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1604 @@
+#!/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 *
+from wxPython.grid import *
+import wx
+from types import *
+
+from plcopen.structures import *
+from graphics.GraphicCommons import *
+from graphics.SFC_Objects import *
+from Viewer import *
+
+class SFC_Viewer(Viewer):
+
+ def __init__(self, parent, window, controler):
+ Viewer.__init__(self, parent, window, controler)
+
+ def ConnectConnectors(self, start, end):
+ startpoint = [start.GetPosition(False), start.GetDirection()]
+ endpoint = [end.GetPosition(False), end.GetDirection()]
+ wire = Wire(self, startpoint, endpoint)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ start.Connect((wire, 0), False)
+ end.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start)
+ wire.ConnectEndPoint(None, end)
+ return wire
+
+ def CreateTransition(self, connector, next = None):
+ previous = connector.GetParentBlock()
+ id = self.GetNewId()
+ transition = SFC_Transition(self, "reference", "", id)
+ pos = connector.GetPosition(False)
+ transition.SetPosition(pos.x, pos.y + SFC_WIRE_MIN_SIZE)
+ transition_connectors = transition.GetConnectors()
+ wire = self.ConnectConnectors(transition_connectors["input"], connector)
+ if isinstance(previous, SFC_Divergence):
+ previous.RefreshConnectedPosition(connector)
+ else:
+ previous.RefreshOutputPosition()
+ wire.SetPoints([wxPoint(pos.x, pos.y + GetWireSize(previous)), wxPoint(pos.x, pos.y)])
+ self.Blocks.append(transition)
+ self.Elements.append(transition)
+ self.Controler.AddCurrentElementEditingTransition(id)
+ self.RefreshTransitionModel(transition)
+ if next:
+ wire = self.ConnectConnectors(next, transition_connectors["output"])
+ pos = transition_connectors["output"].GetPosition(False)
+ next_block = next.GetParentBlock()
+ next_pos = next.GetPosition(False)
+ transition.RefreshOutputPosition((0, pos.y + SFC_WIRE_MIN_SIZE - next_pos.y))
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ next_block.RefreshModel()
+ return transition
+
+ def RemoveTransition(self, transition):
+ connectors = transition.GetConnectors()
+ input_wires = connectors["input"].GetWires()
+ if len(input_wires) != 1:
+ return
+ input_wire = input_wires[0][0]
+ previous = input_wire.EndConnected
+ input_wire.Clean()
+ self.Wires.remove(input_wire)
+ self.Elements.remove(input_wire)
+ output_wires = connectors["output"].GetWires()
+ if len(output_wires) != 1:
+ return
+ output_wire = output_wires[0][0]
+ next = output_wire.StartConnected
+ output_wire.Clean()
+ self.Wires.remove(output_wire)
+ self.Elements.remove(output_wire)
+ transition.Clean()
+ self.Blocks.remove(transition)
+ self.Elements.remove(transition)
+ self.Controler.RemoveCurrentElementEditingInstance(transition.GetId())
+ wire = self.ConnectConnectors(next, previous)
+ return wire
+
+ def CreateStep(self, name, connector, next = None):
+ previous = connector.GetParentBlock()
+ id = self.GetNewId()
+ step = SFC_Step(self, name, False, id)
+ if next:
+ step.AddOutput()
+ min_width, min_height = step.GetMinSize()
+ pos = connector.GetPosition(False)
+ step.SetPosition(pos.x, pos.y + SFC_WIRE_MIN_SIZE)
+ step.SetSize(min_width, min_height)
+ step_connectors = step.GetConnectors()
+ wire = self.ConnectConnectors(step_connectors["input"], connector)
+ if isinstance(previous, SFC_Divergence):
+ previous.RefreshConnectedPosition(connector)
+ else:
+ previous.RefreshOutputPosition()
+ wire.SetPoints([wxPoint(pos.x, pos.y + GetWireSize(previous)), wxPoint(pos.x, pos.y)])
+ self.Blocks.append(step)
+ self.Elements.append(step)
+ self.Controler.AddCurrentElementEditingStep(id)
+ self.RefreshStepModel(step)
+ if next:
+ wire = self.ConnectConnectors(next, step_connectors["output"])
+ pos = step_connectors["output"].GetPosition(False)
+ next_block = next.GetParentBlock()
+ next_pos = next.GetPosition(False)
+ step.RefreshOutputPosition((0, pos.y + SFC_WIRE_MIN_SIZE - next_pos.y))
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ next_block.RefreshModel()
+ return step
+
+ def RemoveStep(self, step):
+ connectors = step.GetConnectors()
+ if connectors["input"]:
+ input_wires = connectors["input"].GetWires()
+ if len(input_wires) != 1:
+ return
+ input_wire = input_wires[0][0]
+ previous = input_wire.EndConnected
+ input_wire.Clean()
+ self.Wires.remove(input_wire)
+ self.Elements.remove(input_wire)
+ else:
+ previous = None
+ if connectors["output"]:
+ output_wires = connectors["output"].GetWires()
+ if len(output_wires) != 1:
+ return
+ output_wire = output_wires[0][0]
+ next = output_wire.StartConnected
+ output_wire.Clean()
+ self.Wires.remove(output_wire)
+ self.Elements.remove(output_wire)
+ else:
+ next = None
+ action = step.GetActionConnector()
+ if action:
+ self.DeleteActionBlock(action.GetParentBlock())
+ step.Clean()
+ self.Blocks.remove(step)
+ self.Elements.remove(step)
+ self.Controler.RemoveCurrentElementEditingInstance(step.GetId())
+ if next and previous:
+ wire = self.ConnectConnectors(next, previous)
+ return wire
+ else:
+ return None
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerLeftDown(self, event):
+ if self.Mode == MODE_SELECTION:
+ pos = event.GetPosition()
+ if event.ControlDown():
+ element = self.FindElement(pos, True)
+ if element and element not in self.Wires:
+ if isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.SelectElement(element)
+ else:
+ group = Graphic_Group(self)
+ self.SelectedElement.SetSelected(False)
+ group.SelectElement(self.SelectedElement)
+ group.SelectElement(element)
+ self.SelectedElement = group
+ elements = self.SelectedElement.GetElements()
+ if len(elements) == 0:
+ self.SelectedElement = element
+ elif len(elements) == 1:
+ self.SelectedElement = elements[0]
+ else:
+ element = self.FindElement(pos)
+ if self.SelectedElement and self.SelectedElement != element:
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(None)
+ else:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ self.Refresh()
+ if element:
+ self.SelectedElement = element
+ self.SelectedElement.OnLeftDown(event, self.Scaling)
+ self.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.Scaling)
+ elif self.Mode == MODE_COMMENT:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.Scaling)
+ elif self.Mode == MODE_WIRE:
+ pos = GetScaledEventPosition(event, self.Scaling)
+ wire = Wire(self, [wxPoint(pos.x, pos.y), SOUTH], [wxPoint(pos.x, pos.y), NORTH])
+ wire.oldPos = pos
+ wire.Handle = (HANDLE_POINT, 0)
+ wire.ProcessDragging(0, 0)
+ wire.Handle = (HANDLE_POINT, 1)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+ if self.SelectedElement:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = wire
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ if self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ if len(elements) > 0:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(elements)
+ self.SelectedElement.SetSelected(True)
+ self.Refresh()
+ elif self.Mode == MODE_COMMENT:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.AddComment, bbox)
+ elif self.Mode == MODE_INITIAL_STEP:
+ wxCallAfter(self.AddInitialStep, GetScaledEventPosition(event, self.Scaling))
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.OnLeftUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.RefreshModel()
+ self.SelectedElement.SetSelected(True)
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ pos = event.GetPosition()
+ element = self.FindElement(pos)
+ if element:
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = element
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.OnRightUp(event, self.Scaling)
+ wxCallAfter(self.SetCursor, wxNullCursor)
+ self.ReleaseMouse()
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ if self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnLeftDClick(event, self.Scaling)
+ self.Refresh()
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ if self.rubberBand.IsShown():
+ self.rubberBand.OnMotion(event, self.Scaling)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ if self.SelectedElement not in self.Wires:
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.Refresh()
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.Refresh()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ keycode = event.GetKeyCode()
+ if self.Scaling:
+ scaling = self.Scaling
+ else:
+ scaling = (8, 8)
+ if keycode == WXK_DELETE and self.SelectedElement:
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ elif keycode == WXK_LEFT and self.SelectedElement:
+ self.SelectedElement.Move(-scaling[0], 0)
+ elif keycode == WXK_RIGHT and self.SelectedElement:
+ self.SelectedElement.Move(scaling[0], 0)
+ elif keycode == WXK_UP and self.SelectedElement:
+ self.SelectedElement.Move(0, -scaling[1])
+ elif keycode == WXK_DOWN and self.SelectedElement:
+ self.SelectedElement.Move(0, scaling[1])
+ self.Refresh()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Adding element functions
+#-------------------------------------------------------------------------------
+
+ def AddInitialStep(self, pos):
+ dialog = wxTextEntryDialog(self.Parent, "Add a new initial step", "Please enter step name", "", wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ id = self.GetNewId()
+ name = dialog.GetValue()
+ step = SFC_Step(self, name, True, id)
+ min_width, min_height = step.GetMinSize()
+ step.SetPosition(pos.x, pos.y)
+ width, height = step.GetSize()
+ step.SetSize(max(min_width, width), max(min_height, height))
+ self.Blocks.append(step)
+ self.Elements.append(step)
+ self.Controler.AddCurrentElementEditingStep(id)
+ self.RefreshStepModel(step)
+ self.Parent.RefreshProjectTree()
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddStep(self):
+ if self.SelectedElement in self.Wires or isinstance(self.SelectedElement, SFC_Step):
+ dialog = wxTextEntryDialog(self.Parent, "Add a new step", "Please enter step name", "", wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ name = dialog.GetValue()
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement.SetSelectedSegment(None)
+ previous = self.SelectedElement.EndConnected
+ next = self.SelectedElement.StartConnected
+ self.SelectedElement.Clean()
+ self.Wires.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ else:
+ connectors = self.SelectedElement.GetConnectors()
+ if connectors["output"]:
+ previous = connectors["output"]
+ wires = previous.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ next = wire.StartConnected
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ else:
+ self.SelectedElement.AddOutput()
+ connectors = self.SelectedElement.GetConnectors()
+ self.RefreshStepModel(self.SelectedElement)
+ previous = connectors["output"]
+ next = None
+ previous_block = previous.GetParentBlock()
+ if isinstance(previous_block, SFC_Step) or isinstance(previous_block, SFC_Divergence) and previous_block.GetType() in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ transition = self.CreateTransition(previous)
+ transition_connectors = transition.GetConnectors()
+ step = self.CreateStep(name, transition_connectors["output"], next)
+ else:
+ step = self.CreateStep(name, previous)
+ step.AddOutput()
+ step.RefreshModel()
+ step_connectors = step.GetConnectors()
+ transition = self.CreateTransition(step_connectors["output"], next)
+ if self.SelectedElement in self.Wires:
+ self.SelectedElement = wire
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = step
+ self.SelectedElement.SetSelected(True)
+ self.Parent.RefreshProjectTree()
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddStepAction(self):
+ if isinstance(self.SelectedElement, SFC_Step):
+ connectors = self.SelectedElement.GetConnectors()
+ if not connectors["action"]:
+ dialog = ActionBlockDialog(self.Parent)
+ dialog.SetQualifierList(self.Controler.GetQualifierTypes())
+ dialog.SetActionList(self.Controler.GetCurrentElementEditingActions())
+ dialog.SetVariableList(self.Controler.GetCurrentElementEditingInterfaceVars())
+ if dialog.ShowModal() == wxID_OK:
+ actions = dialog.GetValues()
+ self.SelectedElement.AddAction()
+ self.RefreshStepModel(self.SelectedElement)
+ connectors = self.SelectedElement.GetConnectors()
+ pos = connectors["action"].GetPosition(False)
+ id = self.GetNewId()
+ actionblock = SFC_ActionBlock(self, [], id)
+ actionblock.SetPosition(pos.x + SFC_WIRE_MIN_SIZE, pos.y - SFC_STEP_DEFAULT_SIZE[1] / 2)
+ actionblock_connector = actionblock.GetConnector()
+ wire = self.ConnectConnectors(actionblock_connector, connectors["action"])
+ wire.SetPoints([wxPoint(pos.x + SFC_WIRE_MIN_SIZE, pos.y), wxPoint(pos.x, pos.y)])
+ actionblock.SetActions(actions)
+ self.Blocks.append(actionblock)
+ self.Elements.append(actionblock)
+ self.Controler.AddCurrentElementEditingActionBlock(id)
+ self.RefreshActionBlockModel(actionblock)
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddDivergence(self):
+ if self.SelectedElement in self.Wires or isinstance(self.SelectedElement, Graphic_Group) or isinstance(self.SelectedElement, SFC_Step):
+ dialog = DivergenceCreateDialog(self.Parent)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValues()
+ if value["type"] == SELECTION_DIVERGENCE:
+ if self.SelectedElement in self.Wires and isinstance(self.SelectedElement.EndConnected.GetParentBlock(), SFC_Step):
+ self.SelectedElement.SetSelectedSegment(None)
+ previous = self.SelectedElement.EndConnected
+ next = self.SelectedElement.StartConnected
+ self.SelectedElement.Clean()
+ self.Wires.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ self.SelectedElement = None
+ elif isinstance(self.SelectedElement, SFC_Step):
+ connectors = self.SelectedElement.GetConnectors()
+ if connectors["output"]:
+ previous = connectors["output"]
+ wires = previous.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ next = wire.StartConnected
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ else:
+ self.SelectedElement.AddOutput()
+ connectors = self.SelectedElement.GetConnectors()
+ self.RefreshStepModel(self.SelectedElement)
+ previous = connectors["output"]
+ next = None
+ else:
+ return
+ id = self.GetNewId()
+ divergence = SFC_Divergence(self, SELECTION_DIVERGENCE, value["number"], id)
+ pos = previous.GetPosition(False)
+ previous_block = previous.GetParentBlock()
+ wire_size = GetWireSize(previous_block)
+ divergence.SetPosition(pos.x, pos.y + wire_size)
+ divergence_connectors = divergence.GetConnectors()
+ wire = self.ConnectConnectors(divergence_connectors["inputs"][0], previous)
+ previous_block.RefreshOutputPosition()
+ wire.SetPoints([wxPoint(pos.x, pos.y + wire_size), wxPoint(pos.x, pos.y)])
+ self.Blocks.append(divergence)
+ self.Elements.append(divergence)
+ self.Controler.AddCurrentElementEditingDivergence(id, value["type"])
+ self.RefreshDivergenceModel(divergence)
+ for index, connector in enumerate(divergence_connectors["outputs"]):
+ if next:
+ wire = self.ConnectConnectors(next, connector)
+ pos = connector.GetPosition(False)
+ next_pos = next.GetPosition(False)
+ next_block = next.GetParentBlock()
+ divergence.RefreshOutputPosition((0, pos.y + SFC_WIRE_MIN_SIZE - next_pos.y))
+ divergence.RefreshConnectedPosition(connector)
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ next_block.RefreshModel()
+ next = None
+ else:
+ transition = self.CreateTransition(connector)
+ transition_connectors = transition.GetConnectors()
+ step = self.CreateStep("Step", transition_connectors["output"])
+ elif value["type"] == SIMULTANEOUS_DIVERGENCE:
+ if self.SelectedElement in self.Wires and isinstance(self.SelectedElement.EndConnected.GetParentBlock(), SFC_Transition):
+ self.SelectedElement.SetSelectedSegment(None)
+ previous = self.SelectedElement.EndConnected
+ next = self.SelectedElement.StartConnected
+ self.SelectedElement.Clean()
+ self.Wires.remove(self.SelectedElement)
+ self.Elements.remove(self.SelectedElement)
+ self.SelectedElement = None
+ elif isinstance(self.SelectedElement, SFC_Step):
+ connectors = self.SelectedElement.GetConnectors()
+ if connectors["output"]:
+ previous = connectors["output"]
+ wires = previous.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ next = wire.StartConnected
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ else:
+ self.SelectedElement.AddOutput()
+ connectors = self.SelectedElement.GetConnectors()
+ self.RefreshStepModel(self.SelectedElement)
+ previous = connectors["output"]
+ next = None
+ transition = self.CreateTransition(previous)
+ transition_connectors = transition.GetConnectors()
+ previous = transition_connectors["output"]
+ else:
+ return
+ id = self.GetNewId()
+ divergence = SFC_Divergence(self, SIMULTANEOUS_DIVERGENCE, value["number"], id)
+ pos = previous.GetPosition(False)
+ previous_block = previous.GetParentBlock()
+ wire_size = GetWireSize(previous_block)
+ divergence.SetPosition(pos.x, pos.y + wire_size)
+ divergence_connectors = divergence.GetConnectors()
+ wire = self.ConnectConnectors(divergence_connectors["inputs"][0], previous)
+ previous_block.RefreshOutputPosition()
+ wire.SetPoints([wxPoint(pos.x, pos.y + wire_size), wxPoint(pos.x, pos.y)])
+ self.Blocks.append(divergence)
+ self.Elements.append(divergence)
+ self.Controler.AddCurrentElementEditingDivergence(id, value["type"])
+ self.RefreshDivergenceModel(divergence)
+ for index, connector in enumerate(divergence_connectors["outputs"]):
+ if next:
+ wire = self.ConnectConnectors(next, connector)
+ pos = connector.GetPosition(False)
+ next_pos = next.GetPosition(False)
+ next_block = next.GetParentBlock()
+ divergence.RefreshOutputPosition((0, pos.y + SFC_WIRE_MIN_SIZE - next_pos.y))
+ divergence.RefreshConnectedPosition(connector)
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ next_block.RefreshModel()
+ next = None
+ else:
+ step = self.CreateStep("Step", connector)
+ elif isinstance(self.SelectedElement, Graphic_Group) and len(self.SelectedElement.GetElements()) > 1:
+ next = None
+ for element in self.SelectedElement.GetElements():
+ connectors = element.GetConnectors()
+ if not isinstance(element, SFC_Step) or connectors["output"] and next:
+ return
+ elif connectors["output"] and not next:
+ wires = connectors["output"].GetWires()
+ if len(wires) != 1:
+ return
+ if value["type"] == SELECTION_CONVERGENCE:
+ transition = wires[0][0].StartConnected.GetParentBlock()
+ transition_connectors = transition.GetConnectors()
+ wires = transition_connectors["output"].GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ next = wire.StartConnected
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ inputs = []
+ for input in self.SelectedElement.GetElements():
+ input_connectors = input.GetConnectors()
+ if not input_connectors["output"]:
+ input.AddOutput()
+ input.RefreshModel()
+ input_connectors = input.GetConnectors()
+ if value["type"] == SELECTION_CONVERGENCE:
+ transition = self.CreateTransition(input_connectors["output"])
+ transition_connectors = transition.GetConnectors()
+ inputs.append(transition_connectors["output"])
+ else:
+ inputs.append(input_connectors["output"])
+ elif value["type"] == SELECTION_CONVERGENCE:
+ wires = input_connectors["output"].GetWires()
+ transition = wires[0][0].StartConnected.GetParentBlock()
+ transition_connectors = transition.GetConnectors()
+ inputs.append(transition_connectors["output"])
+ else:
+ inputs.append(input_connectors["output"])
+ id = self.GetNewId()
+ divergence = SFC_Divergence(self, value["type"], len(inputs), id)
+ pos = inputs[0].GetPosition(False)
+ divergence.SetPosition(pos.x, pos.y + SFC_WIRE_MIN_SIZE)
+ divergence_connectors = divergence.GetConnectors()
+ for i, input in enumerate(inputs):
+ pos = input.GetPosition(False)
+ wire = self.ConnectConnectors(divergence_connectors["inputs"][i], input)
+ wire_size = GetWireSize(input)
+ wire.SetPoints([wxPoint(pos.x, pos.y + wire_size), wxPoint(pos.x, pos.y)])
+ input_block = input.GetParentBlock()
+ input_block.RefreshOutputPosition()
+ divergence.RefreshPosition()
+ pos = divergence_connectors["outputs"][0].GetRelPosition()
+ divergence.MoveConnector(divergence_connectors["outputs"][0], - pos.x)
+ self.Blocks.append(divergence)
+ self.Elements.append(divergence)
+ self.Controler.AddCurrentElementEditingDivergence(id, value["type"])
+ self.RefreshDivergenceModel(divergence)
+ if next:
+ wire = self.ConnectConnectors(next, divergence_connectors["outputs"][0])
+ pos = divergence_connectors["outputs"][0].GetPosition(False)
+ next_pos = next.GetPosition(False)
+ next_block = next.GetParentBlock()
+ divergence.RefreshOutputPosition((0, pos.y + SFC_WIRE_MIN_SIZE - next_pos.y))
+ divergence.RefreshConnectedPosition(divergence_connectors["outputs"][0])
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ next_block.RefreshModel()
+ else:
+ if value["type"] == SELECTION_CONVERGENCE:
+ previous = divergence_connectors["outputs"][0]
+ else:
+ transition = self.CreateTransition(divergence_connectors["outputs"][0])
+ transition_connectors = transition.GetConnectors()
+ previous = transition_connectors["output"]
+ self.CreateStep("Step", previous)
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddDivergenceBranch(self, divergence):
+ if isinstance(divergence, SFC_Divergence):
+ type = divergence.GetType()
+ if type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ divergence.AddBranch()
+ divergence_connectors = divergence.GetConnectors()
+ if type == SELECTION_DIVERGENCE:
+ transition = self.CreateTransition(divergence_connectors["outputs"][-1])
+ transition_connectors = transition.GetConnectors()
+ previous = transition_connectors["output"]
+ else:
+ previous = divergence_connectors["outputs"][-1]
+ step = self.CreateStep("Step", previous)
+ self.Refresh()
+
+ def AddJump(self):
+ if isinstance(self.SelectedElement, SFC_Step) and not self.SelectedElement.Output:
+ choices = []
+ for block in self.Blocks:
+ if isinstance(block, SFC_Step):
+ choices.append(block.GetName())
+ dialog = wxSingleChoiceDialog(self.Parent, "Add a new jump", "Please choose a target", choices, wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetStringSelection()
+ self.SelectedElement.AddOutput()
+ self.RefreshStepModel(self.SelectedElement)
+ step_connectors = self.SelectedElement.GetConnectors()
+ transition = self.CreateTransition(step_connectors["output"])
+ transition_connectors = transition.GetConnectors()
+ id = self.GetNewId()
+ jump = SFC_Jump(self, value, id)
+ pos = transition_connectors["output"].GetPosition(False)
+ jump.SetPosition(pos.x, pos.y + SFC_WIRE_MIN_SIZE)
+ self.Blocks.append(jump)
+ self.Elements.append(jump)
+ self.Controler.AddCurrentElementEditingJump(id)
+ jump_connector = jump.GetConnector()
+ wire = self.ConnectConnectors(jump_connector, transition_connectors["output"])
+ transition.RefreshOutputPosition()
+ wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
+ self.RefreshJumpModel(jump)
+ self.Refresh()
+ dialog.Destroy()
+
+ def AddComment(self, bbox):
+ dialog = wxTextEntryDialog(self.Parent, "Add a new comment", "Please enter comment text", "", wxOK|wxCANCEL|wxTE_MULTILINE)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ id = self.GetNewId()
+ comment = Comment(self, value, id)
+ comment.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = comment.GetMinSize()
+ comment.SetSize(max(min_width,bbox.width),max(min_height,bbox.height))
+ self.Elements.append(comment)
+ self.Controler.AddCurrentElementEditingComment(id)
+ self.RefreshCommentModel(comment)
+ self.Refresh()
+ dialog.Destroy()
+
+ def EditStepContent(self, step):
+ dialog = wxTextEntryDialog(self.Parent, "Edit step name", "Please enter step name", step.GetName(), wxOK|wxCANCEL)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ step.SetName(value)
+ min_size = step.GetMinSize()
+ size = step.GetSize()
+ step.UpdateSize(max(min_size[0], size[0]), max(min_size[1], size[1]))
+ step.RefreshModel()
+ self.Refresh()
+ dialog.Destroy()
+
+ def EditTransitionContent(self, transition):
+ dialog = TransitionContentDialog(self.Parent)
+ dialog.SetTransitions(self.Controler.GetCurrentElementEditingTransitions())
+ dialog.SetValues({"type":transition.GetType(),"value":transition.GetCondition()})
+ if dialog.ShowModal() == wxID_OK:
+ values = dialog.GetValues()
+ transition.SetType(values["type"])
+ transition.SetCondition(values["value"])
+ transition.RefreshModel()
+ self.Refresh()
+ dialog.Destroy()
+
+ def EditJumpContent(self, jump):
+ choices = []
+ for block in self.Blocks:
+ if isinstance(block, SFC_Step):
+ choices.append(block.GetName())
+ dialog = wxSingleChoiceDialog(self.Parent, "Edit jump target", "Please choose a target", choices, wxOK|wxCANCEL)
+ dialog.SetSelection(choices.index(jump.GetTarget()))
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetStringSelection()
+ jump.SetTarget(value)
+ jump.RefreshModel()
+ self.Refresh()
+ dialog.Destroy()
+
+ def EditActionBlockContent(self, actionblock):
+ dialog = ActionBlockDialog(self.Parent)
+ dialog.SetQualifierList(self.Controler.GetQualifierTypes())
+ dialog.SetActionList(self.Controler.GetCurrentElementEditingActions())
+ dialog.SetVariableList(self.Controler.GetCurrentElementEditingInterfaceVars())
+ dialog.SetValues(actionblock.GetActions())
+ if dialog.ShowModal() == wxID_OK:
+ actions = dialog.GetValues()
+ actionblock.SetActions(actions)
+ actionblock.RefreshModel()
+ self.Refresh()
+ dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+# Delete element functions
+#-------------------------------------------------------------------------------
+
+ def DeleteStep(self, step):
+ step_connectors = step.GetConnectors()
+ if not step.GetInitial() or not step_connectors["output"]:
+ previous = step.GetPreviousConnector()
+ if previous:
+ previous_block = previous.GetParentBlock()
+ else:
+ previous_block = None
+ next = step.GetNextConnector()
+ if next:
+ next_block = next.GetParentBlock()
+ else:
+ next_block = None
+ if isinstance(next_block, SFC_Transition):
+ self.RemoveTransition(next_block)
+ next = step.GetNextConnector()
+ if next:
+ next_block = next.GetParentBlock()
+ else:
+ next_block = None
+ elif isinstance(previous_block, SFC_Transition):
+ self.RemoveTransition(previous_block)
+ previous = step.GetPreviousConnector()
+ if previous:
+ previous_block = previous.GetParentBlock()
+ else:
+ previous_block = None
+ wire = self.RemoveStep(step)
+ self.SelectedElement = None
+ if next_block:
+ if isinstance(next_block, SFC_Divergence) and next_block.GetType() == SIMULTANEOUS_CONVERGENCE and isinstance(previous_block, SFC_Divergence) and previous_block.GetType() == SIMULTANEOUS_DIVERGENCE:
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ next_block.RemoveBranch(next)
+ if next_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(next_block)
+ else:
+ next_block.RefreshModel()
+ previous_block.RemoveBranch(previous)
+ if previous_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(previous_block)
+ else:
+ previous_block.RefreshModel()
+ else:
+ pos = previous.GetPosition(False)
+ next_pos = next.GetPosition(False)
+ wire_size = GetWireSize(previous_block)
+ previous_block.RefreshOutputPosition((0, pos.y + wire_size - next_pos.y))
+ wire.SetPoints([wxPoint(pos.x, pos.y + wire_size), wxPoint(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ next_block.RefreshModel()
+ else:
+ if isinstance(previous_block, SFC_Step):
+ previous_block.RemoveOutput()
+ self.RefreshStepModel(previous_block)
+ elif isinstance(previous_block, SFC_Divergence):
+ if previous_block.GetType() in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.DeleteDivergence(previous_block)
+ else:
+ previous_block.RemoveBranch(previous)
+ if previous_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(previous_block)
+ else:
+ self.RefreshDivergenceModel(previous_block)
+
+ def DeleteTransition(self, transition):
+ previous = transition.GetPreviousConnector()
+ previous_block = previous.GetParentBlock()
+ next = transition.GetNextConnector()
+ next_block = next.GetParentBlock()
+ if isinstance(previous_block, SFC_Divergence) and previous_block.GetType() == SELECTION_DIVERGENCE and isinstance(next_block, SFC_Divergence) and next_block.GetType() == SELECTION_CONVERGENCE:
+ wires = previous.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ wires = next.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ transition.Clean()
+ self.Blocks.remove(transition)
+ self.Elements.remove(transition)
+ self.Controler.RemoveCurrentElementEditingInstance(transition.GetId())
+ previous_block.RemoveBranch(previous)
+ if previous_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(previous_block)
+ else:
+ self.RefreshDivergenceModel(previous_block)
+ next_block.RemoveBranch(next)
+ if next_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(next_block)
+ else:
+ self.RefreshDivergenceModel(next_block)
+ self.Parent.RefreshProjectTree()
+
+ def DeleteDivergence(self, divergence):
+ connectors = divergence.GetConnectors()
+ type = divergence.GetType()
+ if type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ wires = connectors["outputs"][0].GetWires()
+ if len(wires) > 1:
+ return
+ elif len(wires) == 1:
+ next = wires[0][0].StartConnected
+ next_block = next.GetParentBlock()
+ wire = wires[0][0]
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ else:
+ next = None
+ next_block = None
+ for index, connector in enumerate(connectors["inputs"]):
+ if next and index == 0:
+ wires = connector.GetWires()
+ wire = wires[0][0]
+ previous = wires[0][0].EndConnected
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ else:
+ if type == SELECTION_CONVERGENCE:
+ wires = connector.GetWires()
+ previous_block = wires[0][0].EndConnected.GetParentBlock()
+ self.RemoveTransition(previous_block)
+ wires = connector.GetWires()
+ wire = wires[0][0]
+ previous_connector = wire.EndConnected
+ previous_block = previous_connector.GetParentBlock()
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ if isinstance(previous_block, SFC_Step):
+ previous_block.RemoveOutput()
+ self.RefreshStepModel(previous_block)
+ elif isinstance(previous_block, SFC_Divergence):
+ if previous_block.GetType() in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ previous_block.RemoveBranch(previous_connector)
+ if previous_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(previous_block)
+ else:
+ self.RefreshDivergenceModel(previous_block)
+ else:
+ self.DeleteDivergence(previous_block)
+ divergence.Clean()
+ self.Blocks.remove(divergence)
+ self.Elements.remove(divergence)
+ self.Controler.RemoveCurrentElementEditingInstance(divergence.GetId())
+ if next:
+ wire = self.ConnectConnectors(next, previous)
+ previous_block = previous.GetParentBlock()
+ pos = previous.GetPosition(False)
+ next_pos = next.GetPosition(False)
+ wire_size = GetWireSize(previous_block)
+ previous_block.RefreshOutputPosition((0, previous_pos.y + wire_size - next_pos.y))
+ wire.SetPoints([wxPoint(previous_pos.x, previous_pos.y + wire_size),
+ wxPoint(previous_pos.x, previous_pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ next_block.RefreshModel()
+ elif divergence.GetBranchNumber() == 1:
+ wires = connectors["inputs"][0].GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ previous = wire.EndConnected
+ previous_block = previous.GetParentBlock()
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ wires = connectors["outputs"][0].GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ next = wire.StartConnected
+ next_block = next.GetParentBlock()
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ divergence.Clean()
+ self.Blocks.remove(divergence)
+ self.Elements.remove(divergence)
+ self.Controler.RemoveCurrentElementEditingInstance(divergence.GetId())
+ wire = self.ConnectConnectors(next, previous)
+ previous_pos = previous.GetPosition(False)
+ next_pos = next.GetPosition(False)
+ wire_size = GetWireSize(previous_block)
+ previous_block.RefreshOutputPosition((previous_pos.x - next_pos.x, previous_pos.y + wire_size - next_pos.y))
+ wire.SetPoints([wxPoint(previous_pos.x, previous_pos.y + wire_size),
+ wxPoint(previous_pos.x, previous_pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ next_block.RefreshModel()
+ self.Parent.RefreshProjectTree()
+
+ def DeleteJump(self, jump):
+ previous = jump.GetPreviousConnector()
+ previous_block = previous.GetParentBlock()
+ if isinstance(previous_block, SFC_Transition):
+ self.RemoveTransition(previous_block)
+ previous = jump.GetPreviousConnector()
+ if previous:
+ previous_block = previous.GetParentBlock()
+ else:
+ previous_block = None
+ wires = previous.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ jump.Clean()
+ self.Blocks.remove(jump)
+ self.Elements.remove(jump)
+ self.Controler.RemoveCurrentElementEditingInstance(jump.GetId())
+ if isinstance(previous_block, SFC_Step):
+ previous_block.RemoveOutput()
+ self.RefreshStepModel(previous_block)
+ elif isinstance(previous_block, SFC_Divergence):
+ if previous_block.GetType() in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.DeleteDivergence(previous_block)
+ else:
+ previous_block.RemoveBranch(previous)
+ if previous_block.GetBranchNumber() < 2:
+ self.DeleteDivergence(previous_block)
+ else:
+ previous_block.RefreshModel()
+ self.Parent.RefreshProjectTree()
+
+ def DeleteActionBlock(self, actionblock):
+ connector = actionblock.GetConnector()
+ wires = connector.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ step = wire.EndConnected.GetParentBlock()
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ actionblock.Clean()
+ self.Blocks.remove(actionblock)
+ self.Elements.remove(actionblock)
+ self.Controler.RemoveCurrentElementEditingInstance(actionblock.GetId())
+ step.RemoveAction()
+ self.RefreshStepModel(step)
+ step.RefreshOutputPosition()
+ step.RefreshOutputModel(True)
+ self.Parent.RefreshProjectTree()
+
+ def DeleteComment(self, comment):
+ self.Elements.remove(self.SelectedElement)
+ self.Controler.RemoveCurrentElementEditingInstance(comment.GetId())
+
+ def DeleteWire(self, wire):
+ pass
+
+
+#-------------------------------------------------------------------------------
+# Edit Transition Content Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_TRANSITIONCONTENTDIALOG, wxID_TRANSITIONCONTENTDIALOGMAINPANEL,
+ wxID_TRANSITIONCONTENTDIALOGREFERENCE, wxID_TRANSITIONCONTENTDIALOGINLINE,
+ wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON1, wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON2,
+] = [wx.NewId() for _init_ctrls in range(6)]
+
+class TransitionContentDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_TRANSITIONCONTENTDIALOG,
+ name='ProjectDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(300, 200), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Edit transition')
+ self.SetClientSize(wx.Size(300, 200))
+
+ self.MainPanel = wx.Panel(id=wxID_TRANSITIONCONTENTDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(300, 200), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.radioButton1 = wx.RadioButton(id=wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON1,
+ label='Reference', name='radioButton1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(114, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON1, self.OnTypeChanged)
+ self.radioButton1.SetValue(True)
+
+ self.Reference = wx.Choice(id=wxID_TRANSITIONCONTENTDIALOGREFERENCE,
+ name='Reference', parent=self.MainPanel, pos=wx.Point(48, 48),
+ size=wx.Size(200, 24), style=0)
+
+ self.radioButton2 = wx.RadioButton(id=wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON2,
+ label='Inline', name='radioButton2', parent=self.MainPanel,
+ pos=wx.Point(24, 72), size=wx.Size(114, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_TRANSITIONCONTENTDIALOGRADIOBUTTON2, self.OnTypeChanged)
+ self.radioButton2.SetValue(False)
+
+ self.Inline = wx.TextCtrl(id=wxID_TRANSITIONCONTENTDIALOGINLINE,
+ name='Inline', parent=self.MainPanel, pos=wx.Point(48, 96),
+ size=wx.Size(200, 24), style=0)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ EVT_BUTTON(self, self.ButtonSizer.GetAffirmativeButton().GetId(), self.OnOK)
+
+ def OnOK(self, event):
+ error = []
+ if self.radioButton1.GetValue() and self.Reference.GetStringSelection() == "":
+ error.append("Reference")
+ if self.radioButton2.GetValue() and self.Inline.GetValue() == "":
+ error.append("Inline")
+ if len(error) > 0:
+ text = ""
+ for i, item in enumerate(error):
+ if i == 0:
+ text += item
+ elif i == len(error) - 1:
+ text += " and %s"%item
+ else:
+ text += ", %s"%item
+ message = wxMessageDialog(self, "Form isn't complete. %s must be filled!"%text, "Error", wxOK|wxICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wxID_OK)
+
+ def OnTypeChanged(self, event):
+ if self.radioButton1.GetValue():
+ self.Reference.Enable(True)
+ self.Inline.Enable(False)
+ else:
+ self.Reference.Enable(False)
+ self.Inline.Enable(True)
+ event.Skip()
+
+ def SetTransitions(self, transitions):
+ for transition in transitions:
+ self.Reference.Append(transition)
+
+ def SetValues(self, values):
+ if values["type"] == "reference":
+ self.radioButton1.SetValue(True)
+ self.radioButton2.SetValue(False)
+ self.Reference.Enable(True)
+ self.Inline.Enable(False)
+ self.Reference.SetStringSelection(values["value"])
+ elif values["type"] == "inline":
+ self.radioButton1.SetValue(False)
+ self.radioButton2.SetValue(True)
+ self.Reference.Enable(False)
+ self.Inline.Enable(True)
+ self.Inline.SetValue(values["value"])
+
+ def GetValues(self):
+ values = {}
+ if self.radioButton1.GetValue():
+ values["type"] = "reference"
+ values["value"] = self.Reference.GetStringSelection()
+ else:
+ values["type"] = "inline"
+ values["value"] = self.Inline.GetValue()
+ return values
+
+#-------------------------------------------------------------------------------
+# Create New Divergence Dialog
+#-------------------------------------------------------------------------------
+
+[wxID_DIVERGENCECREATEDIALOG, wxID_DIVERGENCECREATEDIALOGMAINPANEL,
+ wxID_DIVERGENCECREATEDIALOGRADIOBUTTON1, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON2,
+ wxID_DIVERGENCECREATEDIALOGRADIOBUTTON3, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON4,
+ wxID_DIVERGENCECREATEDIALOGSEQUENCES, wxID_DIVERGENCECREATEDIALOGPREVIEW,
+ wxID_DIVERGENCECREATEDIALOGSTATICTEXT1, wxID_DIVERGENCECREATEDIALOGSTATICTEXT2,
+ wxID_DIVERGENCECREATEDIALOGSTATICTEXT3,
+] = [wx.NewId() for _init_ctrls in range(11)]
+
+class DivergenceCreateDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_DIVERGENCECREATEDIALOG,
+ name='DivergencePropertiesDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(500, 300), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Create a new divergence or convergence')
+ self.SetClientSize(wx.Size(500, 260))
+
+ self.MainPanel = wx.Panel(id=wxID_DIVERGENCECREATEDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(600, 220), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_DIVERGENCECREATEDIALOGSTATICTEXT1,
+ label='Type:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(200, 17), style=0)
+
+ self.radioButton1 = wx.RadioButton(id=wxID_DIVERGENCECREATEDIALOGRADIOBUTTON1,
+ label='Selection Divergence', name='radioButton1', parent=self.MainPanel,
+ pos=wx.Point(24, 48), size=wx.Size(200, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON1, self.OnTypeChanged)
+ self.radioButton1.SetValue(True)
+
+ self.radioButton2 = wx.RadioButton(id=wxID_DIVERGENCECREATEDIALOGRADIOBUTTON2,
+ label='Selection Convergence', name='radioButton2', parent=self.MainPanel,
+ pos=wx.Point(24, 72), size=wx.Size(200, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON2, self.OnTypeChanged)
+ self.radioButton2.SetValue(False)
+
+ self.radioButton3 = wx.RadioButton(id=wxID_DIVERGENCECREATEDIALOGRADIOBUTTON3,
+ label='Simultaneous Divergence', name='radioButton3', parent=self.MainPanel,
+ pos=wx.Point(24, 96), size=wx.Size(200, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON3, self.OnTypeChanged)
+ self.radioButton3.SetValue(False)
+
+ self.radioButton4 = wx.RadioButton(id=wxID_DIVERGENCECREATEDIALOGRADIOBUTTON4,
+ label='Simultaneous Convergence', name='radioButton4', parent=self.MainPanel,
+ pos=wx.Point(24, 120), size=wx.Size(200, 24), style=0)
+ EVT_RADIOBUTTON(self, wxID_DIVERGENCECREATEDIALOGRADIOBUTTON4, self.OnTypeChanged)
+ self.radioButton4.SetValue(False)
+
+ self.staticText2 = wx.StaticText(id=wxID_DIVERGENCECREATEDIALOGSTATICTEXT2,
+ label='Number of sequences:', name='staticText2', parent=self.MainPanel,
+ pos=wx.Point(24, 150), size=wx.Size(200, 17), style=0)
+
+ self.Sequences = wx.SpinCtrl(id=wxID_DIVERGENCECREATEDIALOGSEQUENCES,
+ name='Sequences', parent=self.MainPanel, pos=wx.Point(24, 174),
+ size=wx.Size(200, 24), style=0, min=2, max=20)
+ EVT_SPINCTRL(self, wxID_DIVERGENCECREATEDIALOGSEQUENCES, self.OnSequencesChanged)
+
+ self.staticText3 = wx.StaticText(id=wxID_DIVERGENCECREATEDIALOGSTATICTEXT3,
+ label='Preview:', name='staticText3', parent=self.MainPanel,
+ pos=wx.Point(250, 24), size=wx.Size(100, 17), style=0)
+
+ self.Preview = wx.Panel(id=wxID_DIVERGENCECREATEDIALOGPREVIEW,
+ name='Preview', parent=self.MainPanel, pos=wx.Point(250, 48),
+ size=wx.Size(225, 150), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wxColour(255,255,255))
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ self.Divergence = None
+
+ EVT_PAINT(self, self.OnPaint)
+
+ def GetValues(self):
+ values = {}
+ if self.radioButton1.GetValue():
+ values["type"] = SELECTION_DIVERGENCE
+ elif self.radioButton2.GetValue():
+ values["type"] = SELECTION_CONVERGENCE
+ elif self.radioButton3.GetValue():
+ values["type"] = SIMULTANEOUS_DIVERGENCE
+ else:
+ values["type"] = SIMULTANEOUS_CONVERGENCE
+ values["number"] = self.Sequences.GetValue()
+ return values
+
+ def OnTypeChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnSequencesChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wxClientDC(self.Preview)
+ dc.Clear()
+ if self.radioButton1.GetValue():
+ self.Divergence = SFC_Divergence(self.Preview, SELECTION_DIVERGENCE, self.Sequences.GetValue())
+ elif self.radioButton2.GetValue():
+ self.Divergence = SFC_Divergence(self.Preview, SELECTION_CONVERGENCE, self.Sequences.GetValue())
+ elif self.radioButton3.GetValue():
+ self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_DIVERGENCE, self.Sequences.GetValue())
+ else:
+ self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_CONVERGENCE, self.Sequences.GetValue())
+ width, height = self.Divergence.GetSize()
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - width) / 2
+ y = (clientsize.height - height) / 2
+ self.Divergence.SetPosition(x, y)
+ self.Divergence.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+
+
+#-------------------------------------------------------------------------------
+# Action Block Dialog
+#-------------------------------------------------------------------------------
+
+class ActionTable(wxPyGridTableBase):
+
+ """
+ A custom wxGrid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ wxPyGridTableBase.__init__(self)
+ self.data = data
+ self.colnames = colnames
+ self.Parent = parent
+ # XXX
+ # we need to store the row length and collength to
+ # see if the table has changed size
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+
+ def GetNumberCols(self):
+ return len(self.colnames)
+
+ def GetNumberRows(self):
+ return len(self.data)
+
+ def GetColLabelValue(self, col):
+ if col < len(self.colnames):
+ return self.colnames[col]
+
+ def GetRowLabelValues(self, row):
+ return row
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ name = str(self.data[row].get(self.GetColLabelValue(col), ""))
+ return name
+
+ def GetValueByName(self, row, colname):
+ return self.data[row].get(colname)
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.data[row][self.GetColLabelValue(col)] = value
+
+ def ResetView(self, grid):
+ """
+ (wxGrid) -> Reset the grid view. Call this to
+ update the grid if rows and columns have been added or deleted
+ """
+ grid.BeginBatch()
+ for current, new, delmsg, addmsg in [
+ (self._rows, self.GetNumberRows(), wxGRIDTABLE_NOTIFY_ROWS_DELETED, wxGRIDTABLE_NOTIFY_ROWS_APPENDED),
+ (self._cols, self.GetNumberCols(), wxGRIDTABLE_NOTIFY_COLS_DELETED, wxGRIDTABLE_NOTIFY_COLS_APPENDED),
+ ]:
+ if new < current:
+ msg = wxGridTableMessage(self,delmsg,new,current-new)
+ grid.ProcessTableMessage(msg)
+ elif new > current:
+ msg = wxGridTableMessage(self,addmsg,new-current)
+ grid.ProcessTableMessage(msg)
+ self.UpdateValues(grid)
+ grid.EndBatch()
+
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+ # update the column rendering scheme
+ self._updateColAttrs(grid)
+
+ # update the scrollbars and the displayed part of the grid
+ grid.AdjustScrollbars()
+ grid.ForceRefresh()
+
+ def UpdateValues(self, grid):
+ """Update all displayed values"""
+ # This sends an event to the grid table to update all of the values
+ msg = wxGridTableMessage(self, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES)
+ grid.ProcessTableMessage(msg)
+
+ def _updateColAttrs(self, grid):
+ """
+ wxGrid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for col in range(self.GetNumberCols()):
+ attr = wxGridCellAttr()
+ attr.SetAlignment(self.Parent.ColAlignements[col], wxALIGN_CENTRE)
+ grid.SetColAttr(col, attr)
+ grid.SetColSize(col, self.Parent.ColSizes[col])
+
+ typelist = None
+ accesslist = None
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ readonly = False
+ colname = self.GetColLabelValue(col)
+ if colname == "Qualifier":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.QualifierList)
+ if colname == "Duration":
+ editor = wxGridCellTextEditor()
+ renderer = wxGridCellStringRenderer()
+ if self.Parent.DurationList[self.data[row]["Qualifier"]]:
+ readonly = False
+ else:
+ readonly = True
+ self.data[row]["Duration"] = ""
+ elif colname == "Type":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TypeList)
+ elif colname == "Value":
+ type = self.data[row]["Type"]
+ if type == "Action":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.ActionList)
+ elif type == "Variable":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+ elif type == "Inline":
+ editor = wxGridCellTextEditor()
+ renderer = wxGridCellStringRenderer()
+ elif colname == "Indicator":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+ grid.SetReadOnly(row, col, readonly)
+
+ grid.SetCellBackgroundColour(row, col, wxWHITE)
+
+ def SetData(self, data):
+ self.data = data
+
+ def GetData(self):
+ return self.data
+
+ def GetCurrentIndex(self):
+ return self.CurrentIndex
+
+ def SetCurrentIndex(self, index):
+ self.CurrentIndex = index
+
+ def AppendRow(self, row_content):
+ self.data.append(row_content)
+
+ def RemoveRow(self, row_index):
+ self.data.pop(row_index)
+
+ def MoveRow(self, row_index, move, grid):
+ new_index = max(0, min(row_index + move, len(self.data) - 1))
+ if new_index != row_index:
+ self.data.insert(new_index, self.data.pop(row_index))
+ grid.SetGridCursor(new_index, grid.GetGridCursorCol())
+
+ def Empty(self):
+ self.data = []
+ self.editors = []
+
+[wxID_ACTIONBLOCKDIALOG, wxID_ACTIONBLOCKDIALOGMAINPANEL,
+ wxID_ACTIONBLOCKDIALOGVARIABLESGRID, wxID_ACTIONBLOCKDIALOGSTATICTEXT1,
+ wxID_ACTIONBLOCKDIALOGADDBUTTON,wxID_ACTIONBLOCKDIALOGDELETEBUTTON,
+ wxID_ACTIONBLOCKDIALOGUPBUTTON, wxID_ACTIONBLOCKDIALOGDOWNBUTTON,
+] = [wx.NewId() for _init_ctrls in range(8)]
+
+class ActionBlockDialog(wx.Dialog):
+ def _init_coll_flexGridSizer1_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.MainPanel, 0, border=0, flag=0)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+
+ self._init_coll_flexGridSizer1_Items(self.flexGridSizer1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Dialog.__init__(self, id=wxID_ACTIONBLOCKDIALOG,
+ name='ActionBlockDialog', parent=prnt, pos=wx.Point(376, 223),
+ size=wx.Size(500, 300), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Edit action block properties')
+ self.SetClientSize(wx.Size(500, 300))
+
+ self.MainPanel = wx.Panel(id=wxID_ACTIONBLOCKDIALOGMAINPANEL,
+ name='MainPanel', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
+ self.MainPanel.SetAutoLayout(True)
+
+ self.staticText1 = wx.StaticText(id=wxID_ACTIONBLOCKDIALOGSTATICTEXT1,
+ label='Actions:', name='staticText1', parent=self.MainPanel,
+ pos=wx.Point(24, 24), size=wx.Size(95, 17), style=0)
+
+ self.ActionsGrid = wx.grid.Grid(id=wxID_ACTIONBLOCKDIALOGVARIABLESGRID,
+ name='ActionsGrid', parent=self.MainPanel, pos=wx.Point(24, 44),
+ size=wx.Size(450, 150), style=wxVSCROLL)
+ self.ActionsGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False,
+ 'Sans'))
+ self.ActionsGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL,
+ False, 'Sans'))
+ self.ActionsGrid.DisableDragGridSize()
+ self.ActionsGrid.EnableScrolling(False, True)
+ EVT_GRID_CELL_CHANGE(self.ActionsGrid, self.OnActionsGridCellChange)
+
+ self.AddButton = wx.Button(id=wxID_ACTIONBLOCKDIALOGADDBUTTON, label='Add',
+ name='AddButton', parent=self.MainPanel, pos=wx.Point(245, 204),
+ size=wx.Size(72, 32), style=0)
+ EVT_BUTTON(self, wxID_ACTIONBLOCKDIALOGADDBUTTON, self.OnAddButton)
+
+ self.DeleteButton = wx.Button(id=wxID_ACTIONBLOCKDIALOGDELETEBUTTON, label='Delete',
+ name='DeleteButton', parent=self.MainPanel, pos=wx.Point(325, 204),
+ size=wx.Size(72, 32), style=0)
+ EVT_BUTTON(self, wxID_ACTIONBLOCKDIALOGDELETEBUTTON, self.OnDeleteButton)
+
+ self.UpButton = wx.Button(id=wxID_ACTIONBLOCKDIALOGUPBUTTON, label='^',
+ name='UpButton', parent=self.MainPanel, pos=wx.Point(405, 204),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_ACTIONBLOCKDIALOGUPBUTTON, self.OnUpButton)
+
+ self.DownButton = wx.Button(id=wxID_ACTIONBLOCKDIALOGDOWNBUTTON, label='v',
+ name='DownButton', parent=self.MainPanel, pos=wx.Point(445, 204),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_ACTIONBLOCKDIALOGDOWNBUTTON, self.OnDownButton)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+ self.ButtonSizer = self.CreateButtonSizer(wxOK|wxCANCEL)
+ self.flexGridSizer1.Add(self.ButtonSizer, 1, wxALIGN_RIGHT)
+
+ self.DefaultValue = {"Qualifier" : "N", "Duration" : "", "Type" : "Action", "Value" : "", "Indicator" : ""}
+ self.Table = ActionTable(self, [], ["Qualifier","Duration","Type","Value","Indicator"])
+ self.TypeList = "Action,Variable,Inline"
+ self.ColSizes = [60, 90, 80, 110, 80]
+ self.ColAlignements = [wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT]
+
+ self.ActionsGrid.SetTable(self.Table)
+ self.ActionsGrid.SetRowLabelSize(0)
+
+ self.Table.ResetView(self.ActionsGrid)
+
+ def OnAddButton(self, event):
+ self.Table.AppendRow(self.DefaultValue.copy())
+ self.Table.ResetView(self.ActionsGrid)
+ event.Skip()
+
+ def OnDeleteButton(self, event):
+ row = self.ActionsGrid.GetGridCursorRow()
+ self.Table.RemoveRow(row)
+ self.Table.ResetView(self.ActionsGrid)
+ event.Skip()
+
+ def OnUpButton(self, event):
+ row = self.ActionsGrid.GetGridCursorRow()
+ self.Table.MoveRow(row, -1, self.ActionsGrid)
+ self.Table.ResetView(self.ActionsGrid)
+ event.Skip()
+
+ def OnDownButton(self, event):
+ row = self.ActionsGrid.GetGridCursorRow()
+ self.Table.MoveRow(row, 1, self.ActionsGrid)
+ self.Table.ResetView(self.ActionsGrid)
+ event.Skip()
+
+ def OnActionsGridCellChange(self, event):
+ self.Table.ResetView(self.ActionsGrid)
+ event.Skip()
+
+ def SetQualifierList(self, list):
+ self.QualifierList = ""
+ sep = ""
+ for qualifier in list.keys():
+ self.QualifierList += "%s%s"%(sep, qualifier)
+ sep = ","
+ self.DurationList = list
+
+ def SetVariableList(self, list):
+ self.VariableList = ""
+ sep = ""
+ for variable in list:
+ self.VariableList += "%s%s"%(sep, variable["Name"])
+ sep = ","
+
+ def SetActionList(self, list):
+ self.ActionList = ""
+ sep = ""
+ for action in list:
+ self.ActionList += "%s%s"%(sep, action)
+ sep = ","
+
+ def SetValues(self, actions):
+ for action in actions:
+ row = {"Qualifier" : action["qualifier"], "Value" : action["value"],
+ "Indicator" : action["indicator"]}
+ if action["type"] == "reference":
+ if action["value"] in self.ActionList:
+ row["Type"] = "Action"
+ elif action["value"] in self.VariableList:
+ row["Type"] = "Variable"
+ else:
+ row["Type"] = "Inline"
+ else:
+ row["Type"] = "Inline"
+ if "duration" in action:
+ row["Duration"] = action["duration"]
+ self.Table.AppendRow(row)
+ self.Table.ResetView(self.ActionsGrid)
+
+ def GetValues(self):
+ values = []
+ for data in self.Table.GetData():
+ action = {"qualifier" : data["Qualifier"], "value" : data["Value"],
+ "indicator" : data["Indicator"]}
+ if data["Type"] in ["Action", "Variable"]:
+ action["type"] = "reference"
+ else:
+ action["type"] = "inline"
+ if data["Duration"] != "":
+ action["duration"] = data["Duration"]
+ values.append(action)
+ return values
diff -r 000000000000 -r b622defdfd98 Viewer.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Viewer.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1541 @@
+#!/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 *
+from wxPython.stc import *
+from wxPython.grid import *
+import wx
+
+from plcopen.structures import *
+from graphics.GraphicCommons import *
+from graphics.SFC_Objects import *
+from graphics.FBD_Objects import *
+from graphics.LD_Objects import *
+
+import re
+
+#-------------------------------------------------------------------------------
+# Graphic elements Viewer base class
+#-------------------------------------------------------------------------------
+
+# ID Constants for menu items
+[wxID_FBDVIEWERCONTEXTUALMENUITEMS0, wxID_FBDVIEWERCONTEXTUALMENUITEMS1,
+ wxID_FBDVIEWERCONTEXTUALMENUITEMS2, wxID_FBDVIEWERCONTEXTUALMENUITEMS3,
+ wxID_FBDVIEWERCONTEXTUALMENUITEMS5, wxID_FBDVIEWERCONTEXTUALMENUITEMS6,
+ wxID_FBDVIEWERCONTEXTUALMENUITEMS8, wxID_FBDVIEWERCONTEXTUALMENUITEMS9,
+ wxID_FBDVIEWERCONTEXTUALMENUITEMS11,
+] = [wx.NewId() for _init_coll_ContextualMenu_Items in range(9)]
+
+
+"""
+Class that implements a Viewer based on a wxScrolledWindow for drawing and
+manipulating graphic elements
+"""
+
+class Viewer(wx.ScrolledWindow):
+
+ # Create Contextual Menu items
+ def _init_coll_ContextualMenu_Items(self, parent):
+ # Create menu items
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS0,
+ kind=wx.ITEM_RADIO, text=u'No Modifier')
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS1,
+ kind=wx.ITEM_RADIO, text=u'Negated')
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS2,
+ kind=wx.ITEM_RADIO, text=u'Rising Edge')
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS3,
+ kind=wx.ITEM_RADIO, text=u'Falling Edge')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS5,
+ kind=wx.ITEM_NORMAL, text=u'Add Wire Segment')
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS6,
+ kind=wx.ITEM_NORMAL, text=u'Delete Wire Segment')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS8,
+ kind=wx.ITEM_NORMAL, text=u'Add Divergence Branch')
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS9,
+ kind=wx.ITEM_NORMAL, text=u'Delete Divergence Branch')
+ parent.AppendSeparator()
+ parent.Append(help='', id=wxID_FBDVIEWERCONTEXTUALMENUITEMS11,
+ kind=wx.ITEM_NORMAL, text=u'Delete')
+ # Link menu event to corresponding called functions
+ self.Bind(wx.EVT_MENU, self.OnNoModifierMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS0)
+ self.Bind(wx.EVT_MENU, self.OnNegatedMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS1)
+ self.Bind(wx.EVT_MENU, self.OnRisingEdgeMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS2)
+ self.Bind(wx.EVT_MENU, self.OnFallingEdgeMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS3)
+ self.Bind(wx.EVT_MENU, self.OnAddSegmentMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS5)
+ self.Bind(wx.EVT_MENU, self.OnDeleteSegmentMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS6)
+ self.Bind(wx.EVT_MENU, self.OnAddBranchMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS8)
+ self.Bind(wx.EVT_MENU, self.OnDeleteBranchMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS9)
+ self.Bind(wx.EVT_MENU, self.OnDeleteMenu,
+ id=wxID_FBDVIEWERCONTEXTUALMENUITEMS11)
+
+ # Create and initialize Contextual Menu
+ def _init_menus(self):
+ self.ContextualMenu = wx.Menu(title='')
+
+ self._init_coll_ContextualMenu_Items(self.ContextualMenu)
+
+ # Create a new Viewer
+ def __init__(self, parent, window, controler):
+ wx.ScrolledWindow.__init__(self, parent, style=wx.SUNKEN_BORDER)
+ self._init_menus()
+ # Adding a rubberband to Viewer
+ self.rubberBand = RubberBand(drawingSurface=self)
+ self.SetBackgroundColour(wxColour(255,255,255))
+ self.ResetView()
+ self.Scaling = None
+ #self.Scaling = (8, 8)
+ self.DrawGrid = True
+ self.current_id = 0
+
+ # Initialize Viewer mode to Selection mode
+ self.Mode = MODE_SELECTION
+
+ self.Parent = window
+ self.Controler = controler
+
+ # Link Viewer event to corresponding methods
+ EVT_PAINT(self, self.OnPaint)
+ EVT_LEFT_DOWN(self, self.OnViewerLeftDown)
+ EVT_LEFT_UP(self, self.OnViewerLeftUp)
+ EVT_LEFT_DCLICK(self, self.OnViewerLeftDClick)
+ EVT_RIGHT_UP(self, self.OnViewerRightUp)
+ EVT_MOTION(self, self.OnViewerMotion)
+ EVT_CHAR(self, self.OnChar)
+
+ # Returns a new id
+ def GetNewId(self):
+ self.current_id += 1
+ return self.current_id
+
+ # Destructor
+ def __del__(self):
+ self.ResetView()
+
+#-------------------------------------------------------------------------------
+# Reset functions
+#-------------------------------------------------------------------------------
+
+ # Resets Viewer lists
+ def ResetView(self):
+ self.Blocks = []
+ self.Wires = []
+ self.Elements = []
+ self.SelectedElement = None
+
+ # Changes Viewer mode
+ def SetMode(self, mode):
+ self.Mode = mode
+ # Reset selection
+ if self.SelectedElement:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ self.Refresh()
+
+#-------------------------------------------------------------------------------
+# Refresh functions
+#-------------------------------------------------------------------------------
+
+ # Refresh Viewer elements
+ def RefreshView(self):
+ self.current_id = 0
+ # Start by reseting Viewer
+ self.ResetView()
+ instance = True
+ # List of ids of already loaded blocks
+ ids = []
+ # Load Blocks until they are all loaded
+ while instance:
+ instance = self.Controler.GetCurrentElementEditingInstanceInfos(exclude=ids)
+ if instance:
+ self.loadInstance(instance, ids)
+ self.Refresh()
+
+ # Load instance from given informations
+ def loadInstance(self, instance, ids):
+ ids.append(instance["id"])
+ self.current_id = max(self.current_id, instance["id"])
+ if instance["type"] == "input":
+ variable = FBD_Variable(self, INPUT, instance["name"], instance["value_type"], instance["id"])
+ variable.SetPosition(instance["x"], instance["y"])
+ variable.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(variable)
+ self.Elements.append(variable)
+ connectors = variable.GetConnectors()
+ connectors["output"].SetPosition(wxPoint(*instance["connector"]["position"]))
+ if instance["connector"]["negated"]:
+ connectors["output"].SetNegated(True)
+ if instance["connector"]["edge"]:
+ connectors["output"].SetEdge(instance["connector"]["edge"])
+ elif instance["type"] == "output":
+ variable = FBD_Variable(self, OUTPUT, instance["name"], instance["value_type"], instance["id"])
+ variable.SetPosition(instance["x"], instance["y"])
+ variable.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(variable)
+ self.Elements.append(variable)
+ connectors = variable.GetConnectors()
+ connectors["input"].SetPosition(wxPoint(*instance["connector"]["position"]))
+ if instance["connector"]["negated"]:
+ connectors["input"].SetNegated(True)
+ if instance["connector"]["edge"]:
+ connectors["input"].SetEdge(instance["connector"]["edge"])
+ self.CreateWires(connectors["input"], instance["connector"]["links"], ids)
+ elif instance["type"] == "inout":
+ variable = FBD_Variable(self, INOUT, instance["name"], instance["value_type"], instance["id"])
+ variable.SetPosition(instance["x"], instance["y"])
+ variable.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(variable)
+ self.Elements.append(variable)
+ connectors = variable.GetConnectors()
+ connectors["output"].SetPosition(wxPoint(*instance["connectors"]["output"]["position"]))
+ connectors["input"].SetPosition(wxPoint(*instance["connectors"]["input"]["position"]))
+ if instance["connectors"]["output"]["negated"]:
+ connectors["output"].SetNegated(True)
+ if instance["connectors"]["output"]["edge"]:
+ connectors["output"].SetEdge(instance["connectors"]["output"]["edge"])
+ if instance["connectors"]["input"]["negated"]:
+ connectors["input"].SetNegated(True)
+ if instance["connectors"]["input"]["edge"]:
+ connectors["input"].SetEdge(instance["connectors"]["input"]["edge"])
+ self.CreateWires(connectors["input"], instance["connectors"]["input"]["links"], ids)
+ elif instance["type"] == "continuation":
+ connection = FBD_Connector(self, CONTINUATION, instance["name"], instance["id"])
+ connection.SetPosition(instance["x"], instance["y"])
+ connection.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(connection)
+ self.Elements.append(connection)
+ connector = connection.GetConnector()
+ connector.SetPosition(wxPoint(*instance["connector"]["position"]))
+ elif instance["type"] == "connection":
+ connection = FBD_Connection(self, CONNECTOR, instance["name"], instance["id"])
+ connection.SetPosition(instance["x"], instance["y"])
+ connection.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(connection)
+ self.Elements.append(connection)
+ connector = connection.GetConnector()
+ connector.SetPosition(wxPoint(*instance["connector"]["position"]))
+ self.CreateWires(connector, instance["connector"]["links"], ids)
+ elif instance["type"] == "comment":
+ comment = Comment(self, instance["content"], instance["id"])
+ comment.SetPosition(instance["x"], instance["y"])
+ comment.SetSize(instance["width"], instance["height"])
+ self.Elements.append(comment)
+ elif instance["type"] == "leftPowerRail":
+ leftpowerrail = LD_PowerRail(self, LEFTRAIL, instance["id"], [True for i in range(len(instance["connectors"]))])
+ leftpowerrail.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(leftpowerrail)
+ self.Elements.append(leftpowerrail)
+ connectors = leftpowerrail.GetConnectors()
+ for i, connector in enumerate(instance["connectors"]):
+ connectors[i].SetPosition(wxPoint(*connector["position"]))
+ elif instance["type"] == "rightPowerRail":
+ rightpowerrail = LD_PowerRail(self, RIGHTRAIL, instance["id"], [True for i in range(len(instance["connectors"]))])
+ rightpowerrail.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(rightpowerrail)
+ self.Elements.append(rightpowerrail)
+ connectors = rightpowerrail.GetConnectors()
+ for i, connector in enumerate(instance["connectors"]):
+ connectors[i].SetPosition(wxPoint(*connector["position"]))
+ self.CreateWires(connectors[i], connector["links"], ids)
+ elif instance["type"] == "contact":
+ if instance["negated"]:
+ negated = instance["negated"]
+ else:
+ negated = False
+ if instance["edge"]:
+ edge = instance["edge"]
+ else:
+ edge = "none"
+ if negated and edge == "none":
+ contact_type = CONTACT_REVERSE
+ elif not negated and edge == "rising":
+ contact_type = CONTACT_RISING
+ elif not negated and edge == "falling":
+ contact_type = CONTACT_FALLING
+ else:
+ contact_type = CONTACT_NORMAL
+ contact = LD_Contact(self, contact_type, instance["name"], instance["id"])
+ contact.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(contact)
+ self.Elements.append(contact)
+ connectors = contact.GetConnectors()
+ connectors["input"].SetPosition(wxPoint(*instance["connectors"]["input"]["position"]))
+ self.CreateWires(connectors["input"], instance["connectors"]["input"]["links"], ids)
+ connectors["output"].SetPosition(wxPoint(*instance["connectors"]["output"]["position"]))
+ elif instance["type"] == "coil":
+ if instance["negated"]:
+ negated = instance["negated"]
+ else:
+ negated = False
+ if instance["storage"]:
+ storage = instance["storage"]
+ else:
+ storage = "none"
+ if negated and storage == "none":
+ coil_type = COIL_REVERSE
+ elif not negated and storage == "set":
+ coil_type = COIL_SET
+ elif not negated and storage == "reset":
+ coil_type = COIL_RESET
+ else:
+ coil_type = COIL_NORMAL
+ coil = LD_Coil(self, coil_type, instance["name"], instance["id"])
+ coil.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(coil)
+ self.Elements.append(coil)
+ connectors = coil.GetConnectors()
+ connectors["input"].SetPosition(wxPoint(*instance["connectors"]["input"]["position"]))
+ self.CreateWires(connectors["input"], instance["connectors"]["input"]["links"], ids)
+ connectors["output"].SetPosition(wxPoint(*instance["connectors"]["output"]["position"]))
+ elif instance["type"] == "step":
+ if instance["initial"]:
+ initial = instance["initial"]
+ else:
+ initial = False
+ step = SFC_Step(self, instance["name"], initial, instance["id"])
+ step.SetPosition(instance["x"], instance["y"])
+ step.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(step)
+ self.Elements.append(step)
+ if "output" in instance["connectors"]:
+ step.AddOutput()
+ if "action" in instance["connectors"]:
+ step.AddAction()
+ connectors = step.GetConnectors()
+ if connectors["input"]:
+ connectors["input"].SetPosition(wxPoint(*instance["connectors"]["input"]["position"]))
+ self.CreateWires(connectors["input"], instance["connectors"]["input"]["links"], ids)
+ if connectors["output"]:
+ connectors["output"].SetPosition(wxPoint(*instance["connectors"]["output"]["position"]))
+ if connectors["action"]:
+ connectors["action"].SetPosition(wxPoint(*instance["connectors"]["action"]["position"]))
+ elif instance["type"] == "transition":
+ transition = SFC_Transition(self, instance["condition_type"], instance["condition"], instance["id"])
+ transition.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(transition)
+ self.Elements.append(transition)
+ connectors = transition.GetConnectors()
+ connectors["input"].SetPosition(wxPoint(*instance["connectors"]["input"]["position"]))
+ self.CreateWires(connectors["input"], instance["connectors"]["input"]["links"], ids)
+ connectors["output"].SetPosition(wxPoint(*instance["connectors"]["output"]["position"]))
+ elif instance["type"] in ["selectionDivergence", "selectionConvergence", "simultaneousDivergence", "simultaneousConvergence"]:
+ if instance["type"] == "selectionDivergence":
+ divergence = SFC_Divergence(self, SELECTION_DIVERGENCE,
+ len(instance["connectors"]["outputs"]), instance["id"])
+ elif instance["type"] == "selectionConvergence":
+ divergence = SFC_Divergence(self, SELECTION_CONVERGENCE,
+ len(instance["connectors"]["inputs"]), instance["id"])
+ elif instance["type"] == "simultaneousDivergence":
+ divergence = SFC_Divergence(self, SIMULTANEOUS_DIVERGENCE,
+ len(instance["connectors"]["outputs"]), instance["id"])
+ else:
+ divergence = SFC_Divergence(self, SIMULTANEOUS_CONVERGENCE,
+ len(instance["connectors"]["inputs"]), instance["id"])
+ divergence.SetPosition(instance["x"], instance["y"])
+ divergence.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(divergence)
+ self.Elements.append(divergence)
+ connectors = divergence.GetConnectors()
+ for i, input_connector in enumerate(instance["connectors"]["inputs"]):
+ connector = connectors["inputs"][i]
+ connector.SetPosition(wxPoint(*input_connector["position"]))
+ self.CreateWires(connector, input_connector["links"], ids)
+ for i, output_connector in enumerate(instance["connectors"]["outputs"]):
+ connector = connectors["outputs"][i]
+ connector.SetPosition(wxPoint(*output_connector["position"]))
+ elif instance["type"] == "jump":
+ jump = SFC_Jump(self, instance["target"], instance["id"])
+ jump.SetPosition(instance["x"], instance["y"])
+ self.Blocks.append(jump)
+ self.Elements.append(jump)
+ connector = jump.GetConnector()
+ connector.SetPosition(wxPoint(*instance["connector"]["position"]))
+ self.CreateWires(connector, instance["connector"]["links"], ids)
+ elif instance["type"] == "actionBlock":
+ actionBlock = SFC_ActionBlock(self, instance["actions"], instance["id"])
+ actionBlock.SetPosition(instance["x"], instance["y"])
+ actionBlock.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(actionBlock)
+ self.Elements.append(actionBlock)
+ connector = actionBlock.GetConnector()
+ connector.SetPosition(wxPoint(*instance["connector"]["position"]))
+ self.CreateWires(connector, instance["connector"]["links"], ids)
+ else:
+ if instance["name"] != None:
+ block = FBD_Block(self, instance["type"], instance["name"], instance["id"], len(instance["connectors"]["inputs"]))
+ else:
+ block = FBD_Block(self, instance["type"], "", instance["id"], len(instance["connectors"]["inputs"]))
+ block.SetPosition(instance["x"], instance["y"])
+ block.SetSize(instance["width"], instance["height"])
+ self.Blocks.append(block)
+ self.Elements.append(block)
+ connectors = block.GetConnectors()
+ for i, input_connector in enumerate(instance["connectors"]["inputs"]):
+ connector = connectors["inputs"][i]
+ connector.SetPosition(wxPoint(*input_connector["position"]))
+ if input_connector["negated"]:
+ connector.SetNegated(True)
+ if input_connector["edge"]:
+ connector.SetEdge(input_connector["edge"])
+ self.CreateWires(connector, input_connector["links"], ids)
+ for i, output_connector in enumerate(instance["connectors"]["outputs"]):
+ connector = connectors["outputs"][i]
+ if output_connector["negated"]:
+ connector.SetNegated(True)
+ if output_connector["edge"]:
+ connector.SetEdge(output_connector["edge"])
+ connector.SetPosition(wxPoint(*output_connector["position"]))
+
+ def CreateWires(self, start_connector, links, ids):
+ for link in links:
+ refLocalId = link["refLocalId"]
+ if refLocalId != None:
+ if refLocalId not in ids:
+ new_instance = self.Controler.GetCurrentElementEditingInstanceInfos(refLocalId)
+ if new_instance:
+ self.loadInstance(new_instance, ids)
+ connected = self.FindElementById(refLocalId)
+ if connected:
+ points = link["points"]
+ end_connector = connected.GetConnector(wxPoint(points[-1][0], points[-1][1]))
+ if end_connector:
+ wire = Wire(self)
+ wire.SetPoints(points)
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ self.Wires.append(wire)
+ self.Elements.append(wire)
+
+#-------------------------------------------------------------------------------
+# Search Element functions
+#-------------------------------------------------------------------------------
+
+ def FindBlock(self, pos):
+ for block in self.Blocks:
+ if block.HitTest(pos) or block.TestHandle(pos) != (0, 0):
+ return block
+ return None
+
+ def FindWire(self, pos):
+ for wire in self.Wires:
+ if wire.HitTest(pos) or wire.TestHandle(pos) != (0, 0):
+ return wire
+ return None
+
+ def FindElement(self, pos, exclude_group = False):
+ if self.SelectedElement and not (exclude_group and isinstance(self.SelectedElement, Graphic_Group)):
+ if self.SelectedElement.HitTest(pos) or self.SelectedElement.TestHandle(pos) != (0, 0):
+ return self.SelectedElement
+ for element in self.Elements:
+ if element.HitTest(pos) or element.TestHandle(pos) != (0, 0):
+ return element
+ return None
+
+ def FindBlockConnector(self, pos):
+ for block in self.Blocks:
+ result = block.TestConnector(pos)
+ if result:
+ return result
+ return None
+
+ def FindElementById(self, id):
+ for element in self.Elements:
+ if element.GetId() == id:
+ return element
+ return None
+
+ def SearchElements(self, bbox):
+ elements = []
+ for element in self.Elements:
+ element_bbox = element.GetBoundingBox()
+ if element_bbox.x >= bbox.x and element_bbox.y >= bbox.y and element_bbox.x + element_bbox.width <= bbox.x + bbox.width and element_bbox.y + element_bbox.height <= bbox.y + bbox.height:
+ elements.append(element)
+ return elements
+
+#-------------------------------------------------------------------------------
+# Popup menu functions
+#-------------------------------------------------------------------------------
+
+ def PopupBlockMenu(self, connector = None):
+ self.ContextualMenu.FindItemByPosition(0).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(1).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(2).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(3).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(5).Enable(False)
+ self.ContextualMenu.FindItemByPosition(6).Enable(False)
+ self.ContextualMenu.FindItemByPosition(8).Enable(False)
+ self.ContextualMenu.FindItemByPosition(9).Enable(False)
+ if connector:
+ if connector.IsNegated():
+ self.ContextualMenu.FindItemByPosition(1).Check(True)
+ elif connector.GetEdge() == "rising":
+ self.ContextualMenu.FindItemByPosition(2).Check(True)
+ elif connector.GetEdge() == "falling":
+ self.ContextualMenu.FindItemByPosition(3).Check(True)
+ else:
+ self.ContextualMenu.FindItemByPosition(0).Check(True)
+ self.PopupMenu(self.ContextualMenu)
+
+ def PopupVariableMenu(self, connector = None):
+ self.ContextualMenu.FindItemByPosition(0).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(1).Enable(connector != None)
+ self.ContextualMenu.FindItemByPosition(2).Enable(False)
+ self.ContextualMenu.FindItemByPosition(3).Enable(False)
+ self.ContextualMenu.FindItemByPosition(5).Enable(False)
+ self.ContextualMenu.FindItemByPosition(6).Enable(False)
+ self.ContextualMenu.FindItemByPosition(8).Enable(False)
+ self.ContextualMenu.FindItemByPosition(9).Enable(False)
+ if connector:
+ if connector.IsNegated():
+ self.ContextualMenu.FindItemByPosition(1).Check(True)
+ else:
+ self.ContextualMenu.FindItemByPosition(0).Check(True)
+ self.PopupMenu(self.ContextualMenu)
+
+ def PopupWireMenu(self):
+ self.ContextualMenu.FindItemByPosition(0).Enable(False)
+ self.ContextualMenu.FindItemByPosition(1).Enable(False)
+ self.ContextualMenu.FindItemByPosition(2).Enable(False)
+ self.ContextualMenu.FindItemByPosition(3).Enable(False)
+ self.ContextualMenu.FindItemByPosition(5).Enable(True)
+ self.ContextualMenu.FindItemByPosition(6).Enable(True)
+ self.ContextualMenu.FindItemByPosition(8).Enable(False)
+ self.ContextualMenu.FindItemByPosition(9).Enable(False)
+ self.PopupMenu(self.ContextualMenu)
+
+ def PopupDivergenceMenu(self, connector):
+ self.ContextualMenu.FindItemByPosition(0).Enable(False)
+ self.ContextualMenu.FindItemByPosition(1).Enable(False)
+ self.ContextualMenu.FindItemByPosition(2).Enable(False)
+ self.ContextualMenu.FindItemByPosition(3).Enable(False)
+ self.ContextualMenu.FindItemByPosition(5).Enable(False)
+ self.ContextualMenu.FindItemByPosition(6).Enable(False)
+ self.ContextualMenu.FindItemByPosition(8).Enable(True)
+ self.ContextualMenu.FindItemByPosition(9).Enable(connector)
+ self.PopupMenu(self.ContextualMenu)
+
+ def PopupDefaultMenu(self):
+ self.ContextualMenu.FindItemByPosition(0).Enable(False)
+ self.ContextualMenu.FindItemByPosition(1).Enable(False)
+ self.ContextualMenu.FindItemByPosition(2).Enable(False)
+ self.ContextualMenu.FindItemByPosition(3).Enable(False)
+ self.ContextualMenu.FindItemByPosition(5).Enable(False)
+ self.ContextualMenu.FindItemByPosition(6).Enable(False)
+ self.ContextualMenu.FindItemByPosition(8).Enable(False)
+ self.ContextualMenu.FindItemByPosition(9).Enable(False)
+ self.PopupMenu(self.ContextualMenu)
+
+ def EditCommentContent(self, comment):
+ dialog = wxTextEntryDialog(self.Parent, "Edit comment", "Please enter comment text", comment.GetContent(), wxOK|wxCANCEL|wxTE_MULTILINE)
+ if dialog.ShowModal() == wxID_OK:
+ value = dialog.GetValue()
+ comment.SetContent(value)
+ infos = {"content" : value}
+ infos["width"], infos["height"] = comment.GetSize()
+ self.Controler.SetCurrentElementEditingCommentInfos(comment.GetId(), infos)
+ self.Refresh()
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Menu items functions
+#-------------------------------------------------------------------------------
+
+ def OnNoModifierMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.SelectedElement.SetConnectorNegated(False)
+ event.Skip()
+
+ def OnNegatedMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.SelectedElement.SetConnectorNegated(True)
+ event.Skip()
+
+ def OnRisingEdgeMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.SelectedElement.SetConnectorEdge("rising")
+ event.Skip()
+
+ def OnFallingEdgeMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.SelectedElement.SetConnectorEdge("falling")
+ event.Skip()
+
+ def OnAddSegmentMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Wires:
+ self.SelectedElement.AddSegment()
+ event.Skip()
+
+ def OnDeleteSegmentMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Wires:
+ self.SelectedElement.DeleteSegment()
+ event.Skip()
+
+ def OnAddBranchMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.AddDivergenceBranch(self.SelectedElement)
+ event.Skip()
+
+ def OnDeleteBranchMenu(self, event):
+ if self.SelectedElement and self.SelectedElement in self.Blocks:
+ self.RemoveDivergenceBranch(self.SelectedElement)
+ event.Skip()
+
+ def OnDeleteMenu(self, event):
+ if self.SelectedElement:
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerLeftDown(self, event):
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Model update functions
+#-------------------------------------------------------------------------------
+
+ def RefreshBlockModel(self, block):
+ blockid = block.GetId()
+ infos = {}
+ infos["type"] = block.GetType()
+ infos["name"] = block.GetName()
+ infos["x"], infos["y"] = block.GetPosition()
+ infos["width"], infos["height"] = block.GetSize()
+ infos["connectors"] = block.GetConnectors()
+ self.Controler.SetCurrentElementEditingBlockInfos(blockid, infos)
+
+ def RefreshVariableModel(self, variable):
+ variableid = variable.GetId()
+ infos = {}
+ infos["name"] = variable.GetName()
+ infos["x"], infos["y"] = variable.GetPosition()
+ infos["width"], infos["height"] = variable.GetSize()
+ infos["connectors"] = variable.GetConnectors()
+ self.Controler.SetCurrentElementEditingVariableInfos(variableid, infos)
+
+ def RefreshConnectionModel(self, connection):
+ connectionid = connection.GetId()
+ infos = {}
+ infos["name"] = connection.GetName()
+ infos["x"], infos["y"] = connection.GetPosition()
+ infos["width"], infos["height"] = connection.GetSize()
+ infos["connector"] = connection.GetConnector()
+ self.Controler.SetCurrentElementEditingConnectionInfos(connectionid, infos)
+
+ def RefreshCommentModel(self, comment):
+ commentid = comment.GetId()
+ infos = {}
+ infos["content"] = comment.GetContent()
+ infos["x"], infos["y"] = comment.GetPosition()
+ infos["width"], infos["height"] = comment.GetSize()
+ self.Controler.SetCurrentElementEditingCommentInfos(commentid, infos)
+
+ def RefreshPowerRailModel(self, powerrail):
+ powerrailid = powerrail.GetId()
+ infos = {}
+ infos["x"], infos["y"] = powerrail.GetPosition()
+ infos["width"], infos["height"] = powerrail.GetSize()
+ infos["connectors"] = powerrail.GetConnectors()
+ self.Controler.SetCurrentElementEditingPowerRailInfos(powerrailid, infos)
+
+ def RefreshContactModel(self, contact):
+ contactid = contact.GetId()
+ infos = {}
+ infos["name"] = contact.GetName()
+ infos["type"] = contact.GetType()
+ infos["x"], infos["y"] = contact.GetPosition()
+ infos["width"], infos["height"] = contact.GetSize()
+ infos["connectors"] = contact.GetConnectors()
+ self.Controler.SetCurrentElementEditingContactInfos(contactid, infos)
+
+ def RefreshCoilModel(self, coil):
+ coilid = coil.GetId()
+ infos = {}
+ infos["name"] = coil.GetName()
+ infos["type"] = coil.GetType()
+ infos["x"], infos["y"] = coil.GetPosition()
+ infos["width"], infos["height"] = coil.GetSize()
+ infos["connectors"] = coil.GetConnectors()
+ self.Controler.SetCurrentElementEditingCoilInfos(coilid, infos)
+
+ def RefreshStepModel(self, step):
+ stepid = step.GetId()
+ infos = {}
+ infos["name"] = step.GetName()
+ infos["initial"] = step.GetInitial()
+ infos["x"], infos["y"] = step.GetPosition()
+ infos["width"], infos["height"] = step.GetSize()
+ infos["connectors"] = step.GetConnectors()
+ self.Controler.SetCurrentElementEditingStepInfos(stepid, infos)
+
+ def RefreshTransitionModel(self, transition):
+ transitionid = transition.GetId()
+ infos = {}
+ infos["type"] = transition.GetType()
+ infos["condition"] = transition.GetCondition()
+ infos["x"], infos["y"] = transition.GetPosition()
+ infos["width"], infos["height"] = transition.GetSize()
+ infos["connectors"] = transition.GetConnectors()
+ self.Controler.SetCurrentElementEditingTransitionInfos(transitionid, infos)
+
+ def RefreshDivergenceModel(self, divergence):
+ divergenceid = divergence.GetId()
+ infos = {}
+ infos["x"], infos["y"] = divergence.GetPosition()
+ infos["width"], infos["height"] = divergence.GetSize()
+ infos["connectors"] = divergence.GetConnectors()
+ self.Controler.SetCurrentElementEditingDivergenceInfos(divergenceid, infos)
+
+ def RefreshJumpModel(self, jump):
+ jumpid = jump.GetId()
+ infos = {}
+ infos["target"] = jump.GetTarget()
+ infos["x"], infos["y"] = jump.GetPosition()
+ infos["width"], infos["height"] = jump.GetSize()
+ infos["connector"] = jump.GetConnector()
+ self.Controler.SetCurrentElementEditingJumpInfos(jumpid, infos)
+
+ def RefreshActionBlockModel(self, actionblock):
+ actionblockid = actionblock.GetId()
+ infos = {}
+ infos["actions"] = actionblock.GetActions()
+ infos["x"], infos["y"] = actionblock.GetPosition()
+ infos["width"], infos["height"] = actionblock.GetSize()
+ infos["connector"] = actionblock.GetConnector()
+ self.Controler.SetCurrentElementEditingActionBlockInfos(actionblockid, infos)
+
+#-------------------------------------------------------------------------------
+# Editing functions
+#-------------------------------------------------------------------------------
+
+ def Cut(self):
+ pass
+
+ def Copy(self):
+ pass
+
+ def Paste(self):
+ pass
+
+#-------------------------------------------------------------------------------
+# Drawing functions
+#-------------------------------------------------------------------------------
+
+ def OnPaint(self, event):
+ dc = wxClientDC(self)
+ dc.Clear()
+ dc.SetPen(wxPen(wxColour(230, 230, 230)))
+ if self.Scaling and self.DrawGrid:
+ width, height = dc.GetSize()
+ for i in xrange(1, width / self.Scaling[0] + 1):
+ dc.DrawLine(i * self.Scaling[0], 0, i * self.Scaling[0], height)
+ for i in xrange(1, height / self.Scaling[1] + 1):
+ dc.DrawLine(0, i * self.Scaling[1], width, i * self.Scaling[1])
+ for wire in self.Wires:
+ if wire != self.SelectedElement:
+ wire.Draw(dc)
+ for element in self.Elements:
+ if element not in self.Wires and element != self.SelectedElement:
+ element.Draw(dc)
+ if self.SelectedElement:
+ self.SelectedElement.Draw(dc)
+ event.Skip()
+
+
+#-------------------------------------------------------------------------------
+# Textual programs Viewer class
+#-------------------------------------------------------------------------------
+
+
+NEWLINE = "\n"
+NUMBERS = [str(i) for i in xrange(10)]
+LETTERS = ['_']
+for i in xrange(26):
+ LETTERS.append(chr(ord('a') + i))
+ LETTERS.append(chr(ord('A') + i))
+
+[wxSTC_PLC_WORD, wxSTC_PLC_COMMENT, wxSTC_PLC_NUMBER, wxSTC_PLC_VARIABLE,
+ wxSTC_PLC_FUNCTION, wxSTC_PLC_JUMP] = range(6)
+[SPACE, WORD, NUMBER, COMMENT] = range(4)
+
+[wxID_TEXTVIEWER,
+] = [wx.NewId() for _init_ctrls in range(1)]
+
+if wx.Platform == '__WXMSW__':
+ faces = { 'times': 'Times New Roman',
+ 'mono' : 'Courier New',
+ 'helv' : 'Arial',
+ 'other': 'Comic Sans MS',
+ 'size' : 10,
+ }
+else:
+ faces = { 'times': 'Times',
+ 'mono' : 'Courier',
+ 'helv' : 'Helvetica',
+ 'other': 'new century schoolbook',
+ 'size' : 12,
+ }
+re_texts = {}
+re_texts["letter"] = "[A-Za-z]"
+re_texts["digit"] = "[0-9]"
+re_texts["identifier"] = "((?:%(letter)s|(?:_(?:%(letter)s|%(digit)s)))(?:_?(?:%(letter)s|%(digit)s))*)"%re_texts
+IDENTIFIER_MODEL = re.compile(re_texts["identifier"])
+LABEL_MODEL = re.compile("[ \t\n]%(identifier)s:[ \t\n]"%re_texts)
+
+class TextViewer(wxStyledTextCtrl):
+
+ def __init__(self, parent, window, controler):
+ wxStyledTextCtrl.__init__(self, parent, wxID_TEXTVIEWER, style=0)
+
+ self.CmdKeyAssign(ord('+'), wxSTC_SCMOD_CTRL, wxSTC_CMD_ZOOMIN)
+ self.CmdKeyAssign(ord('-'), wxSTC_SCMOD_CTRL, wxSTC_CMD_ZOOMOUT)
+
+ self.SetViewWhiteSpace(False)
+
+ self.SetLexer(wxSTC_LEX_CONTAINER)
+
+ # Global default styles for all languages
+ self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+ self.StyleClearAll() # Reset all to be like the default
+
+ self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,size:%(size)d" % faces)
+ self.SetSelBackground(1, "#E0E0E0")
+
+ # Highlighting styles
+ self.StyleSetSpec(wxSTC_PLC_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
+ self.StyleSetSpec(wxSTC_PLC_VARIABLE, "fore:#7F0000,size:%(size)d" % faces)
+ self.StyleSetSpec(wxSTC_PLC_FUNCTION, "fore:#7F7F00,size:%(size)d" % faces)
+ self.StyleSetSpec(wxSTC_PLC_COMMENT, "fore:#7F7F7F,size:%(size)d" % faces)
+ self.StyleSetSpec(wxSTC_PLC_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
+ self.StyleSetSpec(wxSTC_PLC_JUMP, "fore:#007F00,size:%(size)d" % faces)
+
+ # Indicators styles
+ self.IndicatorSetStyle(0, wxSTC_INDIC_SQUIGGLE)
+ self.IndicatorSetForeground(0, wxRED)
+
+ # Line numbers in the margin
+ self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
+ self.SetMarginWidth(1, 50)
+
+ # Indentation size
+ self.SetTabWidth(2)
+
+ self.Keywords = []
+ self.Variables = []
+ self.Functions = []
+ self.Jumps = []
+ self.TextChanged = False
+
+ self.Controler = controler
+
+ EVT_KEY_DOWN(self, self.OnKeyDown)
+ EVT_STC_STYLENEEDED(self, wxID_TEXTVIEWER, self.OnStyleNeeded)
+
+ def SetKeywords(self, keywords):
+ self.Keywords = [keyword.upper() for keyword in keywords]
+ self.Colourise(0, -1)
+
+ def SetVariables(self, variables):
+ self.Variables = [variable.upper() for variable in variables]
+ self.Colourise(0, -1)
+
+ def SetFunctions(self, blocktypes):
+ self.Functions = []
+ for category in blocktypes:
+ for blocktype in category["list"]:
+ if blocktype["name"] not in self.Keywords and blocktype["name"] not in self.Variables:
+ self.Functions.append(blocktype["name"].upper())
+ self.Colourise(0, -1)
+
+ def RefreshJumpList(self):
+ self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())]
+ self.Colourise(0, -1)
+
+ def RefreshView(self):
+ self.SetText(self.Controler.GetCurrentElementEditingText())
+ self.RefreshJumpList()
+
+ def OnStyleNeeded(self, event):
+ self.TextChanged = True
+ line = self.LineFromPosition(self.GetEndStyled())
+ if line == 0:
+ start_pos = 0
+ else:
+ start_pos = self.GetLineEndPosition(line - 1) + 1
+ end_pos = event.GetPosition()
+ self.StartStyling(start_pos, 0xff)
+
+ i = start_pos
+ state = SPACE
+ line = ""
+ word = ""
+ while i < end_pos:
+ char = chr(self.GetCharAt(i)).upper()
+ line += char
+ if char == NEWLINE:
+ if state == COMMENT:
+ self.SetStyling(i - start_pos + 1, wxSTC_PLC_COMMENT)
+ elif state == NUMBER:
+ self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
+ elif state == WORD:
+ if word in self.Keywords:
+ self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
+ elif word in self.Variables:
+ self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
+ elif word in self.Functions:
+ self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
+ elif word in self.Jumps:
+ self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
+ else:
+ self.SetStyling(i - start_pos, 31)
+ if self.GetCurrentPos() < start_pos or self.GetCurrentPos() > i:
+ self.StartStyling(start_pos, wxSTC_INDICS_MASK)
+ self.SetStyling(i - start_pos, wxSTC_INDIC0_MASK)
+ self.StartStyling(i, 0xff)
+ else:
+ self.SetStyling(i - start_pos, 31)
+ start_pos = i
+ state = SPACE
+ line = ""
+ elif line.endswith("(*") and state != COMMENT:
+ self.SetStyling(i - start_pos - 1, 31)
+ start_pos = i
+ state = COMMENT
+ elif state == COMMENT:
+ if line.endswith("*)"):
+ self.SetStyling(i - start_pos + 2, wxSTC_PLC_COMMENT)
+ start_pos = i + 1
+ state = SPACE
+ elif char in LETTERS:
+ if state == NUMBER:
+ word = "#"
+ state = WORD
+ elif state == SPACE:
+ self.SetStyling(i - start_pos, 31)
+ word = char
+ start_pos = i
+ state = WORD
+ else:
+ word += char
+ elif char in NUMBERS or char == '.' and state != WORD:
+ if state == SPACE:
+ self.SetStyling(i - start_pos, 31)
+ start_pos = i
+ state = NUMBER
+ if state == WORD and char != '.':
+ word += char
+ else:
+ if state == WORD:
+ if word in self.Keywords:
+ self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
+ elif word in self.Variables:
+ self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
+ elif word in self.Functions:
+ self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
+ elif word in self.Jumps:
+ self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
+ else:
+ self.SetStyling(i - start_pos, 31)
+ if self.GetCurrentPos() < start_pos or self.GetCurrentPos() > i:
+ self.StartStyling(start_pos, wxSTC_INDICS_MASK)
+ self.SetStyling(i - start_pos, wxSTC_INDIC0_MASK)
+ self.StartStyling(i, 0xff)
+ word = ""
+ start_pos = i
+ state = SPACE
+ elif state == NUMBER:
+ self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
+ start_pos = i
+ state = SPACE
+ i += 1
+ if state == COMMENT:
+ self.SetStyling(i - start_pos + 2, wxSTC_PLC_COMMENT)
+ elif state == NUMBER:
+ self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
+ elif state == WORD:
+ if word in self.Keywords:
+ self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
+ elif word in self.Variables:
+ self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
+ elif word in self.Functions:
+ self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
+ elif word in self.Jumps:
+ self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
+ else:
+ self.SetStyling(i - start_pos, 31)
+ else:
+ self.SetStyling(i - start_pos, 31)
+ event.Skip()
+
+ def Cut(self):
+ self.CmdKeyExecute(wxSTC_CMD_CUT)
+
+ def Copy(self):
+ self.CmdKeyExecute(wxSTC_CMD_COPY)
+
+ def Paste(self):
+ self.CmdKeyExecute(wxSTC_CMD_PASTE)
+
+ def RefreshModel(self):
+ if self.TextChanged:
+ self.RefreshJumpList()
+ self.Controler.SetCurrentElementEditingText(self.GetText())
+
+ def OnKeyDown(self, event):
+ if self.CallTipActive():
+ self.CallTipCancel()
+ key = event.KeyCode()
+
+ if key == WXK_SPACE and event.ControlDown():
+ pos = self.GetCurrentPos()
+
+ # Tips
+ if event.ShiftDown():
+ self.CallTipSetBackground("yellow")
+ self.CallTipShow(pos, 'Here will be some help.')
+
+ # Code completion
+ else:
+ kw = [key for key in self.Keywords]
+
+ kw.sort() # Python sorts are case sensitive
+ self.AutoCompSetIgnoreCase(False) # so this needs to match
+
+ self.AutoCompShow(0, " ".join(kw))
+ else:
+ self.TextChanged = False
+ wxCallAfter(self.RefreshModel)
+ event.Skip()
+
+
+#-------------------------------------------------------------------------------
+# Resource Editor class
+#-------------------------------------------------------------------------------
+
+class ResourceTable(wxPyGridTableBase):
+
+ """
+ A custom wxGrid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ wxPyGridTableBase.__init__(self)
+ self.data = data
+ self.colnames = colnames
+ self.Parent = parent
+
+ self.ColAlignements = []
+ self.ColSizes = []
+ # XXX
+ # we need to store the row length and collength to
+ # see if the table has changed size
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+
+ def GetColAlignements(self):
+ return self.ColAlignements
+
+ def SetColAlignements(self, list):
+ self.ColAlignements = list
+
+ def GetColSizes(self):
+ return self.ColSizes
+
+ def SetColSizes(self, list):
+ self.ColSizes = list
+
+ def GetNumberCols(self):
+ return len(self.colnames)
+
+ def GetNumberRows(self):
+ return len(self.data)
+
+ def GetColLabelValue(self, col):
+ if col < len(self.colnames):
+ return self.colnames[col]
+
+ def GetRowLabelValues(self, row):
+ return row
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ name = str(self.data[row].get(self.GetColLabelValue(col), ""))
+ return name
+
+ def GetValueByName(self, row, colname):
+ return self.data[row].get(colname)
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.data[row][self.GetColLabelValue(col)] = value
+
+ def SetValueByName(self, row, colname, value):
+ if colname in self.colnames:
+ self.data[row][colname] = value
+
+ def ResetView(self, grid):
+ """
+ (wxGrid) -> Reset the grid view. Call this to
+ update the grid if rows and columns have been added or deleted
+ """
+ grid.BeginBatch()
+ for current, new, delmsg, addmsg in [
+ (self._rows, self.GetNumberRows(), wxGRIDTABLE_NOTIFY_ROWS_DELETED, wxGRIDTABLE_NOTIFY_ROWS_APPENDED),
+ (self._cols, self.GetNumberCols(), wxGRIDTABLE_NOTIFY_COLS_DELETED, wxGRIDTABLE_NOTIFY_COLS_APPENDED),
+ ]:
+ if new < current:
+ msg = wxGridTableMessage(self,delmsg,new,current-new)
+ grid.ProcessTableMessage(msg)
+ elif new > current:
+ msg = wxGridTableMessage(self,addmsg,new-current)
+ grid.ProcessTableMessage(msg)
+ self.UpdateValues(grid)
+ grid.EndBatch()
+
+ self._rows = self.GetNumberRows()
+ self._cols = self.GetNumberCols()
+ # update the column rendering scheme
+ self._updateColAttrs(grid)
+
+ # update the scrollbars and the displayed part of the grid
+ grid.AdjustScrollbars()
+ grid.ForceRefresh()
+
+ def UpdateValues(self, grid):
+ """Update all displayed values"""
+ # This sends an event to the grid table to update all of the values
+ msg = wxGridTableMessage(self, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES)
+ grid.ProcessTableMessage(msg)
+
+ def _updateColAttrs(self, grid):
+ """
+ wxGrid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for col in range(self.GetNumberCols()):
+ attr = wxGridCellAttr()
+ attr.SetAlignment(self.ColAlignements[col], wxALIGN_CENTRE)
+ grid.SetColAttr(col, attr)
+ grid.SetColSize(col, self.ColSizes[col])
+
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col)
+ grid.SetReadOnly(row, col, False)
+ if colname in ["Name","Interval"]:
+ editor = wxGridCellTextEditor()
+ renderer = wxGridCellStringRenderer()
+ if colname == "Interval" and self.GetValueByName(row, "Single") != "":
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Single":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+ if self.GetValueByName(row, "Interval") != "":
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Type":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TypeList)
+ elif colname == "Priority":
+ editor = wxGridCellNumberEditor()
+ editor.SetParameters("0,65535")
+ elif colname == "Task":
+ editor = wxGridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TaskList)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ grid.SetCellBackgroundColour(row, col, wxWHITE)
+
+ def SetData(self, data):
+ self.data = data
+
+ def GetData(self):
+ return self.data
+
+ def GetCurrentIndex(self):
+ return self.CurrentIndex
+
+ def SetCurrentIndex(self, index):
+ self.CurrentIndex = index
+
+ def AppendRow(self, row_content):
+ self.data.append(row_content)
+
+ def RemoveRow(self, row_index):
+ self.data.pop(row_index)
+
+ def MoveRow(self, row_index, move, grid):
+ new_index = max(0, min(row_index + move, len(self.data) - 1))
+ if new_index != row_index:
+ self.data.insert(new_index, self.data.pop(row_index))
+ grid.SetGridCursor(new_index, grid.GetGridCursorCol())
+
+ def Empty(self):
+ self.data = []
+ self.editors = []
+
+[wxID_RESOURCEEDITOR, wxID_RESOURCEEDITORSTATICTEXT1,
+ wxID_RESOURCEEDITORSTATICTEXT2, wxID_RESOURCEEDITORINSTANCESGRID,
+ wxID_RESOURCEEDITORTASKSGRID, wxID_RESOURCEEDITORADDINSTANCEBUTTON,
+ wxID_RESOURCEEDITORDELETEINSTANCEBUTTON, wxID_RESOURCEEDITORUPINSTANCEBUTTON,
+ wxID_RESOURCEEDITORDOWNINSTANCEBUTTON, wxID_RESOURCEEDITORADDTASKBUTTON,
+ wxID_RESOURCEEDITORDELETETASKBUTTON, wxID_RESOURCEEDITORUPTASKBUTTON,
+ wxID_RESOURCEEDITORDOWNTASKBUTTON,
+] = [wx.NewId() for _init_ctrls in range(13)]
+
+class ResourceEditor(wx.Panel):
+
+ def _init_coll_InstancesSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(1)
+
+ def _init_coll_InstancesSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddSizer(self.InstancesButtonsSizer, 0, border=0, flag=wxGROW)
+ parent.AddWindow(self.InstancesGrid, 0, border=0, flag=wxGROW)
+
+ def _init_coll_InstancesButtonsSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(0)
+
+ def _init_coll_InstancesButtonsSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.staticText2, 0, border=0, flag=wxALIGN_BOTTOM)
+ parent.AddWindow(self.AddInstanceButton, 0, border=0, flag=0)
+ parent.AddWindow(self.DeleteInstanceButton, 0, border=0, flag=0)
+ parent.AddWindow(self.UpInstanceButton, 0, border=0, flag=0)
+ parent.AddWindow(self.DownInstanceButton, 0, border=0, flag=0)
+
+ def _init_coll_TasksSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(1)
+
+ def _init_coll_TasksSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddSizer(self.TasksButtonsSizer, 0, border=0, flag=wxGROW)
+ parent.AddWindow(self.TasksGrid, 0, border=0, flag=wxGROW)
+
+ def _init_coll_TasksButtonsSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(0)
+
+ def _init_coll_TasksButtonsSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddWindow(self.staticText1, 0, border=0, flag=wxALIGN_BOTTOM)
+ parent.AddWindow(self.AddTaskButton, 0, border=0, flag=0)
+ parent.AddWindow(self.DeleteTaskButton, 0, border=0, flag=0)
+ parent.AddWindow(self.UpTaskButton, 0, border=0, flag=0)
+ parent.AddWindow(self.DownTaskButton, 0, border=0, flag=0)
+
+ def _init_coll_MainGridSizer_Items(self, parent):
+ # generated method, don't edit
+
+ parent.AddSizer(self.TasksSizer, 0, border=0, flag=wxGROW)
+ parent.AddSizer(self.InstancesSizer, 0, border=0, flag=wxGROW)
+
+ def _init_coll_MainGridSizer_Growables(self, parent):
+ # generated method, don't edit
+
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(0)
+ parent.AddGrowableRow(1)
+
+ def _init_sizers(self):
+ # generated method, don't edit
+ self.MainGridSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+
+ self.InstancesSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+
+ self.InstancesButtonsSizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+
+ self.TasksSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+
+ self.TasksButtonsSizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+
+ self._init_coll_MainGridSizer_Growables(self.MainGridSizer)
+ self._init_coll_MainGridSizer_Items(self.MainGridSizer)
+ self._init_coll_InstancesSizer_Growables(self.InstancesSizer)
+ self._init_coll_InstancesSizer_Items(self.InstancesSizer)
+ self._init_coll_InstancesButtonsSizer_Growables(self.InstancesButtonsSizer)
+ self._init_coll_InstancesButtonsSizer_Items(self.InstancesButtonsSizer)
+ self._init_coll_TasksSizer_Growables(self.TasksSizer)
+ self._init_coll_TasksSizer_Items(self.TasksSizer)
+ self._init_coll_TasksButtonsSizer_Growables(self.TasksButtonsSizer)
+ self._init_coll_TasksButtonsSizer_Items(self.TasksButtonsSizer)
+
+ self.SetSizer(self.MainGridSizer)
+
+ def _init_ctrls(self, prnt):
+ # generated method, don't edit
+ wx.Panel.__init__(self, id=wxID_RESOURCEEDITOR, name='', parent=prnt,
+ pos=wx.Point(0, 0), size=wx.Size(-1, -1),
+ style=wx.SUNKEN_BORDER)
+
+ self.staticText1 = wx.StaticText(id=wxID_RESOURCEEDITORSTATICTEXT1,
+ label=u'Tasks:', name='staticText2', parent=self, pos=wx.Point(0,
+ 0), size=wx.Size(60, 17), style=wxALIGN_CENTER)
+
+ self.TasksGrid = wx.grid.Grid(id=wxID_RESOURCEEDITORTASKSGRID,
+ name='TasksGrid', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(-1, -1), style=wxVSCROLL)
+ self.TasksGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False,
+ 'Sans'))
+ self.TasksGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL,
+ False, 'Sans'))
+ EVT_GRID_CELL_CHANGE(self.TasksGrid, self.OnTasksGridCellChange)
+
+ self.AddTaskButton = wx.Button(id=wxID_RESOURCEEDITORADDTASKBUTTON, label='Add Task',
+ name='AddTaskButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(102, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORADDTASKBUTTON, self.OnAddTaskButton)
+
+ self.DeleteTaskButton = wx.Button(id=wxID_RESOURCEEDITORDELETETASKBUTTON, label='Delete Task',
+ name='DeleteTaskButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(102, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORDELETETASKBUTTON, self.OnDeleteTaskButton)
+
+ self.UpTaskButton = wx.Button(id=wxID_RESOURCEEDITORUPTASKBUTTON, label='^',
+ name='UpTaskButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORUPTASKBUTTON, self.OnUpTaskButton)
+
+ self.DownTaskButton = wx.Button(id=wxID_RESOURCEEDITORDOWNTASKBUTTON, label='v',
+ name='DownTaskButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORDOWNTASKBUTTON, self.OnDownTaskButton)
+
+ self.staticText2 = wx.StaticText(id=wxID_RESOURCEEDITORSTATICTEXT2,
+ label=u'Instances:', name='staticText1', parent=self,
+ pos=wx.Point(0, 0), size=wx.Size(85, 17), style=wxALIGN_CENTER)
+
+ self.InstancesGrid = wx.grid.Grid(id=wxID_RESOURCEEDITORINSTANCESGRID,
+ name='InstancesGrid', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(-1, -1), style=wxVSCROLL)
+ self.InstancesGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False,
+ 'Sans'))
+ self.InstancesGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL,
+ False, 'Sans'))
+ EVT_GRID_CELL_CHANGE(self.InstancesGrid, self.OnInstancesGridCellChange)
+
+ self.AddInstanceButton = wx.Button(id=wxID_RESOURCEEDITORADDINSTANCEBUTTON, label='Add Instance',
+ name='AddInstanceButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(122, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORADDINSTANCEBUTTON, self.OnAddInstanceButton)
+
+ self.DeleteInstanceButton = wx.Button(id=wxID_RESOURCEEDITORDELETEINSTANCEBUTTON, label='Delete Instance',
+ name='DeleteInstanceButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(122, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORDELETEINSTANCEBUTTON, self.OnDeleteInstanceButton)
+
+ self.UpInstanceButton = wx.Button(id=wxID_RESOURCEEDITORUPINSTANCEBUTTON, label='^',
+ name='UpInstanceButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORUPINSTANCEBUTTON, self.OnUpInstanceButton)
+
+ self.DownInstanceButton = wx.Button(id=wxID_RESOURCEEDITORDOWNINSTANCEBUTTON, label='v',
+ name='DownInstanceButton', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(32, 32), style=0)
+ EVT_BUTTON(self, wxID_RESOURCEEDITORDOWNINSTANCEBUTTON, self.OnDownInstanceButton)
+
+ self._init_sizers()
+
+ def __init__(self, parent, window, controler):
+ self._init_ctrls(parent)
+
+ self.Parent = window
+ self.Controler = controler
+
+ self.TasksDefaultValue = {"Name" : "", "Single" : "", "Interval" : "", "Priority" : 0}
+ self.TasksTable = ResourceTable(self, [], ["Name", "Single", "Interval", "Priority"])
+ self.TasksTable.SetColAlignements([wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_RIGHT, wxALIGN_RIGHT])
+ self.TasksTable.SetColSizes([200, 100, 100, 100])
+ self.TasksGrid.SetTable(self.TasksTable)
+ self.TasksGrid.SetRowLabelSize(0)
+ self.TasksTable.ResetView(self.TasksGrid)
+
+ self.InstancesDefaultValue = {"Name" : "", "Type" : "", "Task" : ""}
+ self.InstancesTable = ResourceTable(self, [], ["Name", "Type", "Task"])
+ self.InstancesTable.SetColAlignements([wxALIGN_LEFT, wxALIGN_LEFT, wxALIGN_LEFT])
+ self.InstancesTable.SetColSizes([200, 150, 150])
+ self.InstancesGrid.SetTable(self.InstancesTable)
+ self.InstancesGrid.SetRowLabelSize(0)
+ self.InstancesTable.ResetView(self.InstancesGrid)
+
+ def RefreshTypeList(self):
+ self.TypeList = ""
+ blocktypes = self.Controler.GetBlockResource()
+ for blocktype in blocktypes:
+ self.TypeList += ",%s"%blocktype
+
+ def RefreshTaskList(self):
+ self.TaskList = ""
+ for row in xrange(self.TasksTable.GetNumberRows()):
+ self.TaskList += ",%s"%self.TasksTable.GetValueByName(row, "Name")
+
+ def RefreshVariableList(self):
+ self.VariableList = ""
+ for variable in self.Controler.GetCurrentResourceEditingVariables():
+ self.VariableList += ",%s"%variable
+
+ def RefreshModel(self):
+ self.Controler.SetCurrentResourceEditingInfos(self.TasksTable.GetData(), self.InstancesTable.GetData())
+
+ def RefreshView(self):
+ tasks, instances = self.Controler.GetCurrentResourceEditingInfos()
+ self.TasksTable.SetData(tasks)
+ self.InstancesTable.SetData(instances)
+ self.RefreshTypeList()
+ self.RefreshTaskList()
+ self.RefreshVariableList()
+ self.InstancesTable.ResetView(self.InstancesGrid)
+ self.TasksTable.ResetView(self.TasksGrid)
+
+ def OnAddTaskButton(self, event):
+ self.TasksTable.AppendRow(self.TasksDefaultValue.copy())
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnDeleteTaskButton(self, event):
+ row = self.TasksGrid.GetGridCursorRow()
+ self.TasksTable.RemoveRow(row)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnUpTaskButton(self, event):
+ row = self.TasksGrid.GetGridCursorRow()
+ self.TasksTable.MoveRow(row, -1, self.TasksGrid)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnDownTaskButton(self, event):
+ row = self.TasksGrid.GetGridCursorRow()
+ self.TasksTable.MoveRow(row, 1, self.TasksGrid)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnAddInstanceButton(self, event):
+ self.InstancesTable.AppendRow(self.InstancesDefaultValue.copy())
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnDeleteInstanceButton(self, event):
+ row = self.InstancesGrid.GetGridCursorRow()
+ self.InstancesTable.RemoveRow(row)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnUpInstanceButton(self, event):
+ row = self.InstancesGrid.GetGridCursorRow()
+ self.InstancesTable.MoveRow(row, -1, self.InstancesGrid)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnDownInstanceButton(self, event):
+ row = self.InstancesGrid.GetGridCursorRow()
+ self.InstancesTable.MoveRow(row, 1, self.InstancesGrid)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnTasksGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ if self.TasksTable.GetColLabelValue(event.GetCol()) == "Name":
+ tasklist = self.TaskList.split(",")
+ for i in xrange(self.TasksTable.GetNumberRows()):
+ task = self.TasksTable.GetValueByName(i, "Name")
+ if task in tasklist:
+ tasklist.remove(task)
+ tasklist.remove("")
+ if len(tasklist) > 0:
+ old_name = tasklist[0]
+ new_name = self.TasksTable.GetValue(row, col)
+ for i in xrange(self.InstancesTable.GetNumberRows()):
+ if self.InstancesTable.GetValueByName(i, "Task") == old_name:
+ self.InstancesTable.SetValueByName(i, "Task", new_name)
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
+
+ def OnInstancesGridCellChange(self, event):
+ self.RefreshModel()
+ self.RefreshView()
+ event.Skip()
diff -r 000000000000 -r b622defdfd98 examples/example.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example.xml Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,845 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN1
+
+
+
+
+
+
+ IN2
+
+
+
+
+
+
+
+
+
+
+ FBDTest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POU qui sert a tester PLCOpenEditor.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Commentaire
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LDTest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN2
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LD Y1
+ SUB Y2 (* Substract Y2 from Y1 *)
+ ST Temp (* Store Y1-Y2 in Temp *)
+ MUL Temp (* Multiply by Temp to square *)
+ ADD( X1 (* Defer ADD *)
+ SUB X2 (* Substract X1 from X2 *)
+ ST Temp (* Store X1-X2 in Temp *)
+ MUL Temp (* Multiply by Temp to square *)
+ )
+ CAL SQRT (* Call Square root fun *)
+ ST ILTest (* Setup function result *)
+ GT TMax (* Greater than TMax ? *)
+ JMPC ERR (* Yes, Jump to Error *)
+ S ENO (* Set ENO *)
+ RET (* Normal return *)
+
+ ERR:
+ RET (* Error return, ENO not set *)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IF IN1 THEN
+ IN2 := 1;
+ ELSE
+ IN3 := 1;
+ END_IF;
+
+
+
+
+
+
+
+ := AND(IN1, IN2, IN3);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN2 AND IN3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IN5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IF Collision THEN
+ Speed := 0;
+ Brakes := ON;
+ END_IF;
+
+ IF (Gate = CLOSED) AND
+ (Pump = ON) AND (Temp > 200.0) THEN
+ Control_State := Active;
+ ELSE
+ Control_State := Hold;
+ PumpSpeed := 10.0;
+ END_IF;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
diff -r 000000000000 -r b622defdfd98 graphics/GraphicCommons.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/GraphicCommons.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1694 @@
+#!/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 math import *
+
+
+#-------------------------------------------------------------------------------
+# Common constants
+#-------------------------------------------------------------------------------
+
+"""
+Definition of constants for dimensions of graphic elements
+"""
+
+# FBD and SFC constants
+MIN_MOVE = 5 # Minimum move before starting a element dragging
+CONNECTOR_SIZE = 8 # Size of connectors
+BLOCK_LINE_SIZE = 20 # Minimum size of each line in a block
+HANDLE_SIZE = 6 # Size of the squares for handles
+ANCHOR_DISTANCE = 5 # Distance where wire is automativally attached to a connector
+POINT_RADIUS = 2 # Radius of the point of wire ends
+MIN_SEGMENT_SIZE = 2 # Minimum size of the endling segments of a wire
+
+# LD constants
+LD_LINE_SIZE = 40 # Distance between two lines in a ladder rung
+LD_ELEMENT_SIZE = (21, 15) # Size (width, height) of a ladder element (contact or coil)
+LD_WIRE_SIZE = 30 # Size of a wire between two contact
+LD_WIRECOIL_SIZE = 70 # Size of a wire between a coil and a contact
+LD_OFFSET = (10, 10) # Distance (x, y) between each comment and rung of the ladder
+LD_COMMENT_DEFAULTSIZE = (600, 40) # Size (width, height) of a comment box
+
+# SFC constants
+SFC_STEP_DEFAULT_SIZE = (40, 30) # Default size of a SFC step
+SFC_TRANSITION_SIZE = (20, 2) # Size of a SFC transition
+SFC_DEFAULT_SEQUENCE_INTERVAL = 80 # Default size of the interval between two divergence branches
+SFC_SIMULTANEOUS_SEQUENCE_EXTRA = 20 # Size of extra lines for simultaneous divergence and convergence
+SFC_JUMP_SIZE = (12, 13) # Size of a SFC jump to step
+SFC_WIRE_MIN_SIZE = 25 # Size of a wire between two elements
+SFC_ACTION_MIN_SIZE = (100, 30) # Minimum size of an action block line
+
+# Type definition constants for graphic elements
+[INPUT, OUTPUT, INOUT] = range(3)
+[CONNECTOR, CONTINUATION] = range(2)
+[LEFTRAIL, RIGHTRAIL] = range(2)
+[CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = range(4)
+[COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET] = range(4)
+[SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = range(4)
+
+# Constants for defining the type of dragging that has been selected
+[HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = range(5)
+
+# List of value for resize handle that are valid
+VALID_HANDLES = [(1,1), (1,2), (1,3), (2,3), (3,3), (3,2), (3,1), (2,1)]
+
+# Contants for defining the direction of a connector
+[EAST, NORTH, WEST, SOUTH] = [(1,0), (0,-1), (-1,0), (0,1)]
+
+# Contants for defining which mode is selected for each view
+[MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT, MODE_WIRE,
+ MODE_INITIAL_STEP] = range(7)
+
+"""
+Basic vector operations for calculate wire points
+"""
+
+# Calculate the scalar product of two vectors
+def product(v1, v2):
+ return v1[0] * v2[0] + v1[1] * v2[1]
+
+# Create a vector from two points and define if vector must be normal
+def vector(p1, p2, normal = True):
+ vector = (p2.x - p1.x, p2.y - p1.y)
+ if normal:
+ return normalize(vector)
+ return vector
+
+# Calculate the norm of a given vector
+def norm(v):
+ return sqrt(v[0] * v[0] + v[1] * v[1])
+
+# Normalize a given vector
+def normalize(v):
+ v_norm = norm(v)
+ # Verifie if it is not a null vector
+ if v_norm > 0:
+ return (v[0] / v_norm, v[1] / v_norm)
+ else:
+ return v
+
+
+"""
+Function that calculates the nearest point of the grid defined by scaling for the given point
+"""
+
+def GetScaledEventPosition(event, scaling):
+ pos = event.GetPosition()
+ if scaling:
+ pos.x = round(float(pos.x) / float(scaling[0])) * scaling[0]
+ pos.y = round(float(pos.y) / float(scaling[1])) * scaling[1]
+ return pos
+
+
+"""
+Function that choose a direction during the wire points generation
+"""
+
+def DirectionChoice(v_base, v_target, dir_target):
+ dir_product = product(v_base, v_target)
+ if dir_product < 0:
+ return (-v_base[0], -v_base[1])
+ elif dir_product == 0 and product(v_base, dir_target) != 0:
+ return dir_target
+ return v_base
+
+
+#-------------------------------------------------------------------------------
+# Viewer Rubberband
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a rubberband
+"""
+
+class RubberBand:
+
+ # Create a rubberband by indicated on which window it must be drawn
+ def __init__(self, drawingSurface):
+ self.drawingSurface = drawingSurface
+ self.Reset()
+
+ # Method that initializes the internal attributes of the rubberband
+ def Reset(self):
+ self.startPoint = None
+ self.currentBox = None
+ self.lastBox = None
+
+ # Method that return if a box is currently edited
+ def IsShown(self):
+ return self.currentBox != None
+
+ # Method that returns the currently edited box
+ def GetCurrentExtent(self):
+ return self.currentBox
+
+ # Method called when a new box starts to be edited
+ def OnLeftDown(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ # Save the point for calculate the box position and size
+ self.startPoint = pos
+ self.currentBox = wxRect(pos.x, pos.y, 0, 0)
+ self.drawingSurface.SetCursor(wxStockCursor(wxCURSOR_CROSS))
+ self.Redraw()
+
+ # Method called when dragging with a box edited
+ def OnMotion(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ # Save the last position and size of the box for erasing it
+ self.lastBox = wxRect(self.currentBox.x, self.currentBox.y, self.currentBox.width,
+ self.currentBox.height)
+ # Calculate new position and size of the box
+ if pos.x >= self.startPoint.x:
+ self.currentBox.x = self.startPoint.x
+ self.currentBox.width = pos.x - self.startPoint.x + 1
+ else:
+ self.currentBox.x = pos.x
+ self.currentBox.width = self.startPoint.x - pos.x + 1
+ if pos.y >= self.startPoint.y:
+ self.currentBox.y = self.startPoint.y
+ self.currentBox.height = pos.y - self.startPoint.y + 1
+ else:
+ self.currentBox.y = pos.y
+ self.currentBox.height = self.startPoint.y - pos.y + 1
+ self.Redraw()
+
+ # Method called when dragging is stopped
+ def OnLeftUp(self, event, scaling):
+ self.drawingSurface.SetCursor(wxNullCursor)
+ self.lastBox = self.currentBox
+ self.currentBox = None
+ self.Redraw()
+
+ # Method that erase the last box and draw the new box
+ def Redraw(self):
+ dc = wxClientDC(self.drawingSurface)
+ dc.SetPen(wxPen(wxWHITE, 1, wxDOT))
+ dc.SetBrush(wxTRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wxXOR)
+ if self.lastBox:
+ # Erase last box
+ dc.DrawRectangle(self.lastBox.x, self.lastBox.y, self.lastBox.width,
+ self.lastBox.height)
+ if self.currentBox:
+ # Draw current box
+ dc.DrawRectangle(self.currentBox.x, self.currentBox.y, self.currentBox.width,
+ self.currentBox.height)
+
+
+#-------------------------------------------------------------------------------
+# Graphic element base class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic graphic element
+"""
+
+class Graphic_Element:
+
+ # Create a new graphic element
+ def __init__(self, parent, id = None):
+ self.Parent = parent
+ self.Id = id
+ self.oldPos = None
+ self.Handle = False
+ self.Dragging = False
+ self.Selected = False
+ self.Pos = wxPoint(0, 0)
+ self.Size = wxSize(0, 0)
+ self.BoundingBox = wxRect(0, 0, 0, 0)
+
+ # Make a clone of this element
+ def Clone(self):
+ return Graphic_Element(self.Parent, self.Id)
+
+ # Changes the block position
+ def SetPosition(self, x, y):
+ self.Pos.x = x
+ self.Pos.y = y
+ self.RefreshConnected()
+ self.RefreshBoundingBox()
+
+ # Returns the block position
+ def GetPosition(self):
+ return self.Pos.x, self.Pos.y
+
+ # Changes the element size
+ def SetSize(self, width, height):
+ self.Size.SetWidth(width)
+ self.Size.SetHeight(height)
+ self.RefreshConnectors()
+ self.RefreshBoundingBox()
+
+ # Returns the element size
+ def GetSize(self):
+ return self.Size.GetWidth(), self.Size.GetHeight()
+
+ # Refresh the element Bounding Box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
+
+ # Refresh the element connectors position
+ def RefreshConnectors(self):
+ pass
+
+ # Refresh the position of wires connected to element inputs and outputs
+ def RefreshConnected(self):
+ pass
+
+ # Change the parent
+ def SetParent(self, parent):
+ self.Parent = parent
+
+ # Override this method for defining the method to call for deleting this element
+ def Delete(self):
+ pass
+
+ # Returns the Id
+ def GetId(self):
+ return self.Id
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt):
+ rect = self.BoundingBox
+ return rect.InsideXY(pt.x, pt.y)
+
+ # Override this method for refreshing the bounding box
+ def RefreshBoundingBox(self):
+ pass
+
+ # Returns the bounding box
+ def GetBoundingBox(self):
+ return self.BoundingBox
+
+ # Change the variable that indicates if this element is selected
+ def SetSelected(self, selected):
+ self.Selected = selected
+
+ # Test if the point is on a handle of this element
+ def TestHandle(self, pt):
+ # Verify that this element is selected
+ if self.Selected:
+ # Find if point is on a handle horizontally
+ if self.BoundingBox.x - HANDLE_SIZE - 2 <= pt.x < self.BoundingBox.x - 2:
+ handle_x = 1
+ elif self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2 <= pt.x < self.BoundingBox.x + (self.BoundingBox.width + HANDLE_SIZE) / 2:
+ handle_x = 2
+ elif self.BoundingBox.x + self.BoundingBox.width + 2 <= pt.x < self.BoundingBox.x + self.BoundingBox.width + HANDLE_SIZE + 2:
+ handle_x = 3
+ else:
+ handle_x = 0
+ # Find if point is on a handle vertically
+ if self.BoundingBox.y - HANDLE_SIZE - 2 <= pt.y < self.BoundingBox.y - 2:
+ handle_y = 1
+ elif self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2 <= pt.y < self.BoundingBox.y + (self.BoundingBox.height + HANDLE_SIZE) / 2:
+ handle_y = 2
+ elif self.BoundingBox.y + self.BoundingBox.height - 2 <= pt.y < self.BoundingBox.y + self.BoundingBox.height + HANDLE_SIZE + 2:
+ handle_y = 3
+ else:
+ handle_y = 0
+ # Verify that the result is valid
+ if (handle_x, handle_y) in VALID_HANDLES:
+ return handle_x, handle_y
+ return 0, 0
+
+ # Method called when a LeftDown event have been generated
+ def OnLeftDown(self, event, scaling):
+ pos = event.GetPosition()
+ # Test if an handle have been clicked
+ result = self.TestHandle(pos)
+ # Find which type of handle have been clicked,
+ # Save a resize event and change the cursor
+ if result == (1, 1) or result == (3, 3):
+ self.Handle = (HANDLE_RESIZE, result)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENWSE))
+ elif result == (1, 3) or result == (3, 1):
+ self.Handle = (HANDLE_RESIZE, result)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENESW))
+ elif result == (1, 2) or result == (3, 2):
+ self.Handle = (HANDLE_RESIZE, result)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZEWE))
+ elif result == (2, 1) or result == (2, 3):
+ self.Handle = (HANDLE_RESIZE, result)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENS))
+ # If no handle have been clicked, save a move event, and change the cursor
+ else:
+ self.Handle = (HANDLE_MOVE, None)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_HAND))
+ self.SetSelected(False)
+ # Initializes the last position
+ self.oldPos = GetScaledEventPosition(event, scaling)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, scaling):
+ # If a dragging have been initiated
+ if self.Dragging and self.oldPos:
+ # Calculate the movement of cursor and refreshes the element state
+ pos = GetScaledEventPosition(event, scaling)
+ movex = pos.x - self.oldPos.x
+ movey = pos.y - self.oldPos.y
+ self.ProcessDragging(movex, movey)
+ self.RefreshModel()
+ self.SetSelected(True)
+ self.oldPos = None
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ self.SetSelected(True)
+ self.oldPos = None
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ pass
+
+ # Method called when a Motion event have been generated
+ def OnMotion(self, event, scaling):
+ # If the cursor is dragging and the element have been clicked
+ if event.Dragging() and self.oldPos:
+ # Calculate the movement of cursor
+ pos = GetScaledEventPosition(event, scaling)
+ movex = pos.x - self.oldPos.x
+ movey = pos.y - self.oldPos.y
+ # If movement is greater than MIN_MOVE then a dragging is initiated
+ if not self.Dragging and (abs(movex) > MIN_MOVE or abs(movey) > MIN_MOVE):
+ self.Dragging = True
+ # If a dragging have been initiated, refreshes the element state
+ if self.Dragging:
+ self.ProcessDragging(movex, movey)
+ self.oldPos = pos
+ # If cursor just pass over the element, changes the cursor if it is on a handle
+ else:
+ pos = event.GetPosition()
+ handle = self.TestHandle(pos)
+ if handle == (1, 1) or handle == (3, 3):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENWSE))
+ elif handle == (1, 3) or handle == (3, 1):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENESW))
+ elif handle == (1, 2) or handle == (3, 2):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZEWE))
+ elif handle == (2, 1) or handle == (2, 3):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENS))
+ else:
+ wxCallAfter(self.Parent.SetCursor, wxNullCursor)
+
+ # Moves the element
+ def Move(self, dx, dy, exclude = []):
+ self.Pos.x += dx
+ self.Pos.y += dy
+ self.RefreshConnected(exclude)
+ self.RefreshBoundingBox()
+
+ # Resizes the element from position and size given
+ def Resize(self, x, y, width, height):
+ self.Move(x, y)
+ self.SetSize(width, height)
+
+ # Refreshes the element state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ handle_type, handle = self.Handle
+ # If it is a resize handle, calculate the values from resizing
+ if handle_type == HANDLE_RESIZE:
+ x, y = 0, 0
+ width, height = self.GetSize()
+ if handle[0] == 1:
+ x = movex
+ width -= movex
+ elif handle[0] == 3:
+ width += movex
+ if handle[1] == 1:
+ y = movey
+ height -= movey
+ elif handle[1] == 3:
+ height += movey
+ # Verify that new size is not lesser than minimum
+ min_width, min_height = self.GetMinSize()
+ if width >= min_width and height >= min_height:
+ self.Resize(x, y, width, height)
+ # If it is a move handle, Move this element
+ elif handle_type == HANDLE_MOVE:
+ self.Move(movex, movey)
+
+ # Override this method for defining the method to call for refreshing the model of this element
+ def RefreshModel(self, move=True):
+ pass
+
+ # Draws the handles of this element if it is selected
+ def Draw(self, dc):
+ if self.Selected:
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2,
+ self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2,
+ self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2,
+ self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2,
+ self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2,
+ self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE)
+ dc.SetBrush(wxWHITE_BRUSH)
+
+
+#-------------------------------------------------------------------------------
+# Group of graphic elements
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a group of graphic elements
+"""
+
+class Graphic_Group(Graphic_Element):
+
+ # Create a new group of graphic elements
+ def __init__(self, parent):
+ Graphic_Element.__init__(self, parent)
+ self.Elements = []
+ self.RefreshBoundingBox()
+
+ # Destructor
+ def __del__(self):
+ self.Elements = []
+
+ # Make a clone of this group
+ def Clone(self):
+ clone = Graphic_Group(self.Parent)
+ elements = []
+ # Makes a clone of all the elements in this group
+ for element in self.Elements:
+ elements.append(element.Clone())
+ clone.SetElements(elements)
+ return clone
+
+ # Clean this group of elements
+ def Clean(self):
+ # Clean all the elements of the group
+ for element in self.Elements:
+ element.Clean()
+
+ # Delete this group of elements
+ def Delete(self):
+ # Delete all the elements of the group
+ for element in self.Elements:
+ element.Delete()
+
+ # Returns if the point given is in the bounding box of one of the elements of this group
+ def HitTest(self, pt):
+ result = False
+ for element in self.Elements:
+ result |= element.HitTest(pt)
+ return result
+
+ # Returns if the element given is in this group
+ def IsElementIn(self, element):
+ return element in self.Elements
+
+ # Change the elements of the group
+ def SetElements(self, elements):
+ self.Elements = elements
+ self.RefreshBoundingBox()
+
+ # Returns the elements of the group
+ def GetElements(self):
+ return self.Elements
+
+ # Remove or select the given element if it is or not in the group
+ def SelectElement(self, element):
+ if element in self.Elements:
+ self.Elements.remove(element)
+ else:
+ self.Elements.append(element)
+ self.RefreshBoundingBox()
+
+ # Move this group of elements
+ def Move(self, movex, movey):
+ exclude = []
+ for element in self.Elements:
+ if isinstance(element, Wire):
+ exclude.append(element)
+ # Move all the elements of the group
+ for element in self.Elements:
+ if isinstance(element, Wire):
+ element.Move(movex, movey, True)
+ else:
+ element.Move(movex, movey, exclude)
+ self.RefreshBoundingBox()
+
+ # Refreshes the bounding box of this group of elements
+ def RefreshBoundingBox(self):
+ if len(self.Elements) > 0:
+ bbox = self.Elements[0].GetBoundingBox()
+ minx, miny = bbox.x, bbox.y
+ maxx = bbox.x + bbox.width
+ maxy = bbox.y + bbox.height
+ for element in self.Elements[1:]:
+ bbox = element.GetBoundingBox()
+ minx = min(minx, bbox.x)
+ miny = min(miny, bbox.y)
+ maxx = max(maxx, bbox.x + bbox.width)
+ maxy = max(maxy, bbox.y + bbox.height)
+ self.BoundingBox = wxRect(minx, miny, maxx - minx, maxy - miny)
+ else:
+ self.BoundingBox = wxRect(0, 0, 0, 0)
+
+ # Forbids to change the group position
+ def SetPosition(x, y):
+ pass
+
+ # Returns the position of this group
+ def GetPosition(self):
+ return self.BoundingBox.x, self.BoundingBox.y
+
+ # Forbids to change the group size
+ def SetSize(width, height):
+ pass
+
+ # Returns the size of this group
+ def GetSize(self):
+ return self.BoundingBox.width, self.BoundingBox.height
+
+ # Change the variable that indicates if the elemente is selected
+ def SetSelected(self, selected):
+ for element in self.Elements:
+ element.SetSelected(selected)
+
+ # Refreshes the model of all the elements of this group
+ def RefreshModel(self):
+ for element in self.Elements:
+ element.RefreshModel()
+
+
+#-------------------------------------------------------------------------------
+# Connector for all types of blocks
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a connector for any type of block
+"""
+
+class Connector:
+
+ # Create a new connector
+ def __init__(self, parent, name, type, position, direction, negated = False, edge = "none"):
+ self.ParentBlock = parent
+ self.Name = name
+ self.Type = type
+ self.Pos = position
+ self.Direction = direction
+ self.Wires = []
+ self.Negated = negated
+ self.Edge = edge
+ self.Pen = wxBLACK_PEN
+
+ # Change the connector pen
+ def SetPen(self, pen):
+ self.Pen = pen
+
+ # Make a clone of the connector
+ def Clone(self):
+ return Connector(self.Parent, self.Name, self.Type, wxPoint(self.Pos[0], self.Pos[1]),
+ self.Direction, self.Negated)
+
+ # Returns the connector parent block
+ def GetParentBlock(self):
+ return self.ParentBlock
+
+ # Returns the connector name
+ def GetName(self):
+ return self.Name
+
+ # Changes the connector name
+ def SetName(self, name):
+ self.Name = name
+
+ # Returns the wires connected to the connector
+ def GetWires(self):
+ return self.Wires
+
+ # Returns the parent block Id
+ def GetBlockId(self):
+ return self.ParentBlock.GetId()
+
+ # Returns the connector relative position
+ def GetRelPosition(self):
+ return self.Pos
+
+ # Returns the connector absolute position
+ def GetPosition(self, size = True):
+ parent_pos = self.ParentBlock.GetPosition()
+ # If the position of the end of the connector is asked
+ if size:
+ x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
+ y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
+ else:
+ x = parent_pos[0] + self.Pos.x
+ y = parent_pos[1] + self.Pos.y
+ return wxPoint(x, y)
+
+ # Change the connector relative position
+ def SetPosition(self, pos):
+ self.Pos = pos
+
+ # Returns the connector direction
+ def GetDirection(self):
+ return self.Direction
+
+ # Change the connector direction
+ def SetDirection(self, direction):
+ self.Direction = direction
+
+ # Connect a wire to this connector at the last place
+ def Connect(self, wire, refresh = True):
+ self.InsertConnect(len(self.Wires), wire, refresh)
+
+ # Connect a wire to this connector at the place given
+ def InsertConnect(self, idx, wire, refresh = True):
+ if wire not in self.Wires:
+ self.Wires.insert(idx, wire)
+ if refresh:
+ self.ParentBlock.RefreshModel(False)
+
+ # Returns the index of the wire given in the list of connected
+ def GetWireIndex(self, wire):
+ for i, (tmp_wire, handle) in enumerate(self.Wires):
+ if tmp_wire == wire:
+ return i
+ return None
+
+ # Unconnect a wire or all wires connected to the connector
+ def UnConnect(self, wire = None, unconnect = True):
+ i = 0
+ found = False
+ while i < len(self.Wires) and not found:
+ if not wire or self.Wires[i][0] == wire:
+ # If Unconnect haven't been called from a wire, disconnect the connector in the wire
+ if unconnect:
+ if self.Wires[i][1] == 0:
+ self.Wires[i][0].UnConnectStartPoint()
+ else:
+ self.Wires[i][0].UnConnectEndPoint()
+ # Remove wire from connected
+ if wire:
+ self.Wires.pop(i)
+ found = True
+ i += 1
+ # If no wire defined, unconnect all wires
+ if not wire:
+ self.Wires = []
+ self.ParentBlock.RefreshModel(False)
+
+ # Returns if connector has one or more wire connected
+ def IsConnected(self):
+ return len(self.Wires) > 0
+
+ # Move the wires connected
+ def MoveConnected(self, exclude = []):
+ if len(self.Wires) > 0:
+ # Calculate the new position of the end point
+ parent_pos = self.ParentBlock.GetPosition()
+ x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
+ y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
+ # Move the corresponding point on all the wires connected
+ for wire, index in self.Wires:
+ if wire not in exclude:
+ if index == 0:
+ wire.MoveStartPoint(wxPoint(x, y))
+ else:
+ wire.MoveEndPoint(wxPoint(x, y))
+
+ # Refreshes the model of all the wires connected
+ def RefreshWires(self):
+ for wire in self.Wires:
+ wire[0].RefreshModel()
+
+ # Refreshes the parent block model
+ def RefreshParentBlock(self):
+ self.ParentBlock.RefreshModel(False)
+
+ # Returns the connector negated property
+ def IsNegated(self):
+ return self.Negated
+
+ # Changes the connector negated property
+ def SetNegated(self, negated):
+ self.Negated = negated
+ self.Edge = "none"
+
+ # Returns the connector edge property
+ def GetEdge(self):
+ return self.Edge
+
+ # Changes the connector edge property
+ def SetEdge(self, edge):
+ self.Edge = edge
+ self.Negated = False
+
+ # Tests if the point given is near from the end point of this connector
+ def TestPoint(self, pt, exclude = True):
+ parent_pos = self.ParentBlock.GetPosition()
+ if not (len(self.Wires) > 0 and self.Direction == WEST and exclude):
+ # Calculate a square around the end point of this connector
+ x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE - ANCHOR_DISTANCE
+ y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE - ANCHOR_DISTANCE
+ width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
+ height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
+ rect = wxRect(x, y, width, height)
+ return rect.InsideXY(pt.x, pt.y)
+ return False
+
+ # Draws the connector
+ def Draw(self, dc):
+ dc.SetPen(self.Pen)
+ dc.SetBrush(wxWHITE_BRUSH)
+ parent_pos = self.ParentBlock.GetPosition()
+ if self.Negated:
+ # If connector is negated, draw a circle
+ xcenter = parent_pos[0] + self.Pos.x + (CONNECTOR_SIZE * self.Direction[0]) / 2
+ ycenter = parent_pos[1] + self.Pos.y + (CONNECTOR_SIZE * self.Direction[1]) / 2
+ dc.DrawCircle(xcenter, ycenter, CONNECTOR_SIZE / 2)
+ else:
+ xstart = parent_pos[0] + self.Pos.x
+ ystart = parent_pos[1] + self.Pos.y
+ if self.Edge == "rising":
+ # If connector has a rising edge, draw a right arrow
+ dc.DrawLine(xstart, ystart, xstart - 4, ystart - 4)
+ dc.DrawLine(xstart, ystart, xstart - 4, ystart + 4)
+ elif self.Edge == "falling":
+ # If connector has a falling edge, draw a left arrow
+ dc.DrawLine(xstart, ystart, xstart + 4, ystart - 4)
+ dc.DrawLine(xstart, ystart, xstart + 4, ystart + 4)
+ xend = xstart + CONNECTOR_SIZE * self.Direction[0]
+ yend = ystart + CONNECTOR_SIZE * self.Direction[1]
+ dc.DrawLine(xstart + self.Direction[0], ystart + self.Direction[1], xend, yend)
+ # Calculate the position of the text
+ text_size = dc.GetTextExtent(self.Name)
+ if self.Direction[0] != 0:
+ ytext = parent_pos[1] + self.Pos.y - text_size[1] / 2
+ if self.Direction[0] < 0:
+ xtext = parent_pos[0] + self.Pos.x + 5
+ else:
+ xtext = parent_pos[0] + self.Pos.x - (text_size[0] + 5)
+ if self.Direction[1] != 0:
+ xtext = parent_pos[0] + self.Pos.x - text_size[0] / 2
+ if self.Direction[1] < 0:
+ ytext = parent_pos[1] + self.Pos.y + 5
+ else:
+ ytext = parent_pos[1] + self.Pos.y - (text_size[1] + 5)
+ # Draw the text
+ dc.DrawText(self.Name, xtext, ytext)
+
+
+#-------------------------------------------------------------------------------
+# Common Wire Element
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a wire for connecting two blocks
+"""
+
+class Wire(Graphic_Element):
+
+ # Create a new wire
+ def __init__(self, parent, start = None, end = None):
+ Graphic_Element.__init__(self, parent)
+ self.StartPoint = start
+ self.EndPoint = end
+ self.StartConnected = None
+ self.EndConnected = None
+ # If the start and end points are defined, calculate the wire
+ if start and end:
+ self.ResetPoints()
+ self.GeneratePoints()
+ else:
+ self.Points = []
+ self.Segments = []
+ self.SelectedSegment = None
+ self.OverStart = False
+ self.OverEnd = False
+
+ # Destructor of a wire
+ def __del__(self):
+ self.StartConnected = None
+ self.EndConnected = None
+
+ # Forbids to change the wire position
+ def SetPosition(x, y):
+ pass
+
+ # Forbids to change the wire size
+ def SetSize(width, height):
+ pass
+
+ # Unconnect the start and end points
+ def Clean(self):
+ if self.StartConnected:
+ self.UnConnectStartPoint()
+ if self.EndConnected:
+ self.UnConnectEndPoint()
+
+ # Delete this wire by calling the corresponding method
+ def Delete(self):
+ self.Parent.DeleteWire(self)
+
+ # Select a segment and not the whole wire. It's useful for Ladder Diagram
+ def SetSelectedSegment(self, segment):
+ # The last segment is indicated
+ if segment == -1:
+ segment = len(self.Segments) - 1
+ # The selected segment is reinitialised
+ if segment == None:
+ if self.StartConnected:
+ self.StartConnected.SetPen(wxBLACK_PEN)
+ if self.EndConnected:
+ self.EndConnected.SetPen(wxBLACK_PEN)
+ # The segment selected is the first
+ elif segment == 0:
+ if self.StartConnected:
+ self.StartConnected.SetPen(wxRED_PEN)
+ if self.EndConnected:
+ # There is only one segment
+ if len(self.Segments) == 1:
+ self.EndConnected.SetPen(wxRED_PEN)
+ else:
+ self.EndConnected.SetPen(wxBLACK_PEN)
+ # The segment selected is the last
+ elif segment == len(self.Segments) - 1:
+ if self.StartConnected:
+ self.StartConnected.SetPen(wxBLACK_PEN)
+ if self.EndConnected:
+ self.EndConnected.SetPen(wxRED_PEN)
+ self.SelectedSegment = segment
+
+ # Reinitialize the wire points
+ def ResetPoints(self):
+ if self.StartPoint and self.EndPoint:
+ self.Points = [self.StartPoint[0], self.EndPoint[0]]
+ self.Segments = [self.StartPoint[1]]
+ else:
+ self.Points = []
+ self.Segments = []
+
+ # Refresh the wire bounding box
+ def RefreshBoundingBox(self):
+ if len(self.Points) > 0:
+ # If startpoint or endpoint is connected, save the point radius
+ start_radius = end_radius = 0
+ if not self.StartConnected:
+ start_radius = POINT_RADIUS
+ if not self.EndConnected:
+ end_radius = POINT_RADIUS
+ # Initialize minimum and maximum from the first point
+ minx, minbbxx = self.Points[0].x, self.Points[0].x - start_radius
+ maxx, maxbbxx = self.Points[0].x, self.Points[0].x + start_radius
+ miny, minbbxy = self.Points[0].y, self.Points[0].y - start_radius
+ maxy, maxbbxy = self.Points[0].y, self.Points[0].y + start_radius
+ # Actualize minimum and maximum with the other points
+ for point in self.Points[1:-1]:
+ minx, minbbxx = min(minx, point.x), min(minbbxx, point.x)
+ maxx, maxbbxx = max(maxx, point.x), max(maxbbxx, point.x)
+ miny, minbbxy = min(miny, point.y), min(minbbxy, point.y)
+ maxy, maxbbxy = max(maxy, point.y), max(maxbbxy, point.y)
+ if len(self.Points) > 1:
+ minx, minbbxx = min(minx, self.Points[-1].x), min(minbbxx, self.Points[-1].x - end_radius)
+ maxx, maxbbxx = max(maxx, self.Points[-1].x), max(maxbbxx, self.Points[-1].x + end_radius)
+ miny, minbbxy = min(miny, self.Points[-1].y), min(minbbxy, self.Points[-1].y - end_radius)
+ maxy, maxbbxy = max(maxy, self.Points[-1].y), max(maxbbxy, self.Points[-1].y + end_radius)
+ self.Pos = wxPoint(minx, miny)
+ self.Size = wxSize(maxx -minx + 1, maxy - miny + 1)
+ self.BoundingBox = wxRect(minbbxx, minbbxy, maxbbxx - minbbxx + 1, maxbbxy - minbbxy + 1)
+
+ # Refresh the realpoints that permits to keep the proportionality in wire during resizing
+ def RefreshRealPoints(self):
+ if len(self.Points) > 0:
+ self.RealPoints = []
+ # Calculate float relative position of each point with the minimum point
+ for point in self.Points:
+ self.RealPoints.append([float(point.x - self.Pos.x), float(point.y - self.Pos.y)])
+
+ # Returns the wire minimum size
+ def GetMinSize(self):
+ width = 1
+ height = 1
+ dir_product = product(self.StartPoint[1], self.EndPoint[1])
+ # The directions are opposed
+ if dir_product < 0:
+ if self.StartPoint[0] != 0:
+ width = MIN_SEGMENT_SIZE * 2
+ if self.StartPoint[1] != 0:
+ height = MIN_SEGMENT_SIZE * 2
+ # The directions are the same
+ elif dir_product > 0:
+ if self.StartPoint[0] != 0:
+ width = MIN_SEGMENT_SIZE
+ if self.StartPoint[1] != 0:
+ height = MIN_SEGMENT_SIZE
+ # The directions are perpendiculars
+ else:
+ width = MIN_SEGMENT_SIZE
+ height = MIN_SEGMENT_SIZE
+ return width + 1, height + 1
+
+ # Returns if the point given is on one of the wire segments
+ def HitTest(self, pt):
+ test = False
+ for i in xrange(len(self.Points) - 1):
+ rect = wxRect(0, 0, 0, 0)
+ x1, y1 = self.Points[i].x, self.Points[i].y
+ x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
+ # Calculate a rectangle around the segment
+ rect = wxRect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
+ abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
+ test |= rect.InsideXY(pt.x, pt.y)
+ return test
+
+ # Returns the wire start or end point if the point given is on one of them
+ def TestPoint(self, pt):
+ # Test the wire start point
+ rect = wxRect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
+ 2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
+ if rect.InsideXY(pt.x, pt.y):
+ return 0
+ # Test the wire end point
+ if len(self.Points) > 1:
+ rect = wxRect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
+ 2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
+ if rect.InsideXY(pt.x, pt.y):
+ return -1
+ return None
+
+ # Returns the wire segment if the point given is on it
+ def TestSegment(self, pt, all=False):
+ for i in xrange(len(self.Segments)):
+ # If wire is not in a Ladder Diagram, first and last segments are excluded
+ if 0 < i < len(self.Segments) - 1 or all:
+ x1, y1 = self.Points[i].x, self.Points[i].y
+ x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
+ # Calculate a rectangle around the segment
+ rect = wxRect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
+ abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
+ if rect.InsideXY(pt.x, pt.y):
+ return i, self.Segments[i]
+ return None
+
+ # Define the wire points
+ def SetPoints(self, points):
+ if len(points) > 1:
+ self.Points = [wxPoint(x, y) for x, y in points]
+ # Calculate the start and end directions
+ self.StartPoint = [None, vector(self.Points[0], self.Points[1])]
+ self.EndPoint = [None, vector(self.Points[-1], self.Points[-2])]
+ # Calculate the start and end points
+ self.StartPoint[0] = wxPoint(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0],
+ self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
+ self.EndPoint[0] = wxPoint(self.Points[-1].x + CONNECTOR_SIZE * self.EndPoint[1][0],
+ self.Points[-1].y + CONNECTOR_SIZE * self.EndPoint[1][1])
+ self.Points[0] = self.StartPoint[0]
+ self.Points[-1] = self.EndPoint[0]
+ # Calculate the segments directions
+ self.Segments = []
+ for i in xrange(len(self.Points) - 1):
+ self.Segments.append(vector(self.Points[i], self.Points[i + 1]))
+ self.RefreshBoundingBox()
+ self.RefreshRealPoints()
+
+ # Returns the position of the point indicated
+ def GetPoint(self, index):
+ if index < len(self.Points):
+ return self.Points[index].x, self.Points[index].y
+ return None
+
+ # Returns a list of the position of all wire points
+ def GetPoints(self, invert = False):
+ points = self.VerifyPoints()
+ points[0] = wxPoint(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0],
+ points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
+ points[-1] = wxPoint(points[-1].x - CONNECTOR_SIZE * self.EndPoint[1][0],
+ points[-1].y - CONNECTOR_SIZE * self.EndPoint[1][1])
+ # An inversion of the list is asked
+ if invert:
+ points.reverse()
+ return points
+
+ # Returns the position of the two selected segment points
+ def GetSelectedSegmentPoints(self):
+ if self.SelectedSegment != None and len(self.Points) > 1:
+ return self.Points[self.SelectedSegment:self.SelectedSegment + 2]
+ return []
+
+ # Returns if the selected segment is the first and/or the last of the wire
+ def GetSelectedSegmentConnections(self):
+ if self.SelectedSegment != None and len(self.Points) > 1:
+ return self.SelectedSegment == 0, self.SelectedSegment == len(self.Segments) - 1
+ return (True, True)
+
+ # Returns the connectors on which the wire is connected
+ def GetConnected(self):
+ connected = []
+ if self.StartConnected and self.StartPoint[1] == WEST:
+ connected.append(self.StartConnected)
+ if self.EndConnected and self.EndPoint[1] == WEST:
+ connected.append(self.EndConnected)
+ return connected
+
+ # Returns the id of the block connected to the first or the last wire point
+ def GetConnectedId(self, index):
+ if index == 0 and self.StartConnected:
+ return self.StartConnected.GetBlockId()
+ elif index == -1 and self.EndConnected:
+ return self.EndConnected.GetBlockId()
+ return None
+
+ # Update the wire points position by keeping at most possible the current positions
+ def GeneratePoints(self, realpoints = True):
+ i = 0
+ # Calculate the start enad end points with the minimum segment size in the right direction
+ end = wxPoint(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
+ self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
+ start = wxPoint(self.StartPoint[0].x + self.StartPoint[1][0] * MIN_SEGMENT_SIZE,
+ self.StartPoint[0].y + self.StartPoint[1][1] * MIN_SEGMENT_SIZE)
+ # Evaluate the point till it's the last
+ while i < len(self.Points) - 1:
+ # The next point is the last
+ if i + 1 == len(self.Points) - 1:
+ # Calculate the direction from current point to end point
+ v_end = vector(self.Points[i], end)
+ # The current point is the first
+ if i == 0:
+ # If the end point is not in the start direction, a point is added
+ if v_end != self.Segments[0] or v_end == self.EndPoint[1]:
+ self.Points.insert(1, wxPoint(start.x, start.y))
+ self.Segments.insert(1, DirectionChoice((self.Segments[0][1],
+ self.Segments[0][0]), v_end, self.EndPoint[1]))
+ # The current point is the second
+ elif i == 1:
+ # The previous direction and the target direction are mainly opposed, a point is added
+ if product(v_end, self.Segments[0]) < 0:
+ self.Points.insert(2, wxPoint(self.Points[1].x, self.Points[1].y))
+ self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
+ self.Segments[1][0]), v_end, self.EndPoint[1]))
+ # The previous direction and the end direction are the same or they are
+ # perpendiculars and the end direction points towards current segment
+ elif product(self.Segments[0], self.EndPoint[1]) >= 0 and product(self.Segments[1], self.EndPoint[1]) <= 0:
+ # Current point and end point are aligned
+ if self.Segments[0][0] != 0:
+ self.Points[1].x = end.x
+ if self.Segments[0][1] != 0:
+ self.Points[1].y = end.y
+ # If the previous direction and the end direction are the same, a point is added
+ if product(self.Segments[0], self.EndPoint[1]) > 0:
+ self.Points.insert(2, wxPoint(self.Points[1].x, self.Points[1].y))
+ self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
+ self.Segments[1][0]), v_end, self.EndPoint[1]))
+ else:
+ # Current point is positioned in the middle of start point
+ # and end point on the current direction and a point is added
+ if self.Segments[0][0] != 0:
+ self.Points[1].x = (end.x + start.x) / 2
+ if self.Segments[0][1] != 0:
+ self.Points[1].y = (end.y + start.y) / 2
+ self.Points.insert(2, wxPoint(self.Points[1].x, self.Points[1].y))
+ self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
+ self.Segments[1][0]), v_end, self.EndPoint[1]))
+ else:
+ # The previous direction and the end direction are perpendiculars
+ if product(self.Segments[i - 1], self.EndPoint[1]) == 0:
+ # The target direction and the end direction aren't mainly the same
+ if product(v_end, self.EndPoint[1]) <= 0:
+ # Current point and end point are aligned
+ if self.Segments[i - 1][0] != 0:
+ self.Points[i].x = end.x
+ if self.Segments[i - 1][1] != 0:
+ self.Points[i].y = end.y
+ # Previous direction is updated from the new point
+ if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
+ self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
+ else:
+ test = True
+ # If the current point is the third, test if the second
+ # point can be aligned with the end point
+ if i == 2:
+ test_point = wxPoint(self.Points[1].x, self.Points[1].y)
+ if self.Segments[1][0] != 0:
+ test_point.y = end.y
+ if self.Segments[1][1] != 0:
+ test_point.x = end.x
+ test = norm(vector(self.Points[0], test_point, False)) > MIN_SEGMENT_SIZE
+ # The previous point can be aligned
+ if test:
+ self.Points[i].x, self.Points[i].y = end.x, end.y
+ if self.Segments[i - 1][0] != 0:
+ self.Points[i - 1].y = end.y
+ if self.Segments[i - 1][1] != 0:
+ self.Points[i - 1].x = end.x
+ self.Segments[i] = (-self.EndPoint[1][0], -self.EndPoint[1][1])
+ else:
+ # Current point is positioned in the middle of previous point
+ # and end point on the current direction and a point is added
+ if self.Segments[1][0] != 0:
+ self.Points[2].x = (self.Points[1].x + end.x) / 2
+ if self.Segments[1][1] != 0:
+ self.Points[2].y = (self.Points[1].y + end.y) / 2
+ self.Points.insert(3, wxPoint(self.Points[2].x, self.Points[2].y))
+ self.Segments.insert(3, DirectionChoice((self.Segments[2][1],
+ self.Segments[2][0]), v_end, self.EndPoint[1]))
+ else:
+ # Current point is aligned with end point
+ if self.Segments[i - 1][0] != 0:
+ self.Points[i].x = end.x
+ if self.Segments[i - 1][1] != 0:
+ self.Points[i].y = end.y
+ # Previous direction is updated from the new point
+ if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
+ self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
+ # If previous direction and end direction are opposed
+ if product(self.Segments[i - 1], self.EndPoint[1]) < 0:
+ # Current point is positioned in the middle of previous point
+ # and end point on the current direction
+ if self.Segments[i - 1][0] != 0:
+ self.Points[i].x = (end.x + self.Points[i - 1].x) / 2
+ if self.Segments[i - 1][1] != 0:
+ self.Points[i].y = (end.y + self.Points[i - 1].y) / 2
+ # A point is added
+ self.Points.insert(i + 1, wxPoint(self.Points[i].x, self.Points[i].y))
+ self.Segments.insert(i + 1, DirectionChoice((self.Segments[i][1],
+ self.Segments[i][0]), v_end, self.EndPoint[1]))
+ else:
+ # Current point is the first, and second is not mainly in the first direction
+ if i == 0 and product(vector(start, self.Points[1]), self.Segments[0]) < 0:
+ # If first and second directions aren't perpendiculars, a point is added
+ if product(self.Segments[0], self.Segments[1]) != 0:
+ self.Points.insert(1, wxPoint(start.x, start.y))
+ self.Segments.insert(1, DirectionChoice((self.Segments[0][1],
+ self.Segments[0][0]), vector(start, self.Points[1]), self.Segments[1]))
+ else:
+ self.Points[1].x, self.Points[1].y = start.x, start.y
+ else:
+ # Next point is aligned with current point
+ if self.Segments[i][0] != 0:
+ self.Points[i + 1].y = self.Points[i].y
+ if self.Segments[i][1] != 0:
+ self.Points[i + 1].x = self.Points[i].x
+ # Current direction is updated from the new point
+ if product(vector(self.Points[i], self.Points[i + 1]), self.Segments[i]) < 0:
+ self.Segments[i] = (-self.Segments[i][0], -self.Segments[i][1])
+ i += 1
+ self.RefreshBoundingBox()
+ if realpoints:
+ self.RefreshRealPoints()
+
+ # Verify that two consecutive points haven't the same position
+ def VerifyPoints(self):
+ points = [point for point in self.Points]
+ segments = [segment for segment in self.Segments]
+ i = 1
+ while i < len(points) - 1:
+ if points[i] == points[i + 1] and segments[i - 1] == segments[i + 1]:
+ for j in xrange(2):
+ points.pop(i)
+ segments.pop(i)
+ else:
+ i += 1
+ # If the wire isn't in a Ladder Diagram, save the new point list
+ if self.Parent.__class__.__name__ != "LD_Viewer":
+ self.Points = [point for point in points]
+ self.Segments = [segment for segment in segments]
+ self.RefreshBoundingBox()
+ self.RefreshRealPoints()
+ return points
+
+ # Moves all the wire points except the first and the last if they are connected
+ def Move(self, dx, dy, endpoints = False):
+ for i, point in enumerate(self.Points):
+ if endpoints or not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
+ point.x += dx
+ point.y += dy
+ self.StartPoint[0] = self.Points[0]
+ self.EndPoint[0] = self.Points[-1]
+ self.GeneratePoints()
+
+ # Resize the wire from position and size given
+ def Resize(self, x, y, width, height):
+ if len(self.Points) > 1:
+ # Calculate the new position of each point for testing the new size
+ minx, miny = self.Pos.x, self.Pos.y
+ lastwidth, lastheight = self.Size.width, self.Size.height
+ for i, point in enumerate(self.RealPoints):
+ # If start or end point is connected, it's not calculate
+ if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
+ if i == 0:
+ dir = self.StartPoint[1]
+ elif i == len(self.Points) - 1:
+ dir = self.EndPoint[1]
+ else:
+ dir = (0, 0)
+ pointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0] * (width - 1) / float(lastwidth - 1))),
+ width - dir[0] * MIN_SEGMENT_SIZE - 1))
+ pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * (height - 1) / float(lastheight - 1))),
+ height - dir[1] * MIN_SEGMENT_SIZE - 1))
+ self.Points[i] = wxPoint(minx + x + pointx, miny + y + pointy)
+ self.StartPoint[0] = self.Points[0]
+ self.EndPoint[0] = self.Points[-1]
+ self.GeneratePoints(False)
+ # Test if the wire position or size have changed
+ if x != 0 and minx == self.Pos.x:
+ x = 0
+ width = lastwidth
+ if y != 0 and miny == self.Pos.y:
+ y = 0
+ height = lastwidth
+ if width != lastwidth and lastwidth == self.Size.width:
+ width = lastwidth
+ if height != lastheight and lastheight == self.Size.height:
+ height = lastheight
+ # Calculate the real points from the new size, it's important for
+ # keeping a proportionality in the points position with the size
+ # duringa resize dragging
+ for i, point in enumerate(self.RealPoints):
+ if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
+ point[0] = point[0] * (width - 1) / float(lastwidth - 1)
+ point[1] = point[1] * (height - 1) / float(lastheight - 1)
+ # Calculate the correct position of the points from real points
+ for i, point in enumerate(self.RealPoints):
+ if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
+ if i == 0:
+ dir = self.StartPoint[1]
+ elif i == len(self.Points) - 1:
+ dir = self.EndPoint[1]
+ else:
+ dir = (0, 0)
+ realpointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0])),
+ width - dir[0] * MIN_SEGMENT_SIZE - 1))
+ realpointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1])),
+ height - dir[1] * MIN_SEGMENT_SIZE - 1))
+ self.Points[i] = wxPoint(minx + x + realpointx, miny + y + realpointy)
+ self.StartPoint[0] = self.Points[0]
+ self.EndPoint[0] = self.Points[-1]
+ self.GeneratePoints(False)
+
+ # Moves the wire start point and update the wire points
+ def MoveStartPoint(self, point):
+ if len(self.Points) > 1:
+ self.StartPoint[0] = point
+ self.Points[0] = point
+ self.GeneratePoints()
+
+ # Changes the wire start direction and update the wire points
+ def SetStartPointDirection(self, dir):
+ if len(self.Points) > 1:
+ self.StartPoint[1] = dir
+ self.Segments[0] = dir
+ self.GeneratePoints()
+
+ # Rotates the wire start direction by an angle of 90 degrees anticlockwise
+ def RotateStartPoint(self):
+ self.SetStartPointDirection((self.StartPoint[1][1], -self.StartPoint[1][0]))
+
+ # Connects wire start point to the connector given and moves wire start point
+ # to given point
+ def ConnectStartPoint(self, point, connector):
+ if point:
+ self.MoveStartPoint(point)
+ self.StartConnected = connector
+
+ # Unconnects wire start point
+ def UnConnectStartPoint(self):
+ self.StartConnected.UnConnect(self, False)
+ self.StartConnected = None
+
+ # Moves the wire end point and update the wire points
+ def MoveEndPoint(self, point):
+ if len(self.Points) > 1:
+ self.EndPoint[0] = point
+ self.Points[-1] = point
+ self.GeneratePoints()
+
+ # Changes the wire end direction and update the wire points
+ def SetEndPointDirection(self, dir):
+ if len(self.Points) > 1:
+ self.EndPoint[1] = dir
+ self.GeneratePoints()
+
+ # Rotates the wire end direction by an angle of 90 degrees anticlockwise
+ def RotateEndPoint(self):
+ self.SetEndPointDirection((self.EndPoint[1][1], -self.EndPoint[1][0]))
+
+ # Connects wire end point to the connector given and moves wire end point
+ # to given point
+ def ConnectEndPoint(self, point, connector):
+ if point:
+ self.MoveEndPoint(point)
+ self.EndConnected = connector
+
+ # Unconnects wire end point
+ def UnConnectEndPoint(self):
+ self.EndConnected.UnConnect(self, False)
+ self.EndConnected = None
+
+ # Moves the wire segment given by its index
+ def MoveSegment(self, idx, movex, movey):
+ if 0 < idx < len(self.Segments) - 1:
+ if self.Segments[idx] in (NORTH, SOUTH):
+ self.Points[idx].x += movex
+ self.Points[idx + 1].x += movex
+ elif self.Segments[idx] in (EAST, WEST):
+ self.Points[idx].y += movey
+ self.Points[idx + 1].y += movey
+ self.GeneratePoints()
+
+ # Adds two points in the middle of the handled segment
+ def AddSegment(self):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_SEGMENT:
+ segment, dir = handle
+ pointx = self.Points[segment].x
+ pointy = self.Points[segment].y
+ if dir[0] != 0:
+ pointx = (self.Points[segment].x + self.Points[segment + 1].x) / 2
+ if dir[1] != 0:
+ pointy = (self.Points[segment].y + self.Points[segment + 1].y) / 2
+ self.Points.insert(segment + 1, wxPoint(pointx, pointy))
+ self.Segments.insert(segment + 1, (dir[1], dir[0]))
+ self.Points.insert(segment + 2, wxPoint(pointx, pointy))
+ self.Segments.insert(segment + 2, dir)
+ self.GeneratePoints()
+
+ # Delete the handled segment by removing the two segment points
+ def DeleteSegment(self):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_SEGMENT:
+ segment, dir = handle
+ for i in xrange(2):
+ self.Points.pop(segment)
+ self.Segments.pop(segment)
+ self.GeneratePoints()
+ self.RefreshModel()
+
+ # Method called when a LeftDown event have been generated
+ def OnLeftDown(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ # Test if a point have been handled
+ result = self.TestPoint(pos)
+ if result != None:
+ self.Handle = (HANDLE_POINT, result)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_HAND))
+ else:
+ # Test if a segment have been handled
+ result = self.TestSegment(pos)
+ if result != None:
+ if result[1] in (NORTH, SOUTH):
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZEWE))
+ elif result[1] in (EAST, WEST):
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENS))
+ self.Handle = (HANDLE_SEGMENT, result)
+ # Execute the default method for a graphic element
+ else:
+ Graphic_Element.OnLeftDown(self, event, scaling)
+ self.oldPos = pos
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ # Test if a segment has been handled
+ result = self.TestSegment(pos)
+ if result != None:
+ self.Handle = (HANDLE_SEGMENT, result)
+ # Popup the menu with special items for a wire
+ self.Parent.PopupWireMenu()
+ else:
+ # Execute the default method for a graphic element
+ Graphic_Element.OnRightUp(self, event, scaling)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ self.ResetPoints()
+ self.GeneratePoints()
+
+ # Method called when a Motion event have been generated
+ def OnMotion(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ if not event.Dragging():
+ # Test if a segment has been handled
+ result = self.TestSegment(pos)
+ if result:
+ if result[1] in (NORTH, SOUTH):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZEWE))
+ elif result[1] in (EAST, WEST):
+ wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENS))
+ else:
+ # Test if a point has been handled
+ result = self.TestPoint(pos)
+ if result != None:
+ if result == 0 and self.StartConnected:
+ self.OverStart = True
+ elif result != 0 and self.EndConnected:
+ self.OverEnd = True
+ else:
+ self.OverStart = False
+ self.OverEnd = False
+ # Execute the default method for a graphic element
+ Graphic_Element.OnMotion(self, event, scaling)
+ else:
+ # Execute the default method for a graphic element
+ Graphic_Element.OnMotion(self, event, scaling)
+
+ # Refreshes the wire state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ handle_type, handle = self.Handle
+ # A point has been handled
+ if handle_type == HANDLE_POINT:
+ # Try to connect point to a connector
+ new_pos = wxPoint(self.Points[handle].x + movex, self.Points[handle].y + movey)
+ connector = self.Parent.FindBlockConnector(new_pos)
+ if connector:
+ if handle == 0 and self.EndConnected != connector:
+ connector.Connect((self, handle))
+ self.SetStartPointDirection(connector.GetDirection())
+ self.ConnectStartPoint(connector.GetPosition(), connector)
+ self.Dragging = False
+ elif handle != 0 and self.StartConnected != connector:
+ connector.Connect((self, handle))
+ self.SetEndPointDirection(connector.GetDirection())
+ self.ConnectEndPoint(connector.GetPosition(), connector)
+ self.Dragging = False
+ elif handle == 0:
+ self.MoveStartPoint(new_pos)
+ else:
+ self.MoveEndPoint(new_pos)
+ # If there is no connector, move the point
+ elif handle == 0:
+ if self.StartConnected:
+ self.UnConnectStartPoint()
+ self.MoveStartPoint(new_pos)
+ else:
+ if self.EndConnected:
+ self.UnConnectEndPoint()
+ self.MoveEndPoint(new_pos)
+ self.RefreshModel()
+ # A segment has been handled, move a segment
+ elif handle_type == HANDLE_SEGMENT:
+ self.MoveSegment(handle[0], movex, movey)
+ # Execute the default method for a graphic element
+ else:
+ Graphic_Element.ProcessDragging(self, movex, movey)
+
+ # Refreshes the wire model
+ def RefreshModel(self, move=True):
+ if self.StartConnected and self.StartPoint[1] in [WEST, NORTH]:
+ self.StartConnected.RefreshParentBlock()
+ if self.EndConnected and self.EndPoint[1] in [WEST, NORTH]:
+ self.EndConnected.RefreshParentBlock()
+
+ # Draws the wire lines and points
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw the start and end points if they are not connected or the mouse is over them
+ if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
+ dc.DrawCircle(self.Points[0].x, self.Points[0].y, POINT_RADIUS)
+ if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
+ dc.DrawCircle(self.Points[-1].x, self.Points[-1].y, POINT_RADIUS)
+ # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
+ dc.DrawLines(self.Points)
+ dc.DrawPoint(self.Points[-1].x, self.Points[-1].y)
+ # Draw the segment selected in red
+ if self.SelectedSegment != None:
+ dc.SetPen(wxRED_PEN)
+ dc.DrawLine(self.Points[self.SelectedSegment].x, self.Points[self.SelectedSegment].y,
+ self.Points[self.SelectedSegment + 1].x, self.Points[self.SelectedSegment + 1].y)
+ if self.SelectedSegment == len(self.Segments) - 1:
+ dc.DrawPoint(self.Points[-1].x, self.Points[-1].y)
+ Graphic_Element.Draw(self, dc)
+
+
+#-------------------------------------------------------------------------------
+# Graphic comment element
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a comment
+"""
+
+class Comment(Graphic_Element):
+
+ # Create a new comment
+ def __init__(self, parent, content, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Id = id
+ self.Content = content
+ self.Pos = wxPoint(0, 0)
+ self.Size = wxSize(0, 0)
+
+ # Method for keeping compatibility with others
+ def Clean(self):
+ pass
+
+ # Delete this comment by calling the corresponding method
+ def Delete(self):
+ self.Parent.DeleteComment(self)
+
+ # Refresh the comment bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+
+ # Changes the comment size
+ def SetSize(self, width, height):
+ self.Size.SetWidth(width)
+ self.Size.SetHeight(height)
+ self.RefreshBoundingBox()
+
+ # Returns the comment size
+ def GetSize(self):
+ return self.Size.GetWidth(), self.Size.GetHeight()
+
+ # Returns the comment minimum size
+ def GetMinSize(self):
+ dc = wxClientDC(self.Parent)
+ min_width = 0
+ min_height = 0
+ # The comment minimum size is the maximum size of words in the content
+ for line in self.Content.splitlines():
+ for word in line.split(" "):
+ wordwidth, wordheight = dc.GetTextExtent(word)
+ min_width = max(min_width, wordwidth)
+ min_height = max(min_height, wordheight)
+ return min_width + 20, min_height + 20
+
+ # Changes the comment position
+ def SetPosition(self, x, y):
+ self.Pos.x = x
+ self.Pos.y = y
+ self.RefreshBoundingBox()
+
+ # Changes the comment content
+ def SetContent(self, content):
+ self.Content = content
+ min_width, min_height = self.GetMinSize()
+ self.Size[0] = max(self.Size[0], min_width)
+ self.Size[1] = max(self.Size[1], min_height)
+ self.RefreshBoundingBox()
+
+ # Returns the comment content
+ def GetContent(self):
+ return self.Content
+
+ # Returns the comment position
+ def GetPosition(self):
+ return self.Pos.x, self.Pos.y
+
+ # Moves the comment
+ def Move(self, dx, dy, connected = True):
+ self.Pos.x += dx
+ self.Pos.y += dy
+ self.RefreshBoundingBox()
+
+ # Resizes the comment with the position and the size given
+ def Resize(self, x, y, width, height):
+ self.Move(x, y)
+ self.SetSize(width, height)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the comment model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshCommentModel(self)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the comment content
+ self.Parent.EditCommentContent(self)
+
+ # Draws the comment and its content
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxWHITE_BRUSH)
+ # Draws the comment shape
+ polygon = [wxPoint(self.Pos.x, self.Pos.y),
+ wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y),
+ wxPoint(self.Pos.x + self.Size[0], self.Pos.y + 10),
+ wxPoint(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] + 1),
+ wxPoint(self.Pos.x, self.Pos.y + self.Size[1] + 1)]
+ dc.DrawPolygon(polygon)
+ lines = [wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y),
+ wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10),
+ wxPoint(self.Pos.x + self.Size[0], self.Pos.y + 10)]
+ dc.DrawLines(lines)
+ # Draws the comment content
+ y = self.Pos.y + 10
+ for line in self.Content.splitlines():
+ first = True
+ words = line.split(" ")
+ for i, word in enumerate(words):
+ if first:
+ test = word
+ else:
+ test = linetext + " " + word
+ wordwidth, wordheight = dc.GetTextExtent(test)
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+ if wordwidth < self.Size[0] - 20 and i < len(words) - 1:
+ linetext = test
+ first = False
+ else:
+ if wordwidth < self.Size[0] - 20 and i == len(words) - 1:
+ dc.DrawText(test, self.Pos.x + 10, y)
+ else:
+ dc.DrawText(linetext, self.Pos.x + 10, y)
+ if i == len(words) - 1:
+ y += wordheight + 5
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+ dc.DrawText(word, self.Pos.x + 10, y)
+ else:
+ linetext = word
+ y += wordheight + 5
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+ Graphic_Element.Draw(self, dc)
diff -r 000000000000 -r b622defdfd98 graphics/LD_Objects.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/LD_Objects.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,477 @@
+#!/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 *
+
+#-------------------------------------------------------------------------------
+# Ladder Diagram PowerRail
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a power rail
+"""
+
+class LD_PowerRail(Graphic_Element):
+
+ # Create a new power rail
+ def __init__(self, parent, type, id = None, connectors = [True]):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Id = id
+ # Create a connector or a blank according to 'connectors' and add it in
+ # the connectors list
+ self.Connectors = []
+ for connector in connectors:
+ self.AddConnector(connector)
+ self.RefreshSize()
+
+ # Destructor
+ def __del__(self):
+ self.Connectors = []
+
+ # Forbids to change the power rail size
+ def SetSize(self, width, height):
+ pass
+
+ # Forbids to select a power rail
+ def HitTest(self, pt):
+ return False
+
+ # Deletes this power rail by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeletePowerRail(self)
+
+ # Unconnect all connectors
+ def Clean(self):
+ for connector in self.Connectors:
+ if connector:
+ connector.UnConnect()
+
+ # Refresh the power rail bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ if self.Type == LEFTRAIL:
+ bbx_x = self.Pos.x
+ elif self.Type == RIGHTRAIL:
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ self.BoundingBox = wxRect(bbx_x, self.Pos.y, self.Size[0] + CONNECTOR_SIZE + 1, self.Size[1] + 1)
+
+ # Refresh the power rail size
+ def RefreshSize(self):
+ self.Size = wxSize(2, LD_LINE_SIZE * len(self.Connectors))
+ self.RefreshBoundingBox()
+
+ # Add a connector or a blank to this power rail at the last place
+ def AddConnector(self, connector = True):
+ self.InsertConnector(len(self.Connectors), connector)
+
+ # Add a connector or a blank to this power rail at the place given
+ def InsertConnector(self, idx, connector = True):
+ if connector:
+ if self.Type == LEFTRAIL:
+ connector = Connector(self, "", "BOOL", wxPoint(2, 0), EAST)
+ elif self.Type == RIGHTRAIL:
+ connector = Connector(self, "", "BOOL", wxPoint(0, 0), WEST)
+ self.Connectors.insert(idx, connector)
+ else:
+ self.Connectors.insert(idx, None)
+ self.RefreshSize()
+ self.RefreshConnectors()
+
+ # Returns the index in connectors list for the connector given
+ def GetConnectorIndex(self, connector):
+ if connector in self.Connectors:
+ return self.Connectors.index(connector)
+ return None
+
+ # Returns if there is a connector in connectors list at the index given
+ def IsNullConnector(self, idx):
+ if idx < len(self.Connectors):
+ return self.Connectors[idx] == None
+ return False
+
+ # Delete the connector or blank from connectors list at the index given
+ def DeleteConnector(self, idx):
+ self.Connectors.pop(idx)
+ self.RefreshConnectors()
+ self.RefreshSize()
+
+ # Refresh the positions of the power rail connectors
+ def RefreshConnectors(self):
+ position = LD_LINE_SIZE / 2
+ for connector in self.Connectors:
+ if connector:
+ if self.Type == LEFTRAIL:
+ connector.SetPosition(wxPoint(self.Size[0], position))
+ elif self.Type == RIGHTRAIL:
+ connector.SetPosition(wxPoint(0, position))
+ position += LD_LINE_SIZE
+ self.RefreshConnected()
+
+ # Refresh the position of wires connefcted to power rail
+ def RefreshConnected(self, exclude = []):
+ for connector in self.Connectors:
+ connector.MoveConnected(exclude)
+
+ # Returns the power rail connector that starts with the point given if it exists
+ def GetConnector(self, position):
+ for connector in self.Connectors:
+ if connector:
+ connector_pos = connector.GetRelPosition()
+ if position.x == self.Pos.x + connector_pos.x and position.y == self.Pos.y + connector_pos.y:
+ return connector
+ return None
+
+ # Returns all the power rail connectors
+ def GetConnectors(self):
+ return [connector for connector in self.Connectors if connector]
+
+ # Test if point given is on one of the power rail connectors
+ def TestConnector(self, pt, exclude=True):
+ for connector in self.Connectors:
+ if connector and connector.TestPoint(pt, exclude):
+ return connector
+ return None
+
+ # Returns the power rail type
+ def GetType(self):
+ return self.Type
+
+ # Refreshes the power rail model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshPowerRailModel(self)
+ # If power rail has moved and power rail is of type LEFT, refresh the model
+ # of wires connected to connectors
+ if move and self.Type == LEFTRAIL:
+ for connector in self.Connectors:
+ if connector:
+ connector.RefreshWires()
+
+ # Draws power rail
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw a rectangle with the power rail size
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw connectors
+ for connector in self.Connectors:
+ if connector:
+ connector.Draw(dc)
+ Graphic_Element.Draw(self, dc)
+
+
+#-------------------------------------------------------------------------------
+# Ladder Diagram Contact
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a contact
+"""
+
+class LD_Contact(Graphic_Element):
+
+ # Create a new contact
+ def __init__(self, parent, type, name, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Name = name
+ self.Id = id
+ self.Size = wxSize(LD_ELEMENT_SIZE[0], LD_ELEMENT_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", "BOOL", wxPoint(0, self.Size[1] / 2 + 1), WEST)
+ self.Output = Connector(self, "", "BOOL", wxPoint(self.Size[0], self.Size[1] / 2 + 1), EAST)
+
+ # Destructor
+ def __del__(self):
+ self.Input = None
+ self.Output = None
+
+ # Forbids to change the contact size
+ def SetSize(self, width, height):
+ pass
+
+ # Delete this contact by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteContact(self)
+
+ # Unconnect input and output
+ def Clean(self):
+ self.Input.UnConnect()
+ self.Output.UnConnect()
+
+ # Refresh the contact bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ # Calculate the size of the name outside the contact
+ text_width, text_height = dc.GetTextExtent(self.Name)
+ # Calculate the bounding box size
+ if self.Name != "":
+ bbx_x = self.Pos.x - max(0, (text_width - self.Size[0]) / 2)
+ bbx_width = max(self.Size[0], text_width)
+ bbx_y = self.Pos.y - (text_height + 2)
+ bbx_height = self.Size[1] + (text_height + 2)
+ else:
+ bbx_x = self.Pos.x
+ bbx_width = self.Size[0]
+ 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 position of wire connected to contact
+ def RefreshConnected(self, exclude = []):
+ self.Input.MoveConnected(exclude)
+ self.Output.MoveConnected(exclude)
+
+ # Returns the contact connector that starts with the point given if it exists
+ def GetConnector(self, position):
+ # Test input connector
+ 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
+ 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 input and output contact connectors
+ def GetConnectors(self):
+ return {"input":self.Input,"output":self.Output}
+
+ # Test if point given is on contact input or output connector
+ def TestConnector(self, pt, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, exclude):
+ return self.Output
+ return None
+
+ # Changes the contact name
+ def SetName(self, name):
+ self.Name = name
+
+ # Returns the contact name
+ def GetName(self):
+ return self.Name
+
+ # Changes the contact type
+ def SetType(self, type):
+ self.Type = type
+
+ # Returns the contact type
+ def GetType(self):
+ return self.Type
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the contact properties
+ self.Parent.EditContactContent(self)
+
+ # Refreshes the contact model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshContactModel(self)
+ # If contact has moved, refresh the model of wires connected to output
+ if move:
+ self.Output.RefreshWires()
+
+ # Draws contact
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw two rectangles for representing the contact
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, 2, self.Size[1] + 1)
+ dc.DrawRectangle(self.Pos.x + self.Size[0] - 1, self.Pos.y, 2, self.Size[1] + 1)
+ # Draw contact name
+ namewidth, nameheight = dc.GetTextExtent(self.Name)
+ dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2,
+ self.Pos.y - (nameheight + 2))
+ # Draw the modifier symbol in the middle of contact
+ typetext = ""
+ if self.Type == CONTACT_REVERSE:
+ typetext = "/"
+ elif self.Type == CONTACT_RISING:
+ typetext = "P"
+ elif self.Type == CONTACT_FALLING:
+ typetext = "N"
+ if typetext != "":
+ typewidth, typeheight = dc.GetTextExtent(typetext)
+ dc.DrawText(typetext, self.Pos.x + (self.Size[0] - typewidth) / 2 + 1,
+ self.Pos.y + (self.Size[1] - typeheight) / 2)
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+ Graphic_Element.Draw(self, dc)
+
+
+#-------------------------------------------------------------------------------
+# Ladder Diagram Coil
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a coil
+"""
+
+class LD_Coil(Graphic_Element):
+
+ # Create a new coil
+ def __init__(self, parent, type, name, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Name = name
+ self.Id = id
+ self.Size = wxSize(LD_ELEMENT_SIZE[0], LD_ELEMENT_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", "BOOL", wxPoint(0, self.Size[1] / 2 + 1), WEST)
+ self.Output = Connector(self, "", "BOOL", wxPoint(self.Size[0], self.Size[1] / 2 + 1), EAST)
+
+ # Destructor
+ def __del__(self):
+ self.Input = None
+ self.Output = None
+
+ # Forbids to change the contact size
+ def SetSize(self, width, height):
+ pass
+
+ # Delete this coil by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteCoil(self)
+
+ # Unconnect input and output
+ def Clean(self):
+ self.Input.UnConnect()
+ self.Output.UnConnect()
+
+ # Refresh the coil bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ # Calculate the size of the name outside the coil
+ text_width, text_height = dc.GetTextExtent(self.Name)
+ # Calculate the bounding box size
+ if self.Name != "":
+ bbx_x = self.Pos.x - max(0, (text_width - self.Size[0]) / 2)
+ bbx_width = max(self.Size[0], text_width)
+ bbx_y = self.Pos.y - (text_height + 2)
+ bbx_height = self.Size[1] + (text_height + 2)
+ else:
+ bbx_x = self.Pos.x
+ bbx_width = self.Size[0]
+ 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 position of wire connected to coil
+ def RefreshConnected(self, exclude = []):
+ self.Input.MoveConnected(exclude)
+ self.Output.MoveConnected(exclude)
+
+ # Returns the coil connector that starts with the point given if it exists
+ def GetConnector(self, position):
+ # Test input connector
+ 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
+ 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 input and output coil connectors
+ def GetConnectors(self):
+ return {"input":self.Input,"output":self.Output}
+
+ # Test if point given is on coil input or output connector
+ def TestConnector(self, pt, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, exclude):
+ return self.Output
+ return None
+
+ # Changes the coil name
+ def SetName(self, name):
+ self.Name = name
+
+ # Returns the coil name
+ def GetName(self):
+ return self.Name
+
+ # Changes the coil type
+ def SetType(self, type):
+ self.Type = type
+
+ # Returns the coil type
+ def GetType(self):
+ return self.Type
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the coil properties
+ self.Parent.EditCoilContent(self)
+
+ # Refreshes the coil model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshCoilModel(self)
+ # If coil has moved, refresh the model of wires connected to output
+ if move:
+ self.Output.RefreshWires()
+
+ # Draws coil
+ def Draw(self, dc):
+ dc.SetPen(wxPen(wxBLACK, 2, wxSOLID))
+ dc.SetBrush(wxTRANSPARENT_BRUSH)
+ # Draw a two circle arcs for representing the coil
+ dc.DrawEllipticArc(self.Pos.x, self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, self.Size[0], int(self.Size[1] * sqrt(2)) - 1, 135, 225)
+ dc.DrawEllipticArc(self.Pos.x, self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1, self.Size[0], int(self.Size[1] * sqrt(2)) - 1, -45, 45)
+ dc.SetPen(wxBLACK_PEN)
+ dc.DrawPoint(self.Pos.x + 1, self.Pos.y + self.Size[1] / 2 + 1)
+ # Draw coil name
+ namewidth, nameheight = dc.GetTextExtent(self.Name)
+ dc.DrawText(self.Name, self.Pos.x + (self.Size[0] - namewidth) / 2,
+ self.Pos.y - (nameheight + 2))
+ # Draw the modifier symbol in the middle of coil
+ typetext = ""
+ if self.Type == COIL_REVERSE:
+ typetext = "/"
+ elif self.Type == COIL_SET:
+ typetext = "S"
+ elif self.Type == COIL_RESET:
+ typetext = "R"
+ if typetext != "":
+ typewidth, typeheight = dc.GetTextExtent(typetext)
+ dc.DrawText(typetext, self.Pos.x + (self.Size[0] - typewidth) / 2 + 1,
+ self.Pos.y + (self.Size[1] - typeheight) / 2)
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+ Graphic_Element.Draw(self, dc)
diff -r 000000000000 -r b622defdfd98 graphics/SFC_Objects.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/SFC_Objects.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1295 @@
+#!/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 *
+
+def GetWireSize(block):
+ if isinstance(block, SFC_Step):
+ return SFC_WIRE_MIN_SIZE + block.GetActionExtraLineNumber() * SFC_ACTION_MIN_SIZE[1]
+ else:
+ return SFC_WIRE_MIN_SIZE
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Step
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a step
+"""
+
+class SFC_Step(Graphic_Element):
+
+ # Create a new step
+ def __init__(self, parent, name, initial = False, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Name = name
+ self.Initial = initial
+ self.Id = id
+ self.Size = wxSize(SFC_STEP_DEFAULT_SIZE[0], SFC_STEP_DEFAULT_SIZE[1])
+ # Create an input and output connector
+ if not self.Initial:
+ self.Input = Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, 0), NORTH)
+ else:
+ self.Input = None
+ self.Output = None
+ self.Action = None
+
+ # Destructor
+ def __del__(self):
+ self.Input = None
+ self.Output = None
+ self.Action = None
+
+ # Delete this step by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteStep(self)
+
+ # Unconnect input and output
+ def Clean(self):
+ if self.Input:
+ self.Input.UnConnect()
+ if self.Output:
+ self.Output.UnConnect()
+ if self.Action:
+ self.Action.UnConnect()
+
+ # Add output connector to step
+ def AddOutput(self):
+ if not self.Output:
+ self.Output = Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, self.Size[1]), SOUTH)
+ self.RefreshBoundingBox()
+
+ # Remove output connector from step
+ def RemoveOutput(self):
+ if self.Output:
+ self.Output.UnConnect()
+ self.Output = None
+ self.RefreshBoundingBox()
+
+ # Add action connector to step
+ def AddAction(self):
+ if not self.Action:
+ self.Action = Connector(self, "", "ANY", wxPoint(self.Size[0], self.Size[1] / 2), EAST)
+ self.RefreshBoundingBox()
+
+ # Remove action connector from step
+ def RemoveAction(self):
+ if self.Action:
+ self.Action.UnConnect()
+ self.Action = None
+ self.RefreshBoundingBox()
+
+ # Refresh the step bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ # Calculate the bounding box size
+ if self.Action:
+ bbx_width = self.Size[0] + CONNECTOR_SIZE
+ else:
+ bbx_width = self.Size[0]
+ if self.Initial:
+ bbx_y = self.Pos.y
+ bbx_height = self.Size[1]
+ if self.Output:
+ bbx_height += CONNECTOR_SIZE
+ else:
+ bbx_y = self.Pos.y - CONNECTOR_SIZE
+ bbx_height = self.Size[1] + CONNECTOR_SIZE
+ if self.Output:
+ bbx_height += CONNECTOR_SIZE
+ #self.BoundingBox = wxRect(self.Pos.x, bbx_y, bbx_width + 1, bbx_height + 1)
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+
+ # Refresh the positions of the step connectors
+ def RefreshConnectors(self):
+ # Update input position if it exists
+ if self.Input:
+ self.Input.SetPosition(wxPoint(self.Size[0] / 2, 0))
+ # Update output position
+ if self.Output:
+ self.Output.SetPosition(wxPoint(self.Size[0] / 2, self.Size[1]))
+ # Update action position if it exists
+ if self.Action:
+ self.Action.SetPosition(wxPoint(self.Size[0], self.Size[1] / 2))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected to step
+ def RefreshConnected(self, exclude = []):
+ if self.Input:
+ self.Input.MoveConnected(exclude)
+ if self.Output:
+ self.Output.MoveConnected(exclude)
+ if self.Action:
+ self.Action.MoveConnected(exclude)
+
+ # Returns the step 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
+ # Test action connector if it exists
+ if self.Action:
+ action_pos = self.Action.GetRelPosition()
+ if position.x == self.Pos.x + action_pos.x and position.y == self.Pos.y + action_pos.y:
+ return self.Action
+ return None
+
+ # Returns input and output step connectors
+ def GetConnectors(self):
+ return {"input":self.Input,"output":self.Output,"action":self.Action}
+
+ # Test if point given is on step input or output connector
+ def TestConnector(self, pt, exclude=True):
+ # Test input connector if it exists
+ if self.Input and self.Input.TestPoint(pt, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output and self.Output.TestPoint(pt, exclude):
+ return self.Output
+ return None
+
+ # Changes the step name
+ def SetName(self, name):
+ self.Name = name
+
+ # Returns the step name
+ def GetName(self):
+ return self.Name
+
+ # Returns the step initial property
+ def GetInitial(self):
+ return self.Initial
+
+ # Returns the connector connected to input
+ def GetPreviousConnector(self):
+ if self.Input:
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].EndConnected
+ return None
+
+ # Returns the connector connected to output
+ def GetNextConnector(self):
+ if self.Output:
+ wires = self.Output.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].StartConnected
+ return None
+
+ # Returns the connector connected to action
+ def GetActionConnector(self):
+ if self.Action:
+ wires = self.Action.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].StartConnected
+ return None
+
+ # Returns the number of action line
+ def GetActionExtraLineNumber(self):
+ if self.Action:
+ wires = self.Action.GetWires()
+ if len(wires) != 1:
+ return 0
+ action_block = wires[0][0].StartConnected.GetParentBlock()
+ return max(0, action_block.GetLineNumber() - 1)
+ return 0
+
+ # Returns the step minimum size
+ def GetMinSize(self):
+ dc = wxClientDC(self.Parent)
+ text_width, text_height = dc.GetTextExtent(self.Name)
+ if self.Initial:
+ return text_width + 14, text_height + 14
+ else:
+ return text_width + 10, text_height + 10
+
+ # Updates the step size
+ def UpdateSize(self, width, height):
+ diffx = self.Size.GetWidth() / 2 - width / 2
+ diffy = height - self.Size.GetHeight()
+ self.Move(diffx, 0)
+ Graphic_Element.SetSize(self, width, height)
+ self.RefreshOutputPosition((0, diffy))
+
+ # Align input element with this step
+ def RefreshInputPosition(self):
+ if self.Input:
+ current_pos = self.Input.GetPosition(False)
+ input = self.GetPreviousConnector()
+ if input:
+ input_pos = input.GetPosition(False)
+ diffx = current_pos.x - input_pos.x
+ input_block = input.GetParentBlock()
+ if isinstance(input_block, SFC_Divergence):
+ input_block.MoveConnector(input, diffx)
+ else:
+ if isinstance(input_block, SFC_Step):
+ input_block.MoveActionBlock((diffx, 0))
+ input_block.Move(diffx, 0)
+ input_block.RefreshInputPosition()
+
+ # Align output element with this step
+ def RefreshOutputPosition(self, move = None):
+ if self.Output:
+ wires = self.Output.GetWires()
+ if len(wires) != 1:
+ return
+ current_pos = self.Output.GetPosition(False)
+ output = wires[0][0].StartConnected
+ output_pos = output.GetPosition(False)
+ diffx = current_pos.x - output_pos.x
+ output_block = output.GetParentBlock()
+ wire_size = SFC_WIRE_MIN_SIZE + self.GetActionExtraLineNumber() * SFC_ACTION_MIN_SIZE[1]
+ diffy = wire_size - output_pos.y + current_pos.y
+ if diffy != 0:
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock((diffx, diffy))
+ wires[0][0].SetPoints([wxPoint(current_pos.x, current_pos.y + wire_size),
+ wxPoint(current_pos.x, current_pos.y)])
+ if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
+ output_block.Move(diffx, diffy, self.Parent.Wires)
+ output_block.RefreshOutputPosition((diffx, diffy))
+ else:
+ output_block.RefreshPosition()
+ elif move:
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock(move)
+ wires[0][0].Move(move[0], move[1], True)
+ if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
+ output_block.Move(move[0], move[1], self.Parent.Wires)
+ output_block.RefreshOutputPosition(move)
+ else:
+ output_block.RefreshPosition()
+ elif isinstance(output_block, SFC_Divergence):
+ output_block.MoveConnector(output, diffx)
+ else:
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock((diffx, 0))
+ output_block.Move(diffx, 0)
+ output_block.RefreshOutputPosition()
+
+ # Refresh action element with this step
+ def MoveActionBlock(self, move):
+ if self.Action:
+ wires = self.Action.GetWires()
+ if len(wires) != 1:
+ return
+ action_block = wires[0][0].StartConnected.GetParentBlock()
+ action_block.Move(move[0], move[1], self.Parent.Wires)
+ wires[0][0].Move(move[0], move[1], True)
+
+ # Resize the divergence from position and size given
+ def Resize(self, x, y, width, height):
+ self.UpdateSize(width, height)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the step properties
+ self.Parent.EditStepContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ # Popup the menu with special items for a step
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the step state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_MOVE:
+ action_block = None
+ if self.Initial:
+ self.MoveActionBlock((movex, movey))
+ self.Move(movex, movey, self.Parent.Wires)
+ self.RefreshOutputPosition((movex, movey))
+ else:
+ self.MoveActionBlock((movex, 0))
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+ self.RefreshOutputPosition()
+ else:
+ Graphic_Element.ProcessDragging(self, movex, movey)
+
+ # Refresh input element model
+ def RefreshInputModel(self):
+ if self.Input:
+ input = self.GetPreviousConnector()
+ if input:
+ input_block = input.GetParentBlock()
+ input_block.RefreshModel(False)
+ if not isinstance(input_block, SFC_Divergence):
+ input_block.RefreshInputModel()
+
+ # Refresh output element model
+ def RefreshOutputModel(self, move=False):
+ if self.Output:
+ output = self.GetNextConnector()
+ if output:
+ output_block = output.GetParentBlock()
+ output_block.RefreshModel(False)
+ if not isinstance(output_block, SFC_Divergence) or move:
+ output_block.RefreshOutputModel(move)
+
+ # Refreshes the step model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshStepModel(self)
+ if self.Action:
+ action = self.GetActionConnector()
+ if action:
+ action_block = action.GetParentBlock()
+ action_block.RefreshModel(False)
+ # If step has moved, refresh the model of wires connected to output
+ if move:
+ self.RefreshInputModel()
+ self.RefreshOutputModel(self.Initial)
+
+ # Draws step
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxWHITE_BRUSH)
+ # Draw two rectangles for representing the step
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ if self.Initial:
+ dc.DrawRectangle(self.Pos.x + 2, self.Pos.y + 2, self.Size[0] - 3, self.Size[1] - 3)
+ # Draw step 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 input and output connectors
+ if self.Input:
+ self.Input.Draw(dc)
+ if self.Output:
+ self.Output.Draw(dc)
+ if self.Action:
+ self.Action.Draw(dc)
+ Graphic_Element.Draw(self, dc)
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Transition
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a transition
+"""
+
+class SFC_Transition(Graphic_Element):
+
+ # Create a new transition
+ def __init__(self, parent, type = "reference", condition = "", id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Condition = condition
+ self.Id = id
+ self.Size = wxSize(SFC_TRANSITION_SIZE[0], SFC_TRANSITION_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, 0), NORTH)
+ self.Output = Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, self.Size[1]), SOUTH)
+
+ # Destructor
+ def __del__(self):
+ self.Input = None
+ self.Output = None
+
+ # Forbids to change the transition size
+ def SetSize(self, width, height):
+ pass
+
+ # Forbids to resize the transition
+ def Resize(self, x, y, width, height):
+ pass
+
+ # Delete this transition by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteTransition(self)
+
+ # Unconnect input and output
+ def Clean(self):
+ self.Input.UnConnect()
+ self.Output.UnConnect()
+
+ # Refresh the transition bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ if self.Condition != "":
+ text_width, text_height = dc.GetTextExtent(self.Condition)
+ else:
+ text_width, text_height = dc.GetTextExtent("Transition")
+ # Calculate the bounding box size
+ bbx_width = self.Size[0] + CONNECTOR_SIZE + 2 + text_width
+ bbx_y = self.Pos.y - max(0, (text_height - self.Size[1]) / 2)
+ bbx_height = max(self.Size[1], text_height)
+ self.BoundingBox = wxRect(self.Pos.x, bbx_y, bbx_width + 1, bbx_height + 1)
+
+ # Returns the connector connected to input
+ def GetPreviousConnector(self):
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].EndConnected
+ return None
+
+ # Returns the connector connected to output
+ def GetNextConnector(self):
+ wires = self.Output.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].StartConnected
+ return None
+
+ # Refresh the positions of the transition connectors
+ def RefreshConnectors(self):
+ # Update input position
+ self.Input.SetPosition(wxPoint(self.Size[0] / 2, 0))
+ # Update output position
+ self.Output.SetPosition(wxPoint(self.Size[0] / 2, self.Size[1]))
+ self.RefreshConnected()
+
+ # Refresh the position of the wires connected to transition
+ def RefreshConnected(self, exclude = []):
+ self.Input.MoveConnected(exclude)
+ self.Output.MoveConnected(exclude)
+
+ # Returns the transition connector that starts with the point given if it exists
+ def GetConnector(self, position):
+ # Test input connector
+ 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
+ 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 input and output transition connectors
+ def GetConnectors(self):
+ return {"input":self.Input,"output":self.Output}
+
+ # Test if point given is on transition input or output connector
+ def TestConnector(self, pt, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, exclude):
+ return self.Output
+ return None
+
+ # Changes the transition type
+ def SetType(self, type):
+ self.Type = type
+
+ # Returns the transition type
+ def GetType(self):
+ return self.Type
+
+ # Changes the transition condition
+ def SetCondition(self, condition):
+ self.Condition = condition
+ self.RefreshBoundingBox()
+
+ # Returns the transition condition
+ def GetCondition(self):
+ return self.Condition
+
+ # Returns the transition minimum size
+ def GetMinSize(self):
+ return SFC_TRANSITION_SIZE
+
+ # Align input element with this step
+ def RefreshInputPosition(self):
+ wires = self.Input.GetWires()
+ current_pos = self.Input.GetPosition(False)
+ input = self.GetPreviousConnector()
+ if input:
+ input_pos = input.GetPosition(False)
+ diffx = current_pos.x - input_pos.x
+ input_block = input.GetParentBlock()
+ if isinstance(input_block, SFC_Divergence):
+ input_block.MoveConnector(input, diffx)
+ else:
+ if isinstance(input_block, SFC_Step):
+ input_block.MoveActionBlock((diffx, 0))
+ input_block.Move(diffx, 0)
+ input_block.RefreshInputPosition()
+
+ # Align output element with this step
+ def RefreshOutputPosition(self, move = None):
+ wires = self.Output.GetWires()
+ if len(wires) != 1:
+ return
+ current_pos = self.Output.GetPosition(False)
+ output = wires[0][0].StartConnected
+ output_pos = output.GetPosition(False)
+ diffx = current_pos.x - output_pos.x
+ output_block = output.GetParentBlock()
+ if move:
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock(move)
+ wires[0][0].Move(move[0], move[1], True)
+ if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
+ output_block.Move(move[0], move[1], self.Parent.Wires)
+ output_block.RefreshOutputPosition(move)
+ else:
+ output_block.RefreshPosition()
+ elif isinstance(output_block, SFC_Divergence):
+ output_block.MoveConnector(output, diffx)
+ else:
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock((diffx, 0))
+ output_block.Move(diffx, 0)
+ output_block.RefreshOutputPosition()
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the transition properties
+ self.Parent.EditTransitionContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ # Popup the menu with special items for a step
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the transition state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+ self.RefreshOutputPosition()
+
+ # Refresh input element model
+ def RefreshInputModel(self):
+ input = self.GetPreviousConnector()
+ if input:
+ input_block = input.GetParentBlock()
+ input_block.RefreshModel(False)
+ if not isinstance(input_block, SFC_Divergence):
+ input_block.RefreshInputModel()
+
+ # Refresh output element model
+ def RefreshOutputModel(self, move=False):
+ output = self.GetNextConnector()
+ if output:
+ output_block = output.GetParentBlock()
+ output_block.RefreshModel(False)
+ if not isinstance(output_block, SFC_Divergence) or move:
+ output_block.RefreshOutputModel(move)
+
+ # Refreshes the transition model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshTransitionModel(self)
+ # If transition has moved, refresh the model of wires connected to output
+ if move:
+ self.RefreshInputModel()
+ self.RefreshOutputModel()
+
+ # Draws transition
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw plain rectangle for representing the transition
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw transition condition
+ if self.Condition != "":
+ text_width, text_height = dc.GetTextExtent(self.Condition)
+ condition = self.Condition
+ else:
+ text_width, text_height = dc.GetTextExtent("Transition")
+ condition = "Transition"
+ dc.DrawText(condition, self.Pos.x + self.Size[0] + CONNECTOR_SIZE + 2,
+ self.Pos.y + (self.Size[1] - text_height) / 2)
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+ Graphic_Element.Draw(self, dc)
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Divergence and Convergence
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a divergence or convergence,
+selection or simultaneous
+"""
+
+class SFC_Divergence(Graphic_Element):
+
+ # Create a new divergence
+ def __init__(self, parent, type, number = 2, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Id = id
+ self.RealConnectors = None
+ number = max(2, number)
+ if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ self.Size = wxSize((number - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL, 1)
+ elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.Size = wxSize((number - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL, 3)
+ # Create an input and output connector
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ self.Inputs = [Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, 0), NORTH)]
+ self.Outputs = []
+ for i in xrange(number):
+ self.Outputs.append(Connector(self, "", "ANY", wxPoint(i * SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH))
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.Inputs = []
+ for i in xrange(number):
+ self.Inputs.append(Connector(self, "", "ANY", wxPoint(i * SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH))
+ self.Outputs = [Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, self.Size[1]), SOUTH)]
+
+ # Destructor
+ def __del__(self):
+ self.Inputs = []
+ self.Outputs = []
+
+ # Forbids to resize the divergence
+ def Resize(self, x, y, width, height):
+ pass
+
+ # Delete this divergence by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteDivergence(self)
+
+ # Returns the divergence type
+ def GetType(self):
+ return self.Type
+
+ # Unconnect input and output
+ def Clean(self):
+ for input in self.Inputs:
+ input.UnConnect()
+ for output in self.Outputs:
+ output.UnConnect()
+
+ # Add a branch to the divergence
+ def AddBranch(self):
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ maxx = 0
+ for output in self.Outputs:
+ pos = output.GetRelPosition()
+ maxx = max(maxx, pos.x)
+ connector = Connector(self, "", "ANY", wxPoint(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH)
+ self.Outputs.append(connector)
+ self.MoveConnector(connector, 0)
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ maxx = 0
+ for input in self.Inputs:
+ pos = input.GetRelPosition()
+ maxx = max(maxx, pos.x)
+ connector = Connector(self, "", "ANY", wxPoint(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH)
+ self.Inputs.append(connector)
+ self.MoveConnector(connector, SFC_DEFAULT_SEQUENCE_INTERVAL)
+
+ # Remove a branch from the divergence
+ def RemoveBranch(self, connector):
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ if connector in self.Outputs:
+ self.Outputs.remove(connector)
+ self.MoveConnector(self.Outputs[0], 0)
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ if connector in self.Inputs:
+ self.Inputs.remove(connector)
+ self.MoveConnector(self.Inputs[0], 0)
+
+ # Return the number of branches for the divergence
+ def GetBranchNumber(self):
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ return len(self.Outputs)
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ return len(self.Inputs)
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt):
+ rect = self.BoundingBox
+ return rect.InsideXY(pt.x, pt.y) or self.TestConnector(pt, False) != None
+
+ # Refresh the divergence bounding box
+ def RefreshBoundingBox(self):
+ if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y - CONNECTOR_SIZE,
+ self.Size[0] + 1, self.Size[1] + CONNECTOR_SIZE * 2 + 1)
+ elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.BoundingBox = wxRect(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y - CONNECTOR_SIZE,
+ self.Size[0] + 2 * SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Size[1] + CONNECTOR_SIZE * 2 + 1)
+
+ # Refresh the position of wires connected to divergence
+ def RefreshConnected(self, exclude = []):
+ for input in self.Inputs:
+ input.MoveConnected(exclude)
+ for output in self.Outputs:
+ output.MoveConnected(exclude)
+
+ # Moves the divergence connector given
+ def MoveConnector(self, connector, movex):
+ position = connector.GetRelPosition()
+ connector.SetPosition(wxPoint(position.x + movex, position.y))
+ minx = self.Size[0]
+ maxx = 0
+ for input in self.Inputs:
+ input_pos = input.GetRelPosition()
+ minx = min(minx, input_pos.x)
+ maxx = max(maxx, input_pos.x)
+ for output in self.Outputs:
+ output_pos = output.GetRelPosition()
+ minx = min(minx, output_pos.x)
+ maxx = max(maxx, output_pos.x)
+ if minx != 0:
+ for input in self.Inputs:
+ input_pos = input.GetRelPosition()
+ input.SetPosition(wxPoint(input_pos.x - minx, input_pos.y))
+ for output in self.Outputs:
+ output_pos = output.GetRelPosition()
+ output.SetPosition(wxPoint(output_pos.x - minx, output_pos.y))
+ self.Pos.x += minx
+ self.Size[0] = maxx - minx
+ connector.MoveConnected()
+ self.RefreshBoundingBox()
+
+ # Returns the divergence connector that starts with the point given if it exists
+ def GetConnector(self, position):
+ # Test input connector
+ for input in self.Inputs:
+ input_pos = input.GetPosition(False)
+ if position.x == input_pos.x and position.y == input_pos.y:
+ return input
+ # Test output connector
+ for output in self.Outputs:
+ output_pos = output.GetPosition(False)
+ if position.x == output_pos.x and position.y == output_pos.y:
+ return output
+ return None
+
+ # Returns input and output divergence connectors
+ def GetConnectors(self):
+ return {"inputs":self.Inputs,"outputs":self.Outputs}
+
+ # Test if point given is on divergence input or output connector
+ def TestConnector(self, pt, exclude=True):
+ # Test input connector
+ for input in self.Inputs:
+ if input.TestPoint(pt, exclude):
+ return input
+ # Test output connector
+ for output in self.Outputs:
+ if output.TestPoint(pt, exclude):
+ return output
+ return None
+
+ # Changes the divergence size
+ def SetSize(self, width, height):
+ for i, input in enumerate(self.Inputs):
+ position = input.GetRelPosition()
+ if self.RealConnectors:
+ input.SetPosition(wxPoint(int(round(self.RealConnectors["Inputs"][i] * width)), position.y))
+ else:
+ input.SetPosition(wxPoint(int(round(float(position.x)*float(width)/float(self.Size[0]))), position.y))
+ input.MoveConnected()
+ for i, output in enumerate(self.Outputs):
+ position = output.GetRelPosition()
+ if self.RealConnectors:
+ output.SetPosition(wxPoint(int(round(self.RealConnectors["Outputs"][i] * width)), position.y))
+ else:
+ output.SetPosition(wxPoint(int(round(float(position.x)*float(width)/float(self.Size[0]))), position.y))
+ output.MoveConnected()
+ self.Size = wxSize(width, self.Size[1])
+ self.RefreshBoundingBox()
+
+ # Returns the divergence minimum size
+ def GetMinSize(self):
+ return 0, self.Size[1]
+
+ # Refresh the position of the block connected to connector
+ def RefreshConnectedPosition(self, connector):
+ wires = connector.GetWires()
+ if len(wires) != 1:
+ return
+ current_pos = connector.GetPosition(False)
+ if connector in self.Inputs:
+ next = wires[0][0].EndConnected
+ else:
+ next = wires[0][0].StartConnected
+ next_pos = next.GetPosition(False)
+ diffx = current_pos.x - next_pos.x
+ next_block = next.GetParentBlock()
+ if isinstance(next_block, SFC_Divergence):
+ next_block.MoveConnector(next, diffx)
+ else:
+ next_block.Move(diffx, 0)
+ if connector in self.Inputs:
+ next_block.RefreshInputPosition()
+ else:
+ next_block.RefreshOutputPosition()
+
+ # Refresh the position of this divergence
+ def RefreshPosition(self):
+ y = 0
+ for input in self.Inputs:
+ wires = input.GetWires()
+ if len(wires) != 1:
+ return
+ previous = wires[0][0].EndConnected
+ previous_pos = previous.GetPosition(False)
+ y = max(y, previous_pos.y + GetWireSize(previous.GetParentBlock()))
+ diffy = y - self.Pos.y
+ if diffy != 0:
+ self.Move(0, diffy, self.Parent.Wires)
+ self.RefreshOutputPosition((0, diffy))
+ for input in self.Inputs:
+ input.MoveConnected()
+
+ # Align output element with this divergence
+ def RefreshOutputPosition(self, move = None):
+ if move:
+ for output_connector in self.Outputs:
+ wires = output_connector.GetWires()
+ if len(wires) != 1:
+ return
+ current_pos = output_connector.GetPosition(False)
+ output = wires[0][0].StartConnected
+ output_pos = output.GetPosition(False)
+ diffx = current_pos.x - output_pos.x
+ output_block = output.GetParentBlock()
+ if isinstance(output_block, SFC_Step):
+ output_block.MoveActionBlock(move)
+ wires[0][0].Move(move[0], move[1], True)
+ if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
+ output_block.Move(move[0], move[1], self.Parent.Wires)
+ output_block.RefreshOutputPosition(move)
+
+ # Method called when a LeftDown event have been generated
+ def OnLeftDown(self, event, scaling):
+ pos = GetScaledEventPosition(event, scaling)
+ # Test if a connector have been handled
+ connector = self.TestConnector(pos, False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ self.Parent.SetCursor(wxStockCursor(wxCURSOR_HAND))
+ self.Selected = False
+ # Initializes the last position
+ self.oldPos = GetScaledEventPosition(event, scaling)
+ else:
+ self.RealConnectors = {"Inputs":[],"Outputs":[]}
+ for input in self.Inputs:
+ position = input.GetRelPosition()
+ self.RealConnectors["Inputs"].append(float(position.x)/float(self.Size[0]))
+ for output in self.Outputs:
+ position = output.GetRelPosition()
+ self.RealConnectors["Outputs"].append(float(position.x)/float(self.Size[0]))
+ Graphic_Element.OnLeftDown(self, event, scaling)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, scaling):
+ self.RealConnectors = None
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ wires = handle.GetWires()
+ if len(wires) != 1:
+ return
+ if handle in self.Inputs:
+ block = wires[0][0].EndConnected.GetParentBlock()
+ else:
+ block = wires[0][0].StartConnected.GetParentBlock()
+ block.RefreshModel(False)
+ if not isinstance(block, SFC_Divergence):
+ if handle in self.Inputs:
+ block.RefreshInputModel()
+ else:
+ block.RefreshOutputModel()
+ Graphic_Element.OnLeftUp(self, event, scaling)
+
+ # 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.PopupDivergenceMenu(True)
+ else:
+ # Popup the divergence menu without delete branch
+ self.Parent.PopupDivergenceMenu(False)
+
+ # Refreshes the divergence state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ handle_type, handle = self.Handle
+ # A connector has been handled
+ if handle_type == HANDLE_CONNECTOR:
+ self.MoveConnector(handle, movex)
+ self.RefreshConnectedPosition(handle)
+
+ # Refresh output element model
+ def RefreshOutputModel(self, move=False):
+ if move:
+ for output in self.Outputs:
+ wires = output.GetWires()
+ if len(wires) != 1:
+ return
+ output_block = wires[0][0].StartConnected.GetParentBlock()
+ output_block.RefreshModel(False)
+ if not isinstance(output_block, SFC_Divergence) or move:
+ output_block.RefreshOutputModel(move)
+
+ # Refreshes the divergence model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshDivergenceModel(self)
+ # If divergence has moved, refresh the model of wires connected to outputs
+ if move:
+ self.RefreshOutputModel()
+
+ # Draws divergence
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw plain rectangle for representing the divergence
+ if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ dc.DrawLine(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y,
+ self.Pos.x + self.Size[0] + SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Pos.y)
+ dc.DrawLine(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y + 3,
+ self.Pos.x + self.Size[0] + SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Pos.y + 3)
+ # 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)
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Jump to Step
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a jump to step
+"""
+
+class SFC_Jump(Graphic_Element):
+
+ # Create a new jump
+ def __init__(self, parent, target, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Target = target
+ self.Id = id
+ self.Size = wxSize(SFC_JUMP_SIZE[0], SFC_JUMP_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", "ANY", wxPoint(self.Size[0] / 2, 0), NORTH)
+
+ # Destructor
+ def __del__(self):
+ self.Inputs = None
+
+ # Forbids to change the jump size
+ def SetSize(self, width, height):
+ pass
+
+ # Forbids to resize jump
+ def Resize(self, x, y, width, height):
+ pass
+
+ # Delete this jump by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteJump(self)
+
+ # Unconnect input
+ def Clean(self):
+ if self.Input:
+ self.Input.UnConnect()
+
+ # Refresh the jump bounding box
+ def RefreshBoundingBox(self):
+ dc = wxClientDC(self.Parent)
+ text_width, text_height = dc.GetTextExtent(self.Target)
+ # Calculate the bounding box size
+ bbx_width = self.Size[0] + 2 + text_width
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y - CONNECTOR_SIZE,
+ bbx_width + 1, self.Size[1] + CONNECTOR_SIZE + 1)
+
+ # Returns the connector connected to input
+ def GetPreviousConnector(self):
+ if self.Input:
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].EndConnected
+ return None
+
+ # Refresh the position of wires connected to jump
+ def RefreshConnected(self, exclude = []):
+ if self.Input:
+ self.Input.MoveConnected(exclude)
+
+ # Returns input jump connector
+ def GetConnector(self, position = None):
+ return self.Input
+
+ # Test if point given is on jump input connector
+ def TestConnector(self, pt, exclude = True):
+ # Test input connector
+ if self.Input and self.Input.TestPoint(pt, exclude):
+ return self.Input
+ return None
+
+ # Changes the jump target
+ def SetTarget(self, target):
+ self.Target = target
+ self.RefreshBoundingBox()
+
+ # Returns the jump target
+ def GetTarget(self):
+ return self.Target
+
+ # Returns the jump minimum size
+ def GetMinSize(self):
+ return SFC_JUMP_SIZE
+
+ # Align input element with this jump
+ def RefreshInputPosition(self):
+ if self.Input:
+ current_pos = self.Input.GetPosition(False)
+ input = self.GetPreviousConnector()
+ if input:
+ input_pos = input.GetPosition(False)
+ diffx = current_pos.x - input_pos.x
+ input_block = input.GetParentBlock()
+ if isinstance(input_block, SFC_Divergence):
+ input_block.MoveConnector(input, diffx)
+ else:
+ if isinstance(input_block, SFC_Step):
+ input_block.MoveActionBlock((diffx, 0))
+ input_block.Move(diffx, 0)
+ input_block.RefreshInputPosition()
+
+ # Can't align output element, because there is no output
+ def RefreshOutputPosition(self, move = None):
+ pass
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the jump properties
+ self.Parent.EditJumpContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the jump state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+
+ # Refresh input element model
+ def RefreshInputModel(self):
+ input = self.GetPreviousConnector()
+ if input:
+ input_block = input.GetParentBlock()
+ input_block.RefreshModel(False)
+ if not isinstance(input_block, SFC_Divergence):
+ input_block.RefreshInputModel()
+
+ # Refresh output element model
+ def RefreshOutputModel(self, move=False):
+ pass
+
+ # Refreshes the jump model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshJumpModel(self)
+ if move:
+ self.RefreshInputModel()
+
+ # Draws divergence
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxBLACK_BRUSH)
+ # Draw plain rectangle for representing the divergence
+ dc.DrawLine(self.Pos.x + self.Size[0] / 2, self.Pos.y, self.Pos.x + self.Size[0] / 2, self.Pos.y + self.Size[1])
+ points = [wxPoint(self.Pos.x, self.Pos.y),
+ wxPoint(self.Pos.x + self.Size[0] / 2, self.Pos.y + self.Size[1] / 3),
+ wxPoint(self.Pos.x + self.Size[0], self.Pos.y),
+ wxPoint(self.Pos.x + self.Size[0] / 2, self.Pos.y + self.Size[1])]
+ dc.DrawPolygon(points)
+ text_width, text_height = dc.GetTextExtent(self.Target)
+ dc.DrawText(self.Target, self.Pos.x + self.Size[0] + 2,
+ self.Pos.y + (self.Size[1] - text_height) / 2)
+ # Draw input connector
+ if self.Input:
+ self.Input.Draw(dc)
+ Graphic_Element.Draw(self, dc)
+
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Action Block
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of an action block
+"""
+
+class SFC_ActionBlock(Graphic_Element):
+
+ # Create a new action block
+ def __init__(self, parent, actions = [], id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Id = id
+ self.Size = wxSize(SFC_ACTION_MIN_SIZE[0], SFC_ACTION_MIN_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", "ANY", wxPoint(0, SFC_ACTION_MIN_SIZE[1] / 2), WEST)
+ self.SetActions(actions)
+
+ # Destructor
+ def __del__(self):
+ self.Input = None
+
+ # Returns the number of action lines
+ def GetLineNumber(self):
+ return len(self.Actions)
+
+ # Forbids to resize the action block
+ def Resize(self, x, y, width, height):
+ if x == 0:
+ self.SetSize(width, self.Size[1])
+
+ # Delete this action block by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteActionBlock(self)
+
+ # Unconnect input and output
+ def Clean(self):
+ self.Input.UnConnect()
+
+ # Refresh the action block bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wxRect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
+
+ # Refresh the position of wires connected to action block
+ def RefreshConnected(self, exclude = []):
+ self.Input.MoveConnected(exclude)
+
+ # Returns input action block connector
+ def GetConnector(self, position = None):
+ return self.Input
+
+ # Test if point given is on action block input connector
+ def TestConnector(self, pt, exclude = True):
+ # Test input connector
+ if self.Input.TestPoint(pt, exclude):
+ return self.Input
+ return None
+
+ # Changes the action block actions
+ def SetActions(self, actions):
+ dc = wxClientDC(self.Parent)
+ self.Actions = actions
+ self.ColSize = [0, 0, 0]
+ for action in self.Actions:
+ width, height = dc.GetTextExtent(action["qualifier"])
+ self.ColSize[0] = max(self.ColSize[0], width + 10)
+ if "duration" in action:
+ width, height = dc.GetTextExtent("T#%s"%action["duration"])
+ self.ColSize[0] = max(self.ColSize[0], width + 10)
+ width, height = dc.GetTextExtent(action["value"])
+ self.ColSize[1] = max(self.ColSize[1], width + 10)
+ if "indicator" in action and action["indicator"] != "":
+ width, height = dc.GetTextExtent(action["indicator"])
+ self.ColSize[2] = max(self.ColSize[2], width + 10)
+ self.Size = wxSize(max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
+ self.Size[0]), len(self.Actions) * SFC_ACTION_MIN_SIZE[1])
+ self.RefreshBoundingBox()
+ if self.Input:
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ input_block = wires[0][0].EndConnected.GetParentBlock()
+ input_block.RefreshOutputPosition()
+ input_block.RefreshOutputModel(True)
+
+ # Returns the action block actions
+ def GetActions(self):
+ return self.Actions
+
+ # Returns the action block minimum size
+ def GetMinSize(self):
+ return SFC_ACTION_MIN_SIZE[0], len(self.Actions) * SFC_ACTION_MIN_SIZE[1]
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, scaling):
+ # Edit the action block properties
+ self.Parent.EditActionBlockContent(self)
+
+ # Refreshes the action block state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_MOVE:
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ input_pos = wires[0][0].EndConnected.GetPosition(False)
+ if self.Pos.x - input_pos.x + movex >= SFC_WIRE_MIN_SIZE:
+ self.Move(movex, 0)
+ else:
+ Graphic_Element.ProcessDragging(self, movex, movey)
+
+ # Refreshes the action block model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshActionBlockModel(self)
+
+ # Draws divergence
+ def Draw(self, dc):
+ dc.SetPen(wxBLACK_PEN)
+ dc.SetBrush(wxWHITE_BRUSH)
+ colsize = [self.ColSize[0], self.Size[0] - self.ColSize[0] - self.ColSize[2], self.ColSize[2]]
+ # Draw plain rectangle for representing the action block
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ dc.DrawLine(self.Pos.x + colsize[0], self.Pos.y,
+ self.Pos.x + colsize[0], self.Pos.y + self.Size[1])
+ dc.DrawLine(self.Pos.x + colsize[0] + colsize[1], self.Pos.y,
+ self.Pos.x + colsize[0] + colsize[1], self.Pos.y + self.Size[1])
+ for i, action in enumerate(self.Actions):
+ if i != 0:
+ dc.DrawLine(self.Pos.x, self.Pos.y + i * SFC_ACTION_MIN_SIZE[1],
+ self.Pos.x + self.Size[0], self.Pos.y + i * SFC_ACTION_MIN_SIZE[1])
+ text_width, text_height = dc.GetTextExtent(action["qualifier"])
+ if "duration" in action:
+ dc.DrawText(action["qualifier"], self.Pos.x + (colsize[0] - text_width) / 2,
+ self.Pos.y + i * SFC_ACTION_MIN_SIZE[1] + SFC_ACTION_MIN_SIZE[1] / 2 - text_height)
+ text_width, text_height = dc.GetTextExtent("T#%s"%action["duration"])
+ dc.DrawText("T#%s"%action["duration"], self.Pos.x + (colsize[0] - text_width) / 2,
+ self.Pos.y + i * SFC_ACTION_MIN_SIZE[1] + SFC_ACTION_MIN_SIZE[1] / 2)
+ else:
+ dc.DrawText(action["qualifier"], self.Pos.x + (colsize[0] - text_width) / 2,
+ self.Pos.y + i * SFC_ACTION_MIN_SIZE[1] + (SFC_ACTION_MIN_SIZE[1] - text_height) / 2)
+ text_width, text_height = dc.GetTextExtent(action["value"])
+ dc.DrawText(action["value"], self.Pos.x + colsize[0] + (colsize[1] - text_width) / 2,
+ self.Pos.y + i * SFC_ACTION_MIN_SIZE[1] + (SFC_ACTION_MIN_SIZE[1] - text_height) / 2)
+ if "indicator" in action:
+ text_width, text_height = dc.GetTextExtent(action["indicator"])
+ dc.DrawText(action["indicator"], self.Pos.x + colsize[0] + colsize[1] + (colsize[2] - text_width) / 2,
+ self.Pos.y + i * SFC_ACTION_MIN_SIZE[1] + (SFC_ACTION_MIN_SIZE[1] - text_height) / 2)
+ # Draw input connector
+ self.Input.Draw(dc)
+ Graphic_Element.Draw(self, dc)
diff -r 000000000000 -r b622defdfd98 graphics/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/__init__.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,29 @@
+#!/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
+
+# Package initialisation
+
+from GraphicCommons import *
+from FBD_Objects import *
+from LD_Objects import *
\ No newline at end of file
diff -r 000000000000 -r b622defdfd98 minixsv/datatypes.xsd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/datatypes.xsd Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]>
+
+
+
+
+
+
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation>
+ elements) below, is not normative, but rather highlights important
+ aspects of the W3C Recommendation of which this is a part
+
+
+
+
+
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic. Note in
+ particular that there is no type named 'anySimpleType'. The
+ primitives should really be derived from no type at all, and
+ anySimpleType should be derived as a union of all the primitives.
+
+
+
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Now the derived primitive types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 1766
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 7 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 5 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 4 from the Namespaces in XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #all or (possibly empty) subset of {restriction, union, list}
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Can be restricted to required or forbidden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required at the top level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Forbidden when nested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/datatypes2.xsd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/datatypes2.xsd Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]>
+
+
+
+
+
+
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation>
+ elements) below, is not normative, but rather highlights important
+ aspects of the W3C Recommendation of which this is a part
+
+
+
+
+
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic. Note in
+ particular that there is no type named 'anySimpleType'. The
+ primitives should really be derived from no type at all, and
+ anySimpleType should be derived as a union of all the primitives.
+
+
+
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Now the derived primitive types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 1766
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 7 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 5 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 4 from the Namespaces in XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #all or (possibly empty) subset of {restriction, union, list}
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Can be restricted to required or forbidden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required at the top level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Forbidden when nested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/elemtreeif.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/elemtreeif.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,217 @@
+#
+# minixsv, Release 0.3
+# file: elemtreeif.py
+#
+# XML interface class to elementtree toolkit by Fredrik Lundh
+#
+# history:
+# 2004-09-09 rl created
+# 2004-09-22 rl XML interface classes completely re-designed
+# 2004-09-23 rl added filename and line number support
+# 2004-09-29 rl URL processing added
+# 2004-10-01 rl URL processing improved
+# 2004-10-12 rl XML text processing added
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+
+import urllib
+import urlparse
+from xmlifbase import *
+from xml.parsers.expat import ExpatError
+from elementtree.ElementTree import *
+
+#########################################################
+# Derived interface class for elementtree toolkit
+
+class ElementTreeInterface (XmlInterfaceBase):
+ #####################################################
+ # for description of the interface methods see xmlifbase.py
+ #####################################################
+
+ def __init__ (self, verbose):
+ XmlInterfaceBase.__init__ (self, verbose)
+ if self.verbose:
+ print "Using elementtree interface module..."
+
+
+ def parse (self, file, baseUrl):
+ url = self._convertToUrl(file)
+ absUrl = self._convertToAbsUrl (url, baseUrl)
+ fp = urllib.urlopen (absUrl)
+ try:
+ tree = ElementTree()
+ parser = ExtXMLTreeBuilder(file, url, absUrl)
+ tree.parse(fp, parser)
+ finally:
+ fp.close()
+
+ return TreeWrapper(self, tree)
+
+
+ def parseString (self, text):
+ parser = ExtXMLTreeBuilder("", "", "")
+ parser.feed(text)
+ return TreeWrapper (self, ElementTree(parser.close()))
+
+
+#########################################################
+# Wrapper class for ElementTree class
+
+class TreeWrapper (TreeWrapperBase):
+
+ def getRootNode (self):
+ return ElementWrapper(self.xmlIf, self, self.tree.getroot())
+
+
+ def insertSubtree (self, nextSiblingWrapper, file, baseUrl):
+ subTreeWrapper = self.xmlIf.extParse (file, baseUrl)
+ assert self.getRootNode().getTagName() == subTreeWrapper.getRootNode().getTagName(),\
+ "%s: line %d: root node of include file %s does not match!" %(self.getRootNode().getUrl(), self.getRootNode().getStartLineNumber(), file)
+
+ if nextSiblingWrapper != None:
+ insertIndex = self.tree.getroot().getchildren().index (nextSiblingWrapper.element)
+ else:
+ insertIndex = 0
+ elementWrapperList = subTreeWrapper.getRootNode().getChildren()
+ elementWrapperList.reverse()
+ for elementWrapper in elementWrapperList:
+ self.tree.getroot().insert (insertIndex, elementWrapper.element)
+
+
+#########################################################
+# Wrapper class for Element class
+
+class ElementWrapper (ElementWrapperBase):
+
+ def getTagName (self):
+ return self.element.tag
+
+
+ def getChildren (self, filterTag=None):
+ if filterTag in (None, '*'):
+ children = self.element.getchildren()
+ else:
+ children = self.element.findall(filterTag)
+
+ return map(lambda element: ElementWrapper(self.xmlIf, self.treeWrapper, element), children)
+
+
+ def getFirstChild (self, filterTag=None):
+ # replace base method (performance optimized)
+ if filterTag in (None, '*'):
+ children = self.element.getchildren()
+ if children != []:
+ element = children[0]
+ else:
+ element = None
+ else:
+ element = self.element.find(filterTag)
+
+ if element != None:
+ return ElementWrapper(self.xmlIf, self.treeWrapper, element)
+ else:
+ return None
+
+
+ def getElementsByTagName (self, filterTag=None):
+ if filterTag == '*':
+ filterTag = None
+ elements = self.element.getiterator (filterTag)
+
+ return map(lambda element: ElementWrapper(self.xmlIf, self.treeWrapper, element), elements)
+
+
+ def removeChild (self, childElementWrapper):
+ self.element.remove (childElementWrapper.element)
+
+
+ def getAttributeDict (self):
+ return self.element.attrib
+
+
+ def getAttribute (self, attributeName):
+ if self.element.attrib.has_key(attributeName):
+ return self.element.attrib[attributeName]
+ else:
+ return None
+
+
+ def hasAttribute (self, attributeName):
+ return self.element.attrib.has_key(attributeName)
+
+
+ def setAttribute (self, attributeName, attributeValue):
+ self.element.attrib[attributeName] = attributeValue
+
+
+ ###############################################################
+ # Caution! Currently returns only the element value until the first
+ # child element!!
+ def getElementValue (self):
+ if self.element.text != None:
+ return self.element.text
+ else:
+ return ""
+
+
+ ###############################################################
+ # Caution! Currently only sets the element value until the first
+ # child element!!
+ def setElementValue (self, value):
+ self.element.text = value
+
+
+###################################################
+# Element tree builder class derived from XMLTreeBuilder
+# extended to store related line numbers in the Element object
+
+class ExtXMLTreeBuilder (XMLTreeBuilder):
+ def __init__(self, filePath, url, absUrl):
+ self.filePath = filePath
+ self.url = url
+ self.absUrl = absUrl
+ XMLTreeBuilder.__init__(self)
+
+ def _start(self, tag, attrib_in):
+ element = XMLTreeBuilder._start(self, tag, attrib_in)
+ element.filePath = self.filePath
+ element.url = self.url
+ element.absUrl = self.absUrl
+ element.startLineNumber = self._parser.ErrorLineNumber
+ return element
+
+ def _end(self, tag):
+ element = XMLTreeBuilder._end(self, tag)
+ element.endLineNumber = self._parser.ErrorLineNumber
+ return element
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/minidomif.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/minidomif.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,218 @@
+#
+# # minixsv, Release 0.3
+# file: minidomif.py
+#
+# XML interface class to minidom
+#
+# history:
+# 2004-09-09 rl created
+# 2004-09-22 rl XML interface classes completely re-designed
+# 2004-09-23 rl added filename and line number support
+# 2004-09-29 rl URL processing added
+# 2004-10-01 rl URL processing improved
+# 2004-10-12 rl XML text processing added
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+import urllib
+from xmlifbase import *
+from xml.parsers.expat import ExpatError
+from xml.dom.expatbuilder import ExpatBuilderNS
+from xml.dom.minidom import *
+
+
+class MiniDomInterface (XmlInterfaceBase):
+ #####################################################
+ # for description of the interface methods see xmlifbase.py
+ #####################################################
+
+ def __init__ (self, verbose):
+ XmlInterfaceBase.__init__ (self, verbose)
+ if self.verbose:
+ print "Using minidom interface module..."
+
+
+ def parse (self, file, baseUrl):
+ url = self._convertToUrl(file)
+ absUrl = self._convertToAbsUrl (url, baseUrl)
+ fp = urllib.urlopen (absUrl)
+ try:
+ builder = ExtExpatBuilderNS(file, url, absUrl)
+ tree = builder.parseFile(fp)
+ finally:
+ fp.close()
+
+ return TreeWrapper(self, tree)
+
+
+ def parseString (self, text):
+ builder = ExtExpatBuilderNS("", "", "")
+ tree = builder.parseString (text)
+ return TreeWrapper (self, tree)
+
+
+#########################################################
+# Wrapper class for minidom.Document class
+
+class TreeWrapper (TreeWrapperBase):
+
+ def getRootNode (self):
+ return ElementWrapper (self.xmlIf, self, self.tree.documentElement)
+
+ def insertSubtree (self, nextSiblingWrapper, file, baseUrl):
+ subTreeWrapper = self.xmlIf.extParse (file, baseUrl)
+ assert self.getRootNode().getLocalName() == subTreeWrapper.getRootNode().getLocalName(),\
+ "%s: line %d: root node of include file %s does not match!" %(self.getRootNode().getUrl(), self.getRootNode().getStartLineNumber(), file)
+
+ elementWrapperList = subTreeWrapper.getRootNode().getChildren()
+ for elementWrapper in elementWrapperList:
+ if nextSiblingWrapper != None:
+ self.tree.documentElement.insertBefore(elementWrapper.element, nextSiblingWrapper.element)
+ else:
+ self.tree.documentElement.appendChild(elementWrapper.element)
+
+
+#########################################################
+# Wrapper class for minidom.Document class
+
+class ElementWrapper (ElementWrapperBase):
+
+ def getTagName (self):
+ return self.element.tagName
+
+
+ def getLocalName (self):
+ # base method replaced (optimized method)
+ return self.element.localName
+
+
+ def getNamespaceURI (self):
+ # base method replaced (optimized method)
+ return self.element.namespaceURI
+
+
+ def getChildren (self, filterTag=None):
+ return map(lambda element: ElementWrapper(self.xmlIf, self.treeWrapper, element), self._getElemNodes(filterTag))
+
+
+ def getElementsByTagName (self, filterTag=None):
+ if filterTag == None:
+ filterTag = '*'
+ return map(lambda element: ElementWrapper(self.xmlIf, self.treeWrapper, element), self.element.getElementsByTagName(filterTag))
+
+
+ def removeChild (self, childElementWrapper):
+ self.element.removeChild(childElementWrapper.element)
+
+
+ def getAttributeDict (self):
+ """Return dictionary of attributes"""
+ attribDict = {}
+ for attrName, attrValue in self.element.attributes.items():
+ attribDict[attrName] = attrValue
+ return attribDict
+
+
+ def getAttribute (self, attributeName):
+ if self.element.attributes.has_key (attributeName):
+ return self.element.getAttribute (attributeName)
+ else:
+ return None
+
+
+ def hasAttribute (self, attributeName):
+ return self.element.attributes.has_key (attributeName)
+
+
+ def setAttribute (self, attributeName, attributeValue):
+ if not self.hasAttribute(attributeName):
+ self.element.setAttributeNode (self.treeWrapper.tree.createAttribute (attributeName))
+ self.element.setAttribute(attributeName, attributeValue)
+
+
+ ###############################################################
+ # Caution! Currently returns only the first text node!!
+ def getElementValue (self):
+ textNodes = self._getTextNodes ()
+ if textNodes:
+ return textNodes[0].data
+ else:
+ return ""
+
+
+ ###############################################################
+ # Caution! Currently only sets the first text node!!
+ def setElementValue (self, value):
+ textNodes = self._getTextNodes()
+ if textNodes:
+ textNodes[0].data = value
+
+
+ ###############################################################
+ # PRIVATE methods
+ ###############################################################
+
+ def _getTextNodes (self):
+ """Return list of TEXT nodes."""
+ return filter (lambda e: (e.nodeType == Node.TEXT_NODE), # - only TEXT-NODES
+ self.element.childNodes) # from element's child nodes
+
+
+ def _getElemNodes (self, filterTag):
+ """Return list of ELEMENT nodes and check for tag name (if given)."""
+ return filter (lambda e: (e.nodeType == Node.ELEMENT_NODE) and # - only ELEMENTs
+ (not filterTag or filterTag == '*' or e.tagName == filterTag), # - if tagName given --> check tagName
+ self.element.childNodes) # from element's nodes
+
+
+
+###################################################
+# Expat builder class derived from ExpatBuilderNS
+# extended to store related line numbers in the node object
+
+class ExtExpatBuilderNS (ExpatBuilderNS):
+ def __init__ (self, filePath, url, absUrl):
+ self.filePath = filePath
+ self.url = url
+ self.absUrl = absUrl
+ ExpatBuilderNS.__init__(self)
+
+ def start_element_handler(self, name, attributes):
+ ExpatBuilderNS.start_element_handler(self, name, attributes)
+ self.curNode.url = self.url
+ self.curNode.absUrl = self.absUrl
+ self.curNode.filePath = self.filePath
+ self.curNode.startLineNumber = self.getParser().ErrorLineNumber
+
+ def end_element_handler(self, name):
+ self.curNode.endLineNumber = self.getParser().ErrorLineNumber
+ ExpatBuilderNS.end_element_handler(self, name)
+
diff -r 000000000000 -r b622defdfd98 minixsv/pyxsval.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/pyxsval.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,283 @@
+#
+# minixsv, Release 0.3
+# file: pyxsval.py
+#
+# API for XML schema validator
+#
+# history:
+# 2004-09-09 rl created
+# 2004-09-29 rl adapted to re-designed XML interface classes,
+# ErrorHandler separated, URL processing added, some bugs fixed
+# 2004-10-07 rl Validator classes extracted into separate files
+# 2004-10-12 rl API re-worked, XML text processing added
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+__all__ = [
+ # public symbols
+ "parseAndValidate",
+ "parseAndValidateString",
+ "parseAndValidateXmlInput",
+ "parseAndValidateXmlInputString",
+ "parseAndValidateXmlSchema",
+ "parseAndValidateXmlSchemaString",
+ "XsValidator",
+ "XMLIF_MINIDOM",
+ "XMLIF_ELEMENTTREE"
+ ]
+
+
+from xsvalErrorHandler import *
+from xsvalBase import XsValBase
+from xsvalSchema import XsValSchema
+
+
+XMLIF_MINIDOM = "XMLIF_MINIDOM"
+XMLIF_ELEMENTTREE = "XMLIF_ELEMENTTREE"
+
+
+_XS_VAL_DEFAULT_ERROR_LIMIT = 20
+
+_OWNDIR = os.path.dirname(__file__)
+
+
+########################################
+# convenience function for validating
+# 1. XML schema file
+# 2. XML input file
+# If xsdFile is specified, it will be used for validation
+# If xsdFile=None, the schemaLocation attribute is expected in the root tag of the XML input file
+#
+def parseAndValidate (inputFile, xsdFile=None, **kw):
+ return parseAndValidateXmlInput (inputFile, xsdFile, 1, **kw)
+
+
+########################################
+# convenience function for validating
+# 1. text string containing the XML schema
+# 2. text string containing the XML input
+# If xsdText is given, it will be used for validation
+# If xsdText=None, the schemaLocation attribute is expected in the root tag of the XML input
+#
+def parseAndValidateString (inputText, xsdText=None, **kw):
+ return parseAndValidateXmlInputString (inputText, xsdText, 1, **kw)
+
+
+########################################
+# factory for validating
+# 1. XML schema file (only if validateSchema=1)
+# 2. XML input file
+# If xsdFile is specified, it will be used for validation
+# If xsdFile=None, the schemaLocation attribute is expected in the root tag of the XML input file
+#
+def parseAndValidateXmlInput (inputFile, xsdFile=None, validateSchema=0, **kw):
+ xsValidator = XsValidator (**kw)
+ # parse XML input file
+ inputTreeWrapper = xsValidator.parse (inputFile)
+ # validate XML input file
+ return xsValidator.validateXmlInput (inputFile, inputTreeWrapper, xsdFile, validateSchema)
+
+
+########################################
+# factory for validating
+# 1. text string containing the XML schema (only if validateSchema=1)
+# 2. text string containing the XML input
+# If xsdText is given, it will be used for validation
+# If xsdText=None, the schemaLocation attribute is expected in the root tag of the XML input
+#
+def parseAndValidateXmlInputString (inputText, xsdText=None, validateSchema=0, **kw):
+ xsValidator = XsValidator (**kw)
+ # parse XML input text string
+ inputTreeWrapper = xsValidator.parseString (inputText)
+ # validate XML input text string
+ return xsValidator.validateXmlInputString (inputTreeWrapper, xsdText, validateSchema)
+
+
+########################################
+# factory for validating only given XML schema file
+#
+def parseAndValidateXmlSchema (xsdFile, **kw):
+ xsValidator = XsValidator (**kw)
+ # parse XML schema file
+ xsdTreeWrapper = xsValidator.parse (xsdFile)
+ # validate XML schema file
+ return xsValidator.validateXmlSchema (xsdFile, xsdTreeWrapper)
+
+
+########################################
+# factory for validating only given XML schema file
+#
+def parseAndValidateXmlSchemaString (xsdText, **kw):
+ xsValidator = XsValidator (**kw)
+ # parse XML schema
+ xsdTreeWrapper = xsValidator.parseString (xsdText)
+ # validate XML schema
+ return xsValidator.validateXmlSchema ("", xsdTreeWrapper)
+
+
+########################################
+# XML schema validator class
+#
+class XsValidator:
+ def __init__(self, xmlIfClass=XMLIF_MINIDOM,
+ warningProc=IGNORE_WARNINGS, errorLimit=_XS_VAL_DEFAULT_ERROR_LIMIT, verbose=0):
+
+ self.warningProc = warningProc
+ self.errorLimit = errorLimit
+ self.verbose = verbose
+
+ # enable interface to minidom
+ if xmlIfClass == XMLIF_MINIDOM:
+ import minidomif
+ self.xmlIf = minidomif.MiniDomInterface(verbose)
+
+ # enable interface to elementtree
+ elif xmlIfClass == XMLIF_ELEMENTTREE:
+ import elemtreeif
+ self.xmlIf = elemtreeif.ElementTreeInterface(verbose)
+
+ # create error handler
+ self.errorHandler = ErrorHandler (errorLimit, warningProc, verbose)
+
+
+ ########################################
+ # parse XML file
+ # 'file' may be a filepath or an URI
+ #
+ def parse (self, file, baseUrl=""):
+ try:
+ self._verbosePrint ("Parsing %s..." %(file))
+ treeWrapper = self.xmlIf.extParse(file, baseUrl)
+ except AssertionError, errstr:
+ self.errorHandler.raiseError (errstr)
+ return treeWrapper
+
+
+ ########################################
+ # parse text string containing XML
+ #
+ def parseString (self, text):
+ try:
+ self._verbosePrint ("Parsing XML text string...")
+ treeWrapper = self.xmlIf.extParseString(text)
+ except AssertionError, errstr:
+ self.errorHandler.raiseError (errstr)
+ return treeWrapper
+
+
+ ########################################
+ # validate XML input
+ #
+ def validateXmlInput (self, xmlInputFile, inputTreeWrapper, xsdFile=None, validateSchema=0):
+ # parse XML schema file
+ if xsdFile != None:
+ xsdTreeWrapper = self.parse (xsdFile)
+ else:
+ # a schemaLocation attribute is expected in the root tag of the XML input file
+ xsdFile = self._retrieveReferencedXsdFile (inputTreeWrapper)
+ xsdTreeWrapper = self.parse (xsdFile, inputTreeWrapper.getRootNode().getAbsUrl())
+
+ return self._validateXmlInput (xmlInputFile, xsdFile, inputTreeWrapper, xsdTreeWrapper, validateSchema)
+
+
+ ########################################
+ # validate XML input
+ #
+ def validateXmlInputString (self, inputTreeWrapper, xsdText=None, validateSchema=0):
+ # parse XML schema file
+ if xsdText != None:
+ xsdFile = "schema text"
+ xsdTreeWrapper = self.parseString (xsdText)
+ else:
+ # a schemaLocation attribute is expected in the root tag of the XML input file
+ xsdFile = self._retrieveReferencedXsdFile (inputTreeWrapper)
+ xsdTreeWrapper = self.parse (xsdFile, inputTreeWrapper.getRootNode().getAbsUrl())
+
+ return self._validateXmlInput ("input text", xsdFile, inputTreeWrapper, xsdTreeWrapper, validateSchema)
+
+
+ ########################################
+ # validate XML schema separately
+ #
+ def validateXmlSchema (self, xsdFile, xsdTreeWrapper):
+ # parse minixsv internal schema
+ rulesTreeWrapper = self.parse(os.path.join (_OWNDIR, "xsStructs.xsd"))
+
+ self._verbosePrint ("Validating %s..." %(xsdFile))
+ xsvGivenXsdFile = XsValSchema (self.xmlIf, self.errorHandler)
+ xsvGivenXsdFile.validate(xsdTreeWrapper, rulesTreeWrapper)
+ self.errorHandler.flushOutput()
+ return xsdTreeWrapper
+
+
+ ########################################
+ # validate XML input tree and xsd tree if requested
+ #
+ def _validateXmlInput (self, xmlInputFile, xsdFile, inputTreeWrapper, xsdTreeWrapper, validateSchema=0):
+ # validate XML schema file if requested
+ if validateSchema:
+ self.validateXmlSchema (xsdFile, xsdTreeWrapper)
+
+ try:
+ xsdTreeWrapper.insertSubtree (xsdTreeWrapper.getRootNode().getFirstChild(), os.path.join (_OWNDIR, "datatypes2.xsd"), None)
+ except AssertionError, errstr:
+ self.errorHandler.raiseError (errstr)
+
+ self._verbosePrint ("Validating %s..." %(xmlInputFile))
+ xsvInputFile = XsValBase (self.xmlIf, self.errorHandler)
+ xsvInputFile.validate(inputTreeWrapper, xsdTreeWrapper)
+ self.errorHandler.flushOutput()
+ return inputTreeWrapper
+
+
+ ########################################
+ # retrieve XML schema location from XML input tree
+ #
+ def _retrieveReferencedXsdFile (self, inputTreeWrapper):
+ # a schemaLocation attribute is expected in the root tag of the XML input file
+ for attributeName, attributeValue in inputTreeWrapper.getRootNode().getAttributeDict().items():
+ if self.xmlIf.extractLocalName(attributeName) in ("noNamespaceSchemaLocation", "schemaLocation"):
+ xsdFile = attributeValue
+ return xsdFile
+ else:
+ self.errorHandler.raiseError ("No schema file specified!")
+
+
+ ########################################
+ # print if verbose flag is set
+ #
+ def _verbosePrint (self, text):
+ if self.verbose:
+ print text
+
+
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/readme.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/readme.txt Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,107 @@
+Release Notes for minixsv, Release 0.3
+
+minixsv is a XML schema validator written in "pure" Python.
+
+Currently only a subset of the XML schema standard is supported
+(list of limitations/restrictions see below).
+
+minixsv currently uses minidom or elementtree as XML parser
+Other parsers can easily be adapted by implementing an appropriate interface class
+Whitespaces inside strings are automatically normalized/collapsed as specified in the XML schema file
+Default/Fixed attributes are automatically inserted if not specified in the input file
+
+--------------------------------------------------------------------
+ The minixsv XML schema validator is
+
+ Copyright (c) 2004 by Roland Leuthe
+
+ By obtaining, using, and/or copying this software and/or its
+ associated documentation, you agree that you have read, understood,
+ and will comply with the following terms and conditions:
+
+ Permission to use, copy, modify, and distribute this software and
+ its associated documentation for any purpose and without fee is
+ hereby granted, provided that the above copyright notice appears in
+ all copies, and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the author not be used in advertising or publicity
+ pertaining to distribution of the software without specific, written
+ prior permission.
+
+ THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+ TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+ ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+ BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+ DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ OF THIS SOFTWARE.
+---------------------------------------------------------------------
+
+
+Limitations/restrictions of the current release:
+
+- No support of namespaces
+- Only first text node is validated/processed as Element content
+- No validation of predefined types decimal, float, double, date/time types, DURATION, anyURI, base64Binary
+- Only partial validation for language, QName, DTD types
+- No validation for facets of not validated predefined types (see above)
+- Derivation of complex types from simple types not supported
+- Mixed content (child nodes and text nodes) not supported
+- No check if no element content exists when xsi:nil = "true" is specified
+- No support of attributes "elementFormDefault", "attributeFormDefault", "form"
+- "import" element not supported
+- no check if derived type and base type match
+- no check of attributes "final", "finalDefault"
+- "redefine" element not supported
+- no support of substitution groups
+- no support of abstract elements and types
+- uniqueness of identity constraints (unique/key/keyref) is only checked on string base
+ (in case of element content only the first text node is evaluated)
+- "processContents" attribute not supported
+
+Note: This constraint list may not be complete!!
+
+
+---------------------------------------------------------------------
+
+Contents
+========
+
+readme.txt
+__init__.py
+pyxsval.py
+xsvalSchema.py
+xsvalBase.py
+xsvalSimpleTypes.py
+xsvalErrorHandler.py
+xmlifbase.py
+minidomif.py
+elemtreeif.py
+xsStructs.xsd
+datatypes.xsd
+datatypes2.xsd
+
+---------------------------------------------------------------------
+
+HISTORY:
+=======
+
+Changes to Release 0.2
+======================
+
+- API re-structured
+- XML text processing added
+- several bugs fixed
+- internal re-structuring
+
+
+Changes to Release 0.1
+======================
+
+- Error/warning outputs contain now also filename and linenumber
+- Basic URI support for include directive added
+- XML interface classes completely re-designed
+- several bugs fixed
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/xmlifbase.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xmlifbase.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,465 @@
+#
+# minixsv, Release 0.3
+# file: xmlifbase.py
+#
+# abstract XML interface class
+#
+# history:
+# 2004-09-09 rl created
+# 2004-09-22 rl XML interface classes completely re-designed
+# 2004-09-23 rl added filename and line number support
+# 2004-09-29 rl URL processing added
+# 2004-10-01 rl URL processing improved
+# 2004-10-03 rl xPath processing moved from pyxsval.py
+# 2004-10-12 rl XML text processing added
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+
+import string
+import os
+import re
+import urllib
+import urlparse
+
+
+_reSplitUrlApplication = re.compile (r"(file|http|ftp|gopher):(.+)") # "file:///d:\test.xml" => "file" + "///d:\test.xml"
+
+
+########################################
+# define XML interface base class
+# All not implemented methods have to be overloaded by the derived class!!
+#
+
+class XmlInterfaceBase:
+
+ def __init__(self, verbose):
+ self.verbose = verbose
+ pass
+
+ ##########################################################
+ # calls the parser for 'file'
+ # returns the respective XML tree for the parsed XML file
+ # 'file' may be a file path or an URI
+
+ def parse (self, file, baseUrl):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # calls the parser for 'text'
+ # returns the respective XML tree for the parsed XML text string
+
+ def parseString (self, text):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # calls the parser for 'file' and processes all include directives
+ # returns the respective XML tree wrapper for the parsed XML file
+
+ def extParse (self, file, baseUrl):
+ treeWrapper = self.parse (file, baseUrl)
+ return self._expandIncludes (treeWrapper)
+
+ def _expandIncludes (self, treeWrapper):
+ rootNodeWrapper = treeWrapper.getRootNode()
+ nsAlias = self.extractNamespaceAlias(rootNodeWrapper.getTagName())
+
+ for includeNodeWrapper in rootNodeWrapper.getChildrenNS(nsAlias, "include"):
+ includeUrl = includeNodeWrapper.getAttribute("schemaLocation")
+ if self.verbose:
+ print "including %s..." %(includeUrl)
+ treeWrapper.insertSubtree (includeNodeWrapper, includeUrl, rootNodeWrapper.getAbsUrl())
+ rootNodeWrapper.removeChild (includeNodeWrapper)
+ return treeWrapper
+
+ ##########################################################
+ # calls the parser for 'file' and processes all include directives
+ # returns the respective XML tree wrapper for the parsed XML file
+
+ def extParseString (self, text):
+ treeWrapper = self.parseString (text)
+ return self._expandIncludes (treeWrapper)
+
+
+ ##########################################################
+ # returns namespace part of given 'ncName'
+
+ def extractNamespaceAlias (self, ncName):
+ namespaceAlias, localName = self._splitNamespaceLocalName (ncName)
+ return namespaceAlias
+
+
+ ##########################################################
+ # returns local tag name of given 'ncName'
+
+ def extractLocalName (self, ncName):
+ namespaceAlias, localName = self._splitNamespaceLocalName (ncName)
+ return localName
+
+
+ ##########################################################
+ # add namespace alias to localName
+
+ def addNamespaceAlias (self, namespaceAlias, localName):
+ if namespaceAlias != "":
+ return namespaceAlias + localName
+ else:
+ return localName
+
+
+ ###############################################################
+ # PRIVATE methods
+ ###############################################################
+
+ ##########################################################
+ # convert input parameter 'file' into a valid URL
+
+ def _convertToUrl (self, file):
+ matchObject = _reSplitUrlApplication.match(file)
+ if matchObject:
+ # given file is an absolute URL
+ if matchObject.group(1) == 'file':
+ path = re.sub(':', '|', matchObject.group(2)) # replace ':' by '|' in the path string
+ url = "file:" + path
+ else:
+ url = file
+ elif not os.path.isabs(file):
+ # given file is a relative URL
+ url = file
+ else:
+ # given file is not a valid URL => treated as local filename
+ url = "file:" + urllib.pathname2url (file)
+
+ return url
+
+ def _convertToAbsUrl (self, url, baseUrl):
+ application = urlparse.urlsplit(url)[0]
+ if application == '':
+ if baseUrl != "":
+ url = urlparse.urljoin (baseUrl, url)
+ else:
+ url = "file:" + urllib.pathname2url (os.path.join(os.getcwd(), url))
+ return url
+
+ ##########################################################
+ # split 'ncName' into namespace and local name
+
+ def _splitNamespaceLocalName (self, ncName):
+ namespaceAlias = None
+
+ namespaceEndIndex = string.find (ncName, '}')
+ if namespaceEndIndex != -1:
+ namespaceAlias = ncName[:namespaceEndIndex+1]
+ localName = ncName[namespaceEndIndex+1:]
+ else:
+ namespaceEndIndex = string.find (ncName, ':')
+ if namespaceEndIndex != -1:
+ namespaceAlias = ncName[:namespaceEndIndex+1]
+ localName = ncName[namespaceEndIndex+1:]
+ else:
+ namespaceAlias = ""
+ localName = ncName
+ return namespaceAlias, localName
+
+
+
+########################################
+# define tree wrapper base class
+# All not implemented methods have to be overloaded by the derived class!!
+#
+
+class TreeWrapperBase:
+
+ def __init__(self, xmlIf, tree):
+ self.xmlIf = xmlIf
+ self.tree = tree
+
+ ##########################################################
+ # includes the given XML/XSD file 'file' into the XML tree 'tree'
+ # before 'nextSibling' ('nextSibling' is not removed!)
+ # returns the extended XML tree (containing included XML/XSD file)
+ #
+ # Note: Root tag of 'tree' and 'file' must match!
+
+ def insertSubtree (self, nextSibling, file, baseUrl):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns root node of given XML tree 'tree'
+
+ def getRootNode (self):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns 'tree' of given XML tree
+
+ def getTree (self):
+ return self.tree
+
+########################################
+# define node wrapper base class
+# All not implemented methods have to be overloaded by the derived class!!
+#
+
+class ElementWrapperBase:
+
+ def __init__(self, xmlIf, treeWrapper, element):
+ self.xmlIf = xmlIf
+ self.treeWrapper = treeWrapper
+ self.element = element
+
+ ##########################################################
+ # returns tag name of given XML node 'node'
+
+ def getTagName (self):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns child element nodes (list) of given XML node 'node'
+ # 'filterTag' is optional, 'filterTag' = '*' must be supported
+
+ def getChildren (self, filterTag=None):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns all descendants of node whose tag match 'tagName'
+ # 'filterTag' is optional, 'filterTag' = '*' must be supported
+
+ def getElementsByTagName (self, filterTag=None):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # remove given child node from children of current node
+
+ def removeChild (self, childNodeWrapper):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns dictionary with all attributes of this node
+
+ def getAttributeDict (self):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns attribute value of given attributeName
+ # or None if there is no suitable attribute
+
+ def getAttribute (self, attributeName):
+ raise NotImplementedError
+
+ ##########################################################
+ # returns 1 if attribute 'attributeName' exists
+ # 0 if not
+
+ def hasAttribute (self, attributeName):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # sets value of attribute 'attributeName' to 'attributeValue'
+ # if the attribute does not yet exist, it will be created
+
+ def setAttribute (self, attributeName, attributeValue):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns element value of this node
+
+ def getElementValue (self):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # sets element value of this node to 'value'
+
+ def setElementValue (self, value):
+ raise NotImplementedError
+
+
+ ##########################################################
+ # returns local tag name of given XML node 'node' (without namespace)
+
+ def getLocalName (self):
+ return self.xmlIf.extractLocalName (self.getTagName())
+
+
+ ##########################################################
+ # returns namespace of tag name of given XML node 'node'
+
+ def getNamespaceURI (self):
+ return extractNamespaceAlias (self.getTagName())
+
+
+ ##########################################################
+ # returns child element nodes (list) of given XML node 'node'
+ # 'filterTag' (localTagName) is optional, 'filterTag' = '*' must be supported
+ # 'namespaceAlias' has to contain corresponding namespace
+
+ def getChildrenNS (self, namespaceAlias, filterTag=None):
+ if not filterTag in (None, '*'):
+ filterTag = self.xmlIf.addNamespaceAlias(namespaceAlias, filterTag)
+ return self.getChildren(filterTag)
+
+
+ ##########################################################
+ # returns first child element of given XML node 'node'
+ # or None if there is no suitable child element
+ # 'filterTag' is optional, 'filterTag' = '*' must be supported
+
+ def getFirstChild (self, filterTag=None):
+ children = self.getChildren(filterTag)
+ if children != []:
+ return children[0]
+ else:
+ return None
+
+
+ ##########################################################
+ # returns first child element of given XML node 'node'
+ # or None if there is no suitable child element
+ # 'filterTag' (localTagName) is optional, 'filterTag' = '*' must be supported
+ # 'namespaceAlias' has to contain corresponding namespace
+
+ def getFirstChildNS (self, namespaceAlias, filterTag=None):
+ if not filterTag in (None, '*'):
+ filterTag = self.xmlIf.addNamespaceAlias(namespaceAlias, filterTag)
+ return self.getFirstChild(filterTag)
+
+
+ ##########################################################
+ # returns all descendants of node whose tag match 'localName' of the given namespace
+ # 'filterTag' (localTagName) is optional, 'filterTag' = '*' must be supported
+ # 'namespaceAlias' has to contain corresponding namespace
+
+ def getElementsByTagNameNS (self, namespaceAlias, filterTag=None):
+ if not filterTag in (None, '*'):
+ filterTag = self.xmlIf.addNamespaceAlias(namespaceAlias, filterTag)
+ return self.getElementsByTagName (filterTag)
+
+
+ ##########################################################
+ # returns attribute if it exists or default value if not
+
+ def getAttributeOrDefault (self, attributeName, default):
+ if self.hasAttribute (attributeName):
+ return self.getAttribute (attributeName)
+ else:
+ return default
+
+ ##########################################################
+ # returns the current start line number of the element node in the XML file
+
+ def getStartLineNumber (self):
+ return self.element.startLineNumber
+
+
+ ##########################################################
+ # returns the current end line number of the element node in the XML file
+
+ def getEndLineNumber (self):
+ return self.element.endLineNumber
+
+
+ ##########################################################
+ # returns the URL of the XML file the node belongs to
+
+ def getUrl (self):
+ return self.element.url
+
+ def getAbsUrl (self):
+ return self.element.absUrl
+
+
+ ##########################################################
+ # returns the file path of the XML file the node belongs to
+
+ def getFilePath (self):
+ return self.element.filePath
+
+
+ ########################################
+ # retrieve node list or attribute list for specified XPath
+
+ def getXPathList (self, xPath, defaultNamespace):
+ selectedNodeList = []
+ selectedAttributeList = []
+ xPathList = string.split (xPath, "|")
+ for xRelPath in xPathList:
+ descendantOrSelf = 0
+ if xRelPath[:3] == ".//":
+ descendantOrSelf = 1
+ xRelPath = xRelPath[3:]
+ # no namespaces supported!
+ xPathLocalStepList = map(lambda xPath: self.xmlIf.extractLocalName(xPath), string.split (xRelPath, "/"))
+ childList = [self,]
+ isAttributeList = 0
+ for localStep in xPathLocalStepList:
+ stepChildList = []
+ if localStep == "":
+ raise IOError ("Invalid xPath '%s'!" %(xRelPath))
+ elif localStep == ".":
+ continue
+ elif localStep[0] == '@':
+ for childNode in childList:
+ attrName = localStep[1:]
+ if attrName == '*':
+ stepChildList.extend(childNode.getAttributeDict().values())
+ elif childNode.hasAttribute(attrName):
+ stepChildList.append (childNode.getAttribute(attrName))
+ childList = stepChildList
+ isAttributeList = 1
+ else:
+ if descendantOrSelf:
+ descendantOrSelf = 0
+ stepChildList = self.getElementsByTagNameNS(defaultNamespace, localStep)
+ else:
+ for childNode in childList:
+ stepChildList.extend (childNode.getChildrenNS(defaultNamespace, localStep))
+ childList = stepChildList
+
+ if isAttributeList:
+ selectedAttributeList.extend (childList)
+ else:
+ selectedNodeList.extend (childList)
+ return selectedNodeList, selectedAttributeList
+
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/xsStructs.xsd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xsStructs.xsd Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]>
+
+
+
+
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation> elements)
+ below, is not normative, but rather highlights important aspects of
+ the W3C Recommendation of which this is a part
+
+
+
+
+ The simpleType element and all of its members are defined
+ in datatypes.xsd
+
+
+
+
+
+
+
+ Get access to the xml: attribute groups for xml:lang
+ as declared on 'schema' and 'documentation' below
+
+
+
+
+
+
+
+ This type is extended by almost all schema types
+ to allow attributes from other namespaces to be
+ added to user schemas.
+
+
+
+
+
+
+
+
+
+
+
+
+ This type is extended by all types which allow annotation
+ other than <schema> itself
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which occur freely at the top level of schemas.
+ All of their types are based on the "annotated" type by extension.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which can self-redefine (see <redefine> below).
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+ #all or (possibly empty) subset of {extension, restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ for maxOccurs
+
+
+
+
+
+
+
+
+
+
+
+ for all particles
+
+
+
+
+
+
+ for element, group and attributeGroup,
+ which both define and reference
+
+
+
+
+
+
+
+ 'complexType' uses this
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This branch is short for
+ <complexContent>
+ <restriction base="xs:anyType">
+ ...
+ </restriction>
+ </complexContent>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Will be restricted to required or forbidden
+
+
+
+
+
+
+ Not allowed if simpleContent child is chosen.
+ May be overriden by setting on complexContent child.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Overrides any setting on complexType parent.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No typeDefParticle group reference
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {substitution, extension,
+ restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The element element can be used either
+ at the top level to define an element-type binding globally,
+ or within a content model to either reference a globally-defined
+ element or type or declare an element-type binding locally.
+ The ref form is not allowed at the top level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for explicit groups, named top-level groups and
+ group references
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should derive this from realGroup, but too complicated
+ for now
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for the three kinds of group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ restricted max/min
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Only elements allowed inside
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ simple type for the value of the 'namespace' attr of
+ 'any' and 'anyAttribute'
+
+
+
+ Value is
+ ##any - - any non-conflicting WFXML/attribute at all
+
+ ##other - - any non-conflicting WFXML/attribute from
+ namespace other than targetNS
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+
+ one or - - any non-conflicting WFXML/attribute from
+ more URI the listed namespaces
+ references
+ (space separated)
+
+ ##targetNamespace or ##local may appear in the above list, to
+ refer to the targetNamespace of the enclosing
+ schema or an absent targetNamespace respectively
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in selectors
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the following EBNF:
+ Selector ::= Path ( '|' Path )*
+ Path ::= ('.//')? Step ( '/' Step )*
+ Step ::= '.' | NameTest
+ NameTest ::= QName | '*' | NCName ':' '*'
+ child:: is also allowed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in fields
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the same EBNF as for selector,
+ with the following change:
+ Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The three kinds of identity constraints, all with
+ type of or derived from 'keybase'.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ A public identifier, per ISO 8879
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ notations for use within XML Schema schemas
+
+
+
+
+
+
+
+
+ Not the real urType, but as close an approximation as we can
+ get in the XML representation
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/xsvalBase.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xsvalBase.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,602 @@
+#
+# minixsv, Release 0.3
+# file: xsvalBase.py
+#
+# XML schema validator base class
+#
+# history:
+# 2004-10-07 rl created
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+from xsvalErrorHandler import *
+import xsvalSimpleTypes
+
+###########################################################
+# Validator class for validating one input file against one XML schema file
+
+class XsValBase:
+
+ def __init__(self, xmlIf, errorHandler):
+ self.xmlIf = xmlIf
+ self.errorHandler = errorHandler
+
+ self._raiseError = self.errorHandler.raiseError
+ self._addError = self.errorHandler.addError
+ self._addWarning = self.errorHandler.addWarning
+
+ self.checkKeyrefList = []
+
+
+ ########################################
+ # validate inputTree against xsdTree
+ #
+ def validate (self, inputTree, xsdTree):
+ self.inputTree = inputTree
+ self.xsdTree = xsdTree
+
+ self.xsdRoot = self.xsdTree.getRootNode()
+ self.xsdNSAlias = self.xmlIf.extractNamespaceAlias(self.xsdRoot.getTagName())
+ self.inputRoot = self.inputTree.getRootNode()
+ self.inputNSAlias = self.xmlIf.extractNamespaceAlias(self.inputRoot.getTagName())
+
+ self.simpleTypeVal = xsvalSimpleTypes.XsSimpleTypeVal(self)
+
+ self._setupLookupTables()
+
+ inputRootTagName = self.inputRoot.getTagName()
+ inputRootLocalName = self.inputRoot.getLocalName()
+ if self.xsdElementDict.has_key(inputRootLocalName):
+ # start recursive schema validation
+ try:
+ self._checkElementTag (self.xsdElementDict[inputRootLocalName], inputRootTagName, (self.inputRoot,), 0)
+ except TagException, errInst:
+ self._addError (errInst.errstr, errInst.node, errInst.endTag)
+
+ # validate keyrefs
+ for inputElement, keyrefNode in self.checkKeyrefList:
+ self._checkKeyRefConstraint (keyrefNode, inputElement)
+ else:
+ self._raiseError ("Used root tag %s not found in schema file!" %(inputRootTagName), self.inputRoot)
+
+
+ ########################################
+ # setup lookup dictionaries used during validation
+ #
+ def _setupLookupTables (self):
+ # retrieve all elements
+ self.xsdElementDict = {}
+ for xsdTypeNode in self.xsdRoot.getChildrenNS(self.xsdNSAlias, "element"):
+ self.xsdElementDict[xsdTypeNode.getAttribute("name")] = xsdTypeNode
+
+ # retrieve all type definitions (complex and simple types)
+ self.xsdTypeDict = {}
+ for xsdTypeNode in self.xsdRoot.getChildrenNS(self.xsdNSAlias, "complexType"):
+ self.xsdTypeDict[xsdTypeNode.getAttribute("name")] = xsdTypeNode
+
+ for xsdTypeNode in self.xsdRoot.getChildrenNS(self.xsdNSAlias, "simpleType"):
+ self.xsdTypeDict[xsdTypeNode.getAttribute("name")] = xsdTypeNode
+
+ # retrieve all group definitions
+ self.xsdGroupDict = {}
+ for xsdTypeNode in self.xsdRoot.getChildrenNS(self.xsdNSAlias, "group"):
+ self.xsdGroupDict[xsdTypeNode.getAttribute("name")] = xsdTypeNode
+
+ # retrieve all attribute group definitions
+ self.xsdAttributeGroupDict = {}
+ for xsdTypeNode in self.xsdRoot.getChildrenNS(self.xsdNSAlias, "attributeGroup"):
+ self.xsdAttributeGroupDict[xsdTypeNode.getAttribute("name")] = xsdTypeNode
+
+ # retrieve all identity constraints
+ self.xsdIdentityConstrDict = {}
+ for identConstrTagName in ("unique", "key", "keyref"):
+ identConstrNodeList = self.xsdRoot.getElementsByTagNameNS (self.xsdNSAlias, identConstrTagName)
+ for identConstrNode in identConstrNodeList:
+ identConstrName = identConstrNode.getAttribute("name")
+ if not self.xsdIdentityConstrDict.has_key(identConstrName):
+ self.xsdIdentityConstrDict[identConstrName] = {"Node": identConstrNode, "ValueDict":{}}
+ else:
+ self._raiseError ("Duplicate identity constraint name '%s' found in schema definition!" %(identConstrName), identConstrNode)
+
+
+ ########################################
+ # validate inputElement against complexType node
+ #
+ def _checkComplexTypeTag (self, xsdParentElementNode, xsdElement, inputElement, inputElementChildIndex, usedAsBaseType=None):
+ baseTypeAttributes = {"__SPECIAL_ATTRS__":{}}
+ if xsdParentElementNode.getAttribute ("nillable") == "true":
+ baseTypeAttributes["__SPECIAL_ATTRS__"]["nil"] = "xsd:boolean"
+
+ complexContentElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "complexContent")
+ if complexContentElement != None:
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexContentTag (xsdParentElementNode, complexContentElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ else:
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexTypeContent (xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ return inputElementChildIndex, baseTypeAttributes
+
+ def _checkComplexContentTag (self, xsdParentElementNode, xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes):
+ extensionElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "extension")
+ if extensionElement != None:
+ inputElementChildIndex, baseTypeAttributes = self._checkExtensionComplexContent (xsdParentElementNode, extensionElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ else:
+ restrictionElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "restriction")
+ if restrictionElement != None:
+ inputElementChildIndex, baseTypeAttributes = self._checkRestrictionComplexContent (xsdParentElementNode, restrictionElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ return inputElementChildIndex, baseTypeAttributes
+
+ def _checkExtensionComplexContent (self, xsdParentElementNode, xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes):
+ baseType = self.xmlIf.extractLocalName(xsdElement.getAttribute("base"))
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexTypeTag (xsdParentElementNode, self.xsdTypeDict[baseType], inputElement, inputElementChildIndex, "extension")
+
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexTypeContent (xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ return inputElementChildIndex, baseTypeAttributes
+
+ def _checkRestrictionComplexContent (self, xsdParentElementNode, xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes):
+ # first check against base type (retrieve only the base type attributes)
+ baseType = self.xmlIf.extractLocalName(xsdElement.getAttribute("base"))
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexTypeTag (xsdParentElementNode, self.xsdTypeDict[baseType], inputElement, inputElementChildIndex, "restriction")
+
+ # then check input against derived complex type
+ inputElementChildIndex, baseTypeAttributes = self._checkComplexTypeContent (xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes)
+ return inputElementChildIndex, baseTypeAttributes
+
+ def _checkComplexTypeContent (self, xsdElement, inputElement, inputElementChildIndex, usedAsBaseType, baseTypeAttributes):
+ inputTagName = inputElement.getTagName()
+ childTags = inputElement.getChildren()
+ if usedAsBaseType in (None, "extension"):
+ validChildTags = xsdElement.getChildren()
+ for validChildTag in validChildTags:
+ if validChildTag.getLocalName() not in ("attribute", "attributeGroup", "anyAttribute"):
+ inputElementChildIndex = self._checkParticle (validChildTag, inputElement, childTags, inputElementChildIndex)
+
+ if usedAsBaseType == None and inputElementChildIndex < len (childTags):
+ self._addError ("Unexpected child tag '%s' in tag '%s' found!" %(childTags[inputElementChildIndex].getTagName(), inputTagName), childTags[inputElementChildIndex])
+
+ if usedAsBaseType in (None,):
+ self._checkAttributeTags (xsdElement, inputElement, baseTypeAttributes)
+
+ if usedAsBaseType in ("restriction", "extension"):
+ validAttributes = xsdElement.getChildrenNS(self.xsdNSAlias, "attribute")
+ for validAttrGroup in xsdElement.getChildrenNS(self.xsdNSAlias, "attributeGroup"):
+ attributeGroupRef = self.xmlIf.extractLocalName(validAttrGroup.getAttribute("ref"))
+ validAttributes.extend (self.xsdAttributeGroupDict[attributeGroupRef].getChildrenNS(self.xsdNSAlias, "attribute"))
+ for validAttribute in validAttributes:
+ baseTypeAttributes[validAttribute.getAttribute("name")] = validAttribute
+ return inputElementChildIndex, baseTypeAttributes
+
+
+ ########################################
+ # validate inputNodeList against xsdNode
+ #
+ def _checkList (self, elementMethod, xsdNode, inputParentNode, inputNodeList, currIndex):
+ if not xsdNode.hasAttribute("minOccurs"):
+ xsdNode.setAttribute("minOccurs", "1") # default
+ if not xsdNode.hasAttribute("maxOccurs"):
+ xsdNode.setAttribute("maxOccurs", "1") # default
+
+ minOccurs = string.atoi(xsdNode.getAttribute("minOccurs"))
+ maxOccurs = -1
+ if xsdNode.getAttribute("maxOccurs") != "unbounded":
+ maxOccurs = string.atoi(xsdNode.getAttribute("maxOccurs"))
+ occurs = 0
+ while maxOccurs == -1 or occurs < maxOccurs:
+ try:
+ newIndex = elementMethod (xsdNode, inputParentNode, inputNodeList, currIndex)
+ occurs += 1
+ if newIndex > currIndex:
+ currIndex = newIndex
+ else:
+ break # no suitable element found
+ except TagException, errInst:
+ break
+
+ if occurs == 0 and minOccurs > 0:
+ raise errInst
+ elif occurs < minOccurs:
+ expInputTagName = xsdNode.getAttribute("name")
+ if expInputTagName == None:
+ expInputTagName = self.xmlIf.extractLocalName(xsdNode.getAttribute("ref"))
+
+ errInst.errstr = "Minimum number (%d) of child tags '%s' in tag '%s' not available (only %d)!" %(minOccurs, expInputTagName, inputParentNode.getTagName(), occurs)
+ raise errInst
+
+ return currIndex
+
+ ########################################
+ # validate inputNode against element node
+ #
+ def _checkElementTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ if xsdNode.hasAttribute("ref"):
+ refAttr = self.xmlIf.extractLocalName(xsdNode.getAttribute("ref"))
+ currIndex = self._checkElementTag (self.xsdElementDict[refAttr], inputParentNode, inputNodeList, currIndex)
+
+ else:
+ nameAttr = xsdNode.getAttribute ("name")
+ if currIndex >= len (inputNodeList):
+ raise TagException ("Missing child tag '%s' in tag '%s'!" %(nameAttr, inputParentNode.getTagName()), inputParentNode, 1)
+
+ inputNode = inputNodeList[currIndex]
+ if nameAttr != inputNode.getLocalName():
+ raise TagException ("Missing child tag '%s' in tag '%s'!" %(nameAttr, inputParentNode.getTagName()), inputNode, 0)
+
+ simpleType = None
+ complexTypeNode = xsdNode.getFirstChildNS (self.xsdNSAlias, "complexType")
+ simpleTypeNode = xsdNode.getFirstChildNS (self.xsdNSAlias, "simpleType")
+ if xsdNode.hasAttribute("type"):
+ typeAttr = xsdNode.getAttribute ("type")
+ localTypeAttr = self.xmlIf.extractLocalName(typeAttr)
+ if self.xsdTypeDict.has_key (localTypeAttr) and self.xsdTypeDict[localTypeAttr].getLocalName() == "complexType":
+ complexTypeNode = self.xsdTypeDict[localTypeAttr]
+ else:
+ simpleType = typeAttr
+
+ if complexTypeNode != None:
+ try:
+ self._checkComplexTypeTag (xsdNode, complexTypeNode, inputNode, 0)
+ except TagException, errInst:
+ self._addError (errInst.errstr, errInst.node, errInst.endTag)
+ else:
+ try:
+ simpleTypeReturnDict = {}
+ if simpleTypeNode != None:
+ self.simpleTypeVal.checkSimpleTypeDef (simpleTypeNode, inputNode.getTagName(), inputNode.getElementValue(), simpleTypeReturnDict)
+ elif simpleType != None:
+ self.simpleTypeVal.checkSimpleType (inputNode.getTagName(), simpleType, inputNode.getElementValue(), simpleTypeReturnDict)
+ # TODO: What to check if np type is specified for the element?
+
+ if simpleTypeReturnDict.has_key("adaptedAttrValue"):
+ inputNode.setElementValue(simpleTypeReturnDict["adaptedAttrValue"])
+
+ except xsvalSimpleTypes.SimpleTypeError, errstr:
+ self._addError (str(errstr), inputNode)
+
+ currIndex += 1
+
+ # check unique attributes and keys
+ childUniqueDefList = xsdNode.getChildrenNS (self.xsdNSAlias, "unique")
+ for childUniqueDef in childUniqueDefList:
+ self._checkIdentityConstraint (childUniqueDef, inputNode, "unique")
+
+ childKeyDefList = xsdNode.getChildrenNS (self.xsdNSAlias, "key")
+ for childKeyDef in childKeyDefList:
+ self._checkIdentityConstraint (childKeyDef, inputNode, "key")
+
+ childKeyrefDefList = xsdNode.getChildrenNS (self.xsdNSAlias, "keyref")
+ for childKeyrefDef in childKeyrefDefList:
+ self.checkKeyrefList.append ((inputNode, childKeyrefDef))
+
+ return currIndex
+
+
+ ########################################
+ # validate inputNode against sequence node
+ #
+ def _checkSequenceTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ for xsdChildNode in xsdNode.getChildren():
+ currIndex = self._checkParticle (xsdChildNode, inputParentNode, inputNodeList, currIndex)
+ return currIndex
+
+ ########################################
+ # validate inputNode against choice node
+ #
+ def _checkChoiceTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ for xsdChildNode in xsdNode.getChildren():
+ try:
+ currIndex = self._checkParticle (xsdChildNode, inputParentNode, inputNodeList, currIndex)
+ break
+ except TagException, errInst:
+ pass
+ else:
+ if currIndex < len(inputNodeList):
+ currNode = inputNodeList[currIndex]
+ endTag = 0
+ else:
+ currNode = inputParentNode
+ endTag = 1
+ raise TagException ("No suitable child tag for choice found!", currNode, endTag)
+
+ return currIndex
+
+ ########################################
+ # validate inputNode against group node
+ #
+ def _checkGroupTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ if xsdNode.hasAttribute("ref"):
+ refAttr = self.xmlIf.extractLocalName(xsdNode.getAttribute("ref"))
+ currIndex = self._checkGroupTag (self.xsdGroupDict[refAttr], inputParentNode, inputNodeList, currIndex)
+ else:
+ for xsdChildNode in xsdNode.getChildren():
+ currIndex = self._checkParticle (xsdChildNode, inputParentNode, inputNodeList, currIndex)
+ return currIndex
+
+ ########################################
+ # validate inputNode against all node
+ #
+ def _checkAllTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ oldIndex = currIndex
+ xsdChildDict = {}
+ for xsdChildNode in xsdNode.getChildren():
+ xsdChildDict[xsdChildNode] = 0
+ while currIndex < len(inputNodeList):
+ currNode = inputNodeList[currIndex]
+ for xsdChildNode in xsdChildDict.keys():
+ try:
+ newIndex = self._checkParticle (xsdChildNode, inputParentNode, inputNodeList, currIndex)
+ except TagException, errInst:
+ continue
+
+ if xsdChildDict[xsdChildNode] == 0:
+ xsdChildDict[xsdChildNode] = 1
+ currIndex = newIndex
+ break
+ else:
+ raise TagException ("Ambiguous child tag '%s' found in all-group!" %(currNode.getTagName()), currNode)
+ else:
+ raise TagException ("Unexpected child tag '%s' for all-group found!" %(currNode.getTagName()), currNode)
+
+ for xsdChildNode, occurs in xsdChildDict.items():
+ if xsdChildNode.getAttribute("minOccurs") != "0" and occurs == 0:
+ raise TagException ("Child tag '%s' missing in all-group (%s)" %(xsdChildNode.getAttribute("name"), inputParentNode.getTagName()), inputNodeList[oldIndex])
+
+ return currIndex
+
+
+ ########################################
+ # validate inputNode against any node
+ #
+ def _checkAnyTag (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ processContents = xsdNode.getAttribute("processContents")
+ if processContents == "skip":
+ pass
+ elif processContents == "lax":
+ # TODO: Was muss hier gecheckt werden?
+ pass
+ elif processContents == "strict":
+ # TODO: Was muss hier gecheckt werden?
+ pass
+
+ if currIndex < len(inputNodeList):
+ currIndex = currIndex + 1
+
+ return currIndex
+
+ ########################################
+ # validate inputNode against particle
+ #
+ def _checkParticle (self, xsdNode, inputParentNode, inputNodeList, currIndex):
+ xsdTagName = xsdNode.getTagName()
+ if xsdTagName == self.xsdNSAlias + "element":
+ currIndex = self._checkList (self._checkElementTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "choice":
+ currIndex = self._checkList (self._checkChoiceTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "sequence":
+ currIndex = self._checkList (self._checkSequenceTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "group":
+ currIndex = self._checkList (self._checkGroupTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "all":
+ currIndex = self._checkList (self._checkAllTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "any":
+ currIndex = self._checkList (self._checkAnyTag, xsdNode, inputParentNode, inputNodeList, currIndex)
+ elif xsdTagName == self.xsdNSAlias + "annotation":
+ pass # nothing to check
+ else:
+ self._addError ("Internal error: Invalid tag %s found!" %(xsdTagName))
+ return currIndex
+
+
+ ########################################
+ # validate attributes of inputNode against complexType node
+ #
+ def _checkAttributeTags (self, xsdNode, inputNode, validAttrDict):
+ validAttributes = xsdNode.getChildrenNS(self.xsdNSAlias, "attribute")
+ for validAttrGroup in xsdNode.getChildrenNS(self.xsdNSAlias, "attributeGroup"):
+ attributeGroupRef = self.xmlIf.extractLocalName(validAttrGroup.getAttribute("ref"))
+ validAttributes.extend (self.xsdAttributeGroupDict[attributeGroupRef].getChildrenNS(self.xsdNSAlias, "attribute"))
+ for validAttribute in validAttributes:
+ if validAttribute.hasAttribute("name"):
+ keyAttr = validAttribute.getAttribute("name")
+ else:
+ keyAttr = self.xmlIf.extractLocalName(validAttribute.getAttribute("ref"))
+ validAttrDict[keyAttr] = validAttribute
+
+ inputAttrDict = {}
+ for iAttrName, iAttrValue in inputNode.getAttributeDict().items():
+ if self.xmlIf.extractNamespaceAlias (iAttrName) != "xmlns:" and iAttrName != "xmlns":
+ inputAttrDict[self.xmlIf.extractLocalName(iAttrName)] = iAttrValue
+
+ for attrKey, validAttribute in validAttrDict.items():
+ # handle special attributes, e.g. xsi:nil
+ if attrKey == "__SPECIAL_ATTRS__":
+ for specialAttrName, specialAttrType in validAttribute.items():
+ if inputAttrDict.has_key(specialAttrName):
+ simpleTypeReturnDict = {}
+ try:
+ self.simpleTypeVal.checkSimpleType (specialAttrName, specialAttrType, inputAttrDict[specialAttrName], simpleTypeReturnDict)
+ except xsvalSimpleTypes.SimpleTypeError, errstr:
+ self._addError (str(errstr), inputNode)
+
+ del inputAttrDict[specialAttrName]
+ continue
+
+ attrDict = validAttribute.getAttributeDict()
+ if attrDict.has_key("ref"):
+ # TODO: What to do here??
+ attrRef = self.xmlIf.extractLocalName(attrDict["ref"])
+ if inputAttrDict.has_key(attrRef):
+ del inputAttrDict[attrRef]
+ else:
+ attrName = attrDict["name"]
+ if not attrDict.has_key("use"):
+ attrDict["use"] = "optional"
+
+ if inputAttrDict.has_key(attrName):
+ del inputAttrDict[attrName]
+ else:
+ if attrDict["use"] == "required":
+ self._addError ("Attribute '%s' is missing for tag %s!" %(attrName, inputNode.getTagName()), inputNode)
+ elif attrDict["use"] == "optional":
+ if attrDict.has_key("default"):
+ inputNode.setAttribute(attrName, attrDict["default"])
+ if attrDict.has_key("fixed"):
+ if inputNode.getAttribute(attrName) != attrDict["fixed"]:
+ self._addError ("Attribute '%s' must have fixed value '%s'!" %(attrName, attrDict["fixed"]), inputNode)
+
+ if inputNode.hasAttribute(attrName):
+ if attrDict["use"] == "prohibited":
+ self._addError ("Attribute '%s' is prohibited in this context!" %(attrName), inputNode)
+ else:
+ attributeValue = inputNode.getAttribute(attrName)
+ try:
+ simpleTypeReturnDict = {}
+ if attrDict.has_key("type"):
+ self.simpleTypeVal.checkSimpleType (attrName, attrDict["type"], attributeValue, simpleTypeReturnDict)
+ else:
+ typedefNode = validAttribute.getFirstChildNS(self.xsdNSAlias, "simpleType")
+ if typedefNode != None:
+ self.simpleTypeVal.checkSimpleTypeDef (typedefNode, attrName, attributeValue, simpleTypeReturnDict)
+ else:
+ pass # default if no type attribute is specified
+
+ if simpleTypeReturnDict.has_key("adaptedAttrValue"):
+ inputNode.setAttribute(attrName, simpleTypeReturnDict["adaptedAttrValue"])
+
+ except xsvalSimpleTypes.SimpleTypeError, errstr:
+ self._addError (str(errstr), inputNode)
+
+ for inputAttribute in inputAttrDict.keys():
+ # TODO: adapt for using namespaces!!
+ if self.xmlIf.extractLocalName(inputAttribute) in ("noNamespaceSchemaLocation", "schemaLocation"):
+ del inputAttrDict[inputAttribute]
+
+ for inputAttribute in inputAttrDict.keys():
+ if inputAttribute == "nil":
+ self._addError ("Tag '%s' hasn't been defined as nillable!" %(inputNode.getTagName()), inputNode)
+ else:
+ self._addError ("Unexpected attribute '%s' in Tag '%s'!" %(inputAttribute, inputNode.getTagName()), inputNode)
+
+
+ ########################################
+ # validate unique and key definition
+ #
+ def _checkIdentityConstraint (self, identityConstrNode, inputElement, isKey):
+ identConstrName = identityConstrNode.getAttribute ("name")
+ selectorXPathNode = identityConstrNode.getFirstChildNS (self.xsdNSAlias, "selector")
+ selectorNodeList, dummy = self._getXPath (inputElement, selectorXPathNode.getAttribute("xpath"), self.inputNSAlias)
+
+ valueDict = {}
+ for selectorNode in selectorNodeList:
+ fieldXPathNodeList = identityConstrNode.getChildrenNS (self.xsdNSAlias, "field")
+ keyValue = []
+ for fieldXPathNode in fieldXPathNodeList:
+ fieldXPath = fieldXPathNode.getAttribute("xpath")
+ fieldNodeList, fieldAttributeList = self._getXPath (selectorNode, fieldXPath, self.inputNSAlias, isKey)
+ if len(fieldNodeList) > 1 or len(fieldAttributeList) > 1:
+ self._addError ("The field xPath of identity constraint '%s' must evaluate to exactly 0 or 1 node!" %(identConstrName), fieldXPathNode)
+ return
+
+ # TODO: unique and key check currently only on string base
+ for fieldNode in fieldNodeList:
+ keyValue.append (fieldNode.getElementValue ())
+
+ for attrValue in fieldAttributeList:
+ keyValue.append (attrValue)
+
+ if keyValue != []:
+ keyValue = tuple(keyValue)
+ if not valueDict.has_key (keyValue):
+ valueDict[keyValue] = 1
+ self.xsdIdentityConstrDict[identConstrName]["ValueDict"][keyValue] = 1
+ else:
+ self._addError ("Duplicate identity constraint values '%s' found for identity contraint '%s'!" %(keyValue, identConstrName), selectorNode)
+
+ ########################################
+ # validate unique and key definition
+ #
+ def _checkKeyRefConstraint (self, keyrefNode, inputElement):
+ keyRefName = keyrefNode.getAttribute ("name")
+ keyReference = keyrefNode.getAttribute ("refer")
+ if not self.xsdIdentityConstrDict.has_key (keyReference):
+ self._addError ("keyref refers unknown key '%s'!" %(keyReference), keyrefNode)
+ return
+
+ selectorXPathNode = keyrefNode.getFirstChildNS (self.xsdNSAlias, "selector")
+ selectorNodeList, dummy = self._getXPath (inputElement, selectorXPathNode.getAttribute("xpath"), self.inputNSAlias)
+
+ for selectorNode in selectorNodeList:
+ fieldXPathNodeList = keyrefNode.getChildrenNS(self.xsdNSAlias, "field")
+ keyValue = []
+ for fieldXPathNode in fieldXPathNodeList:
+ fieldXPath = fieldXPathNode.getAttribute("xpath")
+ fieldNodeList, fieldAttributeList = self._getXPath (selectorNode, fieldXPath, self.inputNSAlias, "keyref")
+ if len(fieldNodeList) > 1 or len(fieldAttributeList) > 1:
+ self._addError ("The field xPath of keyref '%s' must evaluate to exactly 0 or 1 node!" %(keyRefName), fieldXPathNode)
+ return
+
+ # TODO: unique and key check currently only on string base
+ for fieldNode in fieldNodeList:
+ keyValue.append(fieldNode.getElementValue())
+
+ for attrValue in fieldAttributeList:
+ keyValue.append(attrValue)
+
+ keyValue = tuple(keyValue)
+ if not self.xsdIdentityConstrDict[keyReference]["ValueDict"].has_key (keyValue):
+ self._addError ("Element value '%s' does not match key type '%s'!" %(keyValue, keyReference), selectorNode)
+
+ ########################################
+ # retrieve nodes/attributes specified by given xPath
+ #
+ def _getXPath (self, node, xPath, defaultNamespace, identityConstraint=None):
+ try:
+ nodeList, attributeList = node.getXPathList (xPath, defaultNamespace)
+ except IOError, errstr:
+ self._addError (errstr, node)
+ nodeList = attributeList = []
+
+ if nodeList == [] and attributeList == []:
+ if identityConstraint == "key":
+ self.errorHandler.addError ("Key is missing! XPath = '%s'!" %(xPath), node)
+ elif identityConstraint in ("unique", "keyref"):
+ self.errorHandler.addWarning ("Identity constraint is missing! XPath = '%s'!" %(xPath), node)
+
+ return nodeList, attributeList
+
+
+########################################
+# define own exception for XML schema validation errors
+#
+class TagException (StandardError):
+ def __init__ (self, errstr="", node=None, endTag=0):
+ self.node = node
+ self.errstr = errstr
+ self.endTag = endTag
+ StandardError.__init__(self)
+
diff -r 000000000000 -r b622defdfd98 minixsv/xsvalErrorHandler.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xsvalErrorHandler.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,161 @@
+#
+# minixsv, Release 0.3
+# file: xsvalErrorHandler.py
+#
+# XML schema validator classes
+#
+# history:
+# 2004-09-23 rl created
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+import string
+import os
+
+IGNORE_WARNINGS = 0
+PRINT_WARNINGS = 1
+STOP_ON_WARNINGS = 2
+
+
+########################################
+# Error-Handler class for XML schema validator
+# handles only validator errors, no parser errors!
+
+class ErrorHandler:
+
+ def __init__(self, errorLimit, warningProc, verbose):
+ self.errorLimit = errorLimit
+ self.warningProc = warningProc
+ self.verbose = verbose
+
+ self.errorList = []
+ self.noOfErrors = 0
+ self.warningList = []
+
+
+ ########################################
+ # add error to errorList (raise exception only if error limit is reached)
+
+ def addError (self, errstr, element=None, endTag=0):
+ filePath = ""
+ lineNo = 0
+ if element:
+ filePath = element.getFilePath()
+ if endTag:
+ lineNo = element.getEndLineNumber()
+ else:
+ lineNo = element.getStartLineNumber()
+ self.errorList.append ((filePath, lineNo, "ERROR", "%s" %(errstr)))
+ self.noOfErrors += 1
+ if self.noOfErrors == self.errorLimit:
+ self._raiseXsvalException ("\nError Limit reached!!")
+
+
+ ########################################
+ # add warning to warningList
+
+ def addWarning (self, warnstr, element=None):
+ filePath = ""
+ lineNo = 0
+ if element:
+ filePath = element.getFilePath()
+ lineNo = element.getStartLineNumber()
+ self.warningList.append ((filePath, lineNo, "WARNING", warnstr))
+
+
+ ########################################
+ # add info string to errorList
+
+ def addInfo (self, infostr, element=None):
+ filePath = ""
+ lineNo = 0
+ if element:
+ filePath = element.getFilePath()
+ lineNo = element.getStartLineNumber()
+ self.errorList.append ((filePath, lineNo, "INFO", infostr))
+
+
+ ########################################
+ # add error to errorList (if given) and raise exception
+
+ def raiseError (self, errstr, element=None):
+ self.addError (errstr, element)
+ self._raiseXsvalException ()
+
+
+ ########################################
+ # raise exception with complete errorList as exception string
+ # (only if errors occurred)
+
+ def flushOutput (self):
+ if self.warningProc == PRINT_WARNINGS and self.warningList != []:
+ print self._assembleSortedOutputList(self.warningList)
+ self.warningList = []
+ elif self.warningProc == STOP_ON_WARNINGS:
+ self.errorList.extend (self.warningList)
+
+ if self.errorList != []:
+ self._raiseXsvalException ()
+
+
+ ########################################
+ # Private methods
+
+ def _raiseXsvalException (self, additionalInfo=""):
+ output = self._assembleSortedOutputList(self.errorList) + additionalInfo
+ self.errorList = self.warningList = []
+ raise XsvalError (output)
+
+
+ def _assembleSortedOutputList (self, outputList):
+ outputList.sort()
+ outputStrList = []
+ for outElement in outputList:
+ outputStrList.append (self._assembleOutString(outElement))
+ return string.join (outputStrList, "\n")
+
+
+ def _assembleOutString (self, listElement):
+ fileStr = ""
+ lineStr = ""
+ if listElement[0] != "":
+ if self.verbose:
+ fileStr = "%s: " %(listElement[0])
+ else:
+ fileStr = "%s: " %(os.path.basename(listElement[0]))
+ if listElement[1] != 0:
+ lineStr = "line %d: " %(listElement[1])
+ return "%s: %s%s%s" %(listElement[2], fileStr, lineStr, listElement[3])
+
+
+class XsvalError (StandardError):
+ pass
+
diff -r 000000000000 -r b622defdfd98 minixsv/xsvalSchema.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xsvalSchema.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,176 @@
+#
+# minixsv, Release 0.3
+# file: xsvalSchema.py
+#
+# Derived validator class (for validation of schema files)
+#
+# history:
+# 2004-10-07 rl created
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+from xsvalErrorHandler import *
+from xsvalBase import XsValBase
+
+###########################################################
+# Derived validator class for validating one input schema file against the XML rules file
+
+class XsValSchema (XsValBase):
+
+ ########################################
+ # overloaded validate method
+ #
+ def validate (self, inputTree, xsdTree):
+ XsValBase.validate(self, inputTree, xsdTree)
+ self._checkSchemaSecondLevel()
+
+
+ ########################################
+ # additional checks for schema files which are not covered by "xsStructs.xsd"
+ #
+ def _checkSchemaSecondLevel(self):
+ self._checkElementNodesSecondLevel()
+ self._checkGroupNodesSecondLevel()
+ self._checkAttrGroupNodesSecondLevel()
+ self._checkAttributeNodesSecondLevel()
+
+ self._checkSimpleTypesSecondLevel()
+
+
+ ########################################
+ # additional checks for element nodes
+ #
+ def _checkElementNodesSecondLevel(self):
+ self.inputElementDict = {}
+ for inputNode in self.inputRoot.getChildrenNS(self.inputNSAlias, "element"):
+ self.inputElementDict[inputNode.getAttribute("name")] = inputNode
+
+ elementNodes = self.inputRoot.getElementsByTagNameNS (self.inputNSAlias, "element")
+ for elementNode in elementNodes:
+ if not elementNode.hasAttribute("name") and not elementNode.hasAttribute("ref"):
+ self._addError ("Element must have 'name' or 'ref' attribute!", elementNode)
+ continue
+
+ if elementNode.hasAttribute("name") and elementNode.hasAttribute("ref"):
+ self._addError ("Element must not have 'name' and 'ref' attribute!", elementNode)
+ continue
+
+ if elementNode.hasAttribute("ref"):
+ elementRef = self.xmlIf.extractLocalName(elementNode.getAttribute("ref"))
+ if not self.inputElementDict.has_key(elementRef):
+ self._addError ("Reference to undefined element '%s'!" %(elementRef), elementNode)
+
+ complexTypeNode = elementNode.getFirstChildNS (self.inputNSAlias, "complexType")
+ simpleTypeNode = elementNode.getFirstChildNS (self.inputNSAlias, "simpleType")
+ if elementNode.hasAttribute("type") and (complexTypeNode != None or simpleTypeNode != None):
+ self._addError ("Element has type attribute and type definition!", elementNode)
+ if not elementNode.hasAttribute("type") and complexTypeNode == None and simpleTypeNode == None:
+ self._addWarning ("Element has no type attribute and no type definition!", elementNode)
+
+ minOccurs = elementNode.getAttributeOrDefault("minOccurs", "1")
+ maxOccurs = elementNode.getAttributeOrDefault("maxOccurs", "1")
+ if maxOccurs != "unbounded":
+ if string.atoi(minOccurs) > string.atoi(maxOccurs):
+ self._addError ("Attribute minOccurs > maxOccurs!", elementNode)
+
+
+ ########################################
+ # additional checks for group nodes
+ #
+ def _checkGroupNodesSecondLevel(self):
+ self.inputGroupDict = {}
+ for groupNode in self.inputRoot.getChildrenNS(self.inputNSAlias, "group"):
+ self.inputGroupDict[groupNode.getAttribute("name")] = groupNode
+
+ groupNodes = self.inputRoot.getElementsByTagNameNS (self.inputNSAlias, "group")
+ for groupNode in groupNodes:
+ if groupNode.hasAttribute("ref"):
+ groupRef = self.xmlIf.extractLocalName(groupNode.getAttribute("ref"))
+ if not self.inputGroupDict.has_key(groupRef):
+ self._addError ("Reference to undefined group '%s'!" %(groupRef), groupNode)
+
+
+ ########################################
+ # additional checks for attributeGroup nodes
+ #
+ def _checkAttrGroupNodesSecondLevel(self):
+ self.inputAttrGroupDict = {}
+ for attrGroupNode in self.inputRoot.getChildrenNS(self.inputNSAlias, "attrGroup"):
+ self.inputAttrGroupDict[attrGroupNode.getAttribute("name")] = attrGroupNode
+
+ attrGroupNodes = self.inputRoot.getElementsByTagNameNS (self.inputNSAlias, "attrGroup")
+ for attrGroupNode in attrGroupNodes:
+ if attrGroupNode.hasAttribute("ref"):
+ attrGroupRef = self.xmlIf.extractLocalName(attrGroupNode.getAttribute("ref"))
+ if not self.inputAttrGroupDict.has_key(attrGroupRef):
+ self._addError ("Reference to undefined attribute group '%s'!" %(attrGroupRef), attrGroupNode)
+
+
+ ########################################
+ # additional checks for attribute nodes
+ #
+ def _checkAttributeNodesSecondLevel(self):
+ attributeNodes = self.inputRoot.getElementsByTagNameNS (self.inputNSAlias, "attribute")
+ for attributeNode in attributeNodes:
+ if not attributeNode.hasAttribute("name") and not attributeNode.hasAttribute("ref"):
+ self._addError ("Attribute must have 'name' or 'ref' attribute!", attributeNode)
+ continue
+
+ if attributeNode.getAttribute("use") == "required" and attributeNode.hasAttribute("default"):
+ self._addError ("Attribute 'default' is not allowed, because 'use' is 'required'!", attributeNode)
+ elif attributeNode.hasAttribute("default") and attributeNode.hasAttribute("fixed"):
+ self._addError ("Attribute must not have 'default' AND 'fixed' attribute!", attributeNode)
+
+
+ ########################################
+ # additional checks for simple types
+ #
+ def _checkSimpleTypesSecondLevel(self):
+ restrictionNodes, dummy = self.inputRoot.getXPathList (".//simpleType/restriction", self.inputNSAlias)
+ for restrictionNode in restrictionNodes:
+ if not restrictionNode.hasAttribute("base") and restrictionNode.getFirstChildNS (self.inputNSAlias, "simpleType") == None:
+ self._addError ("Simple type restriction must have 'base' attribute or 'simpleType' child tag!", restrictionNode)
+
+ minExcl = restrictionNode.getFirstChildNS(self.inputNSAlias, "minExclusive")
+ minIncl = restrictionNode.getFirstChildNS(self.inputNSAlias, "minInclusive")
+ if minExcl != None and minIncl != None:
+ self._addError ("Restriction attributes 'minExclusive' and 'minInclusive' cannot be defined together!", restrictionNode)
+ maxExcl = restrictionNode.getFirstChildNS(self.inputNSAlias, "maxExclusive")
+ maxIncl = restrictionNode.getFirstChildNS(self.inputNSAlias, "maxInclusive")
+ if maxExcl != None and maxIncl != None:
+ self._addError ("Restriction attributes 'maxExclusive' and 'maxInclusive' cannot be defined together!", restrictionNode)
+
+ listNodes = self.inputRoot.getElementsByTagNameNS (self.inputNSAlias, "list")
+ for listNode in listNodes:
+ if not listNode.hasAttribute("itemType") and restrictionNode.getFirstChildNS (self.inputNSAlias, "simpleType") == None:
+ self._addError ("List type must have 'itemType' attribute or 'simpleType' child tag!", listNode)
+
+
diff -r 000000000000 -r b622defdfd98 minixsv/xsvalSimpleTypes.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/minixsv/xsvalSimpleTypes.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,332 @@
+#
+# # minixsv, Release 0.3
+# file: xsvalSimpleTypes.py
+#
+# class for validation of XML schema simple types
+#
+# history:
+# 2004-09-09 rl created
+#
+# Copyright (c) 2004 by Roland Leuthe. All rights reserved.
+#
+# --------------------------------------------------------------------
+# The minixsv XML schema validator is
+#
+# Copyright (c) 2004 by Roland Leuthe
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+
+import sys
+import string
+import re
+
+
+class XsSimpleTypeVal:
+
+ def __init__ (self, parent):
+ self.parent = parent
+ self.xmlIf = parent.xmlIf
+ self.xsdNSAlias = parent.xsdNSAlias
+ self.xsdTree = parent.xsdTree
+
+
+ ########################################
+ # validate given value against simpleType
+ #
+ def checkSimpleType (self, attrName, typeName, attributeValue, returnDict):
+ localTypeName = self.xmlIf.extractLocalName(typeName)
+ if self.parent.xsdTypeDict.has_key(localTypeName):
+ typedefNode = self.parent.xsdTypeDict[localTypeName]
+ if typedefNode.getTagName () == self.xsdNSAlias + "simpleType":
+ self.checkSimpleTypeDef (typedefNode, attrName, attributeValue, returnDict)
+ else:
+ raise SimpleTypeError("Type '%s' must be simple type!" %(typeName))
+ else:
+ try:
+ validateBaseType (typeName, attributeValue, returnDict)
+ except BaseTypeError, errstr:
+ raise SimpleTypeError("Value of '%s' (%s) %s" %(attrName, attributeValue, errstr))
+
+
+ ########################################
+ # validate given value against simpleType node
+ #
+ def checkSimpleTypeDef (self, xsdElement, attrName, attributeValue, returnDict):
+ restrictionElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "restriction")
+ listElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "list")
+ unionElement = xsdElement.getFirstChildNS(self.xsdNSAlias, "union")
+ if restrictionElement != None:
+ self._checkRestrictionTag (restrictionElement, attrName, attributeValue, returnDict)
+ elif listElement != None:
+ self._checkListTag (listElement, attrName, attributeValue, returnDict)
+ elif unionElement != None:
+ self._checkUnionTag (unionElement, attrName, attributeValue, returnDict)
+
+ ########################################
+ # validate given value against restriction node
+ #
+ def _checkRestrictionTag (self, xsdElement, attrName, attributeValue, returnDict):
+ # first check against base type
+ baseType = xsdElement.getAttribute("base")
+ if baseType != None:
+ self.checkSimpleType (attrName, baseType, attributeValue, returnDict)
+ else:
+ baseTypeNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "simpleType")
+ self.checkSimpleTypeDef (baseTypeNode, attrName, attributeValue, returnDict)
+
+ minExcl = xsdElement.getFirstChildNS(self.xsdNSAlias, "minExclusive")
+ minIncl = xsdElement.getFirstChildNS(self.xsdNSAlias, "minInclusive")
+ maxExcl = xsdElement.getFirstChildNS(self.xsdNSAlias, "maxExclusive")
+ maxIncl = xsdElement.getFirstChildNS(self.xsdNSAlias, "maxInclusive")
+
+ if minExcl != None:
+ minExclReturnDict = {}
+ minExclValue = minExcl.getAttribute("value")
+ self.checkSimpleType (attrName, baseType, minExclValue, minExclReturnDict)
+ if returnDict.has_key("orderedValue") and minExclReturnDict.has_key("orderedValue"):
+ if returnDict["orderedValue"] <= minExclReturnDict["orderedValue"]:
+ raise SimpleTypeError ("Value of %s (%s) is <= minExclusive (%s)" %(attrName, attributeValue, minExclValue))
+ elif minIncl != None:
+ minInclReturnDict = {}
+ minInclValue = minIncl.getAttribute("value")
+ self.checkSimpleType (attrName, baseType, minInclValue, minInclReturnDict)
+ if returnDict.has_key("orderedValue") and minInclReturnDict.has_key("orderedValue"):
+ if returnDict["orderedValue"] < minInclReturnDict["orderedValue"]:
+ raise SimpleTypeError ("Value of %s (%s) is < minInclusive (%s)" %(attrName, attributeValue, minInclValue))
+ if maxExcl != None:
+ maxExclReturnDict = {}
+ maxExclValue = maxExcl.getAttribute("value")
+ self.checkSimpleType (attrName, baseType, maxExclValue, maxExclReturnDict)
+ if returnDict.has_key("orderedValue") and maxExclReturnDict.has_key("orderedValue"):
+ if returnDict["orderedValue"] >= maxExclReturnDict["orderedValue"]:
+ raise SimpleTypeError ("Value of %s (%s) is >= maxExclusive (%s)" %(attrName, attributeValue, maxExclValue))
+ elif maxIncl != None:
+ maxInclReturnDict = {}
+ maxInclValue = maxIncl.getAttribute("value")
+ self.checkSimpleType (attrName, baseType, maxInclValue, maxInclReturnDict)
+ if returnDict.has_key("orderedValue") and maxInclReturnDict.has_key("orderedValue"):
+ if returnDict["orderedValue"] > maxInclReturnDict["orderedValue"]:
+ raise SimpleTypeError ("Value of %s (%s) is > maxInclusive (%s)" %(attrName, attributeValue, maxInclValue))
+
+ totalDigitsNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "totalDigits")
+ if totalDigitsNode != None:
+ totalDigitsValue = totalDigitsNode.getAttribute("value")
+ if totalDigitsNode.getAttribute("fixed") == "true":
+ if len(re.findall("\d" ,attributeValue)) != string.atoi(totalDigitsValue):
+ raise SimpleTypeError ("Total number of digits != %s for %s (%s)" %(totalDigitsValue, attrName, attributeValue))
+ else:
+ if len(re.findall("\d" ,attributeValue)) > string.atoi(totalDigitsValue):
+ raise SimpleTypeError ("Total number of digits > %s for %s (%s)" %(totalDigitsValue, attrName, attributeValue))
+ fractionDigitsNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "fractionDigits")
+ if fractionDigitsNode != None:
+ fractionDigitsValue = fractionDigitsNode.getAttribute("value")
+ result = re.search("(?P\d+)(?P\.)(?P\d+)" ,attributeValue)
+ if result != None:
+ numberOfFracDigits = len (result.group('fracDigits'))
+ else:
+ numberOfFracDigits = 0
+ if fractionDigitsNode.getAttribute("fixed") == "true" and numberOfFracDigits != string.atoi(fractionDigitsValue):
+ raise SimpleTypeError ("Fraction number of digits != %s for %s (%s)" %(fractionDigitsValue, attrName, attributeValue))
+ elif numberOfFracDigits > string.atoi(fractionDigitsValue):
+ raise SimpleTypeError ("Fraction number of digits > %s for %s (%s)" %(fractionDigitsValue, attrName, attributeValue))
+
+ if returnDict.has_key("length"):
+ lengthNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "length")
+ if lengthNode != None:
+ length = string.atoi(lengthNode.getAttribute("value"))
+ if returnDict["length"] != length:
+ raise SimpleTypeError ("Length of %s (%s) must be %d!" %(attrName, attributeValue, length))
+ minLengthNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "minLength")
+ if minLengthNode != None:
+ minLength = string.atoi(minLengthNode.getAttribute("value"))
+ if returnDict["length"] < minLength:
+ raise SimpleTypeError ("Length of %s (%s) must be >= %d!" %(attrName, attributeValue, minLength))
+ maxLengthNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "maxLength")
+ if maxLengthNode != None:
+ maxLength = string.atoi(maxLengthNode.getAttribute("value"))
+ if returnDict["length"] > maxLength:
+ raise SimpleTypeError ("Length of %s (%s) must be <= %d!" %(attrName, attributeValue, maxLength))
+
+ enumerationElementList = xsdElement.getChildrenNS(self.xsdNSAlias, "enumeration")
+ if enumerationElementList != []:
+ for enumeration in enumerationElementList:
+ if enumeration.getAttribute ("value") == attributeValue:
+ break
+ else:
+ raise SimpleTypeError ("Enumeration value '%s' not allowed!" %(attributeValue))
+
+ patternNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "pattern")
+ if patternNode != None:
+ rePattern = patternNode.getAttribute("value")
+ regexObj = re.match(rePattern, attributeValue)
+ if not regexObj or regexObj.end() != len(attributeValue):
+ raise SimpleTypeError ("Attribute value '%s' does not match pattern '%s'!" %(attributeValue, rePattern))
+
+ whiteSpace = xsdElement.getFirstChildNS(self.xsdNSAlias, "whiteSpace")
+ if whiteSpace != None:
+ wsAction = whiteSpace.getAttribute("value")
+ if wsAction == "replace":
+ normalizedValue = self._normalizeString(attributeValue)
+ if normalizedValue != attributeValue:
+ returnDict["adaptedAttrValue"] = normalizedValue
+ elif wsAction == "collapse":
+ collapsedValue = self._collapseString(attributeValue)
+ if collapsedValue != attributeValue:
+ returnDict["adaptedAttrValue"] = collapsedValue
+
+
+ ########################################
+ # validate given value against list node
+ #
+ def _checkListTag (self, xsdElement, attrName, attributeValue, returnDict):
+ if attributeValue != "":
+ itemType = xsdElement.getAttribute ("itemType")
+ # substitute multiple whitespace characters by a single ' '
+ collapsedValue = self._collapseString(attributeValue)
+ if collapsedValue != attributeValue:
+ returnDict["adaptedAttrValue"] = collapsedValue
+
+ # divide up attributeValue => store it into list
+ attributeList = string.split(collapsedValue, " ")
+ for attrValue in attributeList:
+ elementReturnDict = {}
+ if itemType != None:
+ self.checkSimpleType (attrName, itemType, attrValue, elementReturnDict)
+ else:
+ itemTypeNode = xsdElement.getFirstChildNS(self.xsdNSAlias, "simpleType")
+ self.checkSimpleTypeDef (itemTypeNode, attrName, attrValue, elementReturnDict)
+
+ returnDict["length"] = len(attributeList)
+ else:
+ returnDict["length"] = 0
+
+
+ ########################################
+ # validate given value against union node
+ #
+ def _checkUnionTag (self, xsdElement, attrName, attributeValue, returnDict):
+ memberTypes = xsdElement.getAttribute ("memberTypes")
+ if memberTypes != None:
+ # substitute multiple whitespace characters by a single ' '
+ # divide up attributeValue => store it into list
+ for memberType in string.split(self._collapseString(memberTypes), " "):
+ try:
+ self.checkSimpleType (attrName, memberType, attributeValue, returnDict)
+ return
+ except SimpleTypeError, errstr:
+ pass
+
+ # memberTypes and additional type definitions is legal!
+ for childSimpleType in xsdElement.getChildrenNS(self.xsdNSAlias, "simpleType"):
+ try:
+ self.checkSimpleTypeDef (childSimpleType, attrName, attributeValue, returnDict)
+ return
+ except SimpleTypeError, errstr:
+ pass
+
+ raise SimpleTypeError ("%s (%s) is no valid union member type (%s)!" %(attrName, attributeValue, memberTypes))
+
+
+ ########################################
+ # substitute multiple whitespace characters by a single ' '
+ #
+ def _collapseString (self, strValue):
+ return re.sub('\s+', ' ', strValue)
+
+ ########################################
+ # substitute each whitespace characters by a single ' '
+ #
+ def _normalizeString (self, strValue):
+ return re.sub('\s', ' ', strValue)
+
+
+
+
+def validateBaseType (simpleType, attributeValue, returnDict):
+# TODO: Many base types are not completely defined by datatypes.xsd
+ simpleTypeDict = {"xsd:string": _checkStringType,
+ "xsd:hexBinary": _checkHexBinaryType,
+ "xsd:integer": _checkIntegerType,
+ "xsd:boolean": _checkBooleanType,
+ "xsd:QName": _checkQNameType,
+ }
+
+ simpleType = string.replace(simpleType, "xs:", "xsd:")
+ if simpleTypeDict.has_key (simpleType):
+ simpleTypeDict[simpleType] (simpleType, attributeValue, returnDict)
+
+ elif simpleType != "xsd:anySimpleType":
+ if simpleType in ("xsd:decimal", "xsd:float", "xsd:double", "xsd:base64Binary", "xsd:anyURI", "xsd:NOTATION",
+ "xsd:duration", "xsd:dateTime", "xsd:time", "xsd:date",
+ "xsd:gYearMonth", "xsd:gMonthDay", "xsd:gYear", "xsd:gMonth", "xsd:gDay"):
+ print "INFO: Check of simple type '%s' currently not supported!" %(simpleType)
+ else:
+ # TODO: Fehler im XSD-File => Check muss an anderer Stelle erfolgen
+ raise BaseTypeError("uses unknown type '%s'!" %(simpleType))
+
+
+def _checkStringType (simpleType, attributeValue, returnDict):
+ # all valid??
+ returnDict["length"] = len(attributeValue)
+
+def _checkHexBinaryType (simpleType, attributeValue, returnDict):
+ _checkIntegerRange (attributeValue, 16, 0, sys.maxint, returnDict)
+ returnDict["length"] = len(attributeValue)
+
+def _checkIntegerType (simpleType, attributeValue, returnDict):
+ _checkIntegerRange (attributeValue, 10, -sys.maxint-1, sys.maxint, returnDict)
+
+def _checkBooleanType (simpleType, attributeValue, returnDict):
+ if attributeValue not in ("true", "false", "1", "0"):
+ raise BaseTypeError("is not a boolean value!")
+
+def _checkQNameType (simpleType, attributeValue, returnDict):
+ # TODO: fill
+ returnDict["length"] = len(attributeValue)
+
+def _checkIntegerRange (attributeValue, base, minBase, maxBase, returnDict):
+ try:
+ value = string.atoi (attributeValue, base=base)
+ except:
+ if base == 16:
+ raise BaseTypeError("is not a hexadecimal value!")
+ else:
+ raise BaseTypeError("is not an integer!")
+
+ if value < minBase or value > maxBase:
+ raise BaseTypeError("is out of range (%d..%d)!" %(minBase, maxBase))
+
+ returnDict["orderedValue"] = value
+
+
+
+########################################
+# define own exception for XML schema validation errors
+#
+class SimpleTypeError (StandardError):
+ pass
+
+class BaseTypeError (StandardError):
+ pass
diff -r 000000000000 -r b622defdfd98 plcopen/TC6_XML_V10.xsd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/TC6_XML_V10.xsd Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1361 @@
+
+
+
+
+ The complete project
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation language of the project e.g. "en-US"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Represents a group of resources and global variables
+
+
+
+
+
+ Represents a group of programs and tasks and global variables
+
+
+
+
+
+ Represents a periodic or triggered task
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A generic data type
+
+
+
+
+
+
+
+
+
+ Defines a range with signed bounds
+
+
+
+
+
+
+ Defines a range with unsigned bounds
+
+
+
+
+
+
+ A generic value
+
+
+
+
+
+ Value that can be represented as a single token string
+
+
+
+
+
+
+
+ Array value consisting of a list of occurrances - value pairs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Struct value consisting of a list of member - value pairs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Implementation part of a POU, action or transistion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+ List of variable declarations that share the same memory attributes (CONSTANT, RETAIN, NON_RETAIN, PERSISTENT)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List of variable declarations without attributes
+
+
+
+
+
+ Declaration of a variable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines a graphical position in X, Y coordinates
+
+
+
+
+
+
+ Describes a connection between the consumer element (eg. input variable of a function block) and the producer element (eg. output variable of a function block). It may contain a list of positions that describes the path of the connection.
+
+
+
+
+ All positions of the directed connection path. If any positions are given, the list has to contain the first (input pin of the consumer element) as well as the last (output pin of the producer element).
+
+
+
+
+
+ Identifies the element the connection starts from.
+
+
+
+
+ If present:
+ This attribute denotes the name of the VAR_OUTPUT / VAR_IN_OUTparameter of the pou block that is the start of the connection.
+ If not present:
+ If the refLocalId attribute refers to a pou block, the start of the connection is the first output of this block, which is not ENO.
+ If the refLocalId attribute refers to any other element type, the start of the connection is the elements single native output.
+
+
+
+
+
+ Defines a connection point on the consumer side
+
+
+
+
+ Relative position of the connection pin. Origin is the anchor position of the block.
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0] or an iec expression or multiple token text e.g. a + b (*sum*). An iec 61131-3 parser has to be used to extract variable information.
+
+
+
+
+
+
+
+ Defines a connection point on the producer side
+
+
+
+
+ Relative position of the connection pin. Origin is the anchor position of the block.
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+ Represents a program or function block instance either running with or without a task
+
+
+
+
+
+
+
+
+
+ Formatted text according to parts of XHTML 1.1
+
+
+
+
+
+
+
+ Collection of elementary IEC 61131-3 datatypes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The single byte character string type
+
+
+
+
+
+
+
+ The wide character (WORD) string type
+
+
+
+
+
+
+
+
+ Collection of derived IEC 61131-3 datatypes
+
+
+
+
+
+
+
+
+
+
+
+
+ Reference to a user defined datatype or POU. Variable declarations use this type to declare e.g. function block instances.
+
+
+
+ The user defined alias type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ An enumeration value used to build up enumeration types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of datatypes not defined in IEC 61131-3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which have no direct iec scope and can be used in any graphical body.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a conversion error. Used to keep information which can not be interpreted by the importing system
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+ Counterpart of the connector element
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Association of an action with qualifier
+
+
+
+
+ Name of an action or boolean variable.
+
+
+
+
+
+
+
+ Inline implementation of an action body.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in fbd. They can be used in all graphical bodies.
+
+
+
+
+
+ Describes a graphical object representing a call statement
+
+
+
+
+ Anchor position of the box. Top left corner excluding the instance name.
+
+
+
+
+ The list of used input variables (consumers)
+
+
+
+
+
+
+ Describes an inputVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The list of used inOut variables
+
+
+
+
+
+
+ Describes a inOutVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The list of used output variables (producers)
+
+
+
+
+
+
+ Describes a outputVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.
+
+
+
+
+
+
+ Expression used as producer
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expression used as consumer
+
+
+
+ Describes a graphical object representing a variable or expression used as l-value
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expression used as producer and consumer
+
+
+
+ Describes a graphical object representing a variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a jump label
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a jump statement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing areturn statement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in ld and are an extension to fbd. They can be used in ld and sfc bodies
+
+
+
+
+
+ Describes a graphical object representing a left powerrail
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a right powerrail
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a boolean variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid boolean iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid boolean iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in sfc. They can only be used in sfc bodies
+
+
+
+
+ A single step in a SFC Sequence. Actions are associated with a step by using an actionBlock element with a connection to the step element
+
+
+
+ Contains actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jump to a step, macro step or simultaneous divergence. Acts like a step. Predecessor should be a transition.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The priority of a transition is evaluated, if the transition is connected to a selectionDivergence element.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines the edge detection behaviour of a variable
+
+
+
+
+
+
+
+
+
+ Defines the storage mode (S/R) behaviour of a variable
+
+
+
+
+
+
+
+
+
+ Defines the different types of a POU
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 plcopen/TC6_XML_V101.pdf
Binary file plcopen/TC6_XML_V101.pdf has changed
diff -r 000000000000 -r b622defdfd98 plcopen/TC6_XML_V10_B.xsd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/TC6_XML_V10_B.xsd Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1364 @@
+
+
+
+
+ The complete project
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation language of the project e.g. "en-US"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Represents a group of resources and global variables
+
+
+
+
+
+ Represents a group of programs and tasks and global variables
+
+
+
+
+
+ Represents a periodic or triggered task
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A generic data type
+
+
+
+
+
+
+
+
+
+ Defines a range with signed bounds
+
+
+
+
+
+
+ Defines a range with unsigned bounds
+
+
+
+
+
+
+ A generic value
+
+
+
+
+
+ Value that can be represented as a single token string
+
+
+
+
+
+
+
+ Array value consisting of a list of occurrances - value pairs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Struct value consisting of a list of member - value pairs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Implementation part of a POU, action or transistion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Additional userspecific information to the element
+
+
+
+
+
+
+ List of variable declarations that share the same memory attributes (CONSTANT, RETAIN, NON_RETAIN, PERSISTENT)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List of variable declarations without attributes
+
+
+
+
+
+ Declaration of a variable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines a graphical position in X, Y coordinates
+
+
+
+
+
+
+ Describes a connection between the consumer element (eg. input variable of a function block) and the producer element (eg. output variable of a function block). It may contain a list of positions that describes the path of the connection.
+
+
+
+
+ All positions of the directed connection path. If any positions are given, the list has to contain the first (input pin of the consumer element) as well as the last (output pin of the producer element).
+
+
+
+
+
+ Identifies the element the connection starts from.
+
+
+
+
+ If present:
+ This attribute denotes the name of the VAR_OUTPUT / VAR_IN_OUTparameter of the pou block that is the start of the connection.
+ If not present:
+ If the refLocalId attribute refers to a pou block, the start of the connection is the first output of this block, which is not ENO.
+ If the refLocalId attribute refers to any other element type, the start of the connection is the elements single native output.
+
+
+
+
+
+ Defines a connection point on the consumer side
+
+
+
+
+ Relative position of the connection pin. Origin is the anchor position of the block.
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0] or an iec expression or multiple token text e.g. a + b (*sum*). An iec 61131-3 parser has to be used to extract variable information.
+
+
+
+
+
+
+
+ Defines a connection point on the producer side
+
+
+
+
+ Relative position of the connection pin. Origin is the anchor position of the block.
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+ Represents a program or function block instance either running with or without a task
+
+
+
+
+
+
+
+
+
+ Formatted text according to parts of XHTML 1.1
+
+
+
+
+
+
+
+ Collection of elementary IEC 61131-3 datatypes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The single byte character string type
+
+
+
+
+
+
+
+ The wide character (WORD) string type
+
+
+
+
+
+
+
+
+ Collection of derived IEC 61131-3 datatypes
+
+
+
+
+
+
+
+
+
+
+
+
+ Reference to a user defined datatype or POU. Variable declarations use this type to declare e.g. function block instances.
+
+
+
+ The user defined alias type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ An enumeration value used to build up enumeration types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of datatypes not defined in IEC 61131-3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which have no direct iec scope and can be used in any graphical body.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a conversion error. Used to keep information which can not be interpreted by the importing system
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+ Counterpart of the connector element
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Association of an action with qualifier
+
+
+
+
+ Name of an action or boolean variable.
+
+
+
+
+
+
+
+ Inline implementation of an action body.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in fbd. They can be used in all graphical bodies.
+
+
+
+
+
+ Describes a graphical object representing a call statement
+
+
+
+
+ Anchor position of the box. Top left corner excluding the instance name.
+
+
+
+
+ The list of used input variables (consumers)
+
+
+
+
+
+
+ Describes an inputVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The list of used inOut variables
+
+
+
+
+
+
+ Describes a inOutVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The list of used output variables (producers)
+
+
+
+
+
+
+ Describes a outputVariable of a Function or a FunctionBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.
+
+
+
+
+
+
+ Expression used as producer
+
+
+
+ Describes a graphical object representing a variable, literal or expression used as r-value
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expression used as consumer
+
+
+
+ Describes a graphical object representing a variable or expression used as l-value
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expression used as producer and consumer
+
+
+
+ Describes a graphical object representing a variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid iec variable e.g. avar[0].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a jump label
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a jump statement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing areturn statement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in ld and are an extension to fbd. They can be used in ld and sfc bodies
+
+
+
+
+
+ Describes a graphical object representing a left powerrail
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a right powerrail
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a boolean variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid boolean iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes a graphical object representing a variable which can be used as l-value and r-value at the same time
+
+
+
+
+
+
+
+ The operand is a valid boolean iec variable e.g. avar[0]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collection of objects which are defined in sfc. They can only be used in sfc bodies
+
+
+
+
+ A single step in a SFC Sequence. Actions are associated with a step by using an actionBlock element with a connection to the step element
+
+
+
+ Contains actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jump to a step, macro step or simultaneous divergence. Acts like a step. Predecessor should be a transition.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The priority of a transition is evaluated, if the transition is connected to a selectionDivergence element.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines the edge detection behaviour of a variable
+
+
+
+
+
+
+
+
+
+ Defines the storage mode (S/R) behaviour of a variable
+
+
+
+
+
+
+
+
+
+ Defines the different types of a POU
+
+
+
+
+
+
+
+
diff -r 000000000000 -r b622defdfd98 plcopen/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/__init__.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,37 @@
+#!/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
+
+# Package initialisation
+
+# plcopen module dynamically creates its classes
+import plcopen
+for classname, cls in plcopen.PLCOpenClasses.items():
+ plcopen.__dict__[classname] = cls
+for typename, typ in plcopen.PLCOpenTypes.items():
+ plcopen.__dict__[typename] = typ
+from plcopen import VarOrder
+from plcopen import HolePseudoFile
+
+from structures import *
+from docpdf import *
\ No newline at end of file
diff -r 000000000000 -r b622defdfd98 plcopen/docpdf.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/docpdf.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,38 @@
+#!/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
+
+# Package initialisation
+
+# plcopen module dynamically creates its classes
+
+import os
+
+def OpenPDFDoc():
+ if not os.path.isfile("plcopen/TC6_XML_V101.pdf"):
+ return """No documentation file available."""
+ try:
+ os.system("xpdf -remote PLCOpen plcopen/TC6_XML_V101.pdf &")
+ return True
+ except:
+ return """Check if xpdf is correctly installed on your computer"""
\ No newline at end of file
diff -r 000000000000 -r b622defdfd98 plcopen/plcopen.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/plcopen.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,1792 @@
+#!/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 xml.parsers import expat
+from minixsv import pyxsval
+from random import *
+
+from types import *
+from datetime import *
+
+import sys,re
+
+classes = {}
+TimeType = time(0,0,0).__class__
+DateType = date(1,1,1).__class__
+DateTimeType = datetime(1,1,1,0,0,0).__class__
+
+"""
+Dictionary that makes the relation between var names in plcopen and displayed values
+"""
+VarTypes = {"Local" : "localVars", "Temp" : "tempVars", "Input" : "inputVars",
+ "Output" : "outputVars", "InOut" : "inOutVars", "External" : "externalVars",
+ "Global" : "globalVars", "Access" : "accessVars"}
+
+"""
+Define in which order var types must be displayed
+"""
+VarOrder = ["Local","Temp","Input","Output","InOut","External","Global","Access"]
+
+"""
+Regular expression models for extracting dates and times from a string
+"""
+time_model = re.compile('([0-9]{2}):([0-9]{2}):([0-9]{2})')
+date_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})')
+datetime_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2}):([0-9]{2})')
+
+"""
+Define which action qualifier must be associated with a duration
+"""
+QualifierList = {"N" : False, "R" : False, "S" : False, "L" : True, "D" : True,
+ "P" : False, "P0" : False, "P1" : False, "SD" : True, "DS" : True, "SL" : True}
+
+"""
+pyxsval is not complete and the parts that are not supported print some error
+reports. This class is used for not displaying them
+"""
+class HolePseudoFile:
+ """ Base class for file like objects to facilitate StdOut for the Shell."""
+ def __init__(self, output = None):
+ if output is None: output = []
+ self.output = output
+
+ def writelines(self, l):
+ map(self.write, l)
+
+ def write(self, s):
+ pass
+
+ def flush(self):
+ pass
+
+ def isatty(self):
+ return false
+
+"""
+This function calculates the number of whitespace for indentation
+"""
+def getIndent(indent, balise):
+ first = indent * 2
+ second = first + len(balise) + 1
+ return "\t".expandtabs(first), "\t".expandtabs(second)
+
+"""
+This function recursively creates a definition of the classes and their attributes
+for plcopen from the xsd file of plcopen opened in a DOM model
+"""
+def Generate_classes(tree, parent, sequence = False):
+ attributes = {}
+ inheritance = []
+ if sequence:
+ order = []
+ # The lists of attributes and inheritance of the node are generated from the childrens
+ for node in tree.childNodes:
+ # We make fun of #text elements and all other tags that don't are xsd tags
+ if node.nodeName != "#text" and node.nodeName.startswith("xsd:"):
+ recursion = False
+ name = node.nodeName[4:].encode()
+
+ # This tags defines an attribute of the class
+ if name in ["element", "attribute"]:
+ nodename = GetAttributeValue(node._attrs["name"])
+ if "type" in node._attrs:
+ nodetype = GetAttributeValue(node._attrs["type"])
+ else:
+ # The type of attribute is defines in the child tree so we generate a new class
+ # No name is defined so we create one from nodename and parent class name
+ # (because some different nodes can have the same name)
+ if parent:
+ classname = "%s_%s"%(parent, nodename)
+ else:
+ classname = nodename
+ Generate_classes(node, classname)
+ nodetype = "ppx:%s"%classname
+ if name == "attribute":
+ if "use" in node._attrs:
+ use = GetAttributeValue(node._attrs["use"])
+ else:
+ use = "optional"
+ if name == "element":
+ # If a tag can be written more than one time we define a list attribute
+ if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
+ nodetype = "%s[]"%nodetype
+ if "minOccurs" in node._attrs and GetAttributeValue(node._attrs["minOccurs"]) == "0":
+ use = "optional"
+ else:
+ use = "required"
+ attributes[nodename] = (nodetype, name, use)
+ if sequence:
+ order.append(nodename)
+
+ # This tag defines a new class
+ elif name == "complexType" or name == "simpleType":
+ if "name" in node._attrs:
+ classname = GetAttributeValue(node._attrs["name"])
+ super, attrs = Generate_classes(node, classname)
+ else:
+ classname = parent
+ super, attrs = Generate_classes(node, classname.split("_")[-1])
+ # When all attributes and inheritances have been extracted, the
+ # values are added in the list of classes to create
+ if classname not in classes:
+ classes[classname] = (super, attrs)
+ elif classes[classname] != (super, attrs):
+ print "A different class has already got %s for name"%classname
+
+ # This tag defines an attribute that can have different types
+ elif name == "choice":
+ super, attrs = Generate_classes(node, parent)
+ if "ref" in attrs.keys():
+ choices = attrs
+ else:
+ choices = {}
+ for attr, (attr_type, xml_type, write_type) in attrs.items():
+ choices[attr] = attr_type
+ if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
+ attributes["multichoice_content"] = choices
+ if sequence:
+ order.append("multichoice_content")
+ else:
+ attributes["choice_content"] = choices
+ if sequence:
+ order.append("choice_content")
+
+ # This tag defines the order in which class attributes must be written
+ # in plcopen xml file. We have to store this order like an attribute
+ elif name in "sequence":
+ super, attrs, order = Generate_classes(node, parent, True)
+ if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
+ for attr, (attr_type, xml_type, write_type) in attrs.items():
+ attrs[attr] = ("%s[]"%attr_type, xml_type, write_type)
+ if "minOccurs" in node._attrs and GetAttributeValue(node._attrs["minOccurs"]) == "0":
+ for attr, (attr_type, xml_type, write_type) in attrs.items():
+ attrs[attr] = (attr_type, xml_type, "optional")
+ inheritance.extend(super)
+ attributes.update(attrs)
+ attributes["order"] = order
+
+ # This tag defines of types
+ elif name == "group":
+ if "name" in node._attrs:
+ nodename = GetAttributeValue(node._attrs["name"])
+ super, attrs = Generate_classes(node, None)
+ classes[nodename] = (super, {"group":attrs["choice_content"]})
+ elif "ref" in node._attrs:
+ if "ref" not in attributes:
+ attributes["ref"] = [GetAttributeValue(node._attrs["ref"])]
+ else:
+ attributes["ref"].append(GetAttributeValue(node._attrs["ref"]))
+
+ # This tag define a base class for the node
+ elif name == "extension":
+ super = GetAttributeValue(node._attrs["base"])
+ inheritance.append(super[4:])
+ recursion = True
+
+ # This tag defines a restriction on the type of attribute
+ elif name == "restriction":
+ attributes["basetype"] = GetAttributeValue(node._attrs["base"])
+ recursion = True
+
+ # This tag defines an enumerated type
+ elif name == "enumeration":
+ if "enum" not in attributes:
+ attributes["enum"] = [GetAttributeValue(node._attrs["value"])]
+ else:
+ attributes["enum"].append(GetAttributeValue(node._attrs["value"]))
+
+ # This tags defines a restriction on a numerical value
+ elif name in ["minInclusive","maxInclusive"]:
+ if "limit" not in attributes:
+ attributes["limit"] = {}
+ if name == "minInclusive":
+ attributes["limit"]["min"] = eval(GetAttributeValue(node._attrs["value"]))
+ elif name == "maxInclusive":
+ attributes["limit"]["max"] = eval(GetAttributeValue(node._attrs["value"]))
+
+ # This tag are not important but their childrens are. The childrens are then parsed.
+ elif name in ["complexContent", "schema"]:
+ recursion = True
+
+ # We make fun of xsd documentation
+ elif name in ["annotation"]:
+ pass
+
+ else:
+ #print name
+ Generate_classes(node, parent)
+
+ # Parse the childrens of node
+ if recursion:
+ super, attrs = Generate_classes(node, parent)
+ inheritance.extend(super)
+ attributes.update(attrs)
+
+ # if sequence tag have been found, order is returned
+ if sequence:
+ return inheritance, attributes, order
+ else:
+ return inheritance, attributes
+"""
+Function that extracts data from a node
+"""
+def GetAttributeValue(attr):
+ if len(attr.childNodes) == 1:
+ return attr.childNodes[0].data.encode()
+ else:
+ return ""
+
+"""
+Funtion that returns the Python type and default value for a given xsd type
+"""
+def GetTypeInitialValue(attr_type):
+ type_compute = attr_type[4:].replace("[]", "")
+ if attr_type.startswith("xsd:"):
+ if type_compute == "boolean":
+ return BooleanType, "False"
+ elif type_compute in ["decimal","unsignedLong","long","integer"]:
+ return IntType, "0"
+ elif type_compute in ["string","anyURI","NMTOKEN"]:
+ return StringType, "\"\""
+ elif type_compute == "time":
+ return TimeType, "time(0,0,0)"
+ elif type_compute == "date":
+ return DateType, "date(1,1,1)"
+ elif type_compute == "dateTime":
+ return DateTimeType, "datetime(1,1,1,0,0,0)"
+ elif type_compute == "language":
+ return StringType, "\"en-US\""
+ else:
+ print "Can't affect: %s"%type_compute
+ elif attr_type.startswith("ppx:"):
+ if type_compute in PLCOpenClasses:
+ return PLCOpenClasses[type_compute],"%s()"%type_compute
+
+"""
+Function that computes value from a python type (Only Boolean are critical because
+there is no uppercase in plcopen)
+"""
+def ComputeValue(value):
+ if type(value) == BooleanType:
+ if value:
+ return "true"
+ else:
+ return "false"
+ else:
+ return str(value)
+
+"""
+Function that extracts a value from a string following the xsd type given
+"""
+def GetComputedValue(attr_type, value):
+ type_compute = attr_type[4:].replace("[]", "")
+ if type_compute == "boolean":
+ if value == "true":
+ return True
+ elif value == "false":
+ return False
+ else:
+ raise ValueError, "\"%s\" is not a valid boolean!"%value
+ elif type_compute in ["decimal","unsignedLong","long","integer"]:
+ return int(value)
+ elif type_compute in ["string","anyURI","NMTOKEN","language"]:
+ return value
+ elif type_compute == "time":
+ result = time_model.match(value)
+ if result:
+ time_values = [int(v) for v in result.groups()]
+ return time(*time_values)
+ else:
+ raise ValueError, "\"%s\" is not a valid time!"%value
+ elif type_compute == "date":
+ result = date_model.match(value)
+ if result:
+ date_values = [int(v) for v in result.groups()]
+ return date(*date_values)
+ else:
+ raise ValueError, "\"%s\" is not a valid date!"%value
+ elif type_compute == "dateTime":
+ result = datetime_model.match(value)
+ if result:
+ datetime_values = [int(v) for v in result.groups()]
+ return datetime(*datetime_values)
+ else:
+ raise ValueError, "\"%s\" is not a valid datetime!"%value
+ else:
+ print "Can't affect: %s"%type_compute
+ return None
+
+"""
+This is the Metaclass for PLCOpen element classes. It generates automatically
+the basic useful methods for manipulate the differents attributes of the classes
+"""
+class MetaClass(type):
+
+ def __init__(cls, name, bases, dict):
+ super(MetaClass, cls).__init__(name, bases, {})
+ #print name, bases, dict, "\n"
+ initialValues = {}
+ for attr, values in dict.items():
+ if attr in ["order", "basetype"]:
+ pass
+
+ # Class is a enumerated type
+ elif attr == "enum":
+ value_type, initial = GetTypeInitialValue(dict["basetype"])
+ initialValues["value"] = "\"%s\""%values[0]
+ setattr(cls, "value", values[0])
+ setattr(cls, "setValue", MetaClass.generateSetEnumMethod(cls, values, value_type))
+ setattr(cls, "getValue", MetaClass.generateGetMethod(cls, "value"))
+
+ # Class is a limited type
+ elif attr == "limit":
+ value_type, initial = GetTypeInitialValue(dict["basetype"])
+ initial = 0
+ if "min" in values:
+ initial = max(initial, values["min"])
+ if "max" in values:
+ initial = min(initial, values["max"])
+ initialValues["value"] = "%d"%initial
+ setattr(cls, "value", initial)
+ setattr(cls, "setValue", MetaClass.generateSetLimitMethod(cls, values, value_type))
+ setattr(cls, "getValue", MetaClass.generateGetMethod(cls, "value"))
+
+ # Class has an attribute that can have different value types
+ elif attr == "choice_content":
+ setattr(cls, "content", None)
+ initialValues["content"] = "None"
+ setattr(cls, "deleteContent", MetaClass.generateDeleteMethod(cls, "content"))
+ setattr(cls, "setContent", MetaClass.generateSetChoiceMethod(cls, values))
+ setattr(cls, "getContent", MetaClass.generateGetMethod(cls, "content"))
+ elif attr == "multichoice_content":
+ setattr(cls, "content", [])
+ initialValues["content"] = "[]"
+ setattr(cls, "appendContent", MetaClass.generateAppendChoiceMethod(cls, values))
+ setattr(cls, "insertContent", MetaClass.generateInsertChoiceMethod(cls, values))
+ setattr(cls, "removeContent", MetaClass.generateRemoveMethod(cls, "content"))
+ setattr(cls, "countContent", MetaClass.generateCountMethod(cls, "content"))
+ setattr(cls, "setContent", MetaClass.generateSetMethod(cls, "content", ListType))
+ setattr(cls, "getContent", MetaClass.generateGetMethod(cls, "content"))
+
+ # It's an attribute of the class
+ else:
+ attrname = attr[0].upper()+attr[1:]
+ attr_type, xml_type, write_type = values
+ value_type, initial = GetTypeInitialValue(attr_type)
+ # Value of the attribute is a list
+ if attr_type.endswith("[]"):
+ setattr(cls, attr, [])
+ initialValues[attr] = "[]"
+ setattr(cls, "append"+attrname, MetaClass.generateAppendMethod(cls, attr, value_type))
+ setattr(cls, "insert"+attrname, MetaClass.generateInsertMethod(cls, attr, value_type))
+ setattr(cls, "remove"+attrname, MetaClass.generateRemoveMethod(cls, attr))
+ setattr(cls, "count"+attrname, MetaClass.generateCountMethod(cls, attr))
+ setattr(cls, "set"+attrname, MetaClass.generateSetMethod(cls, attr, ListType))
+ else:
+ if write_type == "optional":
+ setattr(cls, attr, None)
+ initialValues[attr] = "None"
+ setattr(cls, "add"+attrname, MetaClass.generateAddMethod(cls, attr, initial))
+ setattr(cls, "delete"+attrname, MetaClass.generateDeleteMethod(cls, attr))
+ else:
+ setattr(cls, attr, initial)
+ initialValues[attr] = initial
+ setattr(cls, "set"+attrname, MetaClass.generateSetMethod(cls, attr, value_type))
+ setattr(cls, "get"+attrname, MetaClass.generateGetMethod(cls, attr))
+ setattr(cls, "__init__", MetaClass.generateInitMethod(cls, bases, initialValues))
+ setattr(cls, "loadXMLTree", MetaClass.generateLoadXMLTree(cls, bases, dict))
+ setattr(cls, "generateXMLText", MetaClass.generateGenerateXMLText(cls, bases, dict))
+ setattr(cls, "singleLineAttributes", True)
+
+ """
+ Method that generate the method for loading an xml tree by following the
+ attributes list defined
+ """
+ def generateLoadXMLTree(cls, bases, dict):
+ def loadXMLTreeMethod(self, tree):
+ # If class is derived, values of inheritance classes are loaded
+ for base in bases:
+ base.loadXMLTree(self, tree)
+ # Class is a enumerated or limited value
+ if "enum" in dict.keys() or "limit" in dict.keys():
+ attr_value = GetAttributeValue(tree)
+ attr_type = dict["basetype"]
+ val = GetComputedValue(attr_type, attr_value)
+ self.setValue(val)
+ else:
+
+ # Load the node attributes if they are defined in the list
+ for attrname, attr in tree._attrs.items():
+ if attrname in dict.keys():
+ attr_type, xml_type, write_type = dict[attrname]
+ attr_value = GetAttributeValue(attr)
+ if write_type != "optional" or attr_value != "":
+ # Extracts the value
+ if attr_type.startswith("xsd:"):
+ val = GetComputedValue(attr_type, attr_value)
+ elif attr_type.startswith("ppx:"):
+ val = eval("%s()"%attr_type[4:], globals().update(PLCOpenClasses))
+ val.loadXMLTree(attr)
+ setattr(self, attrname, val)
+
+ # Load the node childs if they are defined in the list
+ for node in tree.childNodes:
+ name = node.nodeName
+ # We make fun of #text elements
+ if name != "#text":
+
+ # Class has an attribute that can have different value types
+ if "choice_content" in dict.keys() and name in dict["choice_content"].keys():
+ attr_type = dict["choice_content"][name]
+ # Extracts the value
+ if attr_type.startswith("xsd:"):
+ attr_value = GetAttributeValue(node)
+ if write_type != "optional" or attr_value != "":
+ val = GetComputedValue(attr_type.replace("[]",""), attr_value)
+ else:
+ val = None
+ elif attr_type.startswith("ppx:"):
+ val = eval("%s()"%attr_type[4:].replace("[]",""), globals().update(PLCOpenClasses))
+ val.loadXMLTree(node)
+ # Stock value in content attribute
+ if val:
+ if attr_type.endswith("[]"):
+ if self.content:
+ self.content["value"].append(val)
+ else:
+ self.content = {"name":name,"value":[val]}
+ else:
+ self.content = {"name":name,"value":val}
+
+ # Class has a list of attributes that can have different value types
+ elif "multichoice_content" in dict.keys() and name in dict["multichoice_content"].keys():
+ attr_type = dict["multichoice_content"][name]
+ # Extracts the value
+ if attr_type.startswith("xsd:"):
+ attr_value = GetAttributeValue(node)
+ if write_type != "optional" or attr_value != "":
+ val = GetComputedValue(attr_type, attr_value)
+ else:
+ val = None
+ elif attr_type.startswith("ppx:"):
+ val = eval("%s()"%attr_type[4:], globals().update(PLCOpenClasses))
+ val.loadXMLTree(node)
+ # Add to content attribute list
+ if val:
+ self.content.append({"name":name,"value":val})
+
+ # The node child is defined in the list
+ elif name in dict.keys():
+ attr_type, xml_type, write_type = dict[name]
+ # Extracts the value
+ if attr_type.startswith("xsd:"):
+ attr_value = GetAttributeValue(node)
+ if write_type != "optional" or attr_value != "":
+ val = GetComputedValue(attr_type.replace("[]",""), attr_value)
+ else:
+ val = None
+ elif attr_type.startswith("ppx:"):
+ val = eval("%s()"%attr_type[4:].replace("[]",""), globals().update(PLCOpenClasses))
+ val.loadXMLTree(node)
+ # Stock value in attribute
+ if val:
+ if attr_type.endswith("[]"):
+ getattr(self, name).append(val)
+ else:
+ setattr(self, name, val)
+ return loadXMLTreeMethod
+
+ """
+ Method that generates the method for generating an xml text by following the
+ attributes list defined
+ """
+ def generateGenerateXMLText(cls, bases, dict):
+ def generateXMLTextMethod(self, name, indent, extras = {}, derived = False):
+ ind1, ind2 = getIndent(indent, name)
+ if not derived:
+ text = ind1 + "<%s"%name
+ else:
+ text = ""
+ if len(bases) > 0:
+ base_extras = {}
+ if "order" in dict.keys():
+ order = dict["order"]
+ else:
+ order = []
+ if "choice_content" in dict.keys() and "choice_content" not in order:
+ order.append("choice_content")
+ if "multichoice_content" in dict.keys() and "multichoice_content" not in order:
+ order.append("multichoice_content")
+ size = 0
+ first = True
+ for attr, value in extras.items():
+ if not first and not self.singleLineAttributes:
+ text += "\n%s"%(ind2)
+ text += " %s=\"%s\""%(attr, ComputeValue(value))
+ first = False
+ for attr, values in dict.items():
+ if attr in ["order","choice_content","multichoice_content"]:
+ pass
+ elif attr in ["enum","limit"]:
+ if not derived:
+ text += ">%s%s>\n"%(ComputeValue(self.value),name)
+ else:
+ text += ComputeValue(self.value)
+ return text
+ elif values[1] == "attribute":
+ value = getattr(self, attr, None)
+ if values[2] != "optional" or value != None:
+ if not first and not self.singleLineAttributes:
+ text += "\n%s"%(ind2)
+ if values[0].startswith("ppx"):
+ if len(bases) > 0:
+ base_extras[attr] = value.getValue()
+ else:
+ text += " %s=\"%s\""%(attr, ComputeValue(value.getValue()))
+ else:
+ if len(bases) > 0:
+ base_extras[attr] = value
+ else:
+ text += " %s=\"%s\""%(attr, ComputeValue(value))
+ first = False
+ if len(bases) > 0:
+ first, new_text = bases[0].generateXMLText(self, name, indent, base_extras, True)
+ text += new_text
+ else:
+ first = True
+ ind3, ind4 = getIndent(indent + 1, name)
+ for attr in order:
+ value = getattr(self, attr, None)
+ if attr == "choice_content":
+ if self.content:
+ if first:
+ text += ">\n"
+ first = False
+ value_type = dict[attr][self.content["name"]]
+ if value_type.startswith("xsd:"):
+ if value_type.endswith("[]"):
+ for content in self.content["value"]:
+ text += ind1 + "<%s>%s%s>\n"%(self.content["name"], ComputeValue(content), self.content["name"])
+ else:
+ text += ind1 + "<%s>%s%s>\n"%(self.content["name"], ComputeValue(self.content["value"]), self.content["name"])
+ elif value_type.endswith("[]"):
+ for content in self.content["value"]:
+ text += content.generateXMLText(self.content["name"], indent + 1)
+ else:
+ text += self.content["value"].generateXMLText(self.content["name"], indent + 1)
+ elif attr == "multichoice_content":
+ if len(self.content) > 0:
+ for element in self.content:
+ if first:
+ text += ">\n"
+ first = False
+ value_type = dict[attr][element["name"]]
+ if value_type.startswith("xsd:"):
+ text += ind1 + "<%s>%s%s>\n"%(element["name"], ComputeValue(element["value"]), element["name"])
+ else:
+ text += element["value"].generateXMLText(element["name"], indent + 1)
+ elif dict[attr][2] != "optional" or value != None:
+ if dict[attr][0].endswith("[]"):
+ if first and len(value) > 0:
+ text += ">\n"
+ first = False
+ for element in value:
+ if dict[attr][0].startswith("xsd:"):
+ text += ind3 + "<%s>%s%s>\n"%(attr, ComputeValue(element), attr)
+ else:
+ text += element.generateXMLText(attr, indent + 1)
+ else:
+ if first:
+ text += ">\n"
+ first = False
+ if dict[attr][0].startswith("xsd:"):
+ text += ind3 + "<%s>%s%s>\n"%(attr, ComputeValue(value), attr)
+ else:
+ text += getattr(self, attr).generateXMLText(attr, indent + 1)
+ if not derived:
+ if first:
+ text += "/>\n"
+ else:
+ text += ind1 + "%s>\n"%(name)
+ return text
+ else:
+ return first, text
+ return generateXMLTextMethod
+
+ """
+ Methods that generates the different methods for setting and getting the attributes
+ """
+
+ def generateInitMethod(cls, bases, dict):
+ def initMethod(self):
+ for base in bases:
+ base.__init__(self)
+ for attr, initial in dict.items():
+ setattr(self, attr, eval(initial, globals().update(PLCOpenClasses)))
+ return initMethod
+
+ def generateSetMethod(cls, attr, choice_type):
+ def setMethod(self, value):
+ setattr(self, attr, value)
+ return setMethod
+
+ def generateSetChoiceMethod(cls, attr_type):
+ def setChoiceMethod(self, name, value):
+ self.content = {"name":name,"value":value}
+ return setChoiceMethod
+
+ def generateSetEnumMethod(cls, enum, attr_type):
+ def setEnumMethod(self, value):
+ if value in enum:
+ self.value = value
+ else:
+ raise ValueError, "%s is not a valid value. Must be in %s"%(value, str(enum))
+ return setEnumMethod
+
+ def generateSetLimitMethod(cls, limit, attr_type):
+ def setMethod(self, value):
+ if "min" in limit and value < limit["min"]:
+ raise ValueError, "%s is not a valid value. Must be greater than %d"%(value, limit["min"])
+ elif "max" in limit and value > limit["max"]:
+ raise ValueError, "%s is not a valid value. Must be smaller than %d"%(value, limit["max"])
+ else:
+ self.value = value
+ return setMethod
+
+ def generateGetMethod(cls, attr):
+ def getMethod(self):
+ return getattr(self, attr, None)
+ return getMethod
+
+ def generateAddMethod(cls, attr, initial):
+ def addMethod(self):
+ setattr(self, attr, eval(initial, globals().update(PLCOpenClasses)))
+ return addMethod
+
+ def generateDeleteMethod(cls, attr):
+ def deleteMethod(self):
+ setattr(self, attr, None)
+ return deleteMethod
+
+ def generateAppendMethod(cls, attr, attr_type):
+ def appendMethod(self, value):
+ getattr(self, attr).append(value)
+ return appendMethod
+
+ def generateInsertMethod(cls, attr, attr_type):
+ def insertMethod(self, index, value):
+ getattr(self, attr).insert(index, value)
+ return insertMethod
+
+ def generateAppendChoiceMethod(cls, choice_types):
+ def appendMethod(self, name, value):
+ self.content.append({"name":name,"value":value})
+ return appendMethod
+
+ def generateInsertChoiceMethod(cls, choice_types):
+ def insertMethod(self, index, name, value):
+ self.content.insert(index, {"name":name,"value":value})
+ return insertMethod
+
+ def generateRemoveMethod(cls, attr):
+ def removeMethod(self, index):
+ getattr(self, attr).pop(index)
+ return removeMethod
+
+ def generateCountMethod(cls, attr):
+ def countMethod(self):
+ return len(getattr(self, attr))
+ return countMethod
+
+"""
+Method that generate the classes
+"""
+def CreateClass(classe):
+ # Checks that classe haven't been generated yet
+ if classe not in PLCOpenClasses and classe not in PLCOpenTypes and classe in classes:
+ inheritance, attributes = classes[classe]
+ #print classe, inheritance, attributes
+ dict = {}
+ bases = []
+
+ # If inheritance classes haven't been generated
+ for base in inheritance:
+ if base not in PLCOpenClasses:
+ CreateClass(base)
+ bases.append(PLCOpenClasses[base])
+
+ # Checks that all attribute types are available
+ for attribute, type_attribute in attributes.items():
+ if attribute == "group":
+ PLCOpenTypes[classe] = type_attribute
+ elif attribute == "ref":
+ PLCOpenTypes[classe] = {}
+ for attr in type_attribute:
+ if attr[4:] not in PLCOpenTypes:
+ CreateClass(attr[4:])
+ PLCOpenTypes[classe].update(PLCOpenTypes[attr[4:]])
+ elif attribute in ["choice_content","multichoice_content"]:
+ element_types = {}
+ for attr, value in type_attribute.items():
+ if attr == "ref":
+ for ref in type_attribute["ref"]:
+ if ref[4:] not in PLCOpenTypes:
+ CreateClass(ref[4:])
+ element_types.update(PLCOpenTypes[ref[4:]])
+ else:
+ element_types[attr] = value
+ dict[attribute] = element_types
+ else:
+ dict[attribute] = type_attribute
+ if attribute == "enum":
+ PLCOpenTypes["%s_enum"%classe] = type_attribute
+ elif attribute not in ["limit", "order"]:
+ if type_attribute[0].startswith("ppx:"):
+ type_compute = type_attribute[0][4:].replace("[]","")
+ if type_compute not in PLCOpenClasses:
+ CreateClass(type_compute)
+ if "group" not in attributes.keys() and "ref" not in attributes.keys():
+ cls = MetaClass.__new__(MetaClass, classe, tuple(bases), dict)
+ MetaClass.__init__(cls, classe, tuple(bases), dict)
+ PLCOpenClasses[classe] = cls
+
+def Generate_Methods():
+ if "dataType" in PLCOpenClasses:
+ cls = PLCOpenClasses["dataType"]
+ cls.value = None
+
+ def getValue(self):
+ return self.value
+ setattr(cls, "getValue", getValue)
+
+ def setValue(self, value):
+ self.value = value
+ setattr(cls, "setValue", setValue)
+
+ def loadXMLTree(self, tree):
+ self.value = tree.childNodes[1].nodeName
+ setattr(cls, "loadXMLTree", loadXMLTree)
+
+ def generateXMLText(self, name, indent, extras = {}):
+ ind1, ind2 = getIndent(indent, name)
+ text = ind1 + "<%s>\n"%name
+ ind3, ind4 = getIndent(indent + 1, self.value)
+ text += ind3 + "<%s/>\n"%self.value
+ text += ind1 + "%s>\n"%name
+ return text
+ setattr(cls, "generateXMLText", generateXMLText)
+
+ if "formattedText" in PLCOpenClasses:
+ cls = PLCOpenClasses["formattedText"]
+ cls.text = ""
+
+ def getText(self):
+ return self.text
+ setattr(cls, "getText", getText)
+
+ def setText(self, text):
+ self.text = text
+ setattr(cls, "setText", setText)
+
+ def loadXMLTree(self, tree):
+ self.text = GetAttributeValue(tree)
+ if len(self.text.splitlines()) > 1:
+ self.text = self.text[1:].rstrip()
+ setattr(cls, "loadXMLTree", loadXMLTree)
+
+ def generateXMLText(self, name, indent, extras = {}):
+ ind1, ind2 = getIndent(indent, name)
+ if len(self.text.splitlines()) > 1:
+ text = ind1 + "<%s>\n"%name
+ text += "%s\n"%self.text
+ text += ind1 + "%s>\n"%name
+ return text
+ else:
+ return ind1 + "<%s>%s%s>\n"%(name, self.text, name)
+ setattr(cls, "generateXMLText", generateXMLText)
+
+ if "project" in PLCOpenClasses:
+ cls = PLCOpenClasses["project"]
+ cls.singleLineAttributes = False
+
+ def getFileHeader(self):
+ fileheader = {}
+ fileheader["companyName"] = self.fileHeader.getCompanyName()
+ if self.fileHeader.getCompanyURL():
+ fileheader["companyURL"] = self.fileHeader.getCompanyURL()
+ fileheader["productName"] = self.fileHeader.getProductName()
+ fileheader["productVersion"] = self.fileHeader.getProductVersion()
+ if self.fileHeader.getProductRelease():
+ fileheader["productRelease"] = self.fileHeader.getProductRelease()
+ fileheader["creationDateTime"] = self.fileHeader.getCreationDateTime()
+ if self.fileHeader.getContentDescription():
+ fileheader["contentDescription"] = self.fileHeader.getContentDescription()
+ return fileheader
+ setattr(cls, "getFileHeader", getFileHeader)
+
+ def setFileHeader(self, fileheader):
+ self.fileHeader.setCompanyName(fileheader["companyName"])
+ if "companyURL" in fileheader:
+ self.fileHeader.setCompanyURL(fileheader["companyURL"])
+ self.fileHeader.setProductName(fileheader["productName"])
+ self.fileHeader.setProductVersion(fileheader["productVersion"])
+ if "productRelease" in fileheader:
+ self.fileHeader.setProductRelease(fileheader["productRelease"])
+ self.fileHeader.setCreationDateTime(fileheader["creationDateTime"])
+ if "contentDescription" in fileheader:
+ self.fileHeader.setContentDescription(fileheader["contentDescription"])
+ setattr(cls, "setFileHeader", setFileHeader)
+
+ def setName(self, name):
+ self.contentHeader.setName(name)
+ setattr(cls, "setName", setName)
+
+ def getName(self):
+ return self.contentHeader.getName()
+ setattr(cls, "getName", getName)
+
+ def getPous(self):
+ return self.types.getPouElements()
+ setattr(cls, "getPous", getPous)
+
+ def getPou(self, name):
+ return self.types.getPouElement(name)
+ setattr(cls, "getPou", getPou)
+
+ def appendPou(self, name, pou_type, body_type):
+ self.types.appendPouElement(name, pou_type, body_type)
+ setattr(cls, "appendPou", appendPou)
+
+ def insertPou(self, index, pou):
+ self.types.insertPouElement(index, pou)
+ setattr(cls, "insertPou", insertPou)
+
+ def removePou(self, name):
+ self.types.removePouElement(name)
+ setattr(cls, "removePou", removePou)
+
+ def getConfigurations(self):
+ configurations = self.instances.configurations.getConfiguration()
+ if configurations:
+ return configurations
+ return []
+ setattr(cls, "getConfigurations", getConfigurations)
+
+ def getConfiguration(self, name):
+ for configuration in self.instances.configurations.getConfiguration():
+ if configuration.getName() == name:
+ return configuration
+ return None
+ setattr(cls, "getConfiguration", getConfiguration)
+
+ def addConfiguration(self, name):
+ for configuration in self.instances.configurations.getConfiguration():
+ if configuration.getName() == name:
+ raise ValueError, "\"%s\" configuration already exists !!!"%name
+ new_configuration = PLCOpenClasses["configurations_configuration"]()
+ new_configuration.setName(name)
+ self.instances.configurations.appendConfiguration(new_configuration)
+ setattr(cls, "addConfiguration", addConfiguration)
+
+ def removeConfiguration(self, name):
+ found = False
+ for idx, configuration in enumerate(self.instances.configurations.getConfiguration()):
+ if configuration.getName() == name:
+ self.instances.configurations.removeConfiguration(idx)
+ found = True
+ break
+ if not found:
+ raise ValueError, "\"%s\" configuration doesn't exist !!!"%name
+ setattr(cls, "removeConfiguration", removeConfiguration)
+
+ def getConfigurationResource(self, config_name, name):
+ configuration = self.getConfiguration(config_name)
+ if configuration:
+ for resource in configuration.getResource():
+ if resource.getName() == name:
+ return resource
+ return None
+ setattr(cls, "getConfigurationResource", getConfigurationResource)
+
+ def addConfigurationResource(self, config_name, name):
+ configuration = self.getConfiguration(config_name)
+ if configuration:
+ for resource in configuration.getResource():
+ if resource.getName() == name:
+ raise ValueError, "\"%s\" resource already exists in \"%s\" configuration !!!"%(name, config_name)
+ new_resource = PLCOpenClasses["configuration_resource"]()
+ new_resource.setName(name)
+ configuration.appendResource(new_resource)
+ setattr(cls, "addConfigurationResource", addConfigurationResource)
+
+ def removeConfigurationResource(self, config_name, name):
+ configuration = self.getConfiguration(config_name)
+ if configuration:
+ found = False
+ for idx, resource in enumerate(configuration.getResource()):
+ if resource.getName() == name:
+ configuration.removeResource(idx)
+ found = True
+ break
+ if not found:
+ raise ValueError, "\"%s\" resource doesn't exist in \"%s\" configuration !!!"%(name, config_name)
+ setattr(cls, "removeConfigurationResource", removeConfigurationResource)
+
+ PLCOpenClasses["project_fileHeader"].singleLineAttributes = False
+
+ if "project_types" in PLCOpenClasses:
+ cls = PLCOpenClasses["project_types"]
+
+ def getPouElements(self):
+ return self.pous.getPou()
+ setattr(cls, "getPouElements", getPouElements)
+
+ def getPouElement(self, name):
+ elements = self.pous.getPou()
+ for element in elements:
+ if element.getName() == name:
+ return element
+ return None
+ setattr(cls, "getPouElement", getPouElement)
+
+ def appendPouElement(self, name, pou_type, body_type):
+ for element in self.pous.getPou():
+ if element.getName() == name:
+ raise ValueError, "\"%s\" POU already exists !!!"%name
+ new_pou = PLCOpenClasses["pous_pou"]()
+ new_pou.setName(name)
+ new_pou.pouType.setValue(pou_type)
+ new_pou.setBody(PLCOpenClasses["body"]())
+ new_pou.setBodyType(body_type)
+ self.pous.appendPou(new_pou)
+ setattr(cls, "appendPouElement", appendPouElement)
+
+ def insertPouElement(self, index, pou):
+ self.pous.insertPou(index, pou)
+ setattr(cls, "insertPouElement", insertPouElement)
+
+ def removePouElement(self, name):
+ found = False
+ for idx, element in enumerate(self.pous.getPou()):
+ if element.getName() == name:
+ self.pous.removePou(idx)
+ found = True
+ break
+ if not found:
+ raise ValueError, "\"%s\" POU doesn't exist !!!"%name
+ setattr(cls, "removePouElement", removePouElement)
+
+ def setBodyType(self, type):
+ if type == "IL":
+ self.body.setContent("IL", PLCOpenClasses["formattedText"]())
+ elif type == "ST":
+ self.body.setContent("ST", PLCOpenClasses["formattedText"]())
+ elif type == "LD":
+ self.body.setContent("LD", PLCOpenClasses["body_LD"]())
+ elif type == "FBD":
+ self.body.setContent("FBD", PLCOpenClasses["body_FBD"]())
+ elif type == "SFC":
+ self.body.setContent("SFC", PLCOpenClasses["body_SFC"]())
+ else:
+ raise ValueError, "%s isn't a valid body type!"%type
+
+ def getBodyType(self):
+ return self.body.getContent()["name"]
+
+ def addInstance(self, name, instance):
+ self.body.appendContentInstance(name, instance)
+
+ def getInstances(self):
+ return self.body.getContentInstances()
+
+ def getInstance(self, id):
+ return self.body.getContentInstance(id)
+
+ def getRandomInstance(self, exclude):
+ return self.body.getContentRandomInstance(exclude)
+
+ def getInstanceByName(self, name):
+ return self.body.getContentInstanceByName(name)
+
+ def removeInstance(self, id):
+ self.body.removeContentInstance(id)
+
+ def setText(self, text):
+ self.body.setText(text)
+
+ def getText(self):
+ return self.body.getText()
+ setattr(cls, "getText", getText)
+
+ if "pous_pou" in PLCOpenClasses:
+ cls = PLCOpenClasses["pous_pou"]
+
+ setattr(cls, "setBodyType", setBodyType)
+ setattr(cls, "getBodyType", getBodyType)
+ setattr(cls, "addInstance", addInstance)
+ setattr(cls, "getInstances", getInstances)
+ setattr(cls, "getInstance", getInstance)
+ setattr(cls, "getRandomInstance", getRandomInstance)
+ setattr(cls, "getInstanceByName", getInstanceByName)
+ setattr(cls, "removeInstance", removeInstance)
+ setattr(cls, "setText", setText)
+ setattr(cls, "getText", getText)
+
+ def getVars(self):
+ vars = []
+ reverse_types = {}
+ for name, value in VarTypes.items():
+ reverse_types[value] = name
+ for varlist in self.interface.getContent():
+ vars.append((reverse_types[varlist["name"]], varlist["value"]))
+ return vars
+ setattr(cls, "getVars", getVars)
+
+ def setVars(self, vars):
+ self.interface.setContent([])
+ for vartype, varlist in vars:
+ self.interface.appendContent(VarTypes[vartype], varlist)
+ setattr(cls, "setVars", setVars)
+
+ def addTransition(self, name, type):
+ if not self.transitions:
+ self.addTransitions()
+ self.transitions.setTransition([])
+ transition = PLCOpenClasses["transitions_transition"]()
+ transition.setName(name)
+ transition.setBodyType(type)
+ self.transitions.appendTransition(transition)
+ setattr(cls, "addTransition", addTransition)
+
+ def getTransition(self, name):
+ if self.transitions:
+ for transition in self.transitions.getTransition():
+ if transition.getName() == name:
+ return transition
+ return None
+ setattr(cls, "getTransition", getTransition)
+
+ def getTransitionList(self):
+ if self.transitions:
+ return self.transitions.getTransition()
+ return []
+ setattr(cls, "getTransitionList", getTransitionList)
+
+ def removeTransition(self, name):
+ if self.transitions:
+ transitions = self.transitions.getTransition()
+ i = 0
+ removed = False
+ while i < len(transitions) and not removed:
+ if transitions[i].getName() == name:
+ transitions.removeTransition(i)
+ removed = True
+ i += 1
+ if not removed:
+ raise ValueError, "Transition with name %s doesn't exists!"%name
+ setattr(cls, "removeTransition", removeTransition)
+
+ def addAction(self, name, type):
+ if not self.actions:
+ self.addActions()
+ self.actions.setAction([])
+ action = PLCOpenClasses["actions_action"]()
+ action.setName(name)
+ action.setBodyType(type)
+ self.actions.appendAction(action)
+ setattr(cls, "addAction", addAction)
+
+ def getAction(self, name):
+ if self.actions:
+ for action in self.actions.getAction():
+ if action.getName() == name:
+ return action
+ return None
+ setattr(cls, "getAction", getAction)
+
+ def getActionList(self):
+ if self.actions:
+ return self.actions.getAction()
+ return []
+ setattr(cls, "getActionList", getActionList)
+
+ def removeAction(self, name):
+ if self.actions:
+ actions = self.actions.getAction()
+ i = 0
+ removed = False
+ while i < len(actions) and not removed:
+ if actions[i].getName() == name:
+ actions.removeAction(i)
+ removed = True
+ i += 1
+ if not removed:
+ raise ValueError, "Action with name %s doesn't exists!"%name
+ setattr(cls, "removeAction", removeAction)
+
+ if "transitions_transition" in PLCOpenClasses:
+ cls = PLCOpenClasses["transitions_transition"]
+
+ setattr(cls, "setBodyType", setBodyType)
+ setattr(cls, "getBodyType", getBodyType)
+ setattr(cls, "addInstance", addInstance)
+ setattr(cls, "getInstances", getInstances)
+ setattr(cls, "getInstance", getInstance)
+ setattr(cls, "getRandomInstance", getRandomInstance)
+ setattr(cls, "getInstanceByName", getInstanceByName)
+ setattr(cls, "removeInstance", removeInstance)
+ setattr(cls, "setText", setText)
+ setattr(cls, "getText", getText)
+
+ if "actions_action" in PLCOpenClasses:
+ cls = PLCOpenClasses["actions_action"]
+
+ setattr(cls, "setBodyType", setBodyType)
+ setattr(cls, "getBodyType", getBodyType)
+ setattr(cls, "addInstance", addInstance)
+ setattr(cls, "getInstances", getInstances)
+ setattr(cls, "getInstance", getInstance)
+ setattr(cls, "getRandomInstance", getRandomInstance)
+ setattr(cls, "getInstanceByName", getInstanceByName)
+ setattr(cls, "removeInstance", removeInstance)
+ setattr(cls, "setText", setText)
+ setattr(cls, "getText", getText)
+
+ if "body" in PLCOpenClasses:
+ cls = PLCOpenClasses["body"]
+
+ def appendContentInstance(self, name, instance):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ self.content["value"].appendContent(name, instance)
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "appendContentInstance", appendContentInstance)
+
+ def getContentInstances(self):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ instances = []
+ for element in self.content["value"].getContent():
+ instances.append(element["value"])
+ return instances
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "getContentInstances", getContentInstances)
+
+ def getContentInstance(self, id):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ for element in self.content["value"].getContent():
+ if element["value"].getLocalId() == id:
+ return element["value"]
+ return None
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "getContentInstance", getContentInstance)
+
+ def getContentRandomInstance(self, exclude):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ for element in self.content["value"].getContent():
+ if element["value"].getLocalId() not in exclude:
+ return element["value"]
+ return None
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "getContentRandomInstance", getContentRandomInstance)
+
+ def getContentInstanceByName(self, name):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ for element in self.content["value"].getContent():
+ if element["value"].getLocalId() == name:
+ return element["value"]
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "getContentInstanceByName", getContentInstanceByName)
+
+ def removeContentInstance(self, id):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ i = 0
+ removed = False
+ elements = self.content["value"].getContent()
+ while i < len(elements) and not removed:
+ if elements[i]["value"].getLocalId() == id:
+ self.content["value"].removeContent(i)
+ removed = True
+ i += 1
+ if not removed:
+ raise ValueError, "Instance with id %d doesn't exists!"%id
+ else:
+ raise TypeError, "%s body don't have instances!"%self.content["name"]
+ setattr(cls, "removeContentInstance", removeContentInstance)
+
+ def setText(self, text):
+ if self.content["name"] in ["IL","ST"]:
+ self.content["value"].setText(text)
+ else:
+ raise TypeError, "%s body don't have text!"%self.content["name"]
+ setattr(cls, "setText", setText)
+
+ def getText(self):
+ if self.content["name"] in ["IL","ST"]:
+ return self.content["value"].getText()
+ else:
+ raise TypeError, "%s body don't have text!"%self.content["name"]
+ setattr(cls, "getText", getText)
+
+ def getX(self):
+ return self.position.getX()
+
+ def getY(self):
+ return self.position.getY()
+
+ def setX(self, x):
+ self.position.setX(x)
+
+ def setY(self, y):
+ self.position.setY(y)
+
+ if "comment" in PLCOpenClasses:
+ cls = PLCOpenClasses["comment"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setContentText(self, text):
+ self.content.setText(text)
+ setattr(cls, "setContentText", setContentText)
+
+ def getContentText(self):
+ return self.content.getText()
+ setattr(cls, "getContentText", getContentText)
+
+ if "block" in PLCOpenClasses:
+ cls = PLCOpenClasses["block"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "inputVariables_variable" in PLCOpenClasses:
+ cls = PLCOpenClasses["inputVariables_variable"]
+
+ def setConnectorEdge(self, edge):
+ if not self.edge:
+ self.edge = PLCOpenClasses["edgeModifierType"]()
+ self.edge.setValue(edge)
+ setattr(cls, "setConnectorEdge", setConnectorEdge)
+
+ def getConnectorEdge(self):
+ if self.edge:
+ return self.edge.getValue()
+ return None
+ setattr(cls, "getConnectorEdge", getConnectorEdge)
+
+ if "outputVariables_variable" in PLCOpenClasses:
+ cls = PLCOpenClasses["outputVariables_variable"]
+
+ def setConnectorEdge(self, edge):
+ if not self.edge:
+ self.edge = PLCOpenClasses["edgeModifierType"]()
+ self.edge.setValue(edge)
+ setattr(cls, "setConnectorEdge", setConnectorEdge)
+
+ def getConnectorEdge(self):
+ if self.edge:
+ return self.edge.getValue()
+ return None
+ setattr(cls, "getConnectorEdge", getConnectorEdge)
+
+ if "leftPowerRail" in PLCOpenClasses:
+ cls = PLCOpenClasses["leftPowerRail"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "contact" in PLCOpenClasses:
+ cls = PLCOpenClasses["contact"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setContactEdge(self, edge):
+ if not self.edge:
+ self.edge = PLCOpenClasses["edgeModifierType"]()
+ self.edge.setValue(edge)
+ setattr(cls, "setContactEdge", setContactEdge)
+
+ def getContactEdge(self):
+ if self.edge:
+ return self.edge.getValue()
+ return None
+ setattr(cls, "getContactEdge", getContactEdge)
+
+ if "coil" in PLCOpenClasses:
+ cls = PLCOpenClasses["coil"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setCoilStorage(self, edge):
+ if not self.storage:
+ self.storage = PLCOpenClasses["storageModifierType"]()
+ self.storage.setValue(edge)
+ setattr(cls, "setCoilStorage", setCoilStorage)
+
+ def getCoilStorage(self):
+ if self.storage:
+ return self.storage.getValue()
+ return None
+ setattr(cls, "getCoilStorage", getCoilStorage)
+
+ if "rightPowerRail" in PLCOpenClasses:
+ cls = PLCOpenClasses["rightPowerRail"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "step" in PLCOpenClasses:
+ cls = PLCOpenClasses["step"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "transition" in PLCOpenClasses:
+ cls = PLCOpenClasses["transition"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setConditionContent(self, type, value):
+ if not self.condition:
+ self.addCondition()
+ if type == "reference":
+ condition = PLCOpenClasses["condition_reference"]()
+ condition.setName(value)
+ elif type == "inline":
+ condition = PLCOpenClasses["condition_inline"]()
+ condition.setContent("ST", PLCOpenClasses["formattedText"]())
+ condition.setText(value)
+ self.condition.setContent(type, condition)
+ setattr(cls, "setConditionContent", setConditionContent)
+
+ def getConditionContent(self):
+ if self.condition:
+ content = self.condition.getContent()
+ values = {"type" : content["name"]}
+ if values["type"] == "reference":
+ values["value"] = content["value"].getName()
+ elif values["type"] == "inline":
+ values["value"] = content["value"].getText()
+ return values
+ return ""
+ setattr(cls, "getConditionContent", getConditionContent)
+
+ if "selectionDivergence" in PLCOpenClasses:
+ cls = PLCOpenClasses["selectionDivergence"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "selectionConvergence" in PLCOpenClasses:
+ cls = PLCOpenClasses["selectionConvergence"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "simultaneousDivergence" in PLCOpenClasses:
+ cls = PLCOpenClasses["simultaneousDivergence"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "simultaneousDivergence" in PLCOpenClasses:
+ cls = PLCOpenClasses["simultaneousConvergence"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "jumpStep" in PLCOpenClasses:
+ cls = PLCOpenClasses["jumpStep"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "actionBlock_action" in PLCOpenClasses:
+ cls = PLCOpenClasses["actionBlock_action"]
+
+ def setQualifierValue(self, value):
+ if self.qualifier:
+ self.qualifier.setValue(value)
+ setattr(cls, "setQualifierValue", setQualifierValue)
+
+ def getQualifierValue(self):
+ if self.qualifier:
+ return self.qualifier.getValue()
+ return None
+ setattr(cls, "getQualifierValue", getQualifierValue)
+
+ def setReferenceName(self, name):
+ if self.reference:
+ self.reference.setName(name)
+ setattr(cls, "setReferenceName", setReferenceName)
+
+ def getReferenceName(self):
+ if self.reference:
+ return self.reference.getName()
+ return None
+ setattr(cls, "getReferenceName", getReferenceName)
+
+ def setInlineContent(self, content):
+ if self.inline:
+ self.inline.setContent("ST", PLCOpenClasses["formattedText"]())
+ self.inline.setText(content)
+ setattr(cls, "setInlineContent", setInlineContent)
+
+ def getInlineContent(self):
+ if self.inline:
+ return self.inline.getText()
+ return None
+ setattr(cls, "getInlineContent", getInlineContent)
+
+ if "actionBlock" in PLCOpenClasses:
+ cls = PLCOpenClasses["actionBlock"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setActions(self, actions):
+ self.action = []
+ for params in actions:
+ action = PLCOpenClasses["actionBlock_action"]()
+ action.addQualifier()
+ action.setQualifierValue(params["qualifier"])
+ if params["type"] == "reference":
+ action.addReference()
+ action.setReferenceName(params["value"])
+ else:
+ action.addInline()
+ action.setInlineContent(params["value"])
+ if params["indicator"] != "":
+ action.setIndicator(params["indicator"])
+ if "duration" in params:
+ action.setDuration(params["duration"])
+ self.action.append(action)
+ setattr(cls, "setActions", setActions)
+
+ def getActions(self):
+ actions = []
+ for action in self.action:
+ params = {}
+ params["qualifier"] = action.getQualifierValue()
+ if action.getReference():
+ params["type"] = "reference"
+ params["value"] = action.getReferenceName()
+ elif action.getInline():
+ params["type"] = "inline"
+ params["value"] = action.getInlineContent()
+ duration = action.getDuration()
+ if duration:
+ params["duration"] = duration
+ indicator = action.getIndicator()
+ if indicator:
+ params["indicator"] = indicator
+ else:
+ params["indicator"] = ""
+ actions.append(params)
+ return actions
+ setattr(cls, "getActions", getActions)
+
+ if "inVariable" in PLCOpenClasses:
+ cls = PLCOpenClasses["inVariable"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setConnectorEdge(self, edge):
+ if not self.edge:
+ self.edge = PLCOpenClasses["edgeModifierType"]()
+ self.edge.setValue(edge)
+ setattr(cls, "setConnectorEdge", setConnectorEdge)
+
+ def getConnectorEdge(self):
+ if self.edge:
+ return self.edge.getValue()
+ return None
+ setattr(cls, "getConnectorEdge", getConnectorEdge)
+
+ if "outVariable" in PLCOpenClasses:
+ cls = PLCOpenClasses["outVariable"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setConnectorEdge(self, edge):
+ if not self.edge:
+ self.edge = PLCOpenClasses["edgeModifierType"]()
+ self.edge.setValue(edge)
+ setattr(cls, "setConnectorEdge", setConnectorEdge)
+
+ def getConnectorEdge(self):
+ if self.edge:
+ return self.edge.getValue()
+ return None
+ setattr(cls, "getConnectorEdge", getConnectorEdge)
+
+ if "inOutVariable" in PLCOpenClasses:
+ cls = PLCOpenClasses["inOutVariable"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ def setInputEdge(self, edge):
+ if not self.edgeIn:
+ self.edgeIn = PLCOpenClasses["edgeModifierType"]()
+ self.edgeIn.setValue(edge)
+ setattr(cls, "setInputEdge", setInputEdge)
+
+ def getInputEdge(self):
+ if self.edgeIn:
+ return self.edgeIn.getValue()
+ return None
+ setattr(cls, "getInputEdge", getInputEdge)
+
+ def setOutputEdge(self, edge):
+ if not self.edgeOut:
+ self.edgeOut = PLCOpenClasses["edgeModifierType"]()
+ self.edgeOut.setValue(edge)
+ setattr(cls, "setInputEdge", setInputEdge)
+
+ def getOutputEdge(self):
+ if self.edgeOut:
+ return self.edgeOut.getValue()
+ return None
+ setattr(cls, "getOutputEdge", getOutputEdge)
+
+ if "continuation" in PLCOpenClasses:
+ cls = PLCOpenClasses["continuation"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "connector" in PLCOpenClasses:
+ cls = PLCOpenClasses["connector"]
+ setattr(cls, "getX", getX)
+ setattr(cls, "getY", getY)
+ setattr(cls, "setX", setX)
+ setattr(cls, "setY", setY)
+
+ if "connection" in PLCOpenClasses:
+ cls = PLCOpenClasses["connection"]
+
+ def setPoints(self, points):
+ self.position = []
+ for point in points:
+ position = PLCOpenClasses["position"]()
+ position.setX(point.x)
+ position.setY(point.y)
+ self.position.append(position)
+ setattr(cls, "setPoints", setPoints)
+
+ def getPoints(self):
+ points = []
+ for position in self.position:
+ points.append((position.getX(),position.getY()))
+ return points
+ setattr(cls, "getPoints", getPoints)
+
+ if "connectionPointIn" in PLCOpenClasses:
+ cls = PLCOpenClasses["connectionPointIn"]
+
+ def setRelPosition(self, x, y):
+ self.relPosition = PLCOpenClasses["position"]()
+ self.relPosition.setX(x)
+ self.relPosition.setY(y)
+ setattr(cls, "setRelPosition", setRelPosition)
+
+ def getRelPosition(self):
+ if self.relPosition:
+ return self.relPosition.getX(), self.relPosition.getY()
+ else:
+ return self.relPosition
+ setattr(cls, "getRelPosition", getRelPosition)
+
+ def addConnection(self):
+ if not self.content:
+ self.content = {"name":"connection","value":[PLCOpenClasses["connection"]()]}
+ else:
+ self.content["value"].append(PLCOpenClasses["connection"]())
+ setattr(cls, "addConnection", addConnection)
+
+ def removeConnection(self, idx):
+ if self.content:
+ self.content["value"].pop(idx)
+ if len(self.content["value"]) == 0:
+ self.content = None
+ setattr(cls, "removeConnection", removeConnection)
+
+ def removeConnections(self):
+ if self.content:
+ self.content = None
+ setattr(cls, "removeConnections", removeConnections)
+
+ def getConnections(self):
+ if self.content:
+ return self.content["value"]
+ setattr(cls, "getConnections", getConnections)
+
+ def setConnectionId(self, idx, id):
+ if self.content:
+ self.content["value"][idx].setRefLocalId(id)
+ setattr(cls, "setConnectionId", setConnectionId)
+
+ def getConnectionId(self, idx):
+ if self.content:
+ return self.content["value"][idx].getRefLocalId()
+ return None
+ setattr(cls, "getConnectionId", getConnectionId)
+
+ def setConnectionPoints(self, idx, points):
+ if self.content:
+ self.content["value"][idx].setPoints(points)
+ setattr(cls, "setConnectionPoints", setConnectionPoints)
+
+ def getConnectionPoints(self, idx):
+ if self.content:
+ return self.content["value"][idx].getPoints()
+ return None
+ setattr(cls, "getConnectionPoints", getConnectionPoints)
+
+ if "connectionPointOut" in PLCOpenClasses:
+ cls = PLCOpenClasses["connectionPointOut"]
+
+ def setRelPosition(self, x, y):
+ self.relPosition = PLCOpenClasses["position"]()
+ self.relPosition.setX(x)
+ self.relPosition.setY(y)
+ setattr(cls, "setRelPosition", setRelPosition)
+
+ def getRelPosition(self):
+ if self.relPosition:
+ return self.relPosition.getX(), self.relPosition.getY()
+ return self.relPosition
+ setattr(cls, "getRelPosition", getRelPosition)
+
+ if "value" in PLCOpenClasses:
+ cls = PLCOpenClasses["value"]
+
+ def setValue(self, value):
+ try:
+ value = eval(value)
+ except:
+ pass
+ if type(value) == ListType:
+ arrayValue = PLCOpenClasses["value_arrayValue"]()
+ arrayValue.setValue(value)
+ self.content = {"name":"arrayValue","value":arrayValue}
+ elif type(value) == DictType:
+ structValue = PLCOpenClasses["value_structValue"]()
+ structValue.setValue(value)
+ self.content = {"name":"structValue","value":structValue}
+ else:
+ simpleValue = PLCOpenClasses["value_simpleValue"]()
+ simpleValue.setValue(str(value))
+ self.content = {"name":"simpleValue","value":simpleValue}
+ setattr(cls, "setValue", setValue)
+
+ def getValue(self):
+ value = self.content["value"].getValue()
+ try:
+ value = eval(value)
+ except:
+ pass
+ return value
+ setattr(cls, "getValue", getValue)
+
+ if "value_arrayValue" in PLCOpenClasses:
+ cls = PLCOpenClasses["value_arrayValue"]
+
+ def setValue(self, array):
+ self.value = []
+ for value in array:
+ element = PLCOpenClasses["arrayValue_value"]()
+ element.setValue(value)
+ self.value.append(element)
+ setattr(cls, "setValue", setValue)
+
+ def getValue(self):
+ return [element.getValue() for element in self.value]
+ setattr(cls, "getValue", getValue)
+
+ if "value_structValue" in PLCOpenClasses:
+ cls = PLCOpenClasses["value_structValue"]
+
+ def setValue(self, dict):
+ self.value = []
+ for name,value in dict.items():
+ element = PLCOpenClasses["structValue_value"]()
+ element.setMember(name)
+ element.setValue(value)
+ self.value.append(element)
+ setattr(cls, "setValue", setValue)
+
+ def getValue(self):
+ value = {}
+ for element in self.value:
+ value[element.getMember()] = element.getValue()
+ return value
+ setattr(cls, "getValue", getValue)
+
+if sys:
+ sys.stdout = HolePseudoFile()
+xsdschema = pyxsval.parseAndValidateXmlSchema("plcopen/TC6_XML_V10_B.xsd")
+if sys:
+ sys.stdout = sys.__stdout__
+Generate_classes(xsdschema.getTree(), None)
+
+PLCOpenClasses = {}
+PLCOpenTypes = {}
+#for classname, classe in classes.items():
+# print "%s : %s\n"%(classname, str(classe))
+"""classnames = classes.keys()
+classnames.sort()
+for classname in classnames:
+ print classname"""
+for classe in classes.keys():
+ CreateClass(classe)
+Generate_Methods()
diff -r 000000000000 -r b622defdfd98 plcopen/structures.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/structures.py Wed Jan 31 16:31:39 2007 +0100
@@ -0,0 +1,437 @@
+#!/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
+
+
+#-------------------------------------------------------------------------------
+# Function Block Types definitions
+#-------------------------------------------------------------------------------
+
+"""
+Ordored list of common Function Blocks defined in the IEC 61131-3
+Each block have this attributes:
+ - "name" : The block name
+ - "type" : The block type. It can be "function", "functionBlock" or "program"
+ - "extensible" : Boolean that define if the block is extensible
+ - "inputs" : List of the block inputs
+ - "outputs" : List of the block outputs
+ - "comment" : Comment that will be displayed in the block popup
+Inputs and outputs are a tuple of characteristics that are in order:
+ - The name
+ - The data type
+ - The default modifier which can be "none", "negated", "rising" or "falling"
+"""
+
+BlockTypes = [{"name" : "Numerical functions", "list":
+ [{"name" : "ADD", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")],
+ "outputs" : [("OUT","ANY_NUM","none")],
+ "comment" : "Addition,\nresult := I1 + I2 + ..."},
+ {"name" : "MUL", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")],
+ "outputs" : [("OUT","ANY_NUM","none")],
+ "comment" : "Multiplication,\nresult := I1 * I2 * ..."},
+ {"name" : "SUB", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")],
+ "outputs" : [("OUT","ANY_NUM","none")],
+ "comment" : "Substration,\nresult := I1 - I2"},
+ {"name" : "DIV", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")],
+ "outputs" : [("OUT","ANY_NUM","none")],
+ "comment" : "Division,\nresult := I1 / I2\n(with integer division, any fractional remainder is truncated)"},
+ {"name" : "MOD", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","ANY_INT","none"),("IN2","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_INT","none")],
+ "comment" : "Modulus,\nresult := I1 MOD I2\n(only valid with integer values)"},
+ {"name" : "EXPT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","ANY_REAL","none"),("IN2","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Exponential,\nresult := I1 ^ I2\n(can only take integer exponents)"},
+ {"name" : "ABS", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_NUM","none")],
+ "outputs" : [("OUT","ANY_NUM","none")],
+ "comment" : "Absolute value (negative values become positive)"},
+ {"name" : "SQRT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Square root"},
+ {"name" : "LOG", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Logarithm"},
+ {"name" : "LN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Natural logarithm"},
+ {"name" : "EXP", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Natural exponential"},
+ {"name" : "SIN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Sine of input as radians"},
+ {"name" : "COS", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Cosine of input as radians"},
+ {"name" : "TAN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Tangent of input as radians"},
+ {"name" : "ASIN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Principal arc-sine, result in radians"},
+ {"name" : "ACOS", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Principal arc-cosine, result in radians"},
+ {"name" : "ATAN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_REAL","none")],
+ "outputs" : [("OUT","ANY_REAL","none")],
+ "comment" : "Principal arc-tangent, result in radians"}
+ ]},
+ {"name" : "Boolean and bit functions", "list":
+ [{"name" : "AND", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY_BIT","none"),("IN2","ANY_BIT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Result := I1 & I2 & ..."},
+ {"name" : "OR", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY_BIT","none"),("IN2","ANY_BIT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Result := I1 OR I2 OR ..."},
+ {"name" : "XOR", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY_BIT","none"),("IN2","ANY_BIT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Result := I1 XOR I2 XOR ..."},
+ {"name" : "NOT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","BIT","none")],
+ "outputs" : [("OUT","BIT","none")],
+ "comment" : "Result := NOT I1"},
+ {"name" : "SHL", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_BIT","none"),("N","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Shift bit-string n bit positions left, zero fill on the right."},
+ {"name" : "SHR", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_BIT","none"),("N","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_BIT")],
+ "comment" : "Shift bit-string right n bit positions, zero fill on the left."},
+ {"name" : "ROR", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_BIT","none"),("N","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Shift bit-string right, rotate by n bit positions."},
+ {"name" : "ROL", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY_BIT","none"),("N","ANY_INT","none")],
+ "outputs" : [("OUT","ANY_BIT","none")],
+ "comment" : "Shift bit-string left, rotate by n bit positions."}
+ ]},
+ {"name" : "Selection functions", "list":
+ [{"name" : "SEL", "type" : "function", "extensible" : False,
+ "inputs" : [("G","BOOL","none"),("IN0","ANY","none"),("IN1","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Selection\nIf G = TRUE then\nResult := IN1 else\nResult := IN2"},
+ {"name" : "MAX", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Maximum\nResult := maximum value of all inputs"},
+ {"name" : "MIN", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("","ANY","none")],
+ "comment" : "Minimum\nResult := minimum value of all inputs"},
+ {"name" : "LIMIT", "type" : "function", "extensible" : False,
+ "inputs" : [("MN","ANY","none"),("IN","ANY","none"),("MX","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Limit\nResult is the value of IN limited between a minimum value of MN and a maximum value of MX."},
+ {"name" : "MUX", "type" : "function", "extensible" : True,
+ "inputs" : [("K","ANY_INT","none"),("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Multiplexer\nResult is the value of the input selected by the value of K."}
+ ]},
+ {"name" : "Comparison functions", "list":
+ [{"name" : "GT", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Greater than\nResult := IN1 > IN2"},
+ {"name" : "GE", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Greater than or equal\nResult := IN1 >= IN2"},
+ {"name" : "EQ", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Equality\nResult := IN1 = IN2"},
+ {"name" : "LE", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Lesser than or equal\nResult := IN1 <= IN2"},
+ {"name" : "LT", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Lesser than\nResult := IN1 < IN2"},
+ {"name" : "NE", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","ANY","none"),("IN2","ANY","none")],
+ "outputs" : [("OUT","ANY","none")],
+ "comment" : "Not equal\nResult := IN1 <> IN2"}
+ ]},
+ {"name" : "Character string functions", "list":
+ [{"name" : "LEFT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","STRING","none"),("L","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Extract left string\nResult is the string formed from L characters from the leftmost character of string IN."},
+ {"name" : "RIGHT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","STRING","none"),("L","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Extract right string\nResult is the string formed from L characters from the rightmost part of string IN."},
+ {"name" : "MID", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","ANY","none"),("L","ANY_INT","none"),("P","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Extract mid string\nResult is a string extracted from the input string IN starting at character position P, and L characters long."},
+ {"name" : "CONCAT", "type" : "function", "extensible" : True,
+ "inputs" : [("IN1","STRING","none"),("IN2","STRING","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Concatenate strings\nResult is a string formed by joining the input strings together. This is an extensible function that can take two or more input strings."},
+ {"name" : "INSERT", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","STRING","none"),("IN2","STRING","none"),("P","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Insert string\nThe result is formed by the string IN2 being inserted into string IN1, P character positions from the start of IN1."},
+ {"name" : "DELETE", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","STRING","none"),("L","ANY_INT","none"),("P","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Delete string\nThe result is formed by a string of characters L in length, being deleted from the input string IN, starting from character position P."},
+ {"name" : "REPLACE", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","STRING","none"),("IN2","STRING","none"),("L","ANY_INT","none"),("P","ANY_INT","none")],
+ "outputs" : [("OUT","STRING","none")],
+ "comment" : "Replace string\nThe result is formed by replacing L characters in string IN1, starting at position P, with character string in IN2."},
+ {"name" : "LEN", "type" : "function", "extensible" : False,
+ "inputs" : [("IN","STRING","none")],
+ "outputs" : [("OUT","INT","none")],
+ "comment" : "Length\nResult is length of the input string."},
+ {"name" : "FIND", "type" : "function", "extensible" : False,
+ "inputs" : [("IN1","STRING","none"),("IN2","STRING","none")],
+ "outputs" : [("OUT","INT","none")],
+ "comment" : "Find string\nResult is the position where string IN2 is first found in string IN1.\nIf string IN2 is not found in IN1, the result is 0."},
+ ]},
+ {"name" : "Standard function blocks", "list":
+ [{"name" : "SR", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("S1","BOOL","none"),("R","BOOL","none")],
+ "outputs" : [("Q1","BOOL","none")],
+ "comment" : "SR bistable\nThe SR bistable is a latch where the Set dominates."},
+ {"name" : "RS", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("S","BOOL","none"),("R1","BOOL","none")],
+ "outputs" : [("Q1","BOOL","none")],
+ "comment" : "RS bistable\nThe RS bistable is a latch where the Reset dominates."},
+ {"name" : "SEMA", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CLAIM","BOOL","none"),("RELEASE","BOOL","none")],
+ "outputs" : [("BUSY","BOOL","none")],
+ "comment" : "Semaphore\nThe semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."},
+ {"name" : "R_TRIG", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CLK","BOOL","none")],
+ "outputs" : [("Q","BOOL","none")],
+ "comment" : "Rising edge detector\nThe output produces a single pulse when a rising edge is detected."},
+ {"name" : "F_TRIG", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CLK","BOOL","none")],
+ "outputs" : [("Q","BOOL","none")],
+ "comment" : "Falling edge detector\nThe output produces a single pulse when a falling edge is detected."},
+ {"name" : "CTU", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CU","BOOL","rising"),("R","BOOL","none"),("PV","INT","none")],
+ "outputs" : [("Q","BOOL","none"),("CV","INT","none")],
+ "comment" : "Up-counter\nThe up-counter can be used to signal when a count has reached a maximum value."},
+ {"name" : "CTD", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CD","BOOL","rising"),("LD","BOOL","none"),("PV","INT","none")],
+ "outputs" : [("Q","BOOL","none"),("CV","INT","none")],
+ "comment" : "Down-counter\nThe down-counter can be used to signal when a count has reached zero, on counting down from a preset value."},
+ {"name" : "CTUD", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("CU","BOOL","rising"),("CD","BOOL","rising"),("R","BOOL","none"),("LD","BOOL","none"),("PV","INT","none")],
+ "outputs" : [("QU","BOOL","none"),("QD","BOOL","none"),("CV","INT","none")],
+ "comment" : "Up-down counter\nThe up-down counter has two inputs CU and CD. It can be used to both count up on one input ans down on the other."},
+ {"name" : "TP", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("IN","BOOL","none"),("PT","TIME","none")],
+ "outputs" : [("Q","BOOL","none"),("ET","TIME","none")],
+ "comment" : "Pulse timer\nThe pulse timer can be used to generate output pulses of a given time duration."},
+ {"name" : "TOF", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("IN","BOOL","none"),("PT","TIME","none")],
+ "outputs" : [("Q","BOOL","none"),("ET","TIME","none")],
+ "comment" : "On-delay timer\nThe on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."},
+ {"name" : "TON", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("IN","BOOL","none"),("PT","TIME","none")],
+ "outputs" : [("Q","BOOL","none"),("ET","TIME","none")],
+ "comment" : "Off-delay timer\nThe off-delay timer can be used to delay setting an output false, for fixed period after input goes false."},
+ {"name" : "RTC", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("EN","BOOL","none"),("PDT","DATE_AND_TIME","none")],
+ "outputs" : [("Q","BOOL","none"),("CDT","DATE_AND_TIME","none")],
+ "comment" : "Real time clock\nThe real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."},
+ {"name" : "INTEGRAL", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("RUN","BOOL","none"),("R1","BOOL","none"),("XIN","REAL","none"),("X0","REAL","none"),("CYCLE","TIME","none")],
+ "outputs" : [("Q","BOOL","none"),("XOUT","REAL","none")],
+ "comment" : "Integral\nThe integral function block integrates the value of input XIN over time."},
+ {"name" : "DERIVATIVE", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("RUN","BOOL","none"),("XIN","REAL","none"),("CYCLE","TIME","none")],
+ "outputs" : [("XOUT","REAL","none")],
+ "comment" : "Derivative\nThe derivative function block produces an output XOUT proportional to the rate of change of the input XIN."},
+ {"name" : "PID", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("AUTO","BOOL","none"),("PV","REAL","none"),("SP","REAL","none"),("X0","REAL","none"),("KP","REAL","none"),("TR","REAL","none"),("TD","REAL","none"),("CYCLE","TIME","none")],
+ "outputs" : [("XOUT","REAL","none")],
+ "comment" : "PID\nThe PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."},
+ {"name" : "RAMP", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("RUN","BOOL","none"),("X0","REAL","none"),("X1","REAL","none"),("TR","TIME","none"),("CYCLE","TIME","none"),("HOLDBACK","BOOL","none"),("ERROR","REAL","none"),("PV","REAL","none")],
+ "outputs" : [("RAMP","BOOL","none"),("XOUT","REAL","none")],
+ "comment" : "Ramp\nThe RAMP function block is modelled on example given in the standard but with the addition of a 'Holdback' feature."},
+ {"name" : "HYSTERESIS", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("XIN1","REAL","none"),("XIN2","REAL","none"),("EPS","REAL","none")],
+ "outputs" : [("Q","BOOL","none")],
+ "comment" : "Hysteresis\nThe hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."},
+ {"name" : "RATIO_MONITOR", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("PV1","REAL","none"),("PV2","REAL","none"),("RATIO","REAL","none"),("TIMON","TIME","none"),("TIMOFF","TIME","none"),("TOLERANCE","BOOL","none"),("RESET","BOOL","none"),("CYCLE","TIME","none")],
+ "outputs" : [("ALARM","BOOL","none"),("TOTAL_ERR","BOOL","none")],
+ "comment" : "Ratio monitor\nThe ratio_monitor function block checks that one process value PV1 is always a given ratio (defined by input RATIO) of a second process value PV2."},
+ ]}
+ ]
+
+"""
+Function that returns the block definition associated to the block type given
+"""
+
+def GetBlockType(type):
+ for category in BlockTypes:
+ for blocktype in category["list"]:
+ if blocktype["name"] == type:
+ return blocktype
+ return None
+
+
+#-------------------------------------------------------------------------------
+# Data Types definitions
+#-------------------------------------------------------------------------------
+
+"""
+Ordored list of common data types defined in the IEC 61131-3
+Each type is associated to his direct parent type. It defines then a hierarchy
+between type that permits to make a comparison of two types
+"""
+
+TypeHierarchy = {"ANY" : None,
+ "ANY_DERIVED" : "ANY",
+ "ANY_ELEMENTARY" : "ANY",
+ "ANY_MAGNITUDE": "ANY_ELEMENTARY",
+ "ANY_BIT" : "ANY_ELEMENTARY",
+ "ANY_STRING" : "ANY_ELEMENTARY",
+ "ANY_DATE" : "ANY_ELEMENTARY",
+ "ANY_NUM" : "ANY_MAGNITUDE",
+ "ANY_REAL" : "ANY_NUM",
+ "ANY_INT" : "ANY_NUM",
+ "REAL" : "ANY_REAL",
+ "LREAL" : "ANY_REAL",
+ "SINT" : "ANY_INT",
+ "INT" : "ANY_INT",
+ "DINT" : "ANY_INT",
+ "LINT" : "ANY_INT",
+ "USINT" : "ANY_INT",
+ "UINT" : "ANY_INT",
+ "UDINT" : "ANY_INT",
+ "ULINT" : "ANY_INT",
+ "TIME" : "ANY_MAGNITUDE",
+ "BOOL" : "ANY_BIT",
+ "BYTE" : "ANY_BIT",
+ "WORD" : "ANY_BIT",
+ "DWORD" : "ANY_BIT",
+ "LWORD" : "ANY_BIT",
+ "STRING" : "ANY_STRING",
+ "WSTRING" : "ANY_STRING",
+ "DATE" : "ANY_DATE",
+ "TOD" : "ANY_DATE",
+ "DT" : "ANY_DATE"
+}
+
+"""
+Function that returns if the given data type is the same that "reference" or one
+of its children types
+"""
+
+def IsOfType(test, reference):
+ while test != None:
+ if test == reference:
+ return True
+ test = TypeHierarchy[test]
+ return False
+
+
+#-------------------------------------------------------------------------------
+# Languages Keywords
+#-------------------------------------------------------------------------------
+
+
+
+# Keywords for Pou Declaration
+POU_KEYWORDS = ["FUNCTION", "END_FUNCTION", "FUNCTION_BLOCK", "END_FUNCTION_BLOCK",
+ "PROGRAM", "END_PROGRAM", "EN", "ENO", "F_EDGE", "R_EDGE"]
+for category in BlockTypes:
+ for block in category["list"]:
+ if block["name"] not in POU_KEYWORDS:
+ POU_KEYWORDS.append(block["name"])
+
+
+# Keywords for Type Declaration
+TYPE_KEYWORDS = ["TYPE", "END_TYPE", "STRUCT", "END_STRUCT", "ARRAY", "OF", "T",
+ "D", "TIME_OF_DAY", "DATE_AND_TIME"]
+TYPE_KEYWORDS.extend([keyword for keyword in TypeHierarchy.keys() if keyword not in TYPE_KEYWORDS])
+
+
+# Keywords for Variable Declaration
+VAR_KEYWORDS = ["VAR", "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP",
+ "VAR_EXTERNAL", "END_VAR", "AT", "CONSTANT", "RETAIN", "NON_RETAIN"]
+
+
+# Keywords for Configuration Declaration
+CONFIG_KEYWORDS = ["CONFIGURATION", "END_CONFIGURATION", "RESOURCE", "ON", "END_RESOURCE",
+ "PROGRAM", "WITH", "READ_ONLY", "READ_WRITE", "TASK", "VAR_ACCESS", "VAR_CONFIG",
+ "VAR_GLOBAL", "END_VAR"]
+
+
+# Keywords for Structured Function Chart
+SFC_KEYWORDS = ["ACTION", "END_ACTION", "INITIAL_STEP", "STEP", "END_STEP", "TRANSITION",
+ "FROM", "TO", "END_TRANSITION"]
+
+
+# Keywords for Instruction List
+IL_KEYWORDS = ["LD", "LDN", "ST", "STN", "S", "R", "AND", "ANDN", "OR", "ORN",
+ "XOR", "XORN", "NOT", "ADD", "SUB", "MUL", "DIV", "MOD", "GT", "GE", "EQ", "NE",
+ "LE", "LT", "JMP", "JMPC", "JMPNC", "CAL", "CALC", "CALNC", "RET", "RETC", "RETNC"]
+
+
+# Keywords for Instruction List and Structured Text
+ST_KEYWORDS = ["IF", "THEN", "ELSIF", "ELSE", "END_IF", "CASE", "OF", "END_CASE",
+ "FOR", "TO", "BY", "DO", "END_FOR", "WHILE", "DO", "END_WHILE", "REPEAT", "UNTIL",
+ "END_REPEAT", "EXIT", "RETURN", "NOT", "MOD", "AND", "XOR", "OR"]
+
+
+# All the keywords of IEC
+IEC_KEYWORDS = ["E", "TRUE", "FALSE"]
+IEC_KEYWORDS.extend([keyword for keyword in POU_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in TYPE_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in VAR_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in CONFIG_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in SFC_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in IL_KEYWORDS if keyword not in IEC_KEYWORDS])
+IEC_KEYWORDS.extend([keyword for keyword in ST_KEYWORDS if keyword not in IEC_KEYWORDS])
+
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_CreateBlockPopup.jpg
Binary file snapshots/PLCOpenEditor_CreateBlockPopup.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_FBDexample.jpg
Binary file snapshots/PLCOpenEditor_FBDexample.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_ILexample.jpg
Binary file snapshots/PLCOpenEditor_ILexample.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_InterfacePopup.jpg
Binary file snapshots/PLCOpenEditor_InterfacePopup.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_LDexample.jpg
Binary file snapshots/PLCOpenEditor_LDexample.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_PropertiesPopup.jpg
Binary file snapshots/PLCOpenEditor_PropertiesPopup.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_SFCexample.jpg
Binary file snapshots/PLCOpenEditor_SFCexample.jpg has changed
diff -r 000000000000 -r b622defdfd98 snapshots/PLCOpenEditor_STexample.jpg
Binary file snapshots/PLCOpenEditor_STexample.jpg has changed