--- a/Beremiz.py Wed Sep 05 11:17:52 2012 +0200
+++ b/Beremiz.py Fri Sep 07 16:45:55 2012 +0200
@@ -141,11 +141,7 @@
if __name__ == '__main__':
__builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
-base_folder = os.path.split(sys.path[0])[0]
-sys.path.append(base_folder)
-sys.path.append(os.path.join(base_folder, "plcopeneditor"))
-
-from utils.BitmapLibrary import AddBitmapFolder, GetBitmap
+from util.BitmapLibrary import AddBitmapFolder, GetBitmap
AddBitmapFolder(os.path.join(CWD, "images"))
if __name__ == '__main__':
@@ -161,17 +157,22 @@
import types, time, re, platform, time, traceback, commands
from docutil import OpenHtmlFrame
-from PLCOpenEditor import IDEFrame, AppendMenu, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-from PLCOpenEditor import EditorPanel, Viewer, TextViewer, GraphicViewer, ResourceEditor, ConfigurationEditor, DataTypeEditor
-from PLCOpenEditor import EncodeFileSystemPath, DecodeFileSystemPath
-from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
-
-from util.TextCtrlAutoComplete import TextCtrlAutoComplete
-from util.BrowseValuesLibraryDialog import BrowseValuesLibraryDialog
+from IDEFrame import IDEFrame, AppendMenu
+from IDEFrame import TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
+from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
+from editors.EditorPanel import EditorPanel
+from editors.Viewer import Viewer
+from editors.TextViewer import TextViewer
+from editors.GraphicViewer import GraphicViewer
+from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
+from editors.DataTypeEditor import DataTypeEditor
from util.MiniTextControler import MiniTextControler
from util.ProcessLogger import ProcessLogger
+
+from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
from ProjectController import ProjectController, MATIEC_ERROR_MODEL, ITEM_CONFNODE
+
MAX_RECENT_PROJECTS = 10
class GenStaticBitmap(wx.lib.statbmp.GenStaticBitmap):
@@ -303,29 +304,29 @@
def _init_coll_FileMenu_Items(self, parent):
AppendMenu(parent, help='', id=wx.ID_NEW,
- kind=wx.ITEM_NORMAL, text=_(u'New\tCTRL+N'))
+ kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
AppendMenu(parent, help='', id=wx.ID_OPEN,
- kind=wx.ITEM_NORMAL, text=_(u'Open\tCTRL+O'))
+ kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
parent.AppendSeparator()
AppendMenu(parent, help='', id=wx.ID_SAVE,
- kind=wx.ITEM_NORMAL, text=_(u'Save\tCTRL+S'))
+ kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
AppendMenu(parent, help='', id=wx.ID_SAVEAS,
- kind=wx.ITEM_NORMAL, text=_(u'Save as\tCTRL+SHIFT+S'))
+ kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S')
AppendMenu(parent, help='', id=wx.ID_CLOSE,
- kind=wx.ITEM_NORMAL, text=_(u'Close Tab\tCTRL+W'))
+ kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
- kind=wx.ITEM_NORMAL, text=_(u'Close Project\tCTRL+SHIFT+W'))
+ kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
parent.AppendSeparator()
AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
- kind=wx.ITEM_NORMAL, text=_(u'Page Setup\tCTRL+ALT+P'))
+ kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
AppendMenu(parent, help='', id=wx.ID_PREVIEW,
- kind=wx.ITEM_NORMAL, text=_(u'Preview\tCTRL+SHIFT+P'))
+ kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
AppendMenu(parent, help='', id=wx.ID_PRINT,
- kind=wx.ITEM_NORMAL, text=_(u'Print\tCTRL+P'))
+ kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
parent.AppendSeparator()
AppendMenu(parent, help='', id=wx.ID_EXIT,
- kind=wx.ITEM_NORMAL, text=_(u'Quit\tCTRL+Q'))
+ kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
@@ -927,7 +928,7 @@
self.Close()
def OnAboutMenu(self, event):
- OpenHtmlFrame(self,_("About Beremiz"), Bpath("doc","about.html"), wx.Size(550, 500))
+ OpenHtmlFrame(self,_("About Beremiz"), Bpath("doc", "about.html"), wx.Size(550, 500))
def OnProjectTreeItemBeginEdit(self, event):
selected = event.GetItem()
--- a/ConfTreeNodeEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,573 +0,0 @@
-
-import os
-import types
-
-import wx
-import wx.lib.buttons
-
-from controls import EditorPanel
-
-from PLCOpenEditor import TITLE, FILEMENU, PROJECTTREE, PAGETITLES
-
-from util.TextCtrlAutoComplete import TextCtrlAutoComplete
-from util.BrowseValuesLibraryDialog import BrowseValuesLibraryDialog
-from utils.BitmapLibrary import GetBitmap
-
-if wx.Platform == '__WXMSW__':
- faces = { 'times': 'Times New Roman',
- 'mono' : 'Courier New',
- 'helv' : 'Arial',
- 'other': 'Comic Sans MS',
- 'size' : 16,
- }
-else:
- faces = { 'times': 'Times',
- 'mono' : 'Courier',
- 'helv' : 'Helvetica',
- 'other': 'new century schoolbook',
- 'size' : 18,
- }
-
-SCROLLBAR_UNIT = 10
-WINDOW_COLOUR = wx.Colour(240, 240, 240)
-
-CWD = os.path.split(os.path.realpath(__file__))[0]
-
-def Bpath(*args):
- return os.path.join(CWD,*args)
-
-# Some helpers to tweak GenBitmapTextButtons
-# TODO: declare customized classes instead.
-gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
-gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,))
-
-def make_genbitmaptogglebutton_flat(button):
- button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button)
- button.labelDelta = 0
- button.SetBezelWidth(0)
- button.SetUseFocusIndicator(False)
-
-# Patch wx.lib.imageutils so that gray is supported on alpha images
-import wx.lib.imageutils
-from wx.lib.imageutils import grayOut as old_grayOut
-def grayOut(anImage):
- if anImage.HasAlpha():
- AlphaData = anImage.GetAlphaData()
- else :
- AlphaData = None
-
- old_grayOut(anImage)
-
- if AlphaData is not None:
- anImage.SetAlphaData(AlphaData)
-
-wx.lib.imageutils.grayOut = grayOut
-
-class GenBitmapTextButton(wx.lib.buttons.GenBitmapTextButton):
- def _GetLabelSize(self):
- """ used internally """
- w, h = self.GetTextExtent(self.GetLabel())
- if not self.bmpLabel:
- return w, h, False # if there isn't a bitmap use the size of the text
-
- w_bmp = self.bmpLabel.GetWidth()+2
- h_bmp = self.bmpLabel.GetHeight()+2
- height = h + h_bmp
- if w_bmp > w:
- width = w_bmp
- else:
- width = w
- return width, height, False
-
- def DrawLabel(self, dc, width, height, dw=0, dy=0):
- bmp = self.bmpLabel
- if bmp != None: # if the bitmap is used
- if self.bmpDisabled and not self.IsEnabled():
- bmp = self.bmpDisabled
- if self.bmpFocus and self.hasFocus:
- bmp = self.bmpFocus
- if self.bmpSelected and not self.up:
- bmp = self.bmpSelected
- bw,bh = bmp.GetWidth(), bmp.GetHeight()
- if not self.up:
- dw = dy = self.labelDelta
- hasMask = bmp.GetMask() != None
- else:
- bw = bh = 0 # no bitmap -> size is zero
-
- dc.SetFont(self.GetFont())
- if self.IsEnabled():
- dc.SetTextForeground(self.GetForegroundColour())
- else:
- dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
-
- label = self.GetLabel()
- tw, th = dc.GetTextExtent(label) # size of text
- if not self.up:
- dw = dy = self.labelDelta
-
- pos_x = (width-bw)/2+dw # adjust for bitmap and text to centre
- pos_y = (height-bh-th)/2+dy
- if bmp !=None:
- dc.DrawBitmap(bmp, pos_x, pos_y, hasMask) # draw bitmap if available
- pos_x = (width-tw)/2+dw # adjust for bitmap and text to centre
- pos_y += bh + 2
-
- dc.DrawText(label, pos_x, pos_y) # draw the text
-
-
-class GenStaticBitmap(wx.lib.statbmp.GenStaticBitmap):
- """ Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32,
- and accept image name as __init__ parameter, fail silently if file do not exist"""
- def __init__(self, parent, ID, bitmapname,
- pos = wx.DefaultPosition, size = wx.DefaultSize,
- style = 0,
- name = "genstatbmp"):
-
- wx.lib.statbmp.GenStaticBitmap.__init__(self, parent, ID,
- GetBitmap(bitmapname),
- pos, size,
- style,
- name)
-
- def OnPaint(self, event):
- dc = wx.PaintDC(self)
- colour = self.GetParent().GetBackgroundColour()
- dc.SetPen(wx.Pen(colour))
- dc.SetBrush(wx.Brush(colour ))
- dc.DrawRectangle(0, 0, *dc.GetSizeTuple())
- if self._bitmap:
- dc.DrawBitmap(self._bitmap, 0, 0, True)
-
-class ConfTreeNodeEditor(EditorPanel):
-
- SHOW_BASE_PARAMS = True
- SHOW_PARAMS = True
-
- def _init_ConfNodeEditor(self, prnt):
- self.ConfNodeEditor = None
-
- def _init_Editor(self, parent):
- self.Editor = wx.SplitterWindow(parent,
- style=wx.SUNKEN_BORDER|wx.SP_3D)
- self.SetNeedUpdating(True)
- self.SetMinimumPaneSize(1)
-
- if self.SHOW_PARAMS:
- self.ParamsEditor = wx.ScrolledWindow(self.Editor,
- style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER|wx.HSCROLL|wx.VSCROLL)
- self.ParamsEditor.SetBackgroundColour(WINDOW_COLOUR)
- self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnWindowResize)
- self.ParamsEditor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
-
- # Variable allowing disabling of ParamsEditor scroll when Popup shown
- self.ScrollingEnabled = True
-
- if self.SHOW_BASE_PARAMS:
- self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- self.ParamsEditorSizer.AddGrowableCol(0)
- self.ParamsEditorSizer.AddGrowableRow(1)
-
- self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
-
- baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
- self.ParamsEditorSizer.AddSizer(baseparamseditor_sizer, border=5,
- flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
-
- self.FullIECChannel = wx.StaticText(self.ParamsEditor, -1)
- self.FullIECChannel.SetFont(
- wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL,
- wx.BOLD, faceName = faces["helv"]))
- baseparamseditor_sizer.AddWindow(self.FullIECChannel,
- flag=wx.ALIGN_CENTER_VERTICAL)
-
- updownsizer = wx.BoxSizer(wx.VERTICAL)
- baseparamseditor_sizer.AddSizer(updownsizer, border=5,
- flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
-
- self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.ParamsEditor,
- bitmap=GetBitmap('IECCDown'), size=wx.Size(16, 16), style=wx.NO_BORDER)
- self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
- self.IECCUpButton)
- updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
-
- self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.ParamsEditor,
- bitmap=GetBitmap('IECCUp'), size=wx.Size(16, 16), style=wx.NO_BORDER)
- self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1),
- self.IECCDownButton)
- updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
-
- self.ConfNodeName = wx.TextCtrl(self.ParamsEditor,
- size=wx.Size(150, 25), style=wx.NO_BORDER)
- self.ConfNodeName.SetFont(
- wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL,
- wx.BOLD, faceName = faces["helv"]))
- self.ConfNodeName.Bind(wx.EVT_TEXT,
- self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True),
- self.ConfNodeName)
- baseparamseditor_sizer.AddWindow(self.ConfNodeName, border=5,
- flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-
- buttons_sizer = self.GenerateMethodButtonSizer()
- baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
-
- else:
- self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
- self.ParamsEditorSizer.AddGrowableCol(0)
- self.ParamsEditorSizer.AddGrowableRow(0)
-
- self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
- self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
- flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
-
- self.RefreshConfNodeParamsSizer()
- else:
- self.ParamsEditor = None
-
- self._init_ConfNodeEditor(self.Editor)
-
- if self.ConfNodeEditor is not None:
- if self.ParamsEditor is not None:
- min_size = self.ParamsEditorSizer.GetMinSize()
- self.Editor.SplitHorizontally(self.ParamsEditor,
- self.ConfNodeEditor,
- min(min_size.height, 200))
- else:
- self.Editor.Initialize(self.ConfNodeEditor)
- elif self.ParamsEditor is not None:
- self.Editor.Initialize(self.ParamsEditor)
-
- def __init__(self, parent, controler, window, tagname=""):
- EditorPanel.__init__(self, parent, tagname, window, controler)
-
- icon_name = self.Controler.GetIconName()
- if icon_name is not None:
- self.SetIcon(GetBitmap(icon_name))
- else:
- self.SetIcon(GetBitmap("Extension"))
-
- def __del__(self):
- self.Controler.OnCloseEditor(self)
-
- def GetTagName(self):
- return self.Controler.CTNFullName()
-
- def GetTitle(self):
- fullname = self.Controler.CTNFullName()
- if self.Controler.CTNTestModified():
- return "~%s~" % fullname
- return fullname
-
- def HasNoModel(self):
- return False
-
- def GetBufferState(self):
- return False, False
-
- def Undo(self):
- pass
-
- def Redo(self):
- pass
-
- def RefreshView(self):
- EditorPanel.RefreshView(self)
- if self.ParamsEditor is not None:
- if self.SHOW_BASE_PARAMS:
- self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
- self.RefreshIECChannelControlsState()
- self.RefreshConfNodeParamsSizer()
-
- def EnableScrolling(self, enable):
- self.ScrollingEnabled = enable
-
- def RefreshIECChannelControlsState(self):
- self.FullIECChannel.SetLabel(self.Controler.GetFullIEC_Channel())
- self.IECCDownButton.Enable(self.Controler.BaseParams.getIEC_Channel() > 0)
-
- def RefreshConfNodeParamsSizer(self):
- self.Freeze()
- self.ConfNodeParamsSizer.Clear(True)
-
- confnode_infos = self.Controler.GetParamsAttributes()
- if len(confnode_infos) > 0:
- self.GenerateSizerElements(self.ConfNodeParamsSizer, confnode_infos, None, False)
-
- self.ParamsEditorSizer.Layout()
- self.Thaw()
-
- def GenerateMethodButtonSizer(self):
- normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"])
- mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"])
-
- msizer = wx.BoxSizer(wx.HORIZONTAL)
-
- for confnode_method in self.Controler.ConfNodeMethods:
- if "method" in confnode_method and confnode_method.get("shown",True):
- button = GenBitmapTextButton(self.ParamsEditor,
- bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")),
- label=confnode_method["name"], style=wx.NO_BORDER)
- button.SetFont(normal_bt_font)
- button.SetToolTipString(confnode_method["tooltip"])
- if confnode_method.get("push", False):
- button.Bind(wx.EVT_LEFT_DOWN, self.GetButtonCallBackFunction(confnode_method["method"], True))
- else:
- button.Bind(wx.EVT_BUTTON, self.GetButtonCallBackFunction(confnode_method["method"]), button)
- # a fancy underline on mouseover
- def setFontStyle(b, s):
- def fn(event):
- b.SetFont(s)
- b.Refresh()
- event.Skip()
- return fn
- button.Bind(wx.EVT_ENTER_WINDOW, setFontStyle(button, mouseover_bt_font))
- button.Bind(wx.EVT_LEAVE_WINDOW, setFontStyle(button, normal_bt_font))
- #hack to force size to mini
- if not confnode_method.get("enabled",True):
- button.Disable()
- msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
- return msizer
-
- def GenerateSizerElements(self, sizer, elements, path, clean = True):
- if clean:
- sizer.Clear(True)
- first = True
- for element_infos in elements:
- if path:
- element_path = "%s.%s"%(path, element_infos["name"])
- else:
- element_path = element_infos["name"]
- if element_infos["type"] == "element":
- label = element_infos["name"]
- staticbox = wx.StaticBox(self.ParamsEditor,
- label=_(label), size=wx.Size(10, 0))
- staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
- if first:
- sizer.AddSizer(staticboxsizer, border=5,
- flag=wx.GROW|wx.TOP|wx.BOTTOM)
- else:
- sizer.AddSizer(staticboxsizer, border=5,
- flag=wx.GROW|wx.BOTTOM)
- self.GenerateSizerElements(staticboxsizer,
- element_infos["children"],
- element_path)
- else:
- boxsizer = wx.FlexGridSizer(cols=3, rows=1)
- boxsizer.AddGrowableCol(1)
- if first:
- sizer.AddSizer(boxsizer, border=5,
- flag=wx.GROW|wx.ALL)
- else:
- sizer.AddSizer(boxsizer, border=5,
- flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
-
- staticbitmap = GenStaticBitmap(ID=-1, bitmapname=element_infos["name"],
- name="%s_bitmap"%element_infos["name"], parent=self.ParamsEditor,
- pos=wx.Point(0, 0), size=wx.Size(24, 24), style=0)
- boxsizer.AddWindow(staticbitmap, border=5, flag=wx.RIGHT)
-
- statictext = wx.StaticText(self.ParamsEditor,
- label="%s:"%_(element_infos["name"]))
- boxsizer.AddWindow(statictext, border=5,
- flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT)
-
- if isinstance(element_infos["type"], types.ListType):
- if isinstance(element_infos["value"], types.TupleType):
- browse_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
- boxsizer.AddSizer(browse_boxsizer)
-
- textctrl = wx.TextCtrl(self.ParamsEditor,
- size=wx.Size(275, 25), style=wx.TE_READONLY)
- if element_infos["value"] is not None:
- textctrl.SetValue(element_infos["value"][0])
- value_infos = element_infos["value"][1]
- else:
- value_infos = None
- browse_boxsizer.AddWindow(textctrl)
-
- button = wx.Button(self.ParamsEditor,
- label="...", size=wx.Size(25, 25))
- browse_boxsizer.AddWindow(button)
- button.Bind(wx.EVT_BUTTON,
- self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"],
- value_infos, element_path),
- button)
- else:
- combobox = wx.ComboBox(self.ParamsEditor,
- size=wx.Size(300, 28), style=wx.CB_READONLY)
- boxsizer.AddWindow(combobox)
-
- if element_infos["use"] == "optional":
- combobox.Append("")
- if len(element_infos["type"]) > 0 and isinstance(element_infos["type"][0], types.TupleType):
- for choice, xsdclass in element_infos["type"]:
- combobox.Append(choice)
- name = element_infos["name"]
- value = element_infos["value"]
-
- staticbox = wx.StaticBox(self.ParamsEditor,
- label="%s - %s"%(_(name), _(value)), size=wx.Size(10, 0))
- staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
- sizer.AddSizer(staticboxsizer, border=5, flag=wx.GROW|wx.BOTTOM)
- self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
- callback = self.GetChoiceContentCallBackFunction(combobox, staticboxsizer, element_path)
- else:
- for choice in element_infos["type"]:
- combobox.Append(choice)
- callback = self.GetChoiceCallBackFunction(combobox, element_path)
- if element_infos["value"] is None:
- combobox.SetStringSelection("")
- else:
- combobox.SetStringSelection(element_infos["value"])
- combobox.Bind(wx.EVT_COMBOBOX, callback, combobox)
-
- elif isinstance(element_infos["type"], types.DictType):
- scmin = -(2**31)
- scmax = 2**31-1
- if "min" in element_infos["type"]:
- scmin = element_infos["type"]["min"]
- if "max" in element_infos["type"]:
- scmax = element_infos["type"]["max"]
- spinctrl = wx.SpinCtrl(self.ParamsEditor,
- size=wx.Size(300, 25), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
- spinctrl.SetRange(scmin, scmax)
- boxsizer.AddWindow(spinctrl)
- if element_infos["value"] is not None:
- spinctrl.SetValue(element_infos["value"])
- spinctrl.Bind(wx.EVT_SPINCTRL,
- self.GetTextCtrlCallBackFunction(spinctrl, element_path),
- spinctrl)
-
- else:
- if element_infos["type"] == "boolean":
- checkbox = wx.CheckBox(self.ParamsEditor, size=wx.Size(17, 25))
- boxsizer.AddWindow(checkbox)
- if element_infos["value"] is not None:
- checkbox.SetValue(element_infos["value"])
- checkbox.Bind(wx.EVT_CHECKBOX,
- self.GetCheckBoxCallBackFunction(checkbox, element_path),
- checkbox)
-
- elif element_infos["type"] in ["unsignedLong", "long","integer"]:
- if element_infos["type"].startswith("unsigned"):
- scmin = 0
- else:
- scmin = -(2**31)
- scmax = 2**31-1
- spinctrl = wx.SpinCtrl(self.ParamsEditor,
- size=wx.Size(300, 25), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
- spinctrl.SetRange(scmin, scmax)
- boxsizer.AddWindow(spinctrl)
- if element_infos["value"] is not None:
- spinctrl.SetValue(element_infos["value"])
- spinctrl.Bind(wx.EVT_SPINCTRL,
- self.GetTextCtrlCallBackFunction(spinctrl, element_path),
- spinctrl)
-
- else:
- choices = self.ParentWindow.GetConfigEntry(element_path, [""])
- textctrl = TextCtrlAutoComplete(name=element_infos["name"],
- parent=self.ParamsEditor,
- appframe=self,
- choices=choices,
- element_path=element_path,
- size=wx.Size(300, 25))
-
- boxsizer.AddWindow(textctrl)
- if element_infos["value"] is not None:
- textctrl.ChangeValue(str(element_infos["value"]))
- textctrl.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(textctrl, element_path))
- first = False
-
-
- def GetItemChannelChangedFunction(self, dir):
- def OnConfNodeTreeItemChannelChanged(event):
- confnode_IECChannel = self.Controler.BaseParams.getIEC_Channel()
- res = self.SetConfNodeParamsAttribute("BaseParams.IEC_Channel", confnode_IECChannel + dir)
- wx.CallAfter(self.RefreshIECChannelControlsState)
- wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE)
- wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
- event.Skip()
- return OnConfNodeTreeItemChannelChanged
-
- def SetConfNodeParamsAttribute(self, *args, **kwargs):
- res, StructChanged = self.Controler.SetParamsAttribute(*args, **kwargs)
- if StructChanged:
- wx.CallAfter(self.RefreshConfNodeParamsSizer)
- wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU)
- return res
-
- def GetButtonCallBackFunction(self, method, push=False):
- """ Generate the callbackfunc for a given confnode method"""
- def OnButtonClick(event):
- # Disable button to prevent re-entrant call
- event.GetEventObject().Disable()
- # Call
- getattr(self.Controler,method)()
- # Re-enable button
- event.GetEventObject().Enable()
-
- if not push:
- event.Skip()
- return OnButtonClick
-
- def GetChoiceCallBackFunction(self, choicectrl, path):
- def OnChoiceChanged(event):
- res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
- choicectrl.SetStringSelection(res)
- event.Skip()
- return OnChoiceChanged
-
- def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, path):
- def OnChoiceContentChanged(event):
- res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
- wx.CallAfter(self.RefreshConfNodeParamsSizer)
- event.Skip()
- return OnChoiceContentChanged
-
- def GetTextCtrlCallBackFunction(self, textctrl, path, refresh=False):
- def OnTextCtrlChanged(event):
- res = self.SetConfNodeParamsAttribute(path, textctrl.GetValue())
- if res != textctrl.GetValue():
- textctrl.ChangeValue(res)
- if refresh:
- wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE, PAGETITLES)
- wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
- event.Skip()
- return OnTextCtrlChanged
-
- def GetCheckBoxCallBackFunction(self, chkbx, path):
- def OnCheckBoxChanged(event):
- res = self.SetConfNodeParamsAttribute(path, chkbx.IsChecked())
- chkbx.SetValue(res)
- event.Skip()
- return OnCheckBoxChanged
-
- def GetBrowseCallBackFunction(self, name, textctrl, library, value_infos, path):
- infos = [value_infos]
- def OnBrowseButton(event):
- dialog = BrowseValuesLibraryDialog(self, name, library, infos[0])
- if dialog.ShowModal() == wx.ID_OK:
- value, value_infos = self.SetConfNodeParamsAttribute(path, dialog.GetValueInfos())
- textctrl.ChangeValue(value)
- infos[0] = value_infos
- dialog.Destroy()
- event.Skip()
- return OnBrowseButton
-
- def OnWindowResize(self, event):
- self.ParamsEditor.GetBestSize()
- xstart, ystart = self.ParamsEditor.GetViewStart()
- window_size = self.ParamsEditor.GetClientSize()
- maxx, maxy = self.ParamsEditorSizer.GetMinSize()
- posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
- posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
- self.ParamsEditor.Scroll(posx, posy)
- self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
- maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
- event.Skip()
-
- def OnMouseWheel(self, event):
- if self.ScrollingEnabled:
- event.Skip()
-
--- a/ConfigTreeNode.py Wed Sep 05 11:17:52 2012 +0200
+++ b/ConfigTreeNode.py Fri Sep 07 16:45:55 2012 +0200
@@ -15,7 +15,7 @@
from util.misc import GetClassImporter
from PLCControler import PLCControler, LOCATION_CONFNODE
-from ConfTreeNodeEditor import ConfTreeNodeEditor
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
_BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/IDEFrame.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,2553 @@
+
+import os, sys
+import cPickle
+from types import TupleType
+
+import wx, wx.grid
+import wx.aui
+
+from editors.EditorPanel import EditorPanel
+from editors.SFCViewer import SFC_Viewer
+from editors.LDViewer import LD_Viewer
+from editors.TextViewer import TextViewer
+from editors.Viewer import Viewer, ZOOM_FACTORS
+from editors.GraphicViewer import GraphicViewer
+from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
+from editors.DataTypeEditor import DataTypeEditor
+from PLCControler import *
+from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, DebugVariablePanel, SearchResultPanel
+from dialogs import ProjectDialog, PouTransitionDialog, PouActionDialog, FindInPouDialog
+from util.BitmapLibrary import GetBitmap
+
+# Define PLCOpenEditor controls id
+[ID_PLCOPENEDITOR, ID_PLCOPENEDITORLEFTNOTEBOOK,
+ ID_PLCOPENEDITORBOTTOMNOTEBOOK, ID_PLCOPENEDITORRIGHTNOTEBOOK,
+ ID_PLCOPENEDITORPROJECTTREE, ID_PLCOPENEDITORMAINSPLITTER,
+ ID_PLCOPENEDITORSECONDSPLITTER, ID_PLCOPENEDITORTHIRDSPLITTER,
+ ID_PLCOPENEDITORLIBRARYPANEL, ID_PLCOPENEDITORLIBRARYSEARCHCTRL,
+ ID_PLCOPENEDITORLIBRARYTREE, ID_PLCOPENEDITORLIBRARYCOMMENT,
+ ID_PLCOPENEDITORTABSOPENED, ID_PLCOPENEDITORTABSOPENED,
+ ID_PLCOPENEDITOREDITORMENUTOOLBAR, ID_PLCOPENEDITOREDITORTOOLBAR,
+ ID_PLCOPENEDITORPROJECTPANEL,
+] = [wx.NewId() for _init_ctrls in range(17)]
+
+# Define PLCOpenEditor EditMenu extra items id
+[ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, ID_PLCOPENEDITOREDITMENUADDDATATYPE,
+ ID_PLCOPENEDITOREDITMENUADDFUNCTION, ID_PLCOPENEDITOREDITMENUADDFUNCTIONBLOCK,
+ ID_PLCOPENEDITOREDITMENUADDPROGRAM, ID_PLCOPENEDITOREDITMENUADDCONFIGURATION,
+ ID_PLCOPENEDITOREDITMENUFINDNEXT, ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
+ ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT,
+] = [wx.NewId() for _init_coll_EditMenu_Items in range(9)]
+
+# Define PLCOpenEditor DisplayMenu extra items id
+[ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE,
+] = [wx.NewId() for _init_coll_DisplayMenu_Items in range(1)]
+
+#-------------------------------------------------------------------------------
+# EditorToolBar definitions
+#-------------------------------------------------------------------------------
+
+# Define PLCOpenEditor Toolbar items id
+[ID_PLCOPENEDITOREDITORTOOLBARSELECTION, ID_PLCOPENEDITOREDITORTOOLBARCOMMENT,
+ ID_PLCOPENEDITOREDITORTOOLBARVARIABLE, ID_PLCOPENEDITOREDITORTOOLBARBLOCK,
+ ID_PLCOPENEDITOREDITORTOOLBARCONNECTION, ID_PLCOPENEDITOREDITORTOOLBARWIRE,
+ ID_PLCOPENEDITOREDITORTOOLBARPOWERRAIL, ID_PLCOPENEDITOREDITORTOOLBARRUNG,
+ ID_PLCOPENEDITOREDITORTOOLBARCOIL, ID_PLCOPENEDITOREDITORTOOLBARCONTACT,
+ ID_PLCOPENEDITOREDITORTOOLBARBRANCH, ID_PLCOPENEDITOREDITORTOOLBARINITIALSTEP,
+ ID_PLCOPENEDITOREDITORTOOLBARSTEP, ID_PLCOPENEDITOREDITORTOOLBARTRANSITION,
+ ID_PLCOPENEDITOREDITORTOOLBARACTIONBLOCK, ID_PLCOPENEDITOREDITORTOOLBARDIVERGENCE,
+ ID_PLCOPENEDITOREDITORTOOLBARJUMP, ID_PLCOPENEDITOREDITORTOOLBARMOTION,
+] = [wx.NewId() for _init_coll_DefaultEditorToolBar_Items in range(18)]
+
+
+
+# Define behaviour of each Toolbar item according to current POU body type
+# Informations meaning are in this order:
+# - Item is toggled
+# - PLCOpenEditor mode where item is displayed (could be more then one)
+# - Item id
+# - Item callback function name
+# - Item icon filename
+# - Item tooltip text
+EditorToolBarItems = {
+ "FBD" : [(True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARMOTION, "OnMotionTool",
+ "move", _("Move the view")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCOMMENT, "OnCommentTool",
+ "add_comment", _("Create a new comment")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARVARIABLE, "OnVariableTool",
+ "add_variable", _("Create a new variable")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARBLOCK, "OnBlockTool",
+ "add_block", _("Create a new block")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCONNECTION, "OnConnectionTool",
+ "add_connection", _("Create a new connection"))],
+ "LD" : [(True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARMOTION, "OnMotionTool",
+ "move", _("Move the view")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCOMMENT, "OnCommentTool",
+ "add_comment", _("Create a new comment")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARPOWERRAIL, "OnPowerRailTool",
+ "add_powerrail", _("Create a new power rail")),
+ (False, DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARRUNG, "OnRungTool",
+ "add_rung", _("Create a new rung")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCOIL, "OnCoilTool",
+ "add_coil", _("Create a new coil")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCONTACT, "OnContactTool",
+ "add_contact", _("Create a new contact")),
+ (False, DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARBRANCH, "OnBranchTool",
+ "add_branch", _("Create a new branch")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARVARIABLE, "OnVariableTool",
+ "add_variable", _("Create a new variable")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARBLOCK, "OnBlockTool",
+ "add_block", _("Create a new block")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCONNECTION, "OnConnectionTool",
+ "add_connection", _("Create a new connection"))],
+ "SFC" : [(True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARMOTION, "OnMotionTool",
+ "move", _("Move the view")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCOMMENT, "OnCommentTool",
+ "add_comment", _("Create a new comment")),
+ (True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARINITIALSTEP, "OnInitialStepTool",
+ "add_initial_step", _("Create a new initial step")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARSTEP, "OnStepTool",
+ "add_step", _("Create a new step")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARTRANSITION, "OnTransitionTool",
+ "add_transition", _("Create a new transition")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARACTIONBLOCK, "OnActionBlockTool",
+ "add_action", _("Create a new action block")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARDIVERGENCE, "OnDivergenceTool",
+ "add_divergence", _("Create a new divergence")),
+ (False, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARJUMP, "OnJumpTool",
+ "add_jump", _("Create a new jump")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARVARIABLE, "OnVariableTool",
+ "add_variable", _("Create a new variable")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARBLOCK, "OnBlockTool",
+ "add_block", _("Create a new block")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCONNECTION, "OnConnectionTool",
+ "add_connection", _("Create a new connection")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARPOWERRAIL, "OnPowerRailTool",
+ "add_powerrail", _("Create a new power rail")),
+ (True, FREEDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARCONTACT, "OnContactTool",
+ "add_contact", _("Create a new contact"))],
+ "ST" : [],
+ "IL" : [],
+ "debug": [(True, FREEDRAWING_MODE|DRIVENDRAWING_MODE,
+ ID_PLCOPENEDITOREDITORTOOLBARMOTION, "OnMotionTool",
+ "move", _("Move the view"))],
+}
+
+#-------------------------------------------------------------------------------
+# Helper Functions
+#-------------------------------------------------------------------------------
+
+import base64
+
+def EncodeFileSystemPath(path, use_base64=True):
+ path = path.encode(sys.getfilesystemencoding())
+ if use_base64:
+ return base64.encodestring(path)
+ return path
+
+def DecodeFileSystemPath(path, is_base64=True):
+ if is_base64:
+ path = base64.decodestring(path)
+ return unicode(path, sys.getfilesystemencoding())
+
+# Compatibility function for wx versions < 2.6
+def AppendMenu(parent, help, id, kind, text):
+ if wx.VERSION >= (2, 6, 0):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+ else:
+ parent.Append(helpString=help, id=id, kind=kind, item=text)
+
+[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
+ POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
+] = range(10)
+
+def GetShortcutKeyCallbackFunction(viewer_function):
+ def ShortcutKeyFunction(self, event):
+ control = self.FindFocus()
+ if control is not None and control.GetName() in ["Viewer", "TextViewer"]:
+ getattr(control.ParentWindow, viewer_function)()
+ elif isinstance(control, wx.stc.StyledTextCtrl):
+ getattr(control, viewer_function)()
+ elif isinstance(control, wx.TextCtrl):
+ control.ProcessEvent(event)
+ return ShortcutKeyFunction
+
+def GetDeleteElementFunction(remove_function, parent_type=None, check_function=None):
+ def DeleteElementFunction(self, selected):
+ name = self.ProjectTree.GetItemText(selected)
+ if check_function is None or not check_function(self.Controler, name):
+ if parent_type is not None:
+ item_infos = self.ProjectTree.GetPyData(selected)
+ parent_name = item_infos["tagname"].split("::")[1]
+ remove_function(self.Controler, parent_name, name)
+ else:
+ remove_function(self.Controler, name)
+ else:
+ self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!")%name)
+ return DeleteElementFunction
+
+if wx.Platform == '__WXMSW__':
+ TAB_BORDER = 6
+ NOTEBOOK_BORDER = 6
+else:
+ TAB_BORDER = 7
+ NOTEBOOK_BORDER = 2
+
+def SimplifyTabLayout(tabs, rect):
+ for tab in tabs:
+ if tab["pos"][0] == rect.x:
+ others = [t for t in tabs if t != tab]
+ others.sort(lambda x,y: cmp(x["pos"][0], y["pos"][0]))
+ for other in others:
+ if (other["pos"][1] == tab["pos"][1] and
+ other["size"][1] == tab["size"][1] and
+ other["pos"][0] == tab["pos"][0] + tab["size"][0] + TAB_BORDER):
+
+ tab["size"] = (tab["size"][0] + other["size"][0] + TAB_BORDER, tab["size"][1])
+ tab["pages"].extend(other["pages"])
+ tabs.remove(other)
+
+ if tab["size"][0] == rect.width:
+ return True
+
+ elif tab["pos"][1] == rect.y:
+ others = [t for t in tabs if t != tab]
+ others.sort(lambda x,y: cmp(x["pos"][1], y["pos"][1]))
+ for other in others:
+ if (other["pos"][0] == tab["pos"][0] and
+ other["size"][0] == tab["size"][0] and
+ other["pos"][1] == tab["pos"][1] + tab["size"][1] + TAB_BORDER):
+
+ tab["size"] = (tab["size"][0], tab["size"][1] + other["size"][1] + TAB_BORDER)
+ tab["pages"].extend(other["pages"])
+ tabs.remove(other)
+
+ if tab["size"][1] == rect.height:
+ return True
+ return False
+
+def ComputeTabsLayout(tabs, rect):
+ if len(tabs) == 0:
+ return tabs
+ if len(tabs) == 1:
+ return tabs[0]
+ split = None
+ for idx, tab in enumerate(tabs):
+ if len(tab["pages"]) == 0:
+ raise ValueError, "Not possible"
+ if tab["size"][0] == rect.width:
+ if tab["pos"][1] == rect.y:
+ split = (wx.TOP, float(tab["size"][1]) / float(rect.height))
+ split_rect = wx.Rect(rect.x, rect.y + tab["size"][1] + TAB_BORDER,
+ rect.width, rect.height - tab["size"][1] - TAB_BORDER)
+ elif tab["pos"][1] == rect.height + 1 - tab["size"][1]:
+ split = (wx.BOTTOM, 1.0 - float(tab["size"][1]) / float(rect.height))
+ split_rect = wx.Rect(rect.x, rect.y,
+ rect.width, rect.height - tab["size"][1] - TAB_BORDER)
+ break
+ elif tab["size"][1] == rect.height:
+ if tab["pos"][0] == rect.x:
+ split = (wx.LEFT, float(tab["size"][0]) / float(rect.width))
+ split_rect = wx.Rect(rect.x + tab["size"][0] + TAB_BORDER, rect.y,
+ rect.width - tab["size"][0] - TAB_BORDER, rect.height)
+ elif tab["pos"][0] == rect.width + 1 - tab["size"][0]:
+ split = (wx.RIGHT, 1.0 - float(tab["size"][0]) / float(rect.width))
+ split_rect = wx.Rect(rect.x, rect.y,
+ rect.width - tab["size"][0] - TAB_BORDER, rect.height)
+ break
+ if split != None:
+ split_tab = tabs.pop(idx)
+ return {"split": split,
+ "tab": split_tab,
+ "others": ComputeTabsLayout(tabs, split_rect)}
+ else:
+ if SimplifyTabLayout(tabs, rect):
+ return ComputeTabsLayout(tabs, rect)
+ return tabs
+
+#-------------------------------------------------------------------------------
+# IDEFrame Base Class
+#-------------------------------------------------------------------------------
+
+UNEDITABLE_NAMES_DICT = dict([(_(name), name) for name in UNEDITABLE_NAMES])
+
+class IDEFrame(wx.Frame):
+
+ Starting = False
+
+ # Compatibility function for wx versions < 2.6
+ if wx.VERSION < (2, 6, 0):
+ def Bind(self, event, function, id = None):
+ if id is not None:
+ event(self, id, function)
+ else:
+ event(self, function)
+
+ def _init_coll_MenuBar_Menus(self, parent):
+ parent.Append(menu=self.FileMenu, title=_(u'&File'))
+ parent.Append(menu=self.EditMenu, title=_(u'&Edit'))
+ parent.Append(menu=self.DisplayMenu, title=_(u'&Display'))
+ parent.Append(menu=self.HelpMenu, title=_(u'&Help'))
+
+ def _init_coll_FileMenu_Items(self, parent):
+ pass
+
+ def _init_coll_AddMenu_Items(self, parent, add_config=True):
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDDATATYPE,
+ kind=wx.ITEM_NORMAL, text=_(u'&Data Type'))
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDFUNCTION,
+ kind=wx.ITEM_NORMAL, text=_(u'&Function'))
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDFUNCTIONBLOCK,
+ kind=wx.ITEM_NORMAL, text=_(u'Function &Block'))
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDPROGRAM,
+ kind=wx.ITEM_NORMAL, text=_(u'&Program'))
+ if add_config:
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDCONFIGURATION,
+ kind=wx.ITEM_NORMAL, text=_(u'&Configuration'))
+
+ def _init_coll_EditMenu_Items(self, parent):
+ AppendMenu(parent, help='', id=wx.ID_UNDO,
+ kind=wx.ITEM_NORMAL, text=_(u'Undo') + '\tCTRL+Z')
+ AppendMenu(parent, help='', id=wx.ID_REDO,
+ kind=wx.ITEM_NORMAL, text=_(u'Redo') + '\tCTRL+Y')
+ #AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO,
+ # kind=wx.ITEM_CHECK, text=_(u'Enable Undo/Redo'))
+ enable_undo_redo = _(u'Enable Undo/Redo') # Keeping text in translations for possible menu reactivation
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_CUT,
+ kind=wx.ITEM_NORMAL, text=_(u'Cut') + '\tCTRL+X')
+ AppendMenu(parent, help='', id=wx.ID_COPY,
+ kind=wx.ITEM_NORMAL, text=_(u'Copy') + '\tCTRL+C')
+ AppendMenu(parent, help='', id=wx.ID_PASTE,
+ kind=wx.ITEM_NORMAL, text=_(u'Paste') + '\tCTRL+V')
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_FIND,
+ kind=wx.ITEM_NORMAL, text=_(u'Find') + '\tCTRL+F')
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDNEXT,
+ kind=wx.ITEM_NORMAL, text=_(u'Find Next') + '\tCTRL+K')
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
+ kind=wx.ITEM_NORMAL, text=_(u'Find Previous') + '\tCTRL+SHIFT+K')
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT,
+ kind=wx.ITEM_NORMAL, text=_(u'Search in Project') + '\tCTRL+SHIFT+F')
+ parent.AppendSeparator()
+ add_menu = wx.Menu(title='')
+ self._init_coll_AddMenu_Items(add_menu)
+ parent.AppendMenu(wx.ID_ADD, _(u"&Add Element"), add_menu)
+ AppendMenu(parent, help='', id=wx.ID_SELECTALL,
+ kind=wx.ITEM_NORMAL, text=_(u'Select All') + '\tCTRL+A')
+ AppendMenu(parent, help='', id=wx.ID_DELETE,
+ kind=wx.ITEM_NORMAL, text=_(u'&Delete'))
+ self.Bind(wx.EVT_MENU, self.OnUndoMenu, id=wx.ID_UNDO)
+ self.Bind(wx.EVT_MENU, self.OnRedoMenu, id=wx.ID_REDO)
+ #self.Bind(wx.EVT_MENU, self.OnEnableUndoRedoMenu, id=ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO)
+ self.Bind(wx.EVT_MENU, self.OnCutMenu, id=wx.ID_CUT)
+ self.Bind(wx.EVT_MENU, self.OnCopyMenu, id=wx.ID_COPY)
+ self.Bind(wx.EVT_MENU, self.OnPasteMenu, id=wx.ID_PASTE)
+ self.Bind(wx.EVT_MENU, self.OnFindMenu, id=wx.ID_FIND)
+ self.Bind(wx.EVT_MENU, self.OnFindNextMenu,
+ id=ID_PLCOPENEDITOREDITMENUFINDNEXT)
+ self.Bind(wx.EVT_MENU, self.OnFindPreviousMenu,
+ id=ID_PLCOPENEDITOREDITMENUFINDPREVIOUS)
+ self.Bind(wx.EVT_MENU, self.OnSearchInProjectMenu,
+ id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT)
+ self.Bind(wx.EVT_MENU, self.OnSearchInProjectMenu,
+ id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT)
+ self.Bind(wx.EVT_MENU, self.OnAddDataTypeMenu,
+ id=ID_PLCOPENEDITOREDITMENUADDDATATYPE)
+ self.Bind(wx.EVT_MENU, self.GenerateAddPouFunction("function"),
+ id=ID_PLCOPENEDITOREDITMENUADDFUNCTION)
+ self.Bind(wx.EVT_MENU, self.GenerateAddPouFunction("functionBlock"),
+ id=ID_PLCOPENEDITOREDITMENUADDFUNCTIONBLOCK)
+ self.Bind(wx.EVT_MENU, self.GenerateAddPouFunction("program"),
+ id=ID_PLCOPENEDITOREDITMENUADDPROGRAM)
+ self.Bind(wx.EVT_MENU, self.OnAddConfigurationMenu,
+ id=ID_PLCOPENEDITOREDITMENUADDCONFIGURATION)
+ self.Bind(wx.EVT_MENU, self.OnSelectAllMenu, id=wx.ID_SELECTALL)
+ self.Bind(wx.EVT_MENU, self.OnDeleteMenu, id=wx.ID_DELETE)
+
+ self.AddToMenuToolBar([(wx.ID_UNDO, "undo", _(u'Undo'), None),
+ (wx.ID_REDO, "redo", _(u'Redo'), None),
+ None,
+ (wx.ID_CUT, "cut", _(u'Cut'), None),
+ (wx.ID_COPY, "copy", _(u'Copy'), None),
+ (wx.ID_PASTE, "paste", _(u'Paste'), None),
+ None,
+ (ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, "find", _(u'Search in Project'), None)])
+
+ def _init_coll_DisplayMenu_Items(self, parent):
+ AppendMenu(parent, help='', id=wx.ID_REFRESH,
+ kind=wx.ITEM_NORMAL, text=_(u'Refresh') + '\tCTRL+R')
+ if self.EnableDebug:
+ AppendMenu(parent, help='', id=wx.ID_CLEAR,
+ kind=wx.ITEM_NORMAL, text=_(u'Clear Errors') + '\tCTRL+K')
+ parent.AppendSeparator()
+ zoommenu = wx.Menu(title='')
+ parent.AppendMenu(wx.ID_ZOOM_FIT, _("Zoom"), zoommenu)
+ for idx, value in enumerate(ZOOM_FACTORS):
+ new_id = wx.NewId()
+ AppendMenu(zoommenu, help='', id=new_id,
+ kind=wx.ITEM_RADIO, text=str(int(round(value * 100))) + "%")
+ self.Bind(wx.EVT_MENU, self.GenerateZoomFunction(idx), id=new_id)
+
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE,
+ kind=wx.ITEM_NORMAL, text=_(u'Reset Perspective'))
+ self.Bind(wx.EVT_MENU, self.OnResetPerspective, id=ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE)
+
+ self.Bind(wx.EVT_MENU, self.OnRefreshMenu, id=wx.ID_REFRESH)
+ if self.EnableDebug:
+ self.Bind(wx.EVT_MENU, self.OnClearErrorsMenu, id=wx.ID_CLEAR)
+
+ def _init_coll_HelpMenu_Items(self, parent):
+ pass
+
+ def _init_utils(self):
+ self.MenuBar = wx.MenuBar()
+
+ self.FileMenu = wx.Menu(title='')
+ self.EditMenu = wx.Menu(title='')
+ self.DisplayMenu = wx.Menu(title='')
+ self.HelpMenu = wx.Menu(title='')
+
+ self._init_coll_MenuBar_Menus(self.MenuBar)
+ self._init_coll_FileMenu_Items(self.FileMenu)
+ self._init_coll_EditMenu_Items(self.EditMenu)
+ self._init_coll_DisplayMenu_Items(self.DisplayMenu)
+ self._init_coll_HelpMenu_Items(self.HelpMenu)
+
+ def _init_ctrls(self, prnt):
+ wx.Frame.__init__(self, id=ID_PLCOPENEDITOR, name='IDEFrame',
+ parent=prnt, pos=wx.DefaultPosition, size=wx.Size(1000, 600),
+ style=wx.DEFAULT_FRAME_STYLE)
+ self.SetClientSize(wx.Size(1000, 600))
+ self.Bind(wx.EVT_ACTIVATE, self.OnActivated)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+
+ self.TabsImageList = wx.ImageList(31, 16)
+ self.TabsImageListIndexes = {}
+
+ #-----------------------------------------------------------------------
+ # Creating main structure
+ #-----------------------------------------------------------------------
+
+ self.AUIManager = wx.aui.AuiManager(self)
+ self.AUIManager.SetDockSizeConstraint(0.5, 0.5)
+ self.Panes = {}
+
+ self.LeftNoteBook = wx.aui.AuiNotebook(self, ID_PLCOPENEDITORLEFTNOTEBOOK,
+ style=wx.aui.AUI_NB_TOP|wx.aui.AUI_NB_TAB_SPLIT|wx.aui.AUI_NB_TAB_MOVE|
+ wx.aui.AUI_NB_SCROLL_BUTTONS|wx.aui.AUI_NB_TAB_EXTERNAL_MOVE)
+ self.LeftNoteBook.Bind(wx.aui.EVT_AUINOTEBOOK_ALLOW_DND,
+ self.OnAllowNotebookDnD)
+ self.AUIManager.AddPane(self.LeftNoteBook,
+ wx.aui.AuiPaneInfo().Name("ProjectPane").
+ Left().Layer(1).
+ BestSize(wx.Size(300, 500)).CloseButton(False))
+
+ self.BottomNoteBook = wx.aui.AuiNotebook(self, ID_PLCOPENEDITORBOTTOMNOTEBOOK,
+ style=wx.aui.AUI_NB_TOP|wx.aui.AUI_NB_TAB_SPLIT|wx.aui.AUI_NB_TAB_MOVE|
+ wx.aui.AUI_NB_SCROLL_BUTTONS|wx.aui.AUI_NB_TAB_EXTERNAL_MOVE)
+ self.BottomNoteBook.Bind(wx.aui.EVT_AUINOTEBOOK_ALLOW_DND,
+ self.OnAllowNotebookDnD)
+ self.AUIManager.AddPane(self.BottomNoteBook,
+ wx.aui.AuiPaneInfo().Name("ResultPane").
+ Bottom().Layer(0).
+ BestSize(wx.Size(800, 300)).CloseButton(False))
+
+ self.RightNoteBook = wx.aui.AuiNotebook(self, ID_PLCOPENEDITORRIGHTNOTEBOOK,
+ style=wx.aui.AUI_NB_TOP|wx.aui.AUI_NB_TAB_SPLIT|wx.aui.AUI_NB_TAB_MOVE|
+ wx.aui.AUI_NB_SCROLL_BUTTONS|wx.aui.AUI_NB_TAB_EXTERNAL_MOVE)
+ self.RightNoteBook.Bind(wx.aui.EVT_AUINOTEBOOK_ALLOW_DND,
+ self.OnAllowNotebookDnD)
+ self.AUIManager.AddPane(self.RightNoteBook,
+ wx.aui.AuiPaneInfo().Name("LibraryPane").
+ Right().Layer(0).
+ BestSize(wx.Size(250, 400)).CloseButton(False))
+
+ self.TabsOpened = wx.aui.AuiNotebook(self, ID_PLCOPENEDITORTABSOPENED,
+ style=wx.aui.AUI_NB_DEFAULT_STYLE|wx.aui.AUI_NB_WINDOWLIST_BUTTON)
+ self.TabsOpened.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGING,
+ self.OnPouSelectedChanging)
+ self.TabsOpened.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED,
+ self.OnPouSelectedChanged)
+ self.TabsOpened.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE,
+ self.OnPageClose)
+ self.TabsOpened.Bind(wx.aui.EVT_AUINOTEBOOK_END_DRAG,
+ self.OnPageDragged)
+ self.AUIManager.AddPane(self.TabsOpened,
+ wx.aui.AuiPaneInfo().CentrePane().Name("TabsPane"))
+
+ #-----------------------------------------------------------------------
+ # Creating PLCopen Project Types Tree
+ #-----------------------------------------------------------------------
+
+ self.MainTabs = {}
+
+ self.ProjectPanel = wx.SplitterWindow(id=ID_PLCOPENEDITORPROJECTPANEL,
+ name='ProjectPanel', parent=self.LeftNoteBook, point=wx.Point(0, 0),
+ size=wx.Size(0, 0), style=wx.SP_3D)
+
+ self.ProjectTree = CustomTree(id=ID_PLCOPENEDITORPROJECTTREE,
+ name='ProjectTree', parent=self.ProjectPanel,
+ pos=wx.Point(0, 0), size=wx.Size(0, 0),
+ style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_EDIT_LABELS)
+ self.ProjectTree.SetBackgroundBitmap(GetBitmap("custom_tree_background"),
+ wx.ALIGN_RIGHT|wx.ALIGN_BOTTOM)
+ add_menu = wx.Menu()
+ self._init_coll_AddMenu_Items(add_menu)
+ self.ProjectTree.SetAddMenu(add_menu)
+ if wx.Platform == '__WXMSW__':
+ self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnProjectTreeRightUp,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnProjectTreeItemSelected,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ else:
+ self.ProjectTree.Bind(wx.EVT_RIGHT_UP, self.OnProjectTreeRightUp)
+ self.ProjectTree.Bind(wx.EVT_LEFT_UP, self.OnProjectTreeLeftUp)
+ self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnProjectTreeItemChanging,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnProjectTreeBeginDrag,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnProjectTreeItemBeginEdit,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnProjectTreeItemEndEdit,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnProjectTreeItemActivated,
+ id=ID_PLCOPENEDITORPROJECTTREE)
+
+ #-----------------------------------------------------------------------
+ # Creating PLCopen Project POU Instance Variables Panel
+ #-----------------------------------------------------------------------
+
+ self.PouInstanceVariablesPanel = PouInstanceVariablesPanel(self.ProjectPanel, self, self.Controler, self.EnableDebug)
+
+ self.MainTabs["ProjectPanel"] = (self.ProjectPanel, _("Project"))
+ self.LeftNoteBook.AddPage(*self.MainTabs["ProjectPanel"])
+
+ self.ProjectPanel.SplitHorizontally(self.ProjectTree, self.PouInstanceVariablesPanel, 300)
+
+ #-----------------------------------------------------------------------
+ # Creating Tool Bar
+ #-----------------------------------------------------------------------
+
+ MenuToolBar = wx.ToolBar(self, ID_PLCOPENEDITOREDITORMENUTOOLBAR, wx.DefaultPosition, wx.DefaultSize,
+ wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
+ MenuToolBar.SetToolBitmapSize(wx.Size(25, 25))
+ MenuToolBar.Realize()
+ self.Panes["MenuToolBar"] = MenuToolBar
+ self.AUIManager.AddPane(MenuToolBar, wx.aui.AuiPaneInfo().
+ Name("MenuToolBar").Caption(_("Menu ToolBar")).
+ ToolbarPane().Top().
+ LeftDockable(False).RightDockable(False))
+
+ EditorToolBar = wx.ToolBar(self, ID_PLCOPENEDITOREDITORTOOLBAR, wx.DefaultPosition, wx.DefaultSize,
+ wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
+ EditorToolBar.SetToolBitmapSize(wx.Size(25, 25))
+ EditorToolBar.AddRadioTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION,
+ GetBitmap("select"), wx.NullBitmap, _("Select an object"))
+ EditorToolBar.Realize()
+ self.Panes["EditorToolBar"] = EditorToolBar
+ self.AUIManager.AddPane(EditorToolBar, wx.aui.AuiPaneInfo().
+ Name("EditorToolBar").Caption(_("Editor ToolBar")).
+ ToolbarPane().Top().Position(1).
+ LeftDockable(False).RightDockable(False))
+
+ self.Bind(wx.EVT_MENU, self.OnSelectionTool,
+ id=ID_PLCOPENEDITOREDITORTOOLBARSELECTION)
+
+ #-----------------------------------------------------------------------
+ # Creating Search Panel
+ #-----------------------------------------------------------------------
+
+ self.SearchResultPanel = SearchResultPanel(self.BottomNoteBook, self)
+ self.MainTabs["SearchResultPanel"] = (self.SearchResultPanel, _("Search"))
+ self.BottomNoteBook.AddPage(*self.MainTabs["SearchResultPanel"])
+
+ #-----------------------------------------------------------------------
+ # Creating Library Panel
+ #-----------------------------------------------------------------------
+
+ self.LibraryPanel = LibraryPanel(self, True)
+ self.MainTabs["LibraryPanel"] = (self.LibraryPanel, _("Library"))
+ self.RightNoteBook.AddPage(*self.MainTabs["LibraryPanel"])
+
+ self._init_utils()
+ self.SetMenuBar(self.MenuBar)
+
+ if self.EnableDebug:
+ self.DebugVariablePanel = DebugVariablePanel(self.RightNoteBook, self.Controler)
+ self.MainTabs["DebugVariablePanel"] = (self.DebugVariablePanel, _("Debugger"))
+ self.RightNoteBook.AddPage(*self.MainTabs["DebugVariablePanel"])
+
+ self.AUIManager.Update()
+
+ self.FindDialog = FindInPouDialog(self)
+ self.FindDialog.Hide()
+
+ ## Constructor of the PLCOpenEditor class.
+ # @param parent The parent window.
+ # @param controler The controler been used by PLCOpenEditor (default: None).
+ # @param fileOpen The filepath to open if no controler defined (default: None).
+ # @param debug The filepath to open if no controler defined (default: False).
+ def __init__(self, parent, enable_debug = False):
+ self.Controler = None
+ self.Config = wx.ConfigBase.Get()
+ self.EnableDebug = enable_debug
+
+ self._init_ctrls(parent)
+
+ # Define Tree item icon list
+ self.TreeImageList = wx.ImageList(16, 16)
+ self.TreeImageDict = {}
+
+ # Icons for languages
+ for language in LANGUAGES:
+ self.TreeImageDict[language] = self.TreeImageList.Add(GetBitmap(language))
+
+ # Icons for other items
+ for imgname, itemtype in [
+ #editables
+ ("PROJECT", ITEM_PROJECT),
+ #("POU", ITEM_POU),
+ #("VARIABLE", ITEM_VARIABLE),
+ ("TRANSITION", ITEM_TRANSITION),
+ ("ACTION", ITEM_ACTION),
+ ("CONFIGURATION", ITEM_CONFIGURATION),
+ ("RESOURCE", ITEM_RESOURCE),
+ ("DATATYPE", ITEM_DATATYPE),
+ # uneditables
+ ("DATATYPES", ITEM_DATATYPES),
+ ("FUNCTION", ITEM_FUNCTION),
+ ("FUNCTIONBLOCK", ITEM_FUNCTIONBLOCK),
+ ("PROGRAM", ITEM_PROGRAM),
+ ("VAR_LOCAL", ITEM_VAR_LOCAL),
+ ("VAR_LOCAL", ITEM_VAR_GLOBAL),
+ ("VAR_LOCAL", ITEM_VAR_EXTERNAL),
+ ("VAR_LOCAL", ITEM_VAR_TEMP),
+ ("VAR_INPUT", ITEM_VAR_INPUT),
+ ("VAR_OUTPUT", ITEM_VAR_OUTPUT),
+ ("VAR_INOUT", ITEM_VAR_INOUT),
+ ("TRANSITIONS", ITEM_TRANSITIONS),
+ ("ACTIONS", ITEM_ACTIONS),
+ ("CONFIGURATIONS", ITEM_CONFIGURATIONS),
+ ("RESOURCES", ITEM_RESOURCES),
+ ("PROPERTIES", ITEM_PROPERTIES)]:
+ self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
+
+ # Assign icon list to TreeCtrls
+ self.ProjectTree.SetImageList(self.TreeImageList)
+ self.PouInstanceVariablesPanel.SetTreeImageList(self.TreeImageList)
+
+ self.CurrentEditorToolBar = []
+ self.CurrentMenu = None
+ self.SelectedItem = None
+ self.SearchParams = None
+ self.Highlights = {}
+ self.DrawingMode = FREEDRAWING_MODE
+ #self.DrawingMode = DRIVENDRAWING_MODE
+ self.AuiTabCtrl = []
+ self.DefaultPerspective = None
+
+ # Initialize Printing configuring elements
+ self.PrintData = wx.PrintData()
+ self.PrintData.SetPaperId(wx.PAPER_A4)
+ self.PrintData.SetPrintMode(wx.PRINT_MODE_PRINTER)
+ self.PageSetupData = wx.PageSetupDialogData(self.PrintData)
+ self.PageSetupData.SetMarginTopLeft(wx.Point(10, 15))
+ self.PageSetupData.SetMarginBottomRight(wx.Point(10, 20))
+
+ self.SetRefreshFunctions()
+
+ def __del__(self):
+ self.FindDialog.Destroy()
+
+ def ResetStarting(self):
+ self.Starting = False
+
+ def Show(self):
+ wx.Frame.Show(self)
+ wx.CallAfter(self.RestoreLastState)
+
+ def OnActivated(self, event):
+ if event.GetActive():
+ wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Saving and restoring frame organization functions
+#-------------------------------------------------------------------------------
+
+ def OnResize(self, event):
+ if self.Starting:
+ self.RestoreLastLayout()
+ if wx.Platform == '__WXMSW__':
+ wx.CallAfter(self.ResetStarting)
+ else:
+ self.ResetStarting()
+ wx.CallAfter(self.RefreshEditor)
+ event.Skip()
+
+ def GetProjectConfiguration(self):
+ projects = {}
+ try:
+ if self.Config.HasEntry("projects"):
+ projects = cPickle.loads(str(self.Config.Read("projects")))
+ except:
+ pass
+
+ return projects.get(
+ EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
+
+ def SavePageState(self, page):
+ state = page.GetState()
+ if state is not None:
+ if self.Config.HasEntry("projects"):
+ projects = cPickle.loads(str(self.Config.Read("projects")))
+ else:
+ projects = {}
+
+ project_infos = projects.setdefault(
+ EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
+ editors_state = project_infos.setdefault("editors_state", {})
+
+ if page.IsDebugging():
+ editors_state[page.GetInstancePath()] = state
+ else:
+ editors_state[page.GetTagName()] = state
+
+ self.Config.Write("projects", cPickle.dumps(projects))
+ self.Config.Flush()
+
+ def GetTabInfos(self, tab):
+ if isinstance(tab, EditorPanel):
+ if tab.IsDebugging():
+ return ("debug", tab.GetInstancePath())
+ else:
+ return ("editor", tab.GetTagName())
+ else:
+ for page_name, (page_ref, page_title) in self.MainTabs.iteritems():
+ if page_ref == tab:
+ return ("main", page_name)
+ return None
+
+ def SaveTabLayout(self, notebook):
+ tabs = []
+ for child in notebook.GetChildren():
+ if isinstance(child, wx.aui.AuiTabCtrl):
+ if child.GetPageCount() > 0:
+ pos = child.GetPosition()
+ tab = {"pos": (pos.x, pos.y), "pages": []}
+ tab_size = child.GetSize()
+ for page_idx in xrange(child.GetPageCount()):
+ page = child.GetWindowFromIdx(page_idx)
+ if not tab.has_key("size"):
+ tab["size"] = (tab_size[0], tab_size[1] + page.GetSize()[1])
+ tab_infos = self.GetTabInfos(page)
+ if tab_infos is not None:
+ tab["pages"].append((tab_infos, page_idx == child.GetActivePage()))
+ tabs.append(tab)
+ tabs.sort(lambda x, y: cmp(x["pos"], y["pos"]))
+ size = notebook.GetSize()
+ return ComputeTabsLayout(tabs, wx.Rect(1, 1, size[0] - NOTEBOOK_BORDER, size[1] - NOTEBOOK_BORDER))
+
+ def LoadTab(self, notebook, page_infos):
+ if page_infos[0] == "main":
+ infos = self.MainTabs.get(page_infos[1])
+ if infos is not None:
+ page_ref, page_title = infos
+ notebook.AddPage(page_ref, page_title)
+ return notebook.GetPageIndex(page_ref)
+ elif page_infos[0] == "editor":
+ tagname = page_infos[1]
+ page_ref = self.EditProjectElement(self.Controler.GetElementType(tagname), tagname)
+ if page_ref is not None:
+ page_ref.RefreshView()
+ return notebook.GetPageIndex(page_ref)
+ elif page_infos[0] == "debug":
+ instance_path = page_infos[1]
+ instance_infos = self.Controler.GetInstanceInfos(instance_path, self.EnableDebug)
+ if instance_infos is not None:
+ return notebook.GetPageIndex(self.OpenDebugViewer(instance_infos["class"], instance_path, instance_infos["type"]))
+ return None
+
+ def LoadTabLayout(self, notebook, tabs, mode="all", first_index=None):
+ if isinstance(tabs, ListType):
+ if len(tabs) == 0:
+ return
+ raise ValueError, "Not supported"
+
+ if tabs.has_key("split"):
+ self.LoadTabLayout(notebook, tabs["others"])
+
+ split_dir, split_ratio = tabs["split"]
+ first_index = self.LoadTabLayout(notebook, tabs["tab"], mode="first")
+ notebook.Split(first_index, split_dir)
+ self.LoadTabLayout(notebook, tabs["tab"], mode="others", first_index=first_index)
+
+ elif mode == "first":
+ return self.LoadTab(notebook, tabs["pages"][0][0])
+ else:
+ selected = first_index
+ if mode == "others":
+ add_tabs = tabs["pages"][1:]
+ else:
+ add_tabs = tabs["pages"]
+ for page_infos, page_selected in add_tabs:
+ page_idx = self.LoadTab(notebook, page_infos)
+ if page_selected:
+ selected = page_idx
+ if selected is not None:
+ wx.CallAfter(notebook.SetSelection, selected)
+
+ def ResetPerspective(self):
+ if self.DefaultPerspective is not None:
+ self.AUIManager.LoadPerspective(self.DefaultPerspective["perspective"])
+
+ for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
+ for idx in xrange(notebook.GetPageCount()):
+ notebook.RemovePage(0)
+
+ notebooks = self.DefaultPerspective["notebooks"]
+ for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+ (self.BottomNoteBook, "bottomnotebook"),
+ (self.RightNoteBook, "rightnotebook")]:
+ self.LoadTabLayout(notebook, notebooks.get(entry_name))
+
+ self._Refresh(EDITORTOOLBAR)
+
+ def RestoreLastState(self):
+ frame_size = None
+ if self.Config.HasEntry("framesize"):
+ frame_size = cPickle.loads(str(self.Config.Read("framesize")))
+
+ self.Starting = True
+ if frame_size is None:
+ self.Maximize()
+ else:
+ self.SetClientSize(frame_size)
+
+ def RestoreLastLayout(self):
+ notebooks = {}
+ for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+ (self.BottomNoteBook, "bottomnotebook"),
+ (self.RightNoteBook, "rightnotebook")]:
+ notebooks[entry_name] = self.SaveTabLayout(notebook)
+ self.DefaultPerspective = {
+ "perspective": self.AUIManager.SavePerspective(),
+ "notebooks": notebooks,
+ }
+
+ try:
+ if self.Config.HasEntry("perspective"):
+ self.AUIManager.LoadPerspective(unicode(self.Config.Read("perspective")))
+
+ if self.Config.HasEntry("notebooks"):
+ notebooks = cPickle.loads(str(self.Config.Read("notebooks")))
+
+ for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
+ for idx in xrange(notebook.GetPageCount()):
+ notebook.RemovePage(0)
+
+ for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+ (self.BottomNoteBook, "bottomnotebook"),
+ (self.RightNoteBook, "rightnotebook")]:
+ self.LoadTabLayout(notebook, notebooks.get(entry_name))
+ except:
+ self.ResetPerspective()
+
+ self.LoadProjectLayout()
+
+ self._Refresh(EDITORTOOLBAR)
+
+ def SaveLastState(self):
+ if not self.IsMaximized():
+ self.Config.Write("framesize", cPickle.dumps(self.GetClientSize()))
+ elif self.Config.HasEntry("framesize"):
+ self.Config.DeleteEntry("framesize")
+
+ notebooks = {}
+ for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+ (self.BottomNoteBook, "bottomnotebook"),
+ (self.RightNoteBook, "rightnotebook")]:
+ notebooks[entry_name] = self.SaveTabLayout(notebook)
+ self.Config.Write("notebooks", cPickle.dumps(notebooks))
+
+ pane = self.AUIManager.GetPane(self.TabsOpened)
+ if pane.IsMaximized():
+ self.AUIManager.RestorePane(pane)
+ self.Config.Write("perspective", self.AUIManager.SavePerspective())
+
+ self.SaveProjectLayout()
+
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ self.SavePageState(self.TabsOpened.GetPage(i))
+
+ self.Config.Flush()
+
+ def SaveProjectLayout(self):
+ if self.Controler is not None:
+ tabs = []
+
+ projects = {}
+ try:
+ if self.Config.HasEntry("projects"):
+ projects = cPickle.loads(str(self.Config.Read("projects")))
+ except:
+ pass
+
+ project_infos = projects.setdefault(
+ EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
+ project_infos["tabs"] = self.SaveTabLayout(self.TabsOpened)
+ if self.EnableDebug:
+ project_infos["debug_vars"] = self.DebugVariablePanel.GetDebugVariables()
+
+ self.Config.Write("projects", cPickle.dumps(projects))
+ self.Config.Flush()
+
+ def LoadProjectLayout(self):
+ if self.Controler is not None:
+ project = self.GetProjectConfiguration()
+
+ try:
+ if project.has_key("tabs"):
+ self.LoadTabLayout(self.TabsOpened, project["tabs"])
+ except:
+ self.DeleteAllPages()
+
+ if self.EnableDebug:
+ try:
+ for variable in project.get("debug_vars", []):
+ self.DebugVariablePanel.InsertValue(variable, force=True)
+ except:
+ self.DebugVariablePanel.ResetGrid()
+
+#-------------------------------------------------------------------------------
+# General Functions
+#-------------------------------------------------------------------------------
+
+ def SetRefreshFunctions(self):
+ self.RefreshFunctions = {
+ TITLE : self.RefreshTitle,
+ EDITORTOOLBAR : self.RefreshEditorToolBar,
+ FILEMENU : self.RefreshFileMenu,
+ EDITMENU : self.RefreshEditMenu,
+ DISPLAYMENU : self.RefreshDisplayMenu,
+ PROJECTTREE : self.RefreshProjectTree,
+ POUINSTANCEVARIABLESPANEL : self.RefreshPouInstanceVariablesPanel,
+ LIBRARYTREE : self.RefreshLibraryPanel,
+ SCALING : self.RefreshScaling,
+ PAGETITLES: self.RefreshPageTitles}
+
+ ## Call PLCOpenEditor refresh functions.
+ # @param elements List of elements to refresh.
+ def _Refresh(self, *elements):
+ try:
+ for element in elements:
+ self.RefreshFunctions[element]()
+ except wx.PyDeadObjectError:
+ # ignore exceptions caused by refresh while quitting
+ pass
+
+ ## Callback function when AUINotebook Page closed with CloseButton
+ # @param event AUINotebook Event.
+ def OnPageClose(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected > -1:
+ window = self.TabsOpened.GetPage(selected)
+
+ if window.CheckSaveBeforeClosing():
+ self.SavePageState(window)
+
+ # Refresh all window elements that have changed
+ wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
+ wx.CallAfter(self.RefreshTabCtrlEvent)
+ wx.CallAfter(self.CloseFindInPouDialog)
+ event.Skip()
+ else:
+ event.Veto()
+
+
+ def GetCopyBuffer(self):
+ data = None
+ if wx.TheClipboard.Open():
+ dataobj = wx.TextDataObject()
+ if wx.TheClipboard.GetData(dataobj):
+ data = dataobj.GetText()
+ wx.TheClipboard.Close()
+ return data
+
+ def SetCopyBuffer(self, text):
+ if wx.TheClipboard.Open():
+ data = wx.TextDataObject()
+ data.SetText(text)
+ wx.TheClipboard.SetData(data)
+ wx.TheClipboard.Flush()
+ wx.TheClipboard.Close()
+ self.RefreshEditMenu()
+
+ def GetDrawingMode(self):
+ return self.DrawingMode
+
+ def RefreshScaling(self):
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ editor = self.TabsOpened.GetPage(i)
+ editor.RefreshScaling()
+
+ def EditProjectSettings(self):
+ old_values = self.Controler.GetProjectProperties()
+ dialog = ProjectDialog(self)
+ dialog.SetValues(old_values)
+ if dialog.ShowModal() == wx.ID_OK:
+ new_values = dialog.GetValues()
+ new_values["creationDateTime"] = old_values["creationDateTime"]
+ if new_values != old_values:
+ self.Controler.SetProjectProperties(None, new_values)
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU,
+ PROJECTTREE, POUINSTANCEVARIABLESPANEL, SCALING)
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Notebook Unified Functions
+#-------------------------------------------------------------------------------
+
+ ## Function that add a tab in Notebook, calling refresh for tab DClick event
+ # for wx.aui.AUINotebook.
+ # @param window Panel to display in tab.
+ # @param text title for the tab ctrl.
+ def AddPage(self, window, text):
+ self.TabsOpened.AddPage(window, text)
+ self.RefreshTabCtrlEvent()
+
+ ## Function that add a tab in Notebook, calling refresh for tab DClick event
+ # for wx.aui.AUINotebook.
+ # @param window Panel to display in tab.
+ # @param text title for the tab ctrl.
+ def DeletePage(self, window):
+ for idx in xrange(self.TabsOpened.GetPageCount()):
+ if self.TabsOpened.GetPage(idx) == window:
+ self.TabsOpened.DeletePage(idx)
+ self.RefreshTabCtrlEvent()
+ return
+
+ ## Function that fix difference in deleting all tabs between
+ # wx.Notebook and wx.aui.AUINotebook.
+ def DeleteAllPages(self):
+ for idx in xrange(self.TabsOpened.GetPageCount()):
+ self.TabsOpened.DeletePage(0)
+ self.RefreshTabCtrlEvent()
+
+ ## Function that fix difference in setting picture on tab between
+ # wx.Notebook and wx.aui.AUINotebook.
+ # @param idx Tab index.
+ # @param bitmap wx.Bitmap to define on tab.
+ # @return True if operation succeeded
+ def SetPageBitmap(self, idx, bitmap):
+ return self.TabsOpened.SetPageBitmap(idx, bitmap)
+
+#-------------------------------------------------------------------------------
+# Dialog Message Functions
+#-------------------------------------------------------------------------------
+
+ ## Function displaying an Error dialog in PLCOpenEditor.
+ # @param message The message to display.
+ def ShowErrorMessage(self, message):
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+ ## Function displaying an Error dialog in PLCOpenEditor.
+ # @return False if closing cancelled.
+ def CheckSaveBeforeClosing(self, title=_("Close Project")):
+ if not self.Controler.ProjectIsSaved():
+ dialog = wx.MessageDialog(self, _("There are changes, do you want to save?"), title, wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
+ answer = dialog.ShowModal()
+ dialog.Destroy()
+ if answer == wx.ID_YES:
+ self.SaveProject()
+ elif answer == wx.ID_CANCEL:
+ return False
+
+ for idx in xrange(self.TabsOpened.GetPageCount()):
+ window = self.TabsOpened.GetPage(idx)
+ if not window.CheckSaveBeforeClosing():
+ return False
+
+ return True
+
+#-------------------------------------------------------------------------------
+# File Menu Functions
+#-------------------------------------------------------------------------------
+
+ def RefreshFileMenu(self):
+ pass
+
+ def ResetView(self):
+ self.DeleteAllPages()
+ self.ProjectTree.DeleteAllItems()
+ self.ProjectTree.Enable(False)
+ self.PouInstanceVariablesPanel.ResetView()
+ self.LibraryPanel.ResetTree()
+ self.LibraryPanel.SetController(None)
+ if self.EnableDebug:
+ self.DebugVariablePanel.ResetGrid()
+ self.Controler = None
+
+ def OnCloseTabMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ self.TabsOpened.DeletePage(selected)
+ if self.TabsOpened.GetPageCount() > 0:
+ new_index = min(selected, self.TabsOpened.GetPageCount() - 1)
+ self.TabsOpened.SetSelection(new_index)
+ # Refresh all window elements that have changed
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
+ self.RefreshTabCtrlEvent()
+
+ def OnPageSetupMenu(self, event):
+ dialog = wx.PageSetupDialog(self, self.PageSetupData)
+ if dialog.ShowModal() == wx.ID_OK:
+ self.PageSetupData = wx.PageSetupDialogData(dialog.GetPageSetupData())
+ self.PrintData = wx.PrintData(self.PageSetupData.GetPrintData())
+ dialog.Destroy()
+
+ def OnPreviewMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ data = wx.PrintDialogData(self.PrintData)
+ properties = self.Controler.GetProjectProperties(window.IsDebugging())
+ page_size = map(int, properties["pageSize"])
+ margins = (self.PageSetupData.GetMarginTopLeft(), self.PageSetupData.GetMarginBottomRight())
+ printout = GraphicPrintout(window, page_size, margins, True)
+ printout2 = GraphicPrintout(window, page_size, margins, True)
+ preview = wx.PrintPreview(printout, printout2, data)
+
+ if preview.Ok():
+ preview_frame = wx.PreviewFrame(preview, self, _("Print preview"), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT)
+
+ preview_frame.Initialize()
+
+ preview_canvas = preview.GetCanvas()
+ preview_canvas.SetMinSize(preview_canvas.GetVirtualSize())
+ preview_frame.Fit()
+
+ preview_frame.Show(True)
+
+ def OnPrintMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ dialog_data = wx.PrintDialogData(self.PrintData)
+ dialog_data.SetToPage(1)
+ properties = self.Controler.GetProjectProperties(window.IsDebugging())
+ page_size = map(int, properties["pageSize"])
+ margins = (self.PageSetupData.GetMarginTopLeft(), self.PageSetupData.GetMarginBottomRight())
+ printer = wx.Printer(dialog_data)
+ printout = GraphicPrintout(window, page_size, margins)
+
+ if not printer.Print(self, printout, True) and printer.GetLastError() != wx.PRINTER_CANCELLED:
+ self.ShowErrorMessage(_("There was a problem printing.\nPerhaps your current printer is not set correctly?"))
+ printout.Destroy()
+
+ def OnPropertiesMenu(self, event):
+ self.EditProjectSettings()
+
+ def OnQuitMenu(self, event):
+ self.Close()
+
+#-------------------------------------------------------------------------------
+# Edit Menu Functions
+#-------------------------------------------------------------------------------
+
+ def RefreshEditMenu(self):
+ MenuToolBar = self.Panes["MenuToolBar"]
+ if self.Controler is not None:
+ selected = self.TabsOpened.GetSelection()
+ if selected > -1:
+ window = self.TabsOpened.GetPage(selected)
+ undo, redo = window.GetBufferState()
+ else:
+ undo, redo = self.Controler.GetBufferState()
+ self.EditMenu.Enable(wx.ID_UNDO, undo)
+ MenuToolBar.EnableTool(wx.ID_UNDO, undo)
+ self.EditMenu.Enable(wx.ID_REDO, redo)
+ MenuToolBar.EnableTool(wx.ID_REDO, redo)
+ #self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, True)
+ #self.EditMenu.Check(ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO,
+ # self.Controler.IsProjectBufferEnabled())
+ self.EditMenu.Enable(wx.ID_FIND, selected > -1)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDNEXT,
+ selected > -1 and self.SearchParams is not None)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
+ selected > -1 and self.SearchParams is not None)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
+ MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
+ self.EditMenu.Enable(wx.ID_ADD, True)
+ self.EditMenu.Enable(wx.ID_DELETE, True)
+ if self.TabsOpened.GetPageCount() > 0:
+ self.EditMenu.Enable(wx.ID_CUT, True)
+ MenuToolBar.EnableTool(wx.ID_CUT, True)
+ self.EditMenu.Enable(wx.ID_COPY, True)
+ MenuToolBar.EnableTool(wx.ID_COPY, True)
+ if self.GetCopyBuffer() is not None:
+ self.EditMenu.Enable(wx.ID_PASTE, True)
+ MenuToolBar.EnableTool(wx.ID_PASTE, True)
+ else:
+ self.EditMenu.Enable(wx.ID_PASTE, False)
+ MenuToolBar.EnableTool(wx.ID_PASTE, False)
+ self.EditMenu.Enable(wx.ID_SELECTALL, True)
+ else:
+ self.EditMenu.Enable(wx.ID_CUT, False)
+ MenuToolBar.EnableTool(wx.ID_CUT, False)
+ self.EditMenu.Enable(wx.ID_COPY, False)
+ MenuToolBar.EnableTool(wx.ID_COPY, False)
+ self.EditMenu.Enable(wx.ID_PASTE, False)
+ MenuToolBar.EnableTool(wx.ID_PASTE, False)
+ self.EditMenu.Enable(wx.ID_SELECTALL, False)
+ else:
+ self.EditMenu.Enable(wx.ID_UNDO, False)
+ MenuToolBar.EnableTool(wx.ID_UNDO, False)
+ self.EditMenu.Enable(wx.ID_REDO, False)
+ MenuToolBar.EnableTool(wx.ID_REDO, False)
+ #self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, False)
+ self.EditMenu.Enable(wx.ID_CUT, False)
+ MenuToolBar.EnableTool(wx.ID_CUT, False)
+ self.EditMenu.Enable(wx.ID_COPY, False)
+ MenuToolBar.EnableTool(wx.ID_COPY, False)
+ self.EditMenu.Enable(wx.ID_PASTE, False)
+ MenuToolBar.EnableTool(wx.ID_PASTE, False)
+ self.EditMenu.Enable(wx.ID_SELECTALL, False)
+ self.EditMenu.Enable(wx.ID_FIND, False)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDNEXT, False)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDPREVIOUS, False)
+ self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
+ MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
+ self.EditMenu.Enable(wx.ID_ADD, False)
+ self.EditMenu.Enable(wx.ID_DELETE, False)
+
+ def CloseTabsWithoutModel(self, refresh=True):
+ idxs = range(self.TabsOpened.GetPageCount())
+ idxs.reverse()
+ for idx in idxs:
+ window = self.TabsOpened.GetPage(idx)
+ if window.HasNoModel():
+ self.TabsOpened.DeletePage(idx)
+ if refresh:
+ self.RefreshEditor()
+
+ def OnUndoMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ window.Undo()
+ else:
+ self.Controler.LoadPrevious()
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE,
+ SCALING, PAGETITLES)
+
+ def OnRedoMenu(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ window.Redo()
+ else:
+ self.Controler.LoadNext()
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE,
+ SCALING, PAGETITLES)
+
+ def OnEnableUndoRedoMenu(self, event):
+ self.Controler.EnableProjectBuffer(event.IsChecked())
+ self.RefreshEditMenu()
+
+ OnCutMenu = GetShortcutKeyCallbackFunction("Cut")
+ OnCopyMenu = GetShortcutKeyCallbackFunction("Copy")
+ OnPasteMenu = GetShortcutKeyCallbackFunction("Paste")
+
+ def OnSelectAllMenu(self, event):
+ control = self.FindFocus()
+ if control is not None and control.GetName() == "Viewer":
+ control.Parent.SelectAll()
+ elif isinstance(control, wx.stc.StyledTextCtrl):
+ control.SelectAll()
+ elif isinstance(control, wx.TextCtrl):
+ control.SetSelection(0, control.GetLastPosition())
+ elif isinstance(control, wx.ComboBox):
+ control.SetMark(0, control.GetLastPosition() + 1)
+
+ DeleteFunctions = {
+ ITEM_DATATYPE: GetDeleteElementFunction(PLCControler.ProjectRemoveDataType, check_function=PLCControler.DataTypeIsUsed),
+ ITEM_POU: GetDeleteElementFunction(PLCControler.ProjectRemovePou, check_function=PLCControler.PouIsUsed),
+ ITEM_TRANSITION: GetDeleteElementFunction(PLCControler.ProjectRemovePouTransition, ITEM_POU),
+ ITEM_ACTION: GetDeleteElementFunction(PLCControler.ProjectRemovePouAction, ITEM_POU),
+ ITEM_CONFIGURATION: GetDeleteElementFunction(PLCControler.ProjectRemoveConfiguration),
+ ITEM_RESOURCE: GetDeleteElementFunction(PLCControler.ProjectRemoveConfigurationResource, ITEM_CONFIGURATION)
+ }
+
+ def OnDeleteMenu(self, event):
+ window = self.FindFocus()
+ if window == self.ProjectTree or window is None:
+ selected = self.ProjectTree.GetSelection()
+ if selected.IsOk():
+ function = self.DeleteFunctions.get(self.ProjectTree.GetPyData(selected)["type"], None)
+ if function is not None:
+ function(self, selected)
+ self.CloseTabsWithoutModel()
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE,
+ POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ elif isinstance(window, (Viewer, TextViewer)):
+ event = wx.KeyEvent(wx.EVT_CHAR._getEvtType())
+ event.m_keyCode = wx.WXK_DELETE
+ window.ProcessEvent(event)
+
+ def OnFindMenu(self, event):
+ if not self.FindDialog.IsShown():
+ self.FindDialog.Show()
+
+ def CloseFindInPouDialog(self):
+ selected = self.TabsOpened.GetSelection()
+ if selected == -1 and self.FindDialog.IsShown():
+ self.FindDialog.Hide()
+
+ def OnFindNextMenu(self, event):
+ self.FindInPou(1)
+
+ def OnFindPreviousMenu(self, event):
+ self.FindInPou(-1)
+
+ def FindInPou(self, direction, search_params=None):
+ if search_params is not None:
+ self.SearchParams = search_params
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ window.Find(direction, self.SearchParams)
+
+ def OnSearchInProjectMenu(self, event):
+ dialog = SearchInProjectDialog(self)
+ if dialog.ShowModal() == wx.ID_OK:
+ criteria = dialog.GetCriteria()
+ result = self.Controler.SearchInProject(criteria)
+ self.ClearSearchResults()
+ self.SearchResultPanel.SetSearchResults(criteria, result)
+ self.BottomNoteBook.SetSelection(self.BottomNoteBook.GetPageIndex(self.SearchResultPanel))
+
+#-------------------------------------------------------------------------------
+# Display Menu Functions
+#-------------------------------------------------------------------------------
+
+ def RefreshDisplayMenu(self):
+ if self.Controler is not None:
+ if self.TabsOpened.GetPageCount() > 0:
+ self.DisplayMenu.Enable(wx.ID_REFRESH, True)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ if isinstance(window, Viewer):
+ self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, True)
+ zoommenu = self.DisplayMenu.FindItemById(wx.ID_ZOOM_FIT).GetSubMenu()
+ zoomitem = zoommenu.FindItemByPosition(window.GetScale())
+ zoomitem.Check(True)
+ else:
+ self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+ else:
+ self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+ else:
+ self.DisplayMenu.Enable(wx.ID_REFRESH, False)
+ self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+ if self.EnableDebug:
+ self.DisplayMenu.Enable(wx.ID_CLEAR, True)
+ else:
+ self.DisplayMenu.Enable(wx.ID_REFRESH, False)
+ if self.EnableDebug:
+ self.DisplayMenu.Enable(wx.ID_CLEAR, False)
+ self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+
+ def OnRefreshMenu(self, event):
+ self.RefreshEditor()
+
+ def OnClearErrorsMenu(self, event):
+ self.ClearErrors()
+
+ def GenerateZoomFunction(self, idx):
+ def ZoomFunction(event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ window.SetScale(idx)
+ window.RefreshVisibleElements()
+ window.RefreshScrollBars()
+ event.Skip()
+ return ZoomFunction
+
+ def OnResetPerspective(self, event):
+ self.ResetPerspective()
+
+#-------------------------------------------------------------------------------
+# Project Editor Panels Management Functions
+#-------------------------------------------------------------------------------
+
+ def OnPageDragged(self, event):
+ wx.CallAfter(self.RefreshTabCtrlEvent)
+ event.Skip()
+
+ def OnAllowNotebookDnD(self, event):
+ event.Allow()
+
+ def RefreshTabCtrlEvent(self):
+ auitabctrl = []
+ for child in self.TabsOpened.GetChildren():
+ if isinstance(child, wx.aui.AuiTabCtrl):
+ auitabctrl.append(child)
+ if child not in self.AuiTabCtrl:
+ child.Bind(wx.EVT_LEFT_DCLICK, self.GetTabsOpenedDClickFunction(child))
+ self.AuiTabCtrl = auitabctrl
+ if self.TabsOpened.GetPageCount() == 0:
+ pane = self.AUIManager.GetPane(self.TabsOpened)
+ if pane.IsMaximized():
+ self.AUIManager.RestorePane(pane)
+ self.AUIManager.Update()
+
+ def EnsureTabVisible(self, tab):
+ notebook = tab.GetParent()
+ notebook.SetSelection(notebook.GetPageIndex(tab))
+
+ def OnPouSelectedChanging(self, event):
+ if not self.Starting:
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ window = self.TabsOpened.GetPage(selected)
+ if not window.IsDebugging():
+ window.ResetBuffer()
+ event.Skip()
+
+ def OnPouSelectedChanged(self, event):
+ if not self.Starting:
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ window = self.TabsOpened.GetPage(selected)
+ tagname = window.GetTagName()
+ if not window.IsDebugging():
+ wx.CallAfter(self.SelectProjectTreeItem, tagname)
+ wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname)
+ window.RefreshView()
+ self.EnsureTabVisible(self.LibraryPanel)
+ else:
+ instance_path = window.GetInstancePath()
+ if tagname == "":
+ instance_path = instance_path.rsplit(".", 1)[0]
+ tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
+ self.EnsureTabVisible(self.DebugVariablePanel)
+ wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname, instance_path)
+ wx.CallAfter(self._Refresh, FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
+ event.Skip()
+
+ def RefreshEditor(self):
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ window = self.TabsOpened.GetPage(selected)
+ tagname = window.GetTagName()
+ if not window.IsDebugging():
+ self.SelectProjectTreeItem(tagname)
+ self.PouInstanceVariablesPanel.SetPouType(tagname)
+ else:
+ instance_path = window.GetInstancePath()
+ if tagname == "":
+ instance_path = instance_path.rsplit(".", 1)[0]
+ tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
+ self.PouInstanceVariablesPanel.SetPouType(tagname, instance_path)
+ for child in self.TabsOpened.GetChildren():
+ if isinstance(child, wx.aui.AuiTabCtrl):
+ active_page = child.GetActivePage()
+ if active_page >= 0:
+ window = child.GetWindowFromIdx(active_page)
+ window.RefreshView()
+ self._Refresh(FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
+
+ def RefreshEditorNames(self, old_tagname, new_tagname):
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ editor = self.TabsOpened.GetPage(i)
+ if editor.GetTagName() == old_tagname:
+ editor.SetTagName(new_tagname)
+
+ def IsOpened(self, tagname):
+ for idx in xrange(self.TabsOpened.GetPageCount()):
+ if self.TabsOpened.GetPage(idx).IsViewing(tagname):
+ return idx
+ return None
+
+ def RefreshPageTitles(self):
+ for idx in xrange(self.TabsOpened.GetPageCount()):
+ window = self.TabsOpened.GetPage(idx)
+ icon = window.GetIcon()
+ if icon is not None:
+ self.SetPageBitmap(idx, icon)
+ self.TabsOpened.SetPageText(idx, window.GetTitle())
+
+ def GetTabsOpenedDClickFunction(self, tabctrl):
+ def OnTabsOpenedDClick(event):
+ pos = event.GetPosition()
+ if tabctrl.TabHitTest(pos.x, pos.y, None):
+ pane = self.AUIManager.GetPane(self.TabsOpened)
+ if pane.IsMaximized():
+ self.AUIManager.RestorePane(pane)
+ else:
+ self.AUIManager.MaximizePane(pane)
+ self.AUIManager.Update()
+ event.Skip()
+ return OnTabsOpenedDClick
+
+#-------------------------------------------------------------------------------
+# Types Tree Management Functions
+#-------------------------------------------------------------------------------
+
+ def RefreshProjectTree(self):
+ infos = self.Controler.GetProjectInfos()
+ root = self.ProjectTree.GetRootItem()
+ if not root.IsOk():
+ root = self.ProjectTree.AddRoot(infos["name"])
+ self.GenerateProjectTreeBranch(root, infos)
+ self.ProjectTree.Expand(root)
+
+ def ResetSelectedItem(self):
+ self.SelectedItem = None
+
+ def GenerateProjectTreeBranch(self, root, infos):
+ to_delete = []
+ item_name = infos["name"]
+ if infos["type"] in ITEMS_UNEDITABLE:
+ if len(infos["values"]) == 1:
+ return self.GenerateProjectTreeBranch(root, infos["values"][0])
+ item_name = _(item_name)
+ self.ProjectTree.SetItemText(root, item_name)
+ self.ProjectTree.SetPyData(root, infos)
+ highlight_colours = self.Highlights.get(infos.get("tagname", None), (wx.WHITE, wx.BLACK))
+ self.ProjectTree.SetItemBackgroundColour(root, highlight_colours[0])
+ self.ProjectTree.SetItemTextColour(root, highlight_colours[1])
+ if infos["type"] == ITEM_POU:
+ self.ProjectTree.SetItemImage(root, self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])])
+ elif infos.has_key("icon") and infos["icon"] is not None:
+ icon_name = infos["icon"]
+ if not self.TreeImageDict.has_key(icon_name):
+ self.TreeImageDict[icon_name] = self.TreeImageList.Add(GetBitmap(icon_name))
+ self.ProjectTree.SetItemImage(root, self.TreeImageDict[icon_name])
+ elif self.TreeImageDict.has_key(infos["type"]):
+ self.ProjectTree.SetItemImage(root, self.TreeImageDict[infos["type"]])
+
+ if wx.VERSION >= (2, 6, 0):
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
+ else:
+ item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
+ for values in infos["values"]:
+ if values["type"] not in ITEMS_UNEDITABLE or len(values["values"]) > 0:
+ if not item.IsOk():
+ item = self.ProjectTree.AppendItem(root, "")
+ if wx.Platform != '__WXMSW__':
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ self.GenerateProjectTreeBranch(item, values)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ while item.IsOk():
+ to_delete.append(item)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ for item in to_delete:
+ self.ProjectTree.Delete(item)
+
+ def SelectProjectTreeItem(self, tagname):
+ if self.ProjectTree is not None:
+ root = self.ProjectTree.GetRootItem()
+ if root.IsOk():
+ words = tagname.split("::")
+ if words[0] == "D":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_DATATYPE)])
+ elif words[0] == "P":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU)])
+ elif words[0] == "T":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_TRANSITION)])
+ elif words[0] == "A":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_ACTION)])
+ elif words[0] == "C":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION)])
+ elif words[0] == "R":
+ return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION), (words[2], ITEM_RESOURCE)])
+ return False
+
+ def RecursiveProjectTreeItemSelection(self, root, items):
+ found = False
+ if wx.VERSION >= (2, 6, 0):
+ item, root_cookie = self.ProjectTree.GetFirstChild(root)
+ else:
+ item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
+ while item.IsOk() and not found:
+ item_infos = self.ProjectTree.GetPyData(item)
+ if (item_infos["name"].split(":")[-1].strip(), item_infos["type"]) == items[0]:
+ if len(items) == 1:
+ self.SelectedItem = item
+ wx.CallAfter(self.ProjectTree.SelectItem, item)
+ wx.CallAfter(self.ResetSelectedItem)
+ return True
+ else:
+ found = self.RecursiveProjectTreeItemSelection(item, items[1:])
+ else:
+ found = self.RecursiveProjectTreeItemSelection(item, items)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ return found
+
+ def OnProjectTreeBeginDrag(self, event):
+ if wx.Platform == '__WXMSW__':
+ self.SelectedItem = event.GetItem()
+ if self.SelectedItem is not None and self.ProjectTree.GetPyData(self.SelectedItem)["type"] == ITEM_POU:
+ block_name = self.ProjectTree.GetItemText(self.SelectedItem)
+ block_type = self.Controler.GetPouType(block_name)
+ if block_type != "program":
+ data = wx.TextDataObject(str((block_name, block_type, "")))
+ dragSource = wx.DropSource(self.ProjectTree)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+ self.ResetSelectedItem()
+
+ def OnProjectTreeItemBeginEdit(self, event):
+ selected = event.GetItem()
+ if self.ProjectTree.GetPyData(selected)["type"] in ITEMS_UNEDITABLE:
+ event.Veto()
+ else:
+ event.Skip()
+
+ def OnProjectTreeItemEndEdit(self, event):
+ message = None
+ abort = False
+ new_name = event.GetLabel()
+ if new_name != "":
+ if not TestIdentifier(new_name):
+ message = _("\"%s\" is not a valid identifier!")%new_name
+ elif new_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!")%new_name
+ else:
+ item = event.GetItem()
+ old_name = self.ProjectTree.GetItemText(item)
+ item_infos = self.ProjectTree.GetPyData(item)
+ if item_infos["type"] == ITEM_PROJECT:
+ self.Controler.SetProjectProperties(name = new_name)
+ elif item_infos["type"] == ITEM_DATATYPE:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectDataTypeNames() if name != old_name]:
+ message = _("\"%s\" data type already exists!")%new_name
+ abort = True
+ if not abort:
+ self.Controler.ChangeDataTypeName(old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputeDataTypeName(old_name),
+ self.Controler.ComputeDataTypeName(new_name))
+ self.RefreshPageTitles()
+ elif item_infos["type"] == ITEM_POU:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames() if name != old_name]:
+ message = _("\"%s\" pou already exists!")%new_name
+ abort = True
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
+ if messageDialog.ShowModal() == wx.ID_NO:
+ abort = True
+ messageDialog.Destroy()
+ if not abort:
+ self.Controler.ChangePouName(old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputePouName(old_name),
+ self.Controler.ComputePouName(new_name))
+ self.RefreshLibraryPanel()
+ self.RefreshPageTitles()
+ elif item_infos["type"] == ITEM_TRANSITION:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
+ message = _("A POU named \"%s\" already exists!")%new_name
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+ message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
+ else:
+ words = item_infos["tagname"].split("::")
+ self.Controler.ChangePouTransitionName(words[1], old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputePouTransitionName(words[1], old_name),
+ self.Controler.ComputePouTransitionName(words[1], new_name))
+ self.RefreshPageTitles()
+ elif item_infos["type"] == ITEM_ACTION:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
+ message = _("A POU named \"%s\" already exists!")%new_name
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+ message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
+ else:
+ words = item_infos["tagname"].split("::")
+ self.Controler.ChangePouActionName(words[1], old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputePouActionName(words[1], old_name),
+ self.Controler.ComputePouActionName(words[1], new_name))
+ self.RefreshPageTitles()
+ elif item_infos["type"] == ITEM_CONFIGURATION:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectConfigNames() if name != old_name]:
+ message = _("\"%s\" config already exists!")%new_name
+ abort = True
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
+ messageDialog = wx.MessageDialog(self, _("There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
+ if messageDialog.ShowModal() == wx.ID_NO:
+ abort = True
+ messageDialog.Destroy()
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
+ if messageDialog.ShowModal() == wx.ID_NO:
+ abort = True
+ messageDialog.Destroy()
+ if not abort:
+ self.Controler.ChangeConfigurationName(old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputeConfigurationName(old_name),
+ self.Controler.ComputeConfigurationName(new_name))
+ self.RefreshPageTitles()
+ elif item_infos["type"] == ITEM_RESOURCE:
+ if new_name.upper() in [name.upper() for name in self.Controler.GetProjectConfigNames()]:
+ message = _("\"%s\" config already exists!")%new_name
+ abort = True
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
+ messageDialog = wx.MessageDialog(self, _("There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
+ if messageDialog.ShowModal() == wx.ID_NO:
+ abort = True
+ messageDialog.Destroy()
+ elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+ messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
+ if messageDialog.ShowModal() == wx.ID_NO:
+ abort = True
+ messageDialog.Destroy()
+ if not abort:
+ words = item_infos["tagname"].split("::")
+ self.Controler.ChangeConfigurationResourceName(words[1], old_name, new_name)
+ self.RefreshEditorNames(self.Controler.ComputeConfigurationResourceName(words[1], old_name),
+ self.Controler.ComputeConfigurationResourceName(words[1], new_name))
+ self.RefreshPageTitles()
+ if message or abort:
+ if message:
+ self.ShowErrorMessage(message)
+ item = event.GetItem()
+ wx.CallAfter(self.ProjectTree.EditLabel, item)
+ event.Veto()
+ else:
+ wx.CallAfter(self.RefreshProjectTree)
+ self.RefreshEditor()
+ self._Refresh(TITLE, FILEMENU, EDITMENU)
+ event.Skip()
+
+ def OnProjectTreeItemActivated(self, event):
+ selected = event.GetItem()
+ name = self.ProjectTree.GetItemText(selected)
+ item_infos = self.ProjectTree.GetPyData(selected)
+ if item_infos["type"] == ITEM_PROJECT:
+ self.EditProjectSettings()
+ else:
+ if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
+ ITEM_CONFIGURATION, ITEM_RESOURCE,
+ ITEM_TRANSITION, ITEM_ACTION]:
+ self.EditProjectElement(item_infos["type"], item_infos["tagname"])
+ event.Skip()
+
+ def ProjectTreeItemSelect(self, select_item):
+ name = self.ProjectTree.GetItemText(select_item)
+ item_infos = self.ProjectTree.GetPyData(select_item)
+ if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
+ ITEM_CONFIGURATION, ITEM_RESOURCE,
+ ITEM_TRANSITION, ITEM_ACTION]:
+ self.EditProjectElement(item_infos["type"], item_infos["tagname"], True)
+ self.PouInstanceVariablesPanel.SetPouType(item_infos["tagname"])
+
+ def OnProjectTreeLeftUp(self, event):
+ if self.SelectedItem is not None:
+ self.ProjectTree.SelectItem(self.SelectedItem)
+ self.ProjectTreeItemSelect(self.SelectedItem)
+ wx.CallAfter(self.ResetSelectedItem)
+ event.Skip()
+
+ def OnProjectTreeItemSelected(self, event):
+ self.ProjectTreeItemSelect(event.GetItem())
+ event.Skip()
+
+ def OnProjectTreeItemChanging(self, event):
+ if self.ProjectTree.GetPyData(event.GetItem())["type"] not in ITEMS_UNEDITABLE and self.SelectedItem is None:
+ self.SelectedItem = event.GetItem()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def EditProjectElement(self, element, tagname, onlyopened = False):
+ openedidx = self.IsOpened(tagname)
+ if openedidx is not None:
+ old_selected = self.TabsOpened.GetSelection()
+ if old_selected != openedidx:
+ if old_selected >= 0:
+ self.TabsOpened.GetPage(old_selected).ResetBuffer()
+ self.TabsOpened.SetSelection(openedidx)
+ self._Refresh(FILEMENU, EDITMENU, EDITORTOOLBAR, PAGETITLES)
+ elif not onlyopened:
+ new_window = None
+ if element == ITEM_CONFIGURATION:
+ new_window = ConfigurationEditor(self.TabsOpened, tagname, self, self.Controler)
+ new_window.SetIcon(GetBitmap("CONFIGURATION"))
+ self.AddPage(new_window, "")
+ elif element == ITEM_RESOURCE:
+ new_window = ResourceEditor(self.TabsOpened, tagname, self, self.Controler)
+ new_window.SetIcon(GetBitmap("RESOURCE"))
+ self.AddPage(new_window, "")
+ elif element in [ITEM_POU, ITEM_TRANSITION, ITEM_ACTION]:
+ bodytype = self.Controler.GetEditedElementBodyType(tagname)
+ if bodytype == "FBD":
+ new_window = Viewer(self.TabsOpened, tagname, self, self.Controler)
+ new_window.RefreshScaling(False)
+ elif bodytype == "LD":
+ new_window = LD_Viewer(self.TabsOpened, tagname, self, self.Controler)
+ new_window.RefreshScaling(False)
+ elif bodytype == "SFC":
+ new_window = SFC_Viewer(self.TabsOpened, tagname, self, self.Controler)
+ new_window.RefreshScaling(False)
+ else:
+ new_window = TextViewer(self.TabsOpened, tagname, self, self.Controler)
+ new_window.SetTextSyntax(bodytype)
+ if bodytype == "IL":
+ new_window.SetKeywords(IL_KEYWORDS)
+ else:
+ new_window.SetKeywords(ST_KEYWORDS)
+ if element == ITEM_POU:
+ pou_type = self.Controler.GetEditedElementType(tagname)[1].upper()
+ icon = GetBitmap(pou_type, bodytype)
+ elif element == ITEM_TRANSITION:
+ icon = GetBitmap("TRANSITION", bodytype)
+ elif element == ITEM_ACTION:
+ icon = GetBitmap("ACTION", bodytype)
+ new_window.SetIcon(icon)
+ self.AddPage(new_window, "")
+ elif element == ITEM_DATATYPE:
+ new_window = DataTypeEditor(self.TabsOpened, tagname, self, self.Controler)
+ new_window.SetIcon(GetBitmap("DATATYPE"))
+ self.AddPage(new_window, "")
+ elif isinstance(element, EditorPanel):
+ new_window = element
+ self.AddPage(element, "")
+ if new_window is not None:
+ project_infos = self.GetProjectConfiguration()
+ if project_infos.has_key("editors_state"):
+ state = project_infos["editors_state"].get(tagname)
+ if state is not None:
+ wx.CallAfter(new_window.SetState, state)
+
+ openedidx = self.IsOpened(tagname)
+ old_selected = self.TabsOpened.GetSelection()
+ if old_selected != openedidx:
+ if old_selected >= 0:
+ self.TabsOpened.GetPage(old_selected).ResetBuffer()
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ window = self.TabsOpened.GetPage(i)
+ if window == new_window:
+ self.TabsOpened.SetSelection(i)
+ window.SetFocus()
+ self.RefreshPageTitles()
+ return new_window
+
+ def OnProjectTreeRightUp(self, event):
+ if wx.Platform == '__WXMSW__':
+ item = event.GetItem()
+ else:
+ item, flags = self.ProjectTree.HitTest(wx.Point(event.GetX(), event.GetY()))
+ self.ProjectTree.SelectItem(item)
+ self.ProjectTreeItemSelect(item)
+ name = self.ProjectTree.GetItemText(item)
+ item_infos = self.ProjectTree.GetPyData(item)
+
+ menu = None
+ if item_infos["type"] in ITEMS_UNEDITABLE + [ITEM_PROJECT]:
+ if item_infos["type"] == ITEM_PROJECT:
+ name = "Project"
+ else:
+ name = UNEDITABLE_NAMES_DICT[name]
+
+ if name == "Data Types":
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add DataType"))
+ self.Bind(wx.EVT_MENU, self.OnAddDataTypeMenu, id=new_id)
+
+ elif name in ["Functions", "Function Blocks", "Programs", "Project"]:
+ menu = wx.Menu(title='')
+
+ if name != "Project":
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add POU"))
+ self.Bind(wx.EVT_MENU, self.GenerateAddPouFunction({"Functions" : "function", "Function Blocks" : "functionBlock", "Programs" : "program"}[name]), id=new_id)
+
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Paste POU"))
+ self.Bind(wx.EVT_MENU, self.OnPastePou, id=new_id)
+ if self.GetCopyBuffer() is None:
+ menu.Enable(new_id, False)
+
+ elif name == "Configurations":
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Configuration"))
+ self.Bind(wx.EVT_MENU, self.OnAddConfigurationMenu, id=new_id)
+
+ elif name == "Transitions":
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Transition"))
+ parent = self.ProjectTree.GetItemParent(item)["type"]
+ parent_type = self.ProjectTree.GetPyData(parent)
+ while parent_type != ITEM_POU:
+ parent = self.ProjectTree.GetItemParent(parent)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ self.Bind(wx.EVT_MENU, self.GenerateAddTransitionFunction(self.ProjectTree.GetItemText(parent)), id=new_id)
+
+ elif name == "Actions":
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Action"))
+ parent = self.ProjectTree.GetItemParent(item)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ while parent_type != ITEM_POU:
+ parent = self.ProjectTree.GetItemParent(parent)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ self.Bind(wx.EVT_MENU, self.GenerateAddActionFunction(self.ProjectTree.GetItemText(parent)), id=new_id)
+
+ elif name == "Resources":
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Resource"))
+ parent = self.ProjectTree.GetItemParent(item)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ while parent_type not in [ITEM_CONFIGURATION, ITEM_PROJECT]:
+ parent = self.ProjectTree.GetItemParent(parent)
+ parent_type = self.ProjectTree.GetPyData(parent)["type"]
+ if parent_type == ITEM_PROJECT:
+ parent_name = None
+ else:
+ parent_name = self.ProjectTree.GetItemText(parent)
+ self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(parent_name), id=new_id)
+
+ else:
+ if item_infos["type"] == ITEM_POU:
+ menu = wx.Menu(title='')
+ if self.Controler.GetPouBodyType(name) == "SFC":
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Transition"))
+ self.Bind(wx.EVT_MENU, self.GenerateAddTransitionFunction(name), id=new_id)
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Action"))
+ self.Bind(wx.EVT_MENU, self.GenerateAddActionFunction(name), id=new_id)
+ menu.AppendSeparator()
+
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy POU"))
+ self.Bind(wx.EVT_MENU, self.OnCopyPou, id=new_id)
+
+ pou_type = self.Controler.GetPouType(name)
+ if pou_type in ["function", "functionBlock"]:
+ change_menu = wx.Menu(title='')
+ if pou_type == "function":
+ new_id = wx.NewId()
+ AppendMenu(change_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Function Block"))
+ self.Bind(wx.EVT_MENU, self.GenerateChangePouTypeFunction(name, "functionBlock"), id=new_id)
+ new_id = wx.NewId()
+ AppendMenu(change_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Program"))
+ self.Bind(wx.EVT_MENU, self.GenerateChangePouTypeFunction(name, "program"), id=new_id)
+ menu.AppendMenu(wx.NewId(), _("Change POU Type To"), change_menu)
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Rename"))
+ self.Bind(wx.EVT_MENU, self.OnRenamePouMenu, id=new_id)
+
+ elif item_infos["type"] == ITEM_CONFIGURATION:
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Resource"))
+ self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(name), id=new_id)
+
+ elif item_infos["type"] in [ITEM_DATATYPE, ITEM_TRANSITION, ITEM_ACTION, ITEM_RESOURCE]:
+ menu = wx.Menu(title='')
+
+ if menu is not None:
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Delete"))
+ self.Bind(wx.EVT_MENU, self.OnDeleteMenu, id=new_id)
+
+ if menu is not None:
+ self.PopupMenu(menu)
+ menu.Destroy()
+
+ event.Skip()
+
+
+#-------------------------------------------------------------------------------
+# Instances Tree Management Functions
+#-------------------------------------------------------------------------------
+
+ def GetTreeImage(self, var_class):
+ return self.TreeImageDict[var_class]
+
+ def RefreshPouInstanceVariablesPanel(self):
+ self.PouInstanceVariablesPanel.RefreshView()
+
+ def OpenDebugViewer(self, instance_category, instance_path, instance_type):
+ openedidx = self.IsOpened(instance_path)
+ if openedidx is not None:
+ old_selected = self.TabsOpened.GetSelection()
+ if old_selected != openedidx:
+ if old_selected >= 0:
+ self.TabsOpened.GetPage(old_selected).ResetBuffer()
+ self.TabsOpened.SetSelection(openedidx)
+
+ elif instance_category in ITEMS_VARIABLE:
+ if self.Controler.IsOfType(instance_type, "ANY_NUM", True) or\
+ self.Controler.IsOfType(instance_type, "ANY_BIT", True):
+
+ return self.OpenGraphicViewer(instance_path)
+
+ else:
+ bodytype = self.Controler.GetEditedElementBodyType(instance_type, True)
+ new_window = None
+ if bodytype == "FBD":
+ new_window = Viewer(self.TabsOpened, instance_type, self, self.Controler, True, instance_path)
+ new_window.RefreshScaling(False)
+ elif bodytype == "LD":
+ new_window = LD_Viewer(self.TabsOpened, instance_type, self, self.Controler, True, instance_path)
+ new_window.RefreshScaling(False)
+ elif bodytype == "SFC":
+ new_window = SFC_Viewer(self.TabsOpened, instance_type, self, self.Controler, True, instance_path)
+ new_window.RefreshScaling(False)
+ else:
+ new_window = TextViewer(self.TabsOpened, instance_type, self, self.Controler, True, instance_path)
+ new_window.SetTextSyntax(bodytype)
+ if bodytype == "IL":
+ new_window.SetKeywords(IL_KEYWORDS)
+ else:
+ new_window.SetKeywords(ST_KEYWORDS)
+ if new_window is not None:
+ project_infos = self.GetProjectConfiguration()
+ if project_infos.has_key("editors_state"):
+ state = project_infos["editors_state"].get(instance_path)
+ if state is not None:
+ wx.CallAfter(new_window.SetState, state)
+
+ if instance_category in [ITEM_FUNCTIONBLOCK, ITEM_PROGRAM]:
+ pou_type = self.Controler.GetEditedElementType(instance_type, True)[1].upper()
+ icon = GetBitmap(pou_type, bodytype)
+ elif instance_category == ITEM_TRANSITION:
+ icon = GetBitmap("TRANSITION", bodytype)
+ elif instance_category == ITEM_ACTION:
+ icon = GetBitmap("ACTION", bodytype)
+ new_window.SetIcon(icon)
+ self.AddPage(new_window, "")
+ new_window.RefreshView()
+ new_window.SetFocus()
+ self.RefreshPageTitles()
+ return new_window
+
+ return None
+
+ def OpenGraphicViewer(self, var_path):
+ new_window = GraphicViewer(self.TabsOpened, self, self.Controler, var_path)
+ new_window.SetIcon(GetBitmap("GRAPH"))
+ self.AddPage(new_window, "")
+ new_window.RefreshView()
+ new_window.SetFocus()
+ self.RefreshPageTitles()
+ return new_window
+
+ def ResetGraphicViewers(self):
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ editor = self.TabsOpened.GetPage(i)
+ if isinstance(editor, GraphicViewer):
+ editor.ResetView()
+
+ def CloseObsoleteDebugTabs(self):
+ if self.EnableDebug:
+ idxs = range(self.TabsOpened.GetPageCount())
+ idxs.reverse()
+ for idx in idxs:
+ editor = self.TabsOpened.GetPage(idx)
+ if editor.IsDebugging():
+ instance_infos = self.Controler.GetInstanceInfos(editor.GetInstancePath(), self.EnableDebug)
+ if instance_infos is None:
+ self.TabsOpened.DeletePage(idx)
+ elif isinstance(editor, GraphicViewer):
+ editor.ResetView(True)
+ else:
+ editor.RefreshView()
+ self.DebugVariablePanel.UnregisterObsoleteData()
+
+ def AddDebugVariable(self, iec_path, force=False):
+ if self.EnableDebug:
+ self.DebugVariablePanel.InsertValue(iec_path, force=force)
+ self.EnsureTabVisible(self.DebugVariablePanel)
+
+#-------------------------------------------------------------------------------
+# Library Panel Management Function
+#-------------------------------------------------------------------------------
+
+ def RefreshLibraryPanel(self):
+ self.LibraryPanel.RefreshTree()
+
+#-------------------------------------------------------------------------------
+# ToolBars Management Functions
+#-------------------------------------------------------------------------------
+
+ def AddToMenuToolBar(self, items):
+ MenuToolBar = self.Panes["MenuToolBar"]
+ if MenuToolBar.GetToolsCount() > 0:
+ MenuToolBar.AddSeparator()
+ for toolbar_item in items:
+ if toolbar_item is None:
+ MenuToolBar.AddSeparator()
+ else:
+ id, bitmap, help, callback = toolbar_item
+ MenuToolBar.AddSimpleTool(id=id, shortHelpString=help, bitmap=GetBitmap(bitmap))
+ if callback is not None:
+ self.Bind(wx.EVT_TOOL, callback, id=id)
+ MenuToolBar.Realize()
+ self.AUIManager.GetPane("MenuToolBar").BestSize(MenuToolBar.GetBestSize())
+
+ def ResetEditorToolBar(self):
+ EditorToolBar = self.Panes["EditorToolBar"]
+
+ for item in self.CurrentEditorToolBar:
+ if wx.VERSION >= (2, 6, 0):
+ self.Unbind(wx.EVT_MENU, id=item)
+ else:
+ self.Disconnect(id=item, eventType=wx.wxEVT_COMMAND_MENU_SELECTED)
+
+ if EditorToolBar:
+ EditorToolBar.DeleteTool(item)
+
+ if EditorToolBar:
+ EditorToolBar.Realize()
+ self.AUIManager.GetPane("EditorToolBar").BestSize(EditorToolBar.GetBestSize())
+ self.AUIManager.GetPane("EditorToolBar").Hide()
+ self.AUIManager.Update()
+
+ def RefreshEditorToolBar(self):
+ selected = self.TabsOpened.GetSelection()
+ menu = None
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ if isinstance(window, (Viewer, TextViewer, GraphicViewer)):
+ if not window.IsDebugging():
+ menu = self.Controler.GetEditedElementBodyType(window.GetTagName())
+ else:
+ menu = "debug"
+ if menu is not None and menu != self.CurrentMenu:
+ self.ResetEditorToolBar()
+ self.CurrentMenu = menu
+ self.CurrentEditorToolBar = []
+ EditorToolBar = self.Panes["EditorToolBar"]
+ if EditorToolBar:
+ for radio, modes, id, method, picture, help in EditorToolBarItems[menu]:
+ if modes & self.DrawingMode:
+ if radio or self.DrawingMode == FREEDRAWING_MODE:
+ EditorToolBar.AddRadioTool(id, GetBitmap(picture), wx.NullBitmap, help)
+ else:
+ EditorToolBar.AddSimpleTool(id, GetBitmap(picture), help)
+ self.Bind(wx.EVT_MENU, getattr(self, method), id=id)
+ self.CurrentEditorToolBar.append(id)
+ EditorToolBar.Realize()
+ self.AUIManager.GetPane("EditorToolBar").BestSize(EditorToolBar.GetBestSize())
+ self.AUIManager.GetPane("EditorToolBar").Show()
+ self.AUIManager.Update()
+ elif menu is None:
+ self.ResetEditorToolBar()
+ self.CurrentMenu = menu
+ self.ResetCurrentMode()
+
+
+#-------------------------------------------------------------------------------
+# EditorToolBar Items Functions
+#-------------------------------------------------------------------------------
+
+ def ResetCurrentMode(self):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ window = self.TabsOpened.GetPage(selected)
+ window.SetMode(MODE_SELECTION)
+ EditorToolBar = self.Panes["EditorToolBar"]
+ if EditorToolBar:
+ EditorToolBar.ToggleTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION, False)
+ EditorToolBar.ToggleTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION, True)
+
+ def ResetToolToggle(self, id):
+ tool = self.Panes["EditorToolBar"].FindById(id)
+ tool.SetToggle(False)
+
+ def OnSelectionTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_SELECTION)
+
+ def OnMotionTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_MOTION)
+
+ def OnCommentTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARCOMMENT)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_COMMENT)
+
+ def OnVariableTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARVARIABLE)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_VARIABLE)
+
+ def OnBlockTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARBLOCK)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_BLOCK)
+
+ def OnConnectionTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARCONNECTION)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_CONNECTION)
+
+ def OnPowerRailTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARPOWERRAIL)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_POWERRAIL)
+
+ def OnRungTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddLadderRung()
+ event.Skip()
+
+ def OnCoilTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARCOIL)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_COIL)
+ event.Skip()
+
+ def OnContactTool(self, event):
+ if self.DrawingMode == FREEDRAWING_MODE:
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARCONTACT)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ if self.DrawingMode == FREEDRAWING_MODE:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_CONTACT)
+ else:
+ self.TabsOpened.GetPage(selected).AddLadderContact()
+
+ def OnBranchTool(self, event):
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).AddLadderBranch()
+
+ def OnInitialStepTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARINITIALSTEP)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_INITIALSTEP)
+
+ def OnStepTool(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARSTEP)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_STEP)
+ else:
+ self.TabsOpened.GetPage(selected).AddStep()
+
+ def OnActionBlockTool(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARACTIONBLOCK)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_ACTION)
+ else:
+ self.TabsOpened.GetPage(selected).AddStepAction()
+
+ def OnTransitionTool(self, event):
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARTRANSITION)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_TRANSITION)
+
+ def OnDivergenceTool(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARDIVERGENCE)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_DIVERGENCE)
+ else:
+ self.TabsOpened.GetPage(selected).AddDivergence()
+
+ def OnJumpTool(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.ResetToolToggle(ID_PLCOPENEDITOREDITORTOOLBARJUMP)
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ self.TabsOpened.GetPage(selected).SetMode(MODE_JUMP)
+ else:
+ self.TabsOpened.GetPage(selected).AddJump()
+
+
+#-------------------------------------------------------------------------------
+# Add Project Elements Functions
+#-------------------------------------------------------------------------------
+
+ def OnAddDataTypeMenu(self, event):
+ tagname = self.Controler.ProjectAddDataType()
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE)
+ self.EditProjectElement(ITEM_DATATYPE, tagname)
+
+ def GenerateAddPouFunction(self, pou_type):
+ def OnAddPouMenu(event):
+ dialog = PouDialog(self, pou_type)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames())
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariables())
+ dialog.SetValues({"pouName": self.Controler.GenerateNewName(None, None, "%s%%d" % pou_type)})
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ tagname = self.Controler.ProjectAddPou(values["pouName"], values["pouType"], values["language"])
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
+ self.EditProjectElement(ITEM_POU, tagname)
+ dialog.Destroy()
+ return OnAddPouMenu
+
+ def GenerateAddTransitionFunction(self, pou_name):
+ def OnAddTransitionMenu(event):
+ dialog = PouTransitionDialog(self)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames())
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+ dialog.SetValues({"transitionName": self.Controler.GenerateNewName(None, None, "transition%d")})
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ tagname = self.Controler.ProjectAddPouTransition(pou_name, values["transitionName"], values["language"])
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE)
+ self.EditProjectElement(ITEM_TRANSITION, tagname)
+ dialog.Destroy()
+ return OnAddTransitionMenu
+
+ def GenerateAddActionFunction(self, pou_name):
+ def OnAddActionMenu(event):
+ dialog = PouActionDialog(self)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames())
+ dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+ dialog.SetValues({"actionName": self.Controler.GenerateNewName(None, None, "action%d")})
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ tagname = self.Controler.ProjectAddPouAction(pou_name, values["actionName"], values["language"])
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE)
+ self.EditProjectElement(ITEM_ACTION, tagname)
+ dialog.Destroy()
+ return OnAddActionMenu
+
+ def OnAddConfigurationMenu(self, event):
+ tagname = self.Controler.ProjectAddConfiguration()
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL)
+ self.EditProjectElement(ITEM_CONFIGURATION, tagname)
+ dialog.Destroy()
+
+ def GenerateAddResourceFunction(self, config_name):
+ def OnAddResourceMenu(event):
+ tagname = self.Controler.ProjectAddConfigurationResource(config_name)
+ if tagname is not None:
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL)
+ self.EditProjectElement(ITEM_RESOURCE, tagname)
+ return OnAddResourceMenu
+
+ def GenerateChangePouTypeFunction(self, name, new_type):
+ def OnChangePouTypeMenu(event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+ self.Controler.ProjectChangePouType(name, new_type)
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
+ return OnChangePouTypeMenu
+
+ def OnCopyPou(self, event):
+ selected = self.ProjectTree.GetSelection()
+ pou_name = self.ProjectTree.GetItemText(selected)
+
+ pou_xml = self.Controler.GetPouXml(pou_name)
+ if pou_xml is not None:
+ self.SetCopyBuffer(pou_xml)
+ self._Refresh(EDITMENU)
+
+ def OnPastePou(self, event):
+ selected = self.ProjectTree.GetSelection()
+
+ if self.ProjectTree.GetPyData(selected)["type"] != ITEM_PROJECT:
+ pou_type = self.ProjectTree.GetItemText(selected)
+ pou_type = UNEDITABLE_NAMES_DICT[pou_type] # one of 'Functions', 'Function Blocks' or 'Programs'
+ pou_type = {'Functions': 'function', 'Function Blocks': 'functionBlock', 'Programs': 'program'}[pou_type]
+ else:
+ pou_type = None
+
+ pou_xml = self.GetCopyBuffer()
+
+ result = self.Controler.PastePou(pou_type, pou_xml)
+
+ if not isinstance(result, TupleType):
+ message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
+ self.EditProjectElement(ITEM_POU, result[0])
+
+#-------------------------------------------------------------------------------
+# Remove Project Elements Functions
+#-------------------------------------------------------------------------------
+
+ def OnRemoveDataTypeMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_DATATYPE:
+ name = self.ProjectTree.GetItemText(selected)
+ if not self.Controler.DataTypeIsUsed(name):
+ self.Controler.ProjectRemoveDataType(name)
+ tagname = self.Controler.ComputeDataTypeName(name)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE)
+ else:
+ self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
+
+ def OnRenamePouMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+ wx.CallAfter(self.ProjectTree.EditLabel, selected)
+
+ def OnRemovePouMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+ name = self.ProjectTree.GetItemText(selected)
+ if not self.Controler.PouIsUsed(name):
+ self.Controler.ProjectRemovePou(name)
+ tagname = self.Controler.ComputePouName(name)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ else:
+ self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
+
+ def OnRemoveTransitionMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ item_infos = self.ProjectTree.GetPyData(selected)
+ if item_infos["type"] == ITEM_TRANSITION:
+ transition = self.ProjectTree.GetItemText(selected)
+ pou_name = item_infos["tagname"].split("::")[1]
+ self.Controler.ProjectRemovePouTransition(pou_name, transition)
+ tagname = self.Controler.ComputePouTransitionName(pou_name, transition)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE)
+
+ def OnRemoveActionMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ item_infos = self.ProjectTree.GetPyData(selected)
+ if item_infos["type"] == ITEM_ACTION:
+ action = self.ProjectTree.GetItemText(selected)
+ pou_name = item_infos["tagname"].split("::")[1]
+ self.Controler.ProjectRemovePouAction(pou_name, action)
+ tagname = self.Controler.ComputePouActionName(pou_name, action)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE)
+
+ def OnRemoveConfigurationMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFIGURATION:
+ name = self.ProjectTree.GetItemText(selected)
+ self.Controler.ProjectRemoveConfiguration(name)
+ tagname = self.Controler.ComputeConfigurationName(name)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL)
+
+ def OnRemoveResourceMenu(self, event):
+ selected = self.ProjectTree.GetSelection()
+ item_infos = self.ProjectTree.GetPyData(selected)
+ if item_infos["type"] == ITEM_RESOURCE:
+ resource = self.ProjectTree.GetItemText(selected)
+ config_name = item_infos["tagname"].split("::")[1]
+ self.Controler.ProjectRemoveConfigurationResource(config_name, resource)
+ tagname = self.Controler.ComputeConfigurationResourceName(config_name, selected)
+ idx = self.IsOpened(tagname)
+ if idx is not None:
+ self.TabsOpened.DeletePage(idx)
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL)
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def ShowHighlight(self, infos, start, end, highlight_type):
+ self.SelectProjectTreeItem(infos[0])
+ if infos[1] == "name":
+ self.Highlights[infos[0]] = highlight_type
+ self.RefreshProjectTree()
+ self.ProjectTree.Unselect()
+ else:
+ self.EditProjectElement(self.Controler.GetElementType(infos[0]), infos[0])
+ selected = self.TabsOpened.GetSelection()
+ if selected != -1:
+ viewer = self.TabsOpened.GetPage(selected)
+ viewer.AddHighlight(infos[1:], start, end, highlight_type)
+
+ def ShowError(self, infos, start, end):
+ self.ShowHighlight(infos, start, end, ERROR_HIGHLIGHT)
+
+ def ShowSearchResult(self, infos, start, end):
+ self.ShowHighlight(infos, start, end, SEARCH_RESULT_HIGHLIGHT)
+
+ def ClearHighlights(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ self.Highlights = dict([(name, highlight) for name, highlight in self.Highlights.iteritems() if highlight != highlight_type])
+ self.RefreshProjectTree()
+ for i in xrange(self.TabsOpened.GetPageCount()):
+ viewer = self.TabsOpened.GetPage(i)
+ viewer.ClearHighlights(highlight_type)
+
+ def ClearErrors(self):
+ self.ClearHighlights(ERROR_HIGHLIGHT)
+
+ def ClearSearchResults(self):
+ self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCControler.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,3043 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from xml.dom import minidom
+from types import StringType, UnicodeType, TupleType
+import cPickle
+import os,sys,re
+import datetime
+from time import localtime
+
+from plcopen import plcopen
+from plcopen.structures import *
+from graphics.GraphicCommons import *
+from PLCGenerator import *
+
+duration_model = re.compile("(?:([0-9]{1,2})h)?(?:([0-9]{1,2})m(?!s))?(?:([0-9]{1,2})s)?(?:([0-9]{1,3}(?:\.[0-9]*)?)ms)?")
+
+ITEMS_EDITABLE = [ITEM_PROJECT,
+ ITEM_POU,
+ ITEM_VARIABLE,
+ ITEM_TRANSITION,
+ ITEM_ACTION,
+ ITEM_CONFIGURATION,
+ ITEM_RESOURCE,
+ ITEM_DATATYPE
+ ] = range(8)
+
+ITEMS_UNEDITABLE = [ITEM_DATATYPES,
+ ITEM_FUNCTION,
+ ITEM_FUNCTIONBLOCK,
+ ITEM_PROGRAM,
+ ITEM_TRANSITIONS,
+ ITEM_ACTIONS,
+ ITEM_CONFIGURATIONS,
+ ITEM_RESOURCES,
+ ITEM_PROPERTIES
+ ] = range(8, 17)
+
+ITEMS_VARIABLE = [ITEM_VAR_LOCAL,
+ ITEM_VAR_GLOBAL,
+ ITEM_VAR_EXTERNAL,
+ ITEM_VAR_TEMP,
+ ITEM_VAR_INPUT,
+ ITEM_VAR_OUTPUT,
+ ITEM_VAR_INOUT
+ ] = range(17, 24)
+
+VAR_CLASS_INFOS = {"Local" : (plcopen.interface_localVars, ITEM_VAR_LOCAL),
+ "Global" : (plcopen.interface_globalVars, ITEM_VAR_GLOBAL),
+ "External" : (plcopen.interface_externalVars, ITEM_VAR_EXTERNAL),
+ "Temp" : (plcopen.interface_tempVars, ITEM_VAR_TEMP),
+ "Input" : (plcopen.interface_inputVars, ITEM_VAR_INPUT),
+ "Output" : (plcopen.interface_outputVars, ITEM_VAR_OUTPUT),
+ "InOut" : (plcopen.interface_inOutVars, ITEM_VAR_INOUT)
+ }
+
+POU_TYPES = {"program": ITEM_PROGRAM,
+ "functionBlock": ITEM_FUNCTIONBLOCK,
+ "function": ITEM_FUNCTION,
+ }
+
+LOCATIONS_ITEMS = [LOCATION_CONFNODE,
+ LOCATION_MODULE,
+ LOCATION_GROUP,
+ LOCATION_VAR_INPUT,
+ LOCATION_VAR_OUTPUT,
+ LOCATION_VAR_MEMORY] = range(6)
+
+ScriptDirectory = os.path.split(os.path.realpath(__file__))[0]
+
+def GetUneditableNames():
+ _ = lambda x:x
+ return [_("User-defined POUs"), _("Functions"), _("Function Blocks"),
+ _("Programs"), _("Data Types"), _("Transitions"), _("Actions"),
+ _("Configurations"), _("Resources"), _("Properties")]
+UNEDITABLE_NAMES = GetUneditableNames()
+[USER_DEFINED_POUS, FUNCTIONS, FUNCTION_BLOCKS, PROGRAMS,
+ DATA_TYPES, TRANSITIONS, ACTIONS, CONFIGURATIONS,
+ RESOURCES, PROPERTIES] = UNEDITABLE_NAMES
+
+#-------------------------------------------------------------------------------
+# Undo Buffer for PLCOpenEditor
+#-------------------------------------------------------------------------------
+
+# Length of the buffer
+UNDO_BUFFER_LENGTH = 20
+
+"""
+Class implementing a buffer of changes made on the current editing model
+"""
+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.ProjectBufferEnabled = True
+ self.ProjectBuffer = None
+ self.ProjectSaved = True
+ self.Buffering = False
+ self.FilePath = ""
+ self.FileName = ""
+ self.ProgramChunks = []
+ self.ProgramOffset = 0
+ self.NextCompiledProject = None
+ self.CurrentCompiledProject = None
+ self.ConfNodeTypes = []
+ self.ProgramFilePath = ""
+
+ def GetQualifierTypes(self):
+ return plcopen.QualifierList
+
+ def GetProject(self, debug = False):
+ if debug and self.CurrentCompiledProject is not None:
+ return self.CurrentCompiledProject
+ else:
+ return self.Project
+
+#-------------------------------------------------------------------------------
+# Project management functions
+#-------------------------------------------------------------------------------
+
+ # Return if a project is opened
+ def HasOpenedProject(self):
+ return self.Project is not None
+
+ # Create a new project by replacing the current one
+ def CreateNewProject(self, properties):
+ # Create the project
+ self.Project = plcopen.project()
+ properties["creationDateTime"] = datetime.datetime(*localtime()[:6])
+ self.Project.setfileHeader(properties)
+ self.Project.setcontentHeader(properties)
+ self.SetFilePath("")
+ # Initialize the project buffer
+ self.CreateProjectBuffer(False)
+ self.ProgramChunks = []
+ self.ProgramOffset = 0
+ self.NextCompiledProject = self.Copy(self.Project)
+ self.CurrentCompiledProject = None
+ self.Buffering = False
+
+ # Return project data type names
+ def GetProjectDataTypeNames(self, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return [datatype.getname() for datatype in project.getdataTypes()]
+ return []
+
+ # Return project pou names
+ def GetProjectPouNames(self, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return [pou.getname() for pou in project.getpous()]
+ return []
+
+ # Return project pou names
+ def GetProjectConfigNames(self, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return [config.getname() for config in project.getconfigurations()]
+ return []
+
+ # Return project pou variables
+ def GetProjectPouVariables(self, pou_name = None, debug = False):
+ variables = []
+ project = self.GetProject(debug)
+ if project is not None:
+ for pou in project.getpous():
+ if pou_name is None or pou_name == pou.getname():
+ variables.extend([var["Name"] for var in self.GetPouInterfaceVars(pou, debug)])
+ for transition in pou.gettransitionList():
+ variables.append(transition.getname())
+ for action in pou.getactionList():
+ variables.append(action.getname())
+ return variables
+
+ # Return file path if project is an open file
+ def GetFilePath(self):
+ return self.FilePath
+
+ # Return file path if project is an open file
+ def GetProgramFilePath(self):
+ return self.ProgramFilePath
+
+ # Return file name and point out if file is up to date
+ def GetFilename(self):
+ if self.Project is not None:
+ if self.ProjectIsSaved():
+ return self.FileName
+ else:
+ return "~%s~"%self.FileName
+ return ""
+
+ # 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, name = None, properties = None, buffer = True):
+ if self.Project is not None:
+ if name is not None:
+ self.Project.setname(name)
+ if properties is not None:
+ self.Project.setfileHeader(properties)
+ self.Project.setcontentHeader(properties)
+ if buffer and (name is not None or properties is not None):
+ self.BufferProject()
+
+ # Return project name
+ def GetProjectName(self, debug=False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return project.getname()
+ return None
+
+ # Return project properties
+ def GetProjectProperties(self, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ properties = project.getfileHeader()
+ properties.update(project.getcontentHeader())
+ return properties
+ return None
+
+ # Return project informations
+ def GetProjectInfos(self, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ infos = {"name": project.getname(), "type": ITEM_PROJECT}
+ datatypes = {"name": DATA_TYPES, "type": ITEM_DATATYPES, "values":[]}
+ for datatype in project.getdataTypes():
+ datatypes["values"].append({"name": datatype.getname(), "type": ITEM_DATATYPE,
+ "tagname": self.ComputeDataTypeName(datatype.getname()), "values": []})
+ pou_types = {"function": {"name": FUNCTIONS, "type": ITEM_FUNCTION, "values":[]},
+ "functionBlock": {"name": FUNCTION_BLOCKS, "type": ITEM_FUNCTIONBLOCK, "values":[]},
+ "program": {"name": PROGRAMS, "type": ITEM_PROGRAM, "values":[]}}
+ for pou in project.getpous():
+ pou_type = pou.getpouType()
+ pou_infos = {"name": pou.getname(), "type": ITEM_POU,
+ "tagname": self.ComputePouName(pou.getname())}
+ pou_values = []
+ if pou.getbodyType() == "SFC":
+ transitions = []
+ for transition in pou.gettransitionList():
+ transitions.append({"name": transition.getname(), "type": ITEM_TRANSITION,
+ "tagname": self.ComputePouTransitionName(pou.getname(), transition.getname()),
+ "values": []})
+ pou_values.append({"name": TRANSITIONS, "type": ITEM_TRANSITIONS, "values": transitions})
+ actions = []
+ for action in pou.getactionList():
+ actions.append({"name": action.getname(), "type": ITEM_ACTION,
+ "tagname": self.ComputePouActionName(pou.getname(), action.getname()),
+ "values": []})
+ pou_values.append({"name": ACTIONS, "type": ITEM_ACTIONS, "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_CONFIGURATIONS, "values": []}
+ for config in project.getconfigurations():
+ config_name = config.getname()
+ config_infos = {"name": config_name, "type": ITEM_CONFIGURATION,
+ "tagname": self.ComputeConfigurationName(config.getname()),
+ "values": []}
+ resources = {"name": RESOURCES, "type": ITEM_RESOURCES, "values": []}
+ for resource in config.getresource():
+ resource_name = resource.getname()
+ resource_infos = {"name": resource_name, "type": ITEM_RESOURCE,
+ "tagname": self.ComputeConfigurationResourceName(config.getname(), resource.getname()),
+ "values": []}
+ resources["values"].append(resource_infos)
+ config_infos["values"] = [resources]
+ configurations["values"].append(config_infos)
+ infos["values"] = [datatypes, pou_types["function"], pou_types["functionBlock"],
+ pou_types["program"], configurations]
+ return infos
+ return None
+
+ def GetPouVariableInfos(self, project, variable, var_class, debug=False):
+ vartype_content = variable.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ var_type = vartype_content["value"].getname()
+ pou_type = None
+ pou = project.getpou(var_type)
+ if pou is not None:
+ pou_type = pou.getpouType()
+ edit = debug = pou_type is not None
+ if pou_type is None:
+ block_infos = self.GetBlockType(var_type, debug = debug)
+ if block_infos is not None:
+ pou_type = block_infos["type"]
+ if pou_type is not None:
+ var_class = None
+ if pou_type == "program":
+ var_class = ITEM_PROGRAM
+ elif pou_type != "function":
+ var_class = ITEM_FUNCTIONBLOCK
+ if var_class is not None:
+ return {"name": variable.getname(),
+ "type": var_type,
+ "class": var_class,
+ "edit": edit,
+ "debug": debug}
+ elif var_type in self.GetDataTypes(debug = debug):
+ return {"name": variable.getname(),
+ "type": var_type,
+ "class": var_class,
+ "edit": False,
+ "debug": False}
+ elif vartype_content["name"] in ["string", "wstring"]:
+ return {"name": variable.getname(),
+ "type": vartype_content["name"].upper(),
+ "class": var_class,
+ "edit": False,
+ "debug": True}
+ else:
+ return {"name": variable.getname(),
+ "type": vartype_content["name"],
+ "class": var_class,
+ "edit": False,
+ "debug": True}
+ return None
+
+ def GetPouVariables(self, tagname, debug = False):
+ vars = []
+ pou_type = None
+ project = self.GetProject(debug)
+ if project is not None:
+ words = tagname.split("::")
+ if words[0] == "P":
+ pou = project.getpou(words[1])
+ if pou is not None:
+ pou_type = pou.getpouType()
+ if (pou_type in ["program", "functionBlock"] and
+ pou.interface is not None):
+ # Extract variables from every varLists
+ for varlist_type, varlist in pou.getvars():
+ var_infos = VAR_CLASS_INFOS.get(varlist_type, None)
+ if var_infos is not None:
+ var_class = var_infos[1]
+ else:
+ var_class = ITEM_VAR_LOCAL
+ for variable in varlist.getvariable():
+ var_infos = self.GetPouVariableInfos(project, variable, var_class, debug)
+ if var_infos is not None:
+ vars.append(var_infos)
+ return {"class": POU_TYPES[pou_type],
+ "type": words[1],
+ "variables": vars,
+ "edit": True,
+ "debug": True}
+ else:
+ block_infos = self.GetBlockType(words[1], debug = debug)
+ if (block_infos is not None and
+ block_infos["type"] in ["program", "functionBlock"]):
+ for varname, vartype, varmodifier in block_infos["inputs"]:
+ vars.append({"name" : varname,
+ "type" : vartype,
+ "class" : ITEM_VAR_INPUT,
+ "edit": False,
+ "debug": True})
+ for varname, vartype, varmodifier in block_infos["outputs"]:
+ vars.append({"name" : varname,
+ "type" : vartype,
+ "class" : ITEM_VAR_OUTPUT,
+ "edit": False,
+ "debug": True})
+ return {"class": POU_TYPES[block_infos["type"]],
+ "type": None,
+ "variables": vars,
+ "edit": False,
+ "debug": False}
+ elif words[0] in ['C', 'R']:
+ if words[0] == 'C':
+ element_type = ITEM_CONFIGURATION
+ element = project.getconfiguration(words[1])
+ if element is not None:
+ for resource in element.getresource():
+ vars.append({"name": resource.getname(),
+ "type": None,
+ "class": ITEM_RESOURCE,
+ "edit": True,
+ "debug": False})
+ elif words[0] == 'R':
+ element_type = ITEM_RESOURCE
+ element = project.getconfigurationResource(words[1], words[2])
+ if element is not None:
+ for task in element.gettask():
+ for pou in task.getpouInstance():
+ vars.append({"name": pou.getname(),
+ "type": pou.gettypeName(),
+ "class": ITEM_PROGRAM,
+ "edit": True,
+ "debug": True})
+ for pou in element.getpouInstance():
+ vars.append({"name": pou.getname(),
+ "type": pou.gettypeName(),
+ "class": ITEM_PROGRAM,
+ "edit": True,
+ "debug": True})
+ if element is not None:
+ for varlist in element.getglobalVars():
+ for variable in varlist.getvariable():
+ var_infos = self.GetPouVariableInfos(project, variable, ITEM_VAR_GLOBAL, debug)
+ if var_infos is not None:
+ vars.append(var_infos)
+ return {"class": element_type,
+ "type": None,
+ "variables": vars,
+ "edit": True,
+ "debug": False}
+ return None
+
+ def RecursiveSearchPouInstances(self, project, pou_type, parent_path, varlists, debug = False):
+ instances = []
+ for varlist in varlists:
+ for variable in varlist.getvariable():
+ vartype_content = variable.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ var_path = "%s.%s" % (parent_path, variable.getname())
+ var_type = vartype_content["value"].getname()
+ if var_type == pou_type:
+ instances.append(var_path)
+ else:
+ pou = project.getpou(var_type)
+ if pou is not None:
+ instances.extend(
+ self.RecursiveSearchPouInstances(
+ project, pou_type, var_path,
+ [varlist for type, varlist in pou.getvars()],
+ debug))
+ return instances
+
+ def SearchPouInstances(self, tagname, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ words = tagname.split("::")
+ if words[0] == "P":
+ instances = []
+ for config in project.getconfigurations():
+ config_name = config.getname()
+ instances.extend(
+ self.RecursiveSearchPouInstances(
+ project, words[1], config_name,
+ config.getglobalVars(), debug))
+ for resource in config.getresource():
+ res_path = "%s.%s" % (config_name, resource.getname())
+ instances.extend(
+ self.RecursiveSearchPouInstances(
+ project, words[1], res_path,
+ resource.getglobalVars(), debug))
+ pou_instances = resource.getpouInstance()[:]
+ for task in resource.gettask():
+ pou_instances.extend(task.getpouInstance())
+ for pou_instance in pou_instances:
+ pou_path = "%s.%s" % (res_path, pou_instance.getname())
+ pou_type = pou_instance.gettypeName()
+ if pou_type == words[1]:
+ instances.append(pou_path)
+ pou = project.getpou(pou_type)
+ if pou is not None:
+ instances.extend(
+ self.RecursiveSearchPouInstances(
+ project, words[1], pou_path,
+ [varlist for type, varlist in pou.getvars()],
+ debug))
+ return instances
+ elif words[0] == 'C':
+ return [words[1]]
+ elif words[0] == 'R':
+ return ["%s.%s" % (words[1], words[2])]
+ return []
+
+ def RecursiveGetPouInstanceTagName(self, project, pou_type, parts):
+ pou = project.getpou(pou_type)
+ if pou is not None:
+ if len(parts) == 0:
+ return self.ComputePouName(pou_type)
+
+ for varlist_type, varlist in pou.getvars():
+ for variable in varlist.getvariable():
+ vartype_content = variable.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ return self.RecursiveGetPouInstanceTagName(
+ project,
+ vartype_content["value"].getname(),
+ parts[1:])
+ return None
+
+ def GetPouInstanceTagName(self, instance_path, debug = False):
+ parts = instance_path.split(".")
+ if len(parts) == 1:
+ return self.ComputeConfigurationName(parts[0])
+ elif len(parts) == 2:
+ return self.ComputeConfigurationResourceName(parts[0], parts[1])
+ else:
+ project = self.GetProject(debug)
+ for config in project.getconfigurations():
+ if config.getname() == parts[0]:
+ for resource in config.getresource():
+ if resource.getname() == parts[1]:
+ pou_instances = resource.getpouInstance()[:]
+ for task in resource.gettask():
+ pou_instances.extend(task.getpouInstance())
+ for pou_instance in pou_instances:
+ if pou_instance.getname() == parts[2]:
+ if len(parts) == 3:
+ return self.ComputePouName(
+ pou_instance.gettypeName())
+ else:
+ return self.RecursiveGetPouInstanceTagName(
+ project,
+ pou_instance.gettypeName(),
+ parts[3:])
+ return None
+
+ def GetInstanceInfos(self, instance_path, debug = False):
+ tagname = self.GetPouInstanceTagName(instance_path)
+ if tagname is not None:
+ return self.GetPouVariables(tagname, debug)
+ else:
+ pou_path, var_name = instance_path.rsplit(".", 1)
+ tagname = self.GetPouInstanceTagName(pou_path)
+ if tagname is not None:
+ pou_infos = self.GetPouVariables(tagname, debug)
+ for var_infos in pou_infos["variables"]:
+ if var_infos["name"] == var_name:
+ return var_infos
+ return None
+
+ # Return if data type given by name is used by another data type or pou
+ def DataTypeIsUsed(self, name, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return project.ElementIsUsed(name) or project.DataTypeIsDerived(name)
+ return False
+
+ # Return if pou given by name is used by another pou
+ def PouIsUsed(self, name, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return project.ElementIsUsed(name)
+ return False
+
+ # Return if pou given by name is directly or undirectly used by the reference pou
+ def PouIsUsedBy(self, name, reference, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ return project.ElementIsUsedBy(name, reference)
+ return False
+
+ def GenerateProgram(self, filepath=None):
+ errors = []
+ warnings = []
+ if self.Project is not None:
+ try:
+ self.ProgramChunks = GenerateCurrentProgram(self, self.Project, errors, warnings)
+ self.NextCompiledProject = self.Copy(self.Project)
+ program_text = "".join([item[0].decode("utf-8") for item in self.ProgramChunks])
+ if filepath is not None:
+ programfile = open(filepath, "w")
+ programfile.write(program_text.encode("utf-8"))
+ programfile.close()
+ self.ProgramFilePath = filepath
+ return program_text, errors, warnings
+ except PLCGenException, e:
+ errors.append(e.message)
+ else:
+ errors.append("No project opened")
+ return "", errors, warnings
+
+ def DebugAvailable(self):
+ return self.CurrentCompiledProject is not None
+
+ def ProgramTransferred(self):
+ if self.NextCompiledProject is None:
+ self.CurrentCompiledProject = self.NextCompiledProject
+ else:
+ self.CurrentCompiledProject = self.Copy(self.Project)
+
+ def GetChunkInfos(self, from_location, to_location):
+ row = self.ProgramOffset + 1
+ col = 1
+ infos = []
+ for chunk, chunk_infos in self.ProgramChunks:
+ lines = chunk.split("\n")
+ if len(lines) > 1:
+ next_row = row + len(lines) - 1
+ next_col = len(lines[-1]) + 1
+ else:
+ next_row = row
+ next_col = col + len(chunk)
+ if (next_row > from_location[0] or next_row == from_location[0] and next_col >= from_location[1]) and len(chunk_infos) > 0:
+ infos.append((chunk_infos, (row, col)))
+ if next_row == to_location[0] and next_col > to_location[1] or next_row > to_location[0]:
+ return infos
+ row, col = next_row, next_col
+ return infos
+
+#-------------------------------------------------------------------------------
+# Project Pous management functions
+#-------------------------------------------------------------------------------
+
+ # Add a Data Type to Project
+ def ProjectAddDataType(self, datatype_name=None):
+ if self.Project is not None:
+ if datatype_name is None:
+ datatype_name = self.GenerateNewName(None, None, "datatype%d")
+ # Add the datatype to project
+ self.Project.appenddataType(datatype_name)
+ self.BufferProject()
+ return self.ComputeDataTypeName(datatype_name)
+ return None
+
+ # Remove a Data Type from project
+ def ProjectRemoveDataType(self, datatype_name):
+ if self.Project is not None:
+ self.Project.removedataType(datatype_name)
+ self.BufferProject()
+
+ # Add a Pou to Project
+ def ProjectAddPou(self, pou_name, pou_type, body_type):
+ if self.Project is not None:
+ # Add the pou to project
+ self.Project.appendpou(pou_name, pou_type, body_type)
+ if pou_type == "function":
+ self.SetPouInterfaceReturnType(pou_name, "BOOL")
+ self.BufferProject()
+ return self.ComputePouName(pou_name)
+ return None
+
+ def ProjectChangePouType(self, name, pou_type):
+ if self.Project is not None:
+ pou = self.Project.getpou(name)
+ if pou is not None:
+ pou.setpouType(pou_type)
+ self.Project.RefreshCustomBlockTypes()
+ self.BufferProject()
+
+ def GetPouXml(self, pou_name):
+ if self.Project is not None:
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ return pou.generateXMLText('pou', 0)
+ return None
+
+ def PastePou(self, pou_type, pou_xml):
+ '''
+ Adds the POU defined by 'pou_xml' to the current project with type 'pou_type'
+ '''
+ try:
+ tree = minidom.parseString(pou_xml.encode("utf-8"))
+ root = tree.childNodes[0]
+ except:
+ return _("Couldn't paste non-POU object.")
+
+ if root.nodeName == "pou":
+ new_pou = plcopen.pous_pou()
+ new_pou.loadXMLTree(root)
+
+ name = new_pou.getname()
+
+ idx = 0
+ new_name = name
+ while self.Project.getpou(new_name):
+ # a POU with that name already exists.
+ # make a new name and test if a POU with that name exists.
+ # append an incrementing numeric suffix to the POU name.
+ idx += 1
+ new_name = "%s%d" % (name, idx)
+
+ # we've found a name that does not already exist, use it
+ new_pou.setname(new_name)
+
+ if pou_type is not None:
+ orig_type = new_pou.getpouType()
+
+ # prevent violations of POU content restrictions:
+ # function blocks cannot be pasted as functions,
+ # programs cannot be pasted as functions or function blocks
+ if orig_type == 'functionBlock' and pou_type == 'function' or \
+ orig_type == 'program' and pou_type in ['function', 'functionBlock']:
+ return _('''%s "%s" can't be pasted as a %s.''') % (orig_type, name, pou_type)
+
+ new_pou.setpouType(pou_type)
+
+ self.Project.insertpou(-1, new_pou)
+ self.BufferProject()
+
+ return self.ComputePouName(new_name),
+ else:
+ return _("Couldn't paste non-POU object.")
+
+ # Remove a Pou from project
+ def ProjectRemovePou(self, pou_name):
+ if self.Project is not None:
+ self.Project.removepou(pou_name)
+ self.BufferProject()
+
+ # Return the name of the configuration if only one exist
+ def GetProjectMainConfigurationName(self):
+ if self.Project is not None:
+ # Found the configuration corresponding to old name and change its name to new name
+ configurations = self.Project.getconfigurations()
+ if len(configurations) == 1:
+ return configurations[0].getname()
+ return None
+
+ # Add a configuration to Project
+ def ProjectAddConfiguration(self, config_name=None):
+ if self.Project is not None:
+ if config_name is None:
+ config_name = self.GenerateNewName(None, None, "configuration%d")
+ self.Project.addconfiguration(config_name)
+ self.BufferProject()
+ return self.ComputeConfigurationName(config_name)
+ return None
+
+ # Remove a configuration from project
+ def ProjectRemoveConfiguration(self, config_name):
+ if self.Project is not None:
+ self.Project.removeconfiguration(config_name)
+ self.BufferProject()
+
+ # Add a resource to a configuration of the Project
+ def ProjectAddConfigurationResource(self, config_name, resource_name=None):
+ if self.Project is not None:
+ if resource_name is None:
+ resource_name = self.GenerateNewName(None, None, "resource%d")
+ self.Project.addconfigurationResource(config_name, resource_name)
+ self.BufferProject()
+ return self.ComputeConfigurationResourceName(config_name, resource_name)
+ return None
+
+ # Remove a resource from a configuration of the project
+ def ProjectRemoveConfigurationResource(self, config_name, resource_name):
+ if self.Project is not None:
+ self.Project.removeconfigurationResource(config_name, resource_name)
+ self.BufferProject()
+
+ # Add a Transition to a Project Pou
+ def ProjectAddPouTransition(self, pou_name, transition_name, transition_type):
+ if self.Project is not None:
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ pou.addtransition(transition_name, transition_type)
+ self.BufferProject()
+ return self.ComputePouTransitionName(pou_name, transition_name)
+ return None
+
+ # Remove a Transition from a Project Pou
+ def ProjectRemovePouTransition(self, pou_name, transition_name):
+ # Search if the pou removed is currently opened
+ if self.Project is not None:
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ pou.removetransition(transition_name)
+ self.BufferProject()
+
+ # Add an Action to a Project Pou
+ def ProjectAddPouAction(self, pou_name, action_name, action_type):
+ if self.Project is not None:
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ pou.addaction(action_name, action_type)
+ self.BufferProject()
+ return self.ComputePouActionName(pou_name, action_name)
+ return None
+
+ # Remove an Action from a Project Pou
+ def ProjectRemovePouAction(self, pou_name, action_name):
+ # Search if the pou removed is currently opened
+ if self.Project is not None:
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ pou.removeaction(action_name)
+ self.BufferProject()
+
+ # Change the name of a pou
+ def ChangeDataTypeName(self, old_name, new_name):
+ if self.Project is not None:
+ # Found the pou corresponding to old name and change its name to new name
+ datatype = self.Project.getdataType(old_name)
+ if datatype is not None:
+ datatype.setname(new_name)
+ self.Project.updateElementName(old_name, new_name)
+ self.Project.RefreshElementUsingTree()
+ self.Project.RefreshDataTypeHierarchy()
+ self.BufferProject()
+
+ # Change the name of a pou
+ def ChangePouName(self, old_name, new_name):
+ if self.Project is not None:
+ # Found the pou corresponding to old name and change its name to new name
+ pou = self.Project.getpou(old_name)
+ if pou is not None:
+ pou.setname(new_name)
+ self.Project.updateElementName(old_name, new_name)
+ self.Project.RefreshElementUsingTree()
+ self.Project.RefreshCustomBlockTypes()
+ self.BufferProject()
+
+ # Change the name of a pou transition
+ def ChangePouTransitionName(self, pou_name, old_name, new_name):
+ if self.Project is not None:
+ # Found the pou transition corresponding to old name and change its name to new name
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ transition = pou.gettransition(old_name)
+ if transition is not None:
+ transition.setname(new_name)
+ pou.updateElementName(old_name, new_name)
+ self.BufferProject()
+
+ # Change the name of a pou action
+ def ChangePouActionName(self, pou_name, old_name, new_name):
+ if self.Project is not None:
+ # Found the pou action corresponding to old name and change its name to new name
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ action = pou.getaction(old_name)
+ if action is not None:
+ action.setname(new_name)
+ pou.updateElementName(old_name, new_name)
+ self.BufferProject()
+
+ # Change the name of a pou variable
+ def ChangePouVariableName(self, pou_name, old_name, new_name):
+ if self.Project is not None:
+ # Found the pou action corresponding to old name and change its name to new name
+ pou = self.Project.getpou(pou_name)
+ if pou is not None:
+ for type, varlist in pou.getvars():
+ for var in varlist.getvariable():
+ if var.getname() == old_name:
+ var.setname(new_name)
+ self.Project.RefreshCustomBlockTypes()
+ self.BufferProject()
+
+ # Change the name of a configuration
+ def ChangeConfigurationName(self, old_name, new_name):
+ if self.Project is not None:
+ # Found the configuration corresponding to old name and change its name to new name
+ configuration = self.Project.getconfiguration(old_name)
+ if configuration is not None:
+ configuration.setname(new_name)
+ self.BufferProject()
+
+ # Change the name of a configuration resource
+ def ChangeConfigurationResourceName(self, config_name, old_name, new_name):
+ if self.Project is not None:
+ # Found the resource corresponding to old name and change its name to new name
+ resource = self.Project.getconfigurationResource(config_name, old_name)
+ if resource is not None:
+ resource.setname(new_name)
+ self.BufferProject()
+
+ # Return the description of the pou given by its name
+ def GetPouDescription(self, name, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its type
+ pou = project.getpou(name)
+ if pou is not None:
+ return pou.getdescription()
+ return ""
+
+ # Return the description of the pou given by its name
+ def SetPouDescription(self, name, description, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its type
+ pou = project.getpou(name)
+ if pou is not None:
+ pou.setdescription(description)
+ project.RefreshCustomBlockTypes()
+ self.BufferProject()
+
+ # Return the type of the pou given by its name
+ def GetPouType(self, name, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its type
+ pou = project.getpou(name)
+ if pou is not None:
+ return pou.getpouType()
+ return None
+
+ # Return pous with SFC language
+ def GetSFCPous(self, debug = False):
+ list = []
+ project = self.GetProject(debug)
+ if project is not None:
+ for pou in 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, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its body language
+ pou = project.getpou(name)
+ if pou is not None:
+ return pou.getbodyType()
+ return None
+
+ # Return the actions of a pou
+ def GetPouTransitions(self, pou_name, debug = False):
+ transitions = []
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its transitions if SFC
+ pou = project.getpou(pou_name)
+ if pou is not None and pou.getbodyType() == "SFC":
+ for transition in pou.gettransitionList():
+ transitions.append(transition.getname())
+ return transitions
+
+ # Return the body language of the transition given by its name
+ def GetTransitionBodyType(self, pou_name, pou_transition, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name
+ pou = project.getpou(pou_name)
+ if pou is not None:
+ # Found the pou transition correponding to name and return its body language
+ transition = pou.gettransition(pou_transition)
+ if transition is not None:
+ return transition.getbodyType()
+ return None
+
+ # Return the actions of a pou
+ def GetPouActions(self, pou_name, debug = False):
+ actions = []
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its actions if SFC
+ pou = project.getpou(pou_name)
+ if pou.getbodyType() == "SFC":
+ for action in pou.getactionList():
+ actions.append(action.getname())
+ return actions
+
+ # Return the body language of the pou given by its name
+ def GetActionBodyType(self, pou_name, pou_action, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its body language
+ pou = project.getpou(pou_name)
+ if pou is not None:
+ action = pou.getaction(pou_action)
+ if action is not None:
+ return action.getbodyType()
+ return None
+
+ # Extract varlists from a list of vars
+ def ExtractVarLists(self, vars):
+ varlist_list = []
+ current_varlist = None
+ current_type = None
+ for var in vars:
+ next_type = (var["Class"],
+ var["Option"],
+ var["Location"] in ["", None] or
+ # When declaring globals, located
+ # and not located variables are
+ # in the same declaration block
+ var["Class"] == "Global")
+ if current_type != next_type:
+ current_type = next_type
+ infos = VAR_CLASS_INFOS.get(var["Class"], None)
+ if infos is not None:
+ current_varlist = infos[0]()
+ else:
+ current_varlist = plcopen.varList()
+ varlist_list.append((var["Class"], current_varlist))
+ if var["Option"] == "Constant":
+ current_varlist.setconstant(True)
+ elif var["Option"] == "Retain":
+ current_varlist.setretain(True)
+ elif var["Option"] == "Non-Retain":
+ current_varlist.setnonretain(True)
+ # Create variable and change its properties
+ tempvar = plcopen.varListPlain_variable()
+ tempvar.setname(var["Name"])
+
+ var_type = plcopen.dataType()
+ if isinstance(var["Type"], TupleType):
+ if var["Type"][0] == "array":
+ array_type, base_type_name, dimensions = var["Type"]
+ array = plcopen.derivedTypes_array()
+ for i, dimension in enumerate(dimensions):
+ dimension_range = plcopen.rangeSigned()
+ dimension_range.setlower(dimension[0])
+ dimension_range.setupper(dimension[1])
+ if i == 0:
+ array.setdimension([dimension_range])
+ else:
+ array.appenddimension(dimension_range)
+ if base_type_name in self.GetBaseTypes():
+ if base_type_name == "STRING":
+ array.baseType.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif base_type_name == "WSTRING":
+ array.baseType.setcontent({"name" : "wstring", "value" : plcopen.wstring()})
+ else:
+ array.baseType.setcontent({"name" : base_type_name, "value" : None})
+ else:
+ derived_datatype = plcopen.derivedTypes_derived()
+ derived_datatype.setname(base_type_name)
+ array.baseType.setcontent({"name" : "derived", "value" : derived_datatype})
+ var_type.setcontent({"name" : "array", "value" : array})
+ elif var["Type"] in self.GetBaseTypes():
+ if var["Type"] == "STRING":
+ var_type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif var["Type"] == "WSTRING":
+ var_type.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
+ else:
+ var_type.setcontent({"name" : var["Type"], "value" : None})
+ else:
+ derived_type = plcopen.derivedTypes_derived()
+ derived_type.setname(var["Type"])
+ var_type.setcontent({"name" : "derived", "value" : derived_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"])
+ else:
+ tempvar.setaddress(None)
+ if var['Documentation'] != "":
+ ft = plcopen.formattedText()
+ ft.settext(var['Documentation'])
+ tempvar.setdocumentation(ft)
+
+ # Add variable to varList
+ current_varlist.appendvariable(tempvar)
+ return varlist_list
+
+ def GetVariableDictionary(self, varlist, var):
+ '''
+ convert a PLC variable to the dictionary representation
+ returned by Get*Vars)
+ '''
+
+ tempvar = {"Name": var.getname()}
+
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ tempvar["Type"] = vartype_content["value"].getname()
+ elif vartype_content["name"] == "array":
+ dimensions = []
+ for dimension in vartype_content["value"].getdimension():
+ dimensions.append((dimension.getlower(), dimension.getupper()))
+ base_type = vartype_content["value"].baseType.getcontent()
+ if base_type["value"] is None:
+ base_type_name = base_type["name"]
+ else:
+ base_type_name = base_type["value"].getname()
+ tempvar["Type"] = ("array", base_type_name, dimensions)
+ elif vartype_content["name"] in ["string", "wstring"]:
+ tempvar["Type"] = vartype_content["name"].upper()
+ else:
+ tempvar["Type"] = vartype_content["name"]
+
+ tempvar["Edit"] = True
+
+ initial = var.getinitialValue()
+ if initial:
+ tempvar["Initial Value"] = initial.getvalue()
+ else:
+ tempvar["Initial Value"] = ""
+
+ address = var.getaddress()
+ if address:
+ tempvar["Location"] = address
+ else:
+ tempvar["Location"] = ""
+
+ if varlist.getconstant():
+ tempvar["Option"] = "Constant"
+ elif varlist.getretain():
+ tempvar["Option"] = "Retain"
+ elif varlist.getnonretain():
+ tempvar["Option"] = "Non-Retain"
+ else:
+ tempvar["Option"] = ""
+
+ doc = var.getdocumentation()
+ if doc:
+ tempvar["Documentation"] = doc.gettext()
+ else:
+ tempvar["Documentation"] = ""
+
+ return tempvar
+
+ # Replace the configuration globalvars by those given
+ def SetConfigurationGlobalVars(self, name, vars):
+ if self.Project is not None:
+ # Found the configuration corresponding to name
+ configuration = self.Project.getconfiguration(name)
+ if configuration is not None:
+ # Set configuration global vars
+ configuration.setglobalVars([])
+ for vartype, varlist in self.ExtractVarLists(vars):
+ configuration.globalVars.append(varlist)
+
+ # Return the configuration globalvars
+ def GetConfigurationGlobalVars(self, name, debug = False):
+ vars = []
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the configuration corresponding to name
+ configuration = project.getconfiguration(name)
+ if configuration is not None:
+ # Extract variables from every varLists
+ for varlist in configuration.getglobalVars():
+ for var in varlist.getvariable():
+ tempvar = self.GetVariableDictionary(varlist, var)
+ tempvar["Class"] = "Global"
+ vars.append(tempvar)
+ return vars
+
+ # Replace the resource globalvars by those given
+ def SetConfigurationResourceGlobalVars(self, config_name, name, vars):
+ if self.Project is not None:
+ # Found the resource corresponding to name
+ resource = self.Project.getconfigurationResource(config_name, name)
+ # Set resource global vars
+ if resource is not None:
+ resource.setglobalVars([])
+ for vartype, varlist in self.ExtractVarLists(vars):
+ resource.globalVars.append(varlist)
+
+ # Return the resource globalvars
+ def GetConfigurationResourceGlobalVars(self, config_name, name, debug = False):
+ vars = []
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the resource corresponding to name
+ resource = project.getconfigurationResource(config_name, name)
+ if resource:
+ # Extract variables from every varLists
+ for varlist in resource.getglobalVars():
+ for var in varlist.getvariable():
+ tempvar = self.GetVariableDictionary(varlist, var)
+ tempvar["Class"] = "Global"
+ vars.append(tempvar)
+ return vars
+
+ # Recursively generate element name tree for a structured variable
+ def GenerateVarTree(self, typename, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ blocktype = self.GetBlockType(typename, debug = debug)
+ if blocktype is not None:
+ tree = []
+ en = False
+ eno = False
+ for var_name, var_type, var_modifier in blocktype["inputs"] + blocktype["outputs"]:
+ en |= var_name.upper() == "EN"
+ eno |= var_name.upper() == "ENO"
+ tree.append((var_name, var_type, self.GenerateVarTree(var_type, debug)))
+ if not eno:
+ tree.insert(0, ("ENO", "BOOL", ([], [])))
+ if not en:
+ tree.insert(0, ("EN", "BOOL", ([], [])))
+ return tree, []
+ datatype = project.getdataType(typename)
+ if datatype is None:
+ datatype = self.GetConfNodeDataType(typename)
+ if datatype is not None:
+ tree = []
+ basetype_content = datatype.baseType.getcontent()
+ if basetype_content["name"] == "derived":
+ return self.GenerateVarTree(basetype_content["value"].getname())
+ elif basetype_content["name"] == "array":
+ dimensions = []
+ base_type = basetype_content["value"].baseType.getcontent()
+ if base_type["name"] == "derived":
+ tree = self.GenerateVarTree(base_type["value"].getname())
+ if len(tree[1]) == 0:
+ tree = tree[0]
+ for dimension in basetype_content["value"].getdimension():
+ dimensions.append((dimension.getlower(), dimension.getupper()))
+ return tree, dimensions
+ elif basetype_content["name"] == "struct":
+ for element in basetype_content["value"].getvariable():
+ element_type = element.type.getcontent()
+ if element_type["name"] == "derived":
+ tree.append((element.getname(), element_type["value"].getname(), self.GenerateVarTree(element_type["value"].getname())))
+ else:
+ tree.append((element.getname(), element_type["name"], ([], [])))
+ return tree, []
+ return [], []
+
+ # Return the interface for the given pou
+ def GetPouInterfaceVars(self, pou, debug = False):
+ vars = []
+ # Verify that the pou has an interface
+ if pou.interface is not None:
+ # Extract variables from every varLists
+ for type, varlist in pou.getvars():
+ for var in varlist.getvariable():
+ tempvar = self.GetVariableDictionary(varlist, var)
+
+ tempvar["Class"] = type
+ tempvar["Tree"] = ([], [])
+
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ tempvar["Edit"] = not pou.hasblock(tempvar["Name"])
+ tempvar["Tree"] = self.GenerateVarTree(tempvar["Type"], debug)
+
+ vars.append(tempvar)
+ return vars
+
+ # Replace the Pou interface by the one given
+ def SetPouInterfaceVars(self, name, vars):
+ if self.Project is not None:
+ # Found the pou corresponding to name and add interface if there isn't one yet
+ pou = self.Project.getpou(name)
+ if pou is not None:
+ if pou.interface is None:
+ pou.interface = plcopen.pou_interface()
+ # Set Pou interface
+ pou.setvars(self.ExtractVarLists(vars))
+ self.Project.RefreshElementUsingTree()
+ self.Project.RefreshCustomBlockTypes()
+
+ # Replace the return type of the pou given by its name (only for functions)
+ def SetPouInterfaceReturnType(self, name, type):
+ if self.Project is not None:
+ pou = self.Project.getpou(name)
+ if pou is not None:
+ if pou.interface is None:
+ 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
+ if type in self.GetBaseTypes():
+ if type == "STRING":
+ return_type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif type == "WSTRING":
+ return_type.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
+ else:
+ return_type.setcontent({"name" : type, "value" : None})
+ else:
+ derived_type = plcopen.derivedTypes_derived()
+ derived_type.setname(type)
+ return_type.setcontent({"name" : "derived", "value" : derived_type})
+ self.Project.RefreshElementUsingTree()
+ self.Project.RefreshCustomBlockTypes()
+
+ def UpdateProjectUsedPous(self, old_name, new_name):
+ if self.Project:
+ self.Project.updateElementName(old_name, new_name)
+
+ def UpdateEditedElementUsedVariable(self, tagname, old_name, new_name):
+ pou = self.GetEditedElement(tagname)
+ if pou:
+ pou.updateElementName(old_name, new_name)
+
+ # Return the return type of the pou given by its name
+ def GetPouInterfaceReturnTypeByName(self, name):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return the return type
+ pou = project.getpou(name)
+ if pou is not None:
+ return self.GetPouInterfaceReturnType(pou)
+ return False
+
+ # Return the return type of the given pou
+ def GetPouInterfaceReturnType(self, pou):
+ # Verify that the pou has an interface
+ if pou.interface is not None:
+ # Return the return type if there is one
+ return_type = pou.interface.getreturnType()
+ if return_type:
+ returntype_content = return_type.getcontent()
+ if returntype_content["name"] == "derived":
+ return returntype_content["value"].getname()
+ elif returntype_content["name"] in ["string", "wstring"]:
+ return returntype_content["name"].upper()
+ else:
+ return returntype_content["name"]
+ return None
+
+ # Function that add a new confnode to the confnode list
+ def AddConfNodeTypesList(self, typeslist):
+ self.ConfNodeTypes.extend(typeslist)
+
+ # Function that clear the confnode list
+ def ClearConfNodeTypes(self):
+ for i in xrange(len(self.ConfNodeTypes)):
+ self.ConfNodeTypes.pop(0)
+
+ def GetConfNodeBlockTypes(self):
+ return [{"name": _("%s POUs") % confnodetypes["name"],
+ "list": confnodetypes["types"].GetCustomBlockTypes()}
+ for confnodetypes in self.ConfNodeTypes]
+
+ def GetConfNodeDataTypes(self, exclude = ""):
+ return [{"name": _("%s Data Types") % confnodetypes["name"],
+ "list": [datatype["name"] for datatype in confnodetypes["types"].GetCustomDataTypes(exclude)]}
+ for confnodetypes in self.ConfNodeTypes]
+
+ def GetConfNodeDataType(self, type):
+ for confnodetype in self.ConfNodeTypes:
+ datatype = confnodetype["types"].getdataType(type)
+ if datatype is not None:
+ return datatype
+ return None
+
+ def GetVariableLocationTree(self):
+ return []
+
+ # Function that returns the block definition associated to the block type given
+ def GetBlockType(self, type, inputs = None, debug = False):
+ result_blocktype = None
+ for category in BlockTypes + self.GetConfNodeBlockTypes():
+ for blocktype in category["list"]:
+ if blocktype["name"] == type:
+ if inputs is not None and inputs != "undefined":
+ block_inputs = tuple([var_type for name, var_type, modifier in blocktype["inputs"]])
+ if reduce(lambda x, y: x and y, map(lambda x: x[0] == "ANY" or self.IsOfType(*x), zip(inputs, block_inputs)), True):
+ return blocktype
+ else:
+ if result_blocktype is not None:
+ if inputs == "undefined":
+ return None
+ else:
+ result_blocktype["inputs"] = [(i[0], "ANY", i[2]) for i in result_blocktype["inputs"]]
+ result_blocktype["outputs"] = [(o[0], "ANY", o[2]) for o in result_blocktype["outputs"]]
+ return result_blocktype
+ result_blocktype = blocktype
+ if result_blocktype is not None:
+ return result_blocktype
+ project = self.GetProject(debug)
+ if project is not None:
+ return project.GetCustomBlockType(type, inputs)
+ return None
+
+ # Return Block types checking for recursion
+ def GetBlockTypes(self, tagname = "", debug = False):
+ type = None
+ words = tagname.split("::")
+ if self.Project:
+ name = ""
+ if words[0] in ["P","T","A"]:
+ name = words[1]
+ type = self.GetPouType(name, debug)
+ if type == "function" or words[0] == "T":
+ blocktypes = []
+ for category in BlockTypes + self.GetConfNodeBlockTypes():
+ cat = {"name" : category["name"], "list" : []}
+ for block in category["list"]:
+ if block["type"] == "function":
+ cat["list"].append(block)
+ if len(cat["list"]) > 0:
+ blocktypes.append(cat)
+ else:
+ blocktypes = [category for category in BlockTypes + self.GetConfNodeBlockTypes()]
+ project = self.GetProject(debug)
+ if project is not None:
+ blocktypes.append({"name" : USER_DEFINED_POUS, "list": project.GetCustomBlockTypes(name, type == "function" or words[0] == "T")})
+ return blocktypes
+
+ # Return Function Block types checking for recursion
+ def GetFunctionBlockTypes(self, tagname = "", debug = False):
+ blocktypes = []
+ for category in BlockTypes + self.GetConfNodeBlockTypes():
+ for block in category["list"]:
+ if block["type"] == "functionBlock":
+ blocktypes.append(block["name"])
+ project = self.GetProject(debug)
+ if project is not None:
+ name = ""
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ name = words[1]
+ blocktypes.extend(project.GetCustomFunctionBlockTypes(name))
+ return blocktypes
+
+ # Return Block types checking for recursion
+ def GetBlockResource(self, debug = False):
+ blocktypes = []
+ for category in BlockTypes[:-1]:
+ for blocktype in category["list"]:
+ if blocktype["type"] == "program":
+ blocktypes.append(blocktype["name"])
+ project = self.GetProject(debug)
+ if project is not None:
+ blocktypes.extend(project.GetCustomBlockResource())
+ return blocktypes
+
+ # Return Data Types checking for recursion
+ def GetDataTypes(self, tagname = "", basetypes = True, only_locatables = False, debug = False):
+ if basetypes:
+ datatypes = self.GetBaseTypes()
+ else:
+ datatypes = []
+ project = self.GetProject(debug)
+ if project is not None:
+ name = ""
+ words = tagname.split("::")
+ if words[0] in ["D"]:
+ name = words[1]
+ datatypes.extend([datatype["name"] for datatype in project.GetCustomDataTypes(name, only_locatables)])
+ return datatypes
+
+ # Return Base Type of given possible derived type
+ def GetBaseType(self, type, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ result = project.GetBaseType(type)
+ if result is not None:
+ return result
+ for confnodetype in self.ConfNodeTypes:
+ result = confnodetype["types"].GetBaseType(type)
+ if result is not None:
+ return result
+ return None
+
+ def GetBaseTypes(self):
+ '''
+ return the list of datatypes defined in IEC 61131-3.
+ TypeHierarchy_list has a rough order to it (e.g. SINT, INT, DINT, ...),
+ which makes it easy for a user to find a type in a menu.
+ '''
+ return [x for x,y in TypeHierarchy_list if not x.startswith("ANY")]
+
+ def IsOfType(self, type, reference, debug = False):
+ if reference is None:
+ return True
+ elif type == reference:
+ return True
+ elif type in TypeHierarchy:
+ return self.IsOfType(TypeHierarchy[type], reference)
+ else:
+ project = self.GetProject(debug)
+ if project is not None and project.IsOfType(type, reference):
+ return True
+ for confnodetype in self.ConfNodeTypes:
+ if confnodetype["types"].IsOfType(type, reference):
+ return True
+ return False
+
+ def IsEndType(self, type):
+ if type is not None:
+ return not type.startswith("ANY")
+ return True
+
+ def IsLocatableType(self, type, debug = False):
+ if isinstance(type, TupleType):
+ return False
+ project = self.GetProject(debug)
+ if project is not None:
+ datatype = project.getdataType(type)
+ if datatype is None:
+ datatype = self.GetConfNodeDataType(type)
+ if datatype is not None:
+ return project.IsLocatableType(datatype)
+ return True
+
+ def IsEnumeratedType(self, type, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ datatype = project.getdataType(type)
+ if datatype is None:
+ datatype = self.GetConfNodeDataType(type)
+ if datatype is not None:
+ basetype_content = datatype.baseType.getcontent()
+ return basetype_content["name"] == "enum"
+ return False
+
+ def GetDataTypeRange(self, type, debug = False):
+ if type in DataTypeRange:
+ return DataTypeRange[type]
+ else:
+ project = self.GetProject(debug)
+ if project is not None:
+ result = project.GetDataTypeRange(type)
+ if result is not None:
+ return result
+ for confnodetype in self.ConfNodeTypes:
+ result = confnodetype["types"].GetDataTypeRange(type)
+ if result is not None:
+ return result
+ return None
+
+ # Return Subrange types
+ def GetSubrangeBaseTypes(self, exclude, debug = False):
+ subrange_basetypes = []
+ project = self.GetProject(debug)
+ if project is not None:
+ subrange_basetypes.extend(project.GetSubrangeBaseTypes(exclude))
+ for confnodetype in self.ConfNodeTypes:
+ subrange_basetypes.extend(confnodetype["types"].GetSubrangeBaseTypes(exclude))
+ return DataTypeRange.keys() + subrange_basetypes
+
+ # Return Enumerated Values
+ def GetEnumeratedDataValues(self, type = None, debug = False):
+ values = []
+ project = self.GetProject(debug)
+ if project is not None:
+ values.extend(project.GetEnumeratedDataTypeValues(type))
+ if type is None and len(values) > 0:
+ return values
+ for confnodetype in self.ConfNodeTypes:
+ values.extend(confnodetype["types"].GetEnumeratedDataTypeValues(type))
+ if type is None and len(values) > 0:
+ return values
+ return values
+
+#-------------------------------------------------------------------------------
+# Project Element tag name computation functions
+#-------------------------------------------------------------------------------
+
+ # Compute a data type name
+ def ComputeDataTypeName(self, datatype):
+ return "D::%s" % datatype
+
+ # Compute a pou name
+ def ComputePouName(self, pou):
+ return "P::%s" % pou
+
+ # 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 ComputeConfigurationName(self, config):
+ return "C::%s" % config
+
+ # Compute a pou name
+ def ComputeConfigurationResourceName(self, config, resource):
+ return "R::%s::%s" % (config, resource)
+
+ def GetElementType(self, tagname):
+ words = tagname.split("::")
+ return {"D" : ITEM_DATATYPE, "P" : ITEM_POU,
+ "T" : ITEM_TRANSITION, "A" : ITEM_ACTION,
+ "C" : ITEM_CONFIGURATION, "R" : ITEM_RESOURCE}[words[0]]
+
+#-------------------------------------------------------------------------------
+# Project opened Data types management functions
+#-------------------------------------------------------------------------------
+
+ # Return the data type informations
+ def GetDataTypeInfos(self, tagname, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ words = tagname.split("::")
+ if words[0] == "D":
+ infos = {}
+ datatype = project.getdataType(words[1])
+ basetype_content = datatype.baseType.getcontent()
+ if basetype_content["value"] is None or basetype_content["name"] in ["string", "wstring"]:
+ infos["type"] = "Directly"
+ infos["base_type"] = basetype_content["name"].upper()
+ elif basetype_content["name"] == "derived":
+ infos["type"] = "Directly"
+ infos["base_type"] = basetype_content["value"].getname()
+ elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned"]:
+ infos["type"] = "Subrange"
+ infos["min"] = basetype_content["value"].range.getlower()
+ infos["max"] = basetype_content["value"].range.getupper()
+ base_type = basetype_content["value"].baseType.getcontent()
+ if base_type["value"] is None:
+ infos["base_type"] = base_type["name"]
+ else:
+ infos["base_type"] = base_type["value"].getname()
+ elif basetype_content["name"] == "enum":
+ infos["type"] = "Enumerated"
+ infos["values"] = []
+ for value in basetype_content["value"].values.getvalue():
+ infos["values"].append(value.getname())
+ elif basetype_content["name"] == "array":
+ infos["type"] = "Array"
+ infos["dimensions"] = []
+ for dimension in basetype_content["value"].getdimension():
+ infos["dimensions"].append((dimension.getlower(), dimension.getupper()))
+ base_type = basetype_content["value"].baseType.getcontent()
+ if base_type["value"] is None or base_type["name"] in ["string", "wstring"]:
+ infos["base_type"] = base_type["name"].upper()
+ else:
+ infos["base_type"] = base_type["value"].getname()
+ elif basetype_content["name"] == "struct":
+ infos["type"] = "Structure"
+ infos["elements"] = []
+ for element in basetype_content["value"].getvariable():
+ element_infos = {}
+ element_infos["Name"] = element.getname()
+ element_type = element.type.getcontent()
+ if element_type["value"] is None or element_type["name"] in ["string", "wstring"]:
+ element_infos["Type"] = element_type["name"].upper()
+ else:
+ element_infos["Type"] = element_type["value"].getname()
+ if element.initialValue is not None:
+ element_infos["Initial Value"] = str(element.initialValue.getvalue())
+ else:
+ element_infos["Initial Value"] = ""
+ infos["elements"].append(element_infos)
+ if datatype.initialValue is not None:
+ infos["initial"] = str(datatype.initialValue.getvalue())
+ else:
+ infos["initial"] = ""
+ return infos
+ return None
+
+ # Change the data type informations
+ def SetDataTypeInfos(self, tagname, infos):
+ words = tagname.split("::")
+ if self.Project is not None and words[0] == "D":
+ datatype = self.Project.getdataType(words[1])
+ if infos["type"] == "Directly":
+ if infos["base_type"] in self.GetBaseTypes():
+ if infos["base_type"] == "STRING":
+ datatype.baseType.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif infos["base_type"] == "WSTRING":
+ datatype.baseType.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
+ else:
+ datatype.baseType.setcontent({"name" : infos["base_type"], "value" : None})
+ else:
+ derived_datatype = plcopen.derivedTypes_derived()
+ derived_datatype.setname(infos["base_type"])
+ datatype.baseType.setcontent({"name" : "derived", "value" : derived_datatype})
+ elif infos["type"] == "Subrange":
+ if infos["base_type"] in GetSubTypes("ANY_UINT"):
+ subrange = plcopen.derivedTypes_subrangeUnsigned()
+ datatype.baseType.setcontent({"name" : "subrangeUnsigned", "value" : subrange})
+ else:
+ subrange = plcopen.derivedTypes_subrangeSigned()
+ datatype.baseType.setcontent({"name" : "subrangeSigned", "value" : subrange})
+ subrange.range.setlower(infos["min"])
+ subrange.range.setupper(infos["max"])
+ if infos["base_type"] in self.GetBaseTypes():
+ subrange.baseType.setcontent({"name" : infos["base_type"], "value" : None})
+ else:
+ derived_datatype = plcopen.derivedTypes_derived()
+ derived_datatype.setname(infos["base_type"])
+ subrange.baseType.setcontent({"name" : "derived", "value" : derived_datatype})
+ elif infos["type"] == "Enumerated":
+ enumerated = plcopen.derivedTypes_enum()
+ for i, enum_value in enumerate(infos["values"]):
+ value = plcopen.values_value()
+ value.setname(enum_value)
+ if i == 0:
+ enumerated.values.setvalue([value])
+ else:
+ enumerated.values.appendvalue(value)
+ datatype.baseType.setcontent({"name" : "enum", "value" : enumerated})
+ elif infos["type"] == "Array":
+ array = plcopen.derivedTypes_array()
+ for i, dimension in enumerate(infos["dimensions"]):
+ dimension_range = plcopen.rangeSigned()
+ dimension_range.setlower(dimension[0])
+ dimension_range.setupper(dimension[1])
+ if i == 0:
+ array.setdimension([dimension_range])
+ else:
+ array.appenddimension(dimension_range)
+ if infos["base_type"] in self.GetBaseTypes():
+ if infos["base_type"] == "STRING":
+ array.baseType.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif infos["base_type"] == "WSTRING":
+ array.baseType.setcontent({"name" : "wstring", "value" : plcopen.wstring()})
+ else:
+ array.baseType.setcontent({"name" : infos["base_type"], "value" : None})
+ else:
+ derived_datatype = plcopen.derivedTypes_derived()
+ derived_datatype.setname(infos["base_type"])
+ array.baseType.setcontent({"name" : "derived", "value" : derived_datatype})
+ datatype.baseType.setcontent({"name" : "array", "value" : array})
+ elif infos["type"] == "Structure":
+ struct = plcopen.varListPlain()
+ for i, element_infos in enumerate(infos["elements"]):
+ element = plcopen.varListPlain_variable()
+ element.setname(element_infos["Name"])
+ if element_infos["Type"] in self.GetBaseTypes():
+ if element_infos["Type"] == "STRING":
+ element.type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+ elif element_infos["Type"] == "WSTRING":
+ element.type.setcontent({"name" : "wstring", "value" : plcopen.wstring()})
+ else:
+ element.type.setcontent({"name" : element_infos["Type"], "value" : None})
+ else:
+ derived_datatype = plcopen.derivedTypes_derived()
+ derived_datatype.setname(element_infos["Type"])
+ element.type.setcontent({"name" : "derived", "value" : derived_datatype})
+ if element_infos["Initial Value"] != "":
+ value = plcopen.value()
+ value.setvalue(element_infos["Initial Value"])
+ element.setinitialValue(value)
+ if i == 0:
+ struct.setvariable([element])
+ else:
+ struct.appendvariable(element)
+ datatype.baseType.setcontent({"name" : "struct", "value" : struct})
+ if infos["initial"] != "":
+ if datatype.initialValue is None:
+ datatype.initialValue = plcopen.value()
+ datatype.initialValue.setvalue(infos["initial"])
+ else:
+ datatype.initialValue = None
+ self.Project.RefreshDataTypeHierarchy()
+ self.Project.RefreshElementUsingTree()
+ self.BufferProject()
+
+#-------------------------------------------------------------------------------
+# Project opened Pous management functions
+#-------------------------------------------------------------------------------
+
+ # Return edited element
+ def GetEditedElement(self, tagname, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ words = tagname.split("::")
+ if words[0] == "D":
+ return project.getdataType(words[1])
+ elif words[0] == "P":
+ return project.getpou(words[1])
+ elif words[0] in ['T', 'A']:
+ pou = project.getpou(words[1])
+ if pou is not None:
+ if words[0] == 'T':
+ return pou.gettransition(words[2])
+ elif words[0] == 'A':
+ return pou.getaction(words[2])
+ elif words[0] == 'C':
+ return project.getconfiguration(words[1])
+ elif words[0] == 'R':
+ return project.getconfigurationResource(words[1], words[2])
+ return None
+
+ # Return edited element name
+ def GetEditedElementName(self, tagname):
+ words = tagname.split("::")
+ if words[0] in ["P","C","D"]:
+ return words[1]
+ else:
+ return words[2]
+ return None
+
+ # Return edited element name and type
+ def GetEditedElementType(self, tagname, debug = False):
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ return words[1], self.GetPouType(words[1], debug)
+ return None, None
+
+ # Return language in which edited element is written
+ def GetEditedElementBodyType(self, tagname, debug = False):
+ words = tagname.split("::")
+ if words[0] == "P":
+ return self.GetPouBodyType(words[1], debug)
+ elif words[0] == 'T':
+ return self.GetTransitionBodyType(words[1], words[2], debug)
+ elif words[0] == 'A':
+ return self.GetActionBodyType(words[1], words[2], debug)
+ return None
+
+ # Return the edited element variables
+ def GetEditedElementInterfaceVars(self, tagname, debug = False):
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ project = self.GetProject(debug)
+ if project is not None:
+ pou = project.getpou(words[1])
+ if pou is not None:
+ return self.GetPouInterfaceVars(pou, debug)
+ return []
+
+ # Return the edited element return type
+ def GetEditedElementInterfaceReturnType(self, tagname, debug = False):
+ words = tagname.split("::")
+ if words[0] == "P":
+ project = self.GetProject(debug)
+ if project is not None:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ return self.GetPouInterfaceReturnType(pou)
+ elif words[0] == 'T':
+ return "BOOL"
+ return None
+
+ # Change the edited element text
+ def SetEditedElementText(self, tagname, text):
+ if self.Project is not None:
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ element.settext(text)
+ self.Project.RefreshElementUsingTree()
+
+ # Return the edited element text
+ def GetEditedElementText(self, tagname, debug = False):
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ return element.gettext()
+ return ""
+
+ # Return the edited element transitions
+ def GetEditedElementTransitions(self, tagname, debug = False):
+ pou = self.GetEditedElement(tagname, debug)
+ if pou is not None and pou.getbodyType() == "SFC":
+ transitions = []
+ for transition in pou.gettransitionList():
+ transitions.append(transition.getname())
+ return transitions
+ return []
+
+ # Return edited element transitions
+ def GetEditedElementActions(self, tagname, debug = False):
+ pou = self.GetEditedElement(tagname, debug)
+ if pou is not None and pou.getbodyType() == "SFC":
+ actions = []
+ for action in pou.getactionList():
+ actions.append(action.getname())
+ return actions
+ return []
+
+ # Return the names of the pou elements
+ def GetEditedElementVariables(self, tagname, debug = False):
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ return self.GetProjectPouVariables(words[1], debug)
+ return []
+
+ def GetEditedElementCopy(self, tagname, debug = False):
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ name = element.__class__.__name__
+ return element.generateXMLText(name.split("_")[-1], 0)
+ return ""
+
+ def GetEditedElementInstancesCopy(self, tagname, blocks_id = None, wires = None, debug = False):
+ element = self.GetEditedElement(tagname, debug)
+ text = ""
+ if element is not None:
+ wires = dict([(wire, True) for wire in wires if wire[0] in blocks_id and wire[1] in blocks_id])
+ for id in blocks_id:
+ instance = element.getinstance(id)
+ if instance is not None:
+ instance_copy = self.Copy(instance)
+ instance_copy.filterConnections(wires)
+ name = instance_copy.__class__.__name__
+ text += instance_copy.generateXMLText(name.split("_")[-1], 0)
+ return text
+
+ def GenerateNewName(self, tagname, name, format, exclude={}, debug=False):
+ names = exclude.copy()
+ if tagname is not None:
+ names.update(dict([(varname.upper(), True) for varname in self.GetEditedElementVariables(tagname, debug)]))
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ for instance in element.getinstances():
+ if isinstance(instance, (plcopen.sfcObjects_step, plcopen.commonObjects_connector, plcopen.commonObjects_continuation)):
+ names[instance.getname().upper()] = True
+ else:
+ project = self.GetProject(debug)
+ if project is not None:
+ for datatype in project.getdataTypes():
+ names[datatype.getname().upper()] = True
+ for pou in project.getpous():
+ names[pou.getname().upper()] = True
+ for var in self.GetPouInterfaceVars(pou, debug):
+ names[var["Name"].upper()] = True
+ for transition in pou.gettransitionList():
+ names[transition.getname().upper()] = True
+ for action in pou.getactionList():
+ names[action.getname().upper()] = True
+ for config in project.getconfigurations():
+ names[config.getname().upper()] = True
+ for resource in config.getresource():
+ names[resource.getname().upper()] = True
+
+ i = 0
+ while name is None or names.get(name.upper(), False):
+ name = (format%i)
+ i += 1
+ return name
+
+ CheckPasteCompatibility = {"SFC": lambda name: True,
+ "LD": lambda name: not name.startswith("sfcObjects"),
+ "FBD": lambda name: name.startswith("fbdObjects") or name.startswith("commonObjects")}
+
+ def PasteEditedElementInstances(self, tagname, text, new_pos, middle=False, debug=False):
+ element = self.GetEditedElement(tagname, debug)
+ element_name, element_type = self.GetEditedElementType(tagname, debug)
+ if element is not None:
+ bodytype = element.getbodyType()
+
+ # Get edited element type scaling
+ scaling = None
+ project = self.GetProject(debug)
+ if project is not None:
+ properties = project.getcontentHeader()
+ scaling = properties["scaling"][bodytype]
+
+ # Get ids already by all the instances in edited element
+ used_id = dict([(instance.getlocalId(), True) for instance in element.getinstances()])
+ new_id = {}
+
+ text = "<paste>%s</paste>"%text
+
+ try:
+ tree = minidom.parseString(text)
+ except:
+ return _("Invalid plcopen element(s)!!!")
+ instances = []
+ exclude = {}
+ for root in tree.childNodes:
+ if root.nodeType == tree.ELEMENT_NODE and root.nodeName == "paste":
+ for child in root.childNodes:
+ if child.nodeType == tree.ELEMENT_NODE:
+ if not child.nodeName in plcopen.ElementNameToClass:
+ return _("\"%s\" element can't be pasted here!!!")%child.nodeName
+
+ classname = plcopen.ElementNameToClass[child.nodeName]
+ if not self.CheckPasteCompatibility[bodytype](classname):
+ return _("\"%s\" element can't be pasted here!!!")%child.nodeName
+
+ classobj = getattr(plcopen, classname, None)
+ if classobj is not None:
+ instance = classobj()
+ instance.loadXMLTree(child)
+ if child.nodeName == "block":
+ blockname = instance.getinstanceName()
+ if blockname is not None:
+ blocktype = instance.gettypeName()
+ if element_type == "function":
+ return _("FunctionBlock \"%s\" can't be pasted in a Function!!!")%blocktype
+ blockname = self.GenerateNewName(tagname, blockname, "%s%%d"%blocktype, debug=debug)
+ exclude[blockname] = True
+ instance.setinstanceName(blockname)
+ self.AddEditedElementPouVar(tagname, blocktype, blockname)
+ elif child.nodeName == "step":
+ stepname = self.GenerateNewName(tagname, instance.getname(), "Step%d", exclude, debug)
+ exclude[stepname] = True
+ instance.setname(stepname)
+ localid = instance.getlocalId()
+ if not used_id.has_key(localid):
+ new_id[localid] = True
+ instances.append((child.nodeName, instance))
+
+ if len(instances) == 0:
+ return _("Invalid plcopen element(s)!!!")
+
+ idx = 1
+ translate_id = {}
+ bbox = plcopen.rect()
+ for name, instance in instances:
+ localId = instance.getlocalId()
+ bbox.union(instance.getBoundingBox())
+ if used_id.has_key(localId):
+ while used_id.has_key(idx) or new_id.has_key(idx):
+ idx += 1
+ new_id[idx] = True
+ instance.setlocalId(idx)
+ translate_id[localId] = idx
+
+ x, y, width, height = bbox.bounding_box()
+ if middle:
+ new_pos[0] -= width / 2
+ new_pos[1] -= height / 2
+ else:
+ new_pos = map(lambda x: x + 30, new_pos)
+ if scaling[0] != 0 and scaling[1] != 0:
+ min_pos = map(lambda x: 30 / x, scaling)
+ minx = round(min_pos[0])
+ if int(min_pos[0]) == round(min_pos[0]):
+ minx += 1
+ miny = round(min_pos[1])
+ if int(min_pos[1]) == round(min_pos[1]):
+ miny += 1
+ minx *= scaling[0]
+ miny *= scaling[1]
+ new_pos = (max(minx, round(new_pos[0] / scaling[0]) * scaling[0]),
+ max(miny, round(new_pos[1] / scaling[1]) * scaling[1]))
+ else:
+ new_pos = (max(30, new_pos[0]), max(30, new_pos[1]))
+ diff = (new_pos[0] - x, new_pos[1] - y)
+
+ connections = {}
+ for name, instance in instances:
+ connections.update(instance.updateConnectionsId(translate_id))
+ if getattr(instance, "setexecutionOrderId", None) is not None:
+ instance.setexecutionOrderId(0)
+ instance.translate(*diff)
+ element.addinstance(name, instance)
+
+ return new_id, connections
+
+ # Return the current pou editing informations
+ def GetEditedElementInstanceInfos(self, tagname, id = None, exclude = [], debug = False):
+ infos = {}
+ instance = None
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ # if id is defined
+ if id is not None:
+ instance = element.getinstance(id)
+ else:
+ instance = element.getrandomInstance(exclude)
+ if instance is not None:
+ infos = instance.getinfos()
+ if infos["type"] in ["input", "output", "inout"]:
+ var_type = self.GetEditedElementVarValueType(tagname, infos["specific_values"]["name"], debug)
+ infos["specific_values"]["value_type"] = var_type
+ return infos
+ return None
+
+ def ClearEditedElementExecutionOrder(self, tagname):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ element.resetexecutionOrder()
+
+ def ResetEditedElementExecutionOrder(self, tagname):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ element.compileexecutionOrder()
+
+ # Return the variable type of the given pou
+ def GetEditedElementVarValueType(self, tagname, varname, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ words = tagname.split("::")
+ if words[0] in ["P","T","A"]:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ if words[0] == "T" and varname == words[2]:
+ return "BOOL"
+ if words[1] == varname:
+ return self.GetPouInterfaceReturnType(pou)
+ for type, varlist in pou.getvars():
+ for var in varlist.getvariable():
+ if var.getname() == varname:
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ return vartype_content["value"].getname()
+ elif vartype_content["name"] in ["string", "wstring"]:
+ return vartype_content["name"].upper()
+ else:
+ return vartype_content["name"]
+ return None
+
+ def SetConnectionWires(self, connection, connector):
+ wires = connector.GetWires()
+ idx = 0
+ for wire, handle in wires:
+ points = wire.GetPoints(handle != 0)
+ if handle == 0:
+ result = wire.GetConnectedInfos(-1)
+ else:
+ result = wire.GetConnectedInfos(0)
+ if result != None:
+ refLocalId, formalParameter = result
+ connections = connection.getconnections()
+ if connections is None or len(connection.getconnections()) <= idx:
+ connection.addconnection()
+ connection.setconnectionId(idx, refLocalId)
+ connection.setconnectionPoints(idx, points)
+ if formalParameter != "":
+ connection.setconnectionParameter(idx, formalParameter)
+ else:
+ connection.setconnectionParameter(idx, None)
+ idx += 1
+
+ def AddEditedElementPouVar(self, tagname, type, name, location="", description=""):
+ if self.Project is not None:
+ words = tagname.split("::")
+ if words[0] in ['P', 'T', 'A']:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ pou.addpouLocalVar(type, name, location, description)
+
+ def AddEditedElementPouExternalVar(self, tagname, type, name):
+ if self.Project is not None:
+ words = tagname.split("::")
+ if words[0] in ['P', 'T', 'A']:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ pou.addpouExternalVar(type, name)
+
+ def ChangeEditedElementPouVar(self, tagname, old_type, old_name, new_type, new_name):
+ if self.Project is not None:
+ words = tagname.split("::")
+ if words[0] in ['P', 'T', 'A']:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ pou.changepouVar(old_type, old_name, new_type, new_name)
+
+ def RemoveEditedElementPouVar(self, tagname, type, name):
+ if self.Project is not None:
+ words = tagname.split("::")
+ if words[0] in ['P', 'T', 'A']:
+ pou = self.Project.getpou(words[1])
+ if pou is not None:
+ pou.removepouVar(type, name)
+
+ def AddEditedElementBlock(self, tagname, id, blocktype, blockname = None):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ block = plcopen.fbdObjects_block()
+ block.setlocalId(id)
+ block.settypeName(blocktype)
+ blocktype_infos = self.GetBlockType(blocktype)
+ if blocktype_infos["type"] != "function" and blockname is not None:
+ block.setinstanceName(blockname)
+ self.AddEditedElementPouVar(tagname, blocktype, blockname)
+ element.addinstance("block", block)
+ self.Project.RefreshElementUsingTree()
+
+ def SetEditedElementBlockInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ block = element.getinstance(id)
+ if block is None:
+ return
+ old_name = block.getinstanceName()
+ old_type = block.gettypeName()
+ new_name = infos.get("name", old_name)
+ new_type = infos.get("type", old_type)
+ if new_type != old_type:
+ old_typeinfos = self.GetBlockType(old_type)
+ new_typeinfos = self.GetBlockType(new_type)
+ if old_typeinfos is None or new_typeinfos is None:
+ self.ChangeEditedElementPouVar(tagname, old_type, old_name, new_type, new_name)
+ elif new_typeinfos["type"] != old_typeinfos["type"]:
+ if new_typeinfos["type"] == "function":
+ self.RemoveEditedElementPouVar(tagname, old_type, old_name)
+ else:
+ self.AddEditedElementPouVar(tagname, new_type, new_name)
+ elif new_typeinfos["type"] != "function":
+ self.ChangeEditedElementPouVar(tagname, old_type, old_name, new_type, new_name)
+ elif new_name != old_name:
+ self.ChangeEditedElementPouVar(tagname, old_type, old_name, new_type, new_name)
+ for param, value in infos.items():
+ if param == "name":
+ block.setinstanceName(value)
+ elif param == "type":
+ block.settypeName(value)
+ elif param == "executionOrder" and block.getexecutionOrderId() != value:
+ element.setelementExecutionOrder(block, 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.setedge(connector.GetEdge())
+ position = connector.GetRelPosition()
+ variable.connectionPointIn.setrelPositionXY(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.setedge(connector.GetEdge())
+ position = connector.GetRelPosition()
+ variable.addconnectionPointOut()
+ variable.connectionPointOut.setrelPositionXY(position.x, position.y)
+ block.outputVariables.appendvariable(variable)
+ self.Project.RefreshElementUsingTree()
+
+ def AddEditedElementVariable(self, tagname, id, type):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ if type == INPUT:
+ name = "inVariable"
+ variable = plcopen.fbdObjects_inVariable()
+ elif type == OUTPUT:
+ name = "outVariable"
+ variable = plcopen.fbdObjects_outVariable()
+ elif type == INOUT:
+ name = "inOutVariable"
+ variable = plcopen.fbdObjects_inOutVariable()
+ variable.setlocalId(id)
+ element.addinstance(name, variable)
+
+ def SetEditedElementVariableInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ variable = element.getinstance(id)
+ if variable is None:
+ return
+ for param, value in infos.items():
+ if param == "name":
+ variable.setexpression(value)
+ elif param == "executionOrder" and variable.getexecutionOrderId() != value:
+ element.setelementExecutionOrder(variable, 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 len(value["outputs"]) > 0:
+ output = value["outputs"][0]
+ if len(value["inputs"]) > 0:
+ variable.setnegatedOut(output.IsNegated())
+ variable.setedgeOut(output.GetEdge())
+ else:
+ variable.setnegated(output.IsNegated())
+ variable.setedge(output.GetEdge())
+ position = output.GetRelPosition()
+ variable.addconnectionPointOut()
+ variable.connectionPointOut.setrelPositionXY(position.x, position.y)
+ if len(value["inputs"]) > 0:
+ input = value["inputs"][0]
+ if len(value["outputs"]) > 0:
+ variable.setnegatedIn(input.IsNegated())
+ variable.setedgeIn(input.GetEdge())
+ else:
+ variable.setnegated(input.IsNegated())
+ variable.setedge(input.GetEdge())
+ position = input.GetRelPosition()
+ variable.addconnectionPointIn()
+ variable.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(variable.connectionPointIn, input)
+
+ def AddEditedElementConnection(self, tagname, id, type):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ if type == CONNECTOR:
+ name = "connector"
+ connection = plcopen.commonObjects_connector()
+ elif type == CONTINUATION:
+ name = "continuation"
+ connection = plcopen.commonObjects_continuation()
+ connection.setlocalId(id)
+ element.addinstance(name, connection)
+
+ def SetEditedElementConnectionInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ connection = element.getinstance(id)
+ if connection is None:
+ return
+ 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.commonObjects_continuation):
+ connection.addconnectionPointOut()
+ connection.connectionPointOut.setrelPositionXY(position.x, position.y)
+ elif isinstance(connection, plcopen.commonObjects_connector):
+ connection.addconnectionPointIn()
+ connection.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(connection.connectionPointIn, value)
+
+ def AddEditedElementComment(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ comment = plcopen.commonObjects_comment()
+ comment.setlocalId(id)
+ element.addinstance("comment", comment)
+
+ def SetEditedElementCommentInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ comment = element.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 AddEditedElementPowerRail(self, tagname, id, type):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ if type == LEFTRAIL:
+ name = "leftPowerRail"
+ powerrail = plcopen.ldObjects_leftPowerRail()
+ elif type == RIGHTRAIL:
+ name = "rightPowerRail"
+ powerrail = plcopen.ldObjects_rightPowerRail()
+ powerrail.setlocalId(id)
+ element.addinstance(name, powerrail)
+
+ def SetEditedElementPowerRailInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ powerrail = element.getinstance(id)
+ if powerrail is None:
+ return
+ 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.ldObjects_leftPowerRail):
+ powerrail.setconnectionPointOut([])
+ for connector in value["outputs"]:
+ position = connector.GetRelPosition()
+ connection = plcopen.leftPowerRail_connectionPointOut()
+ connection.setrelPositionXY(position.x, position.y)
+ powerrail.connectionPointOut.append(connection)
+ elif isinstance(powerrail, plcopen.ldObjects_rightPowerRail):
+ powerrail.setconnectionPointIn([])
+ for connector in value["inputs"]:
+ position = connector.GetRelPosition()
+ connection = plcopen.connectionPointIn()
+ connection.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(connection, connector)
+ powerrail.connectionPointIn.append(connection)
+
+ def AddEditedElementContact(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ contact = plcopen.ldObjects_contact()
+ contact.setlocalId(id)
+ element.addinstance("contact", contact)
+
+ def SetEditedElementContactInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ contact = element.getinstance(id)
+ if contact is None:
+ return
+ for param, value in infos.items():
+ if param == "name":
+ contact.setvariable(value)
+ elif param == "type":
+ if value == CONTACT_NORMAL:
+ contact.setnegated(False)
+ contact.setedge("none")
+ elif value == CONTACT_REVERSE:
+ contact.setnegated(True)
+ contact.setedge("none")
+ elif value == CONTACT_RISING:
+ contact.setnegated(False)
+ contact.setedge("rising")
+ elif value == CONTACT_FALLING:
+ contact.setnegated(False)
+ contact.setedge("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["inputs"][0]
+ position = input_connector.GetRelPosition()
+ contact.addconnectionPointIn()
+ contact.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(contact.connectionPointIn, input_connector)
+ output_connector = value["outputs"][0]
+ position = output_connector.GetRelPosition()
+ contact.addconnectionPointOut()
+ contact.connectionPointOut.setrelPositionXY(position.x, position.y)
+
+ def AddEditedElementCoil(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ coil = plcopen.ldObjects_coil()
+ coil.setlocalId(id)
+ element.addinstance("coil", coil)
+
+ def SetEditedElementCoilInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ coil = element.getinstance(id)
+ if coil is None:
+ return
+ for param, value in infos.items():
+ if param == "name":
+ coil.setvariable(value)
+ elif param == "type":
+ if value == COIL_NORMAL:
+ coil.setnegated(False)
+ coil.setstorage("none")
+ coil.setedge("none")
+ elif value == COIL_REVERSE:
+ coil.setnegated(True)
+ coil.setstorage("none")
+ coil.setedge("none")
+ elif value == COIL_SET:
+ coil.setnegated(False)
+ coil.setstorage("set")
+ coil.setedge("none")
+ elif value == COIL_RESET:
+ coil.setnegated(False)
+ coil.setstorage("reset")
+ coil.setedge("none")
+ elif value == COIL_RISING:
+ coil.setnegated(False)
+ coil.setstorage("none")
+ coil.setedge("rising")
+ elif value == COIL_FALLING:
+ coil.setnegated(False)
+ coil.setstorage("none")
+ coil.setedge("falling")
+ 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["inputs"][0]
+ position = input_connector.GetRelPosition()
+ coil.addconnectionPointIn()
+ coil.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(coil.connectionPointIn, input_connector)
+ output_connector = value["outputs"][0]
+ position = output_connector.GetRelPosition()
+ coil.addconnectionPointOut()
+ coil.connectionPointOut.setrelPositionXY(position.x, position.y)
+
+ def AddEditedElementStep(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ step = plcopen.sfcObjects_step()
+ step.setlocalId(id)
+ element.addinstance("step", step)
+
+ def SetEditedElementStepInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ step = element.getinstance(id)
+ if step is None:
+ return
+ 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":
+ if len(value["inputs"]) > 0:
+ input_connector = value["inputs"][0]
+ position = input_connector.GetRelPosition()
+ step.addconnectionPointIn()
+ step.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(step.connectionPointIn, input_connector)
+ else:
+ step.deleteconnectionPointIn()
+ if len(value["outputs"]) > 0:
+ output_connector = value["outputs"][0]
+ position = output_connector.GetRelPosition()
+ step.addconnectionPointOut()
+ step.connectionPointOut.setrelPositionXY(position.x, position.y)
+ else:
+ step.deleteconnectionPointOut()
+ elif param == "action":
+ if value:
+ position = value.GetRelPosition()
+ step.addconnectionPointOutAction()
+ step.connectionPointOutAction.setrelPositionXY(position.x, position.y)
+ else:
+ step.deleteconnectionPointOutAction()
+
+ def AddEditedElementTransition(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ transition = plcopen.sfcObjects_transition()
+ transition.setlocalId(id)
+ element.addinstance("transition", transition)
+
+ def SetEditedElementTransitionInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ transition = element.getinstance(id)
+ if transition is None:
+ return
+ for param, value in infos.items():
+ if param == "type" and value != "connection":
+ 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 == "priority":
+ if value != 0:
+ transition.setpriority(value)
+ else:
+ transition.setpriority(None)
+ elif param == "connectors":
+ input_connector = value["inputs"][0]
+ position = input_connector.GetRelPosition()
+ transition.addconnectionPointIn()
+ transition.connectionPointIn.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(transition.connectionPointIn, input_connector)
+ output_connector = value["outputs"][0]
+ position = output_connector.GetRelPosition()
+ transition.addconnectionPointOut()
+ transition.connectionPointOut.setrelPositionXY(position.x, position.y)
+ elif infos.get("type", None) == "connection" and param == "connection" and value:
+ transition.setconditionContent("connection", None)
+ self.SetConnectionWires(transition.condition.content["value"], value)
+
+ def AddEditedElementDivergence(self, tagname, id, type):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ if type == SELECTION_DIVERGENCE:
+ name = "selectionDivergence"
+ divergence = plcopen.sfcObjects_selectionDivergence()
+ elif type == SELECTION_CONVERGENCE:
+ name = "selectionConvergence"
+ divergence = plcopen.sfcObjects_selectionConvergence()
+ elif type == SIMULTANEOUS_DIVERGENCE:
+ name = "simultaneousDivergence"
+ divergence = plcopen.sfcObjects_simultaneousDivergence()
+ elif type == SIMULTANEOUS_CONVERGENCE:
+ name = "simultaneousConvergence"
+ divergence = plcopen.sfcObjects_simultaneousConvergence()
+ divergence.setlocalId(id)
+ element.addinstance(name, divergence)
+
+ def SetEditedElementDivergenceInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ divergence = element.getinstance(id)
+ if divergence is None:
+ return
+ 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.sfcObjects_selectionDivergence, plcopen.sfcObjects_simultaneousDivergence)):
+ position = input_connectors[0].GetRelPosition()
+ divergence.addconnectionPointIn()
+ divergence.connectionPointIn.setrelPositionXY(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.sfcObjects_selectionConvergence):
+ connection = plcopen.selectionConvergence_connectionPointIn()
+ else:
+ connection = plcopen.connectionPointIn()
+ connection.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(connection, input_connector)
+ divergence.appendconnectionPointIn(connection)
+ output_connectors = value["outputs"]
+ if isinstance(divergence, (plcopen.sfcObjects_selectionConvergence, plcopen.sfcObjects_simultaneousConvergence)):
+ position = output_connectors[0].GetRelPosition()
+ divergence.addconnectionPointOut()
+ divergence.connectionPointOut.setrelPositionXY(position.x, position.y)
+ else:
+ divergence.setconnectionPointOut([])
+ for output_connector in output_connectors:
+ position = output_connector.GetRelPosition()
+ if isinstance(divergence, plcopen.sfcObjects_selectionDivergence):
+ connection = plcopen.selectionDivergence_connectionPointOut()
+ else:
+ connection = plcopen.simultaneousDivergence_connectionPointOut()
+ connection.setrelPositionXY(position.x, position.y)
+ divergence.appendconnectionPointOut(connection)
+
+ def AddEditedElementJump(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ jump = plcopen.sfcObjects_jumpStep()
+ jump.setlocalId(id)
+ element.addinstance("jumpStep", jump)
+
+ def SetEditedElementJumpInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ jump = element.getinstance(id)
+ if jump is None:
+ return
+ 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.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(jump.connectionPointIn, value)
+
+ def AddEditedElementActionBlock(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ actionBlock = plcopen.commonObjects_actionBlock()
+ actionBlock.setlocalId(id)
+ element.addinstance("actionBlock", actionBlock)
+
+ def SetEditedElementActionBlockInfos(self, tagname, id, infos):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ actionBlock = element.getinstance(id)
+ if actionBlock is None:
+ return
+ 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.setrelPositionXY(position.x, position.y)
+ self.SetConnectionWires(actionBlock.connectionPointIn, value)
+
+ def RemoveEditedElementInstance(self, tagname, id):
+ element = self.GetEditedElement(tagname)
+ if element is not None:
+ instance = element.getinstance(id)
+ if isinstance(instance, plcopen.fbdObjects_block):
+ self.RemoveEditedElementPouVar(tagname, instance.gettypeName(), instance.getinstanceName())
+ element.removeinstance(id)
+ self.Project.RefreshElementUsingTree()
+
+ def GetEditedResourceVariables(self, tagname, debug = False):
+ varlist = []
+ words = tagname.split("::")
+ for var in self.GetConfigurationGlobalVars(words[1], debug):
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ for var in self.GetConfigurationResourceGlobalVars(words[1], words[2], debug):
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ return varlist
+
+ def SetEditedResourceInfos(self, tagname, tasks, instances):
+ resource = self.GetEditedElement(tagname)
+ if resource is not None:
+ resource.settask([])
+ resource.setpouInstance([])
+ task_list = {}
+ for task in tasks:
+ new_task = plcopen.resource_task()
+ new_task.setname(task["Name"])
+ if task["Triggering"] == "Interrupt":
+ new_task.setsingle(task["Single"])
+## result = duration_model.match(task["Interval"]).groups()
+## if reduce(lambda x, y: x or y != None, result):
+## values = []
+## for value in result[:-1]:
+## if value != None:
+## values.append(int(value))
+## else:
+## values.append(0)
+## if result[-1] is not None:
+## values.append(int(float(result[-1]) * 1000))
+## new_task.setinterval(datetime.time(*values))
+ if task["Triggering"] == "Cyclic":
+ new_task.setinterval(task["Interval"])
+ new_task.setpriority(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.settypeName(instance["Type"])
+ task_list.get(instance["Task"], resource).appendpouInstance(new_instance)
+
+ def GetEditedResourceInfos(self, tagname, debug = False):
+ resource = self.GetEditedElement(tagname, debug)
+ if resource is not None:
+ 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 is not None:
+ new_task["Single"] = single
+ else:
+ new_task["Single"] = ""
+ interval = task.getinterval()
+ if interval is not None:
+## text = ""
+## if interval.hour != 0:
+## text += "%dh"%interval.hour
+## if interval.minute != 0:
+## text += "%dm"%interval.minute
+## if interval.second != 0:
+## text += "%ds"%interval.second
+## if interval.microsecond != 0:
+## if interval.microsecond % 1000 != 0:
+## text += "%.3fms"%(float(interval.microsecond) / 1000)
+## else:
+## text += "%dms"%(interval.microsecond / 1000)
+## new_task["Interval"] = text
+ new_task["Interval"] = interval
+ else:
+ new_task["Interval"] = ""
+ if single is not None and interval is None:
+ new_task["Triggering"] = "Interrupt"
+ elif interval is not None and single is None:
+ new_task["Triggering"] = "Cyclic"
+ else:
+ new_task["Triggering"] = ""
+ new_task["Priority"] = str(task.getpriority())
+ tasks_data.append(new_task)
+ for instance in task.getpouInstance():
+ new_instance = {}
+ new_instance["Name"] = instance.getname()
+ new_instance["Type"] = instance.gettypeName()
+ 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.gettypeName()
+ new_instance["Task"] = ""
+ instances_data.append(new_instance)
+ return tasks_data, instances_data
+
+ def OpenXMLFile(self, filepath):
+ xmlfile = open(filepath, 'r')
+ tree = minidom.parse(xmlfile)
+ xmlfile.close()
+
+ self.Project = plcopen.project()
+ for child in tree.childNodes:
+ if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "project":
+ try:
+ result = self.Project.loadXMLTree(child)
+ except ValueError, e:
+ return _("Project file syntax error:\n\n") + str(e)
+ self.SetFilePath(filepath)
+ self.Project.RefreshElementUsingTree()
+ self.Project.RefreshDataTypeHierarchy()
+ self.Project.RefreshCustomBlockTypes()
+ self.CreateProjectBuffer(True)
+ self.ProgramChunks = []
+ self.ProgramOffset = 0
+ self.NextCompiledProject = self.Copy(self.Project)
+ self.CurrentCompiledProject = None
+ self.Buffering = False
+ self.CurrentElementEditing = None
+ return None
+ return _("No PLC project found")
+
+ def SaveXMLFile(self, filepath = None):
+ if not filepath and self.FilePath == "":
+ return False
+ else:
+ contentheader = {"modificationDateTime": datetime.datetime(*localtime()[:6])}
+ self.Project.setcontentHeader(contentheader)
+
+ text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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"}
+ text += self.Project.generateXMLText("project", 0, extras)
+
+ if filepath:
+ xmlfile = open(filepath,"w")
+ else:
+ xmlfile = open(self.FilePath,"w")
+ xmlfile.write(text.encode("utf-8"))
+ xmlfile.close()
+ self.MarkProjectAsSaved()
+ if filepath:
+ self.SetFilePath(filepath)
+ return True
+
+#-------------------------------------------------------------------------------
+# Search in Current Project Functions
+#-------------------------------------------------------------------------------
+
+ def SearchInProject(self, criteria):
+ return self.Project.Search(criteria)
+
+ def SearchInPou(self, tagname, criteria, debug=False):
+ pou = self.GetEditedElement(tagname, debug)
+ if pou is not None:
+ return pou.Search(criteria)
+ return []
+
+#-------------------------------------------------------------------------------
+# Current Buffering Management Functions
+#-------------------------------------------------------------------------------
+
+ """
+ Return a copy of the project
+ """
+ def Copy(self, model):
+ return cPickle.loads(cPickle.dumps(model))
+
+ def CreateProjectBuffer(self, saved):
+ if self.ProjectBufferEnabled:
+ self.ProjectBuffer = UndoBuffer(cPickle.dumps(self.Project), saved)
+ else:
+ self.ProjectBuffer = None
+ self.ProjectSaved = saved
+
+ def IsProjectBufferEnabled(self):
+ return self.ProjectBufferEnabled
+
+ def EnableProjectBuffer(self, enable):
+ self.ProjectBufferEnabled = enable
+ if self.Project is not None:
+ if enable:
+ current_saved = self.ProjectSaved
+ else:
+ current_saved = self.ProjectBuffer.IsCurrentSaved()
+ self.CreateProjectBuffer(current_saved)
+
+ def BufferProject(self):
+ if self.ProjectBuffer is not None:
+ self.ProjectBuffer.Buffering(cPickle.dumps(self.Project))
+ else:
+ self.ProjectSaved = False
+
+ def StartBuffering(self):
+ if self.ProjectBuffer is not None:
+ self.Buffering = True
+ else:
+ self.ProjectSaved = False
+
+ def EndBuffering(self):
+ if self.ProjectBuffer is not None and self.Buffering:
+ self.ProjectBuffer.Buffering(cPickle.dumps(self.Project))
+ self.Buffering = False
+
+ def MarkProjectAsSaved(self):
+ self.EndBuffering()
+ if self.ProjectBuffer is not None:
+ self.ProjectBuffer.CurrentSaved()
+ else:
+ self.ProjectSaved = True
+
+ # Return if project is saved
+ def ProjectIsSaved(self):
+ if self.ProjectBuffer is not None:
+ return self.ProjectBuffer.IsCurrentSaved() and not self.Buffering
+ else:
+ return self.ProjectSaved
+
+ def LoadPrevious(self):
+ self.EndBuffering()
+ if self.ProjectBuffer is not None:
+ self.Project = cPickle.loads(self.ProjectBuffer.Previous())
+
+ def LoadNext(self):
+ if self.ProjectBuffer is not None:
+ self.Project = cPickle.loads(self.ProjectBuffer.Next())
+
+ def GetBufferState(self):
+ if self.ProjectBuffer is not None:
+ first = self.ProjectBuffer.IsFirst() and not self.Buffering
+ last = self.ProjectBuffer.IsLast()
+ return not first, not last
+ return False, False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCGenerator.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1355 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from plcopen import plcopen
+from plcopen.structures import *
+from types import *
+import re
+
+# Dictionary associating PLCOpen variable categories to the corresponding
+# IEC 61131-3 variable categories
+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"}
+
+
+# Dictionary associating PLCOpen POU categories to the corresponding
+# IEC 61131-3 POU categories
+pouTypeNames = {"function" : "FUNCTION", "functionBlock" : "FUNCTION_BLOCK", "program" : "PROGRAM"}
+
+
+errorVarTypes = {
+ "VAR_INPUT": "var_input",
+ "VAR_OUTPUT": "var_output",
+ "VAR_INOUT": "var_inout",
+}
+
+# Helper function for reindenting text
+def ReIndentText(text, nb_spaces):
+ compute = ""
+ lines = text.splitlines()
+ if len(lines) > 0:
+ line_num = 0
+ while line_num < len(lines) and len(lines[line_num].strip()) == 0:
+ line_num += 1
+ if line_num < len(lines):
+ spaces = 0
+ while lines[line_num][spaces] == " ":
+ spaces += 1
+ indent = ""
+ for i in xrange(spaces, nb_spaces):
+ indent += " "
+ for line in lines:
+ if line != "":
+ compute += "%s%s\n"%(indent, line)
+ else:
+ compute += "\n"
+ return compute
+
+def SortInstances(a, b):
+ ax, ay = int(a.getx()), int(a.gety())
+ bx, by = int(b.getx()), int(b.gety())
+ if abs(ay - by) < 10:
+ return cmp(ax, bx)
+ else:
+ return cmp(ay, by)
+
+REAL_MODEL = re.compile("[0-9]+\.[0-9]+$")
+INTEGER_MODEL = re.compile("[0-9]+$")
+
+#-------------------------------------------------------------------------------
+# Specific exception for PLC generating errors
+#-------------------------------------------------------------------------------
+
+
+class PLCGenException(Exception):
+ pass
+
+
+#-------------------------------------------------------------------------------
+# Generator of PLC program
+#-------------------------------------------------------------------------------
+
+
+class ProgramGenerator:
+
+ # Create a new PCL program generator
+ def __init__(self, controler, project, errors, warnings):
+ # Keep reference of the controler and project
+ self.Controler = controler
+ self.Project = project
+ # Reset the internal variables used to generate PLC programs
+ self.Program = []
+ self.DatatypeComputed = {}
+ self.PouComputed = {}
+ self.Errors = errors
+ self.Warnings = warnings
+
+ # Compute value according to type given
+ def ComputeValue(self, value, var_type):
+ base_type = self.Controler.GetBaseType(var_type)
+ if base_type == "STRING":
+ return "'%s'"%value
+ elif base_type == "WSTRING":
+ return "\"%s\""%value
+ return value
+
+ # Generate a data type from its name
+ def GenerateDataType(self, datatype_name):
+ # Verify that data type hasn't been generated yet
+ if not self.DatatypeComputed.get(datatype_name, True):
+ # If not mark data type as computed
+ self.DatatypeComputed[datatype_name] = True
+
+ # Getting datatype model from project
+ datatype = self.Project.getdataType(datatype_name)
+ tagname = self.Controler.ComputeDataTypeName(datatype.getname())
+ datatype_def = [(" ", ()),
+ (datatype.getname(), (tagname, "name")),
+ (" : ", ())]
+ basetype_content = datatype.baseType.getcontent()
+ # Data type derived directly from a string type
+ if basetype_content["name"] in ["string", "wstring"]:
+ datatype_def += [(basetype_content["name"].upper(), (tagname, "base"))]
+ # Data type derived directly from a user defined type
+ elif basetype_content["name"] == "derived":
+ basetype_name = basetype_content["value"].getname()
+ self.GenerateDataType(basetype_name)
+ datatype_def += [(basetype_name, (tagname, "base"))]
+ # Data type is a subrange
+ elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned"]:
+ base_type = basetype_content["value"].baseType.getcontent()
+ # Subrange derived directly from a user defined type
+ if base_type["name"] == "derived":
+ basetype_name = base_type["value"].getname()
+ self.GenerateDataType(basetype_name)
+ # Subrange derived directly from an elementary type
+ else:
+ basetype_name = base_type["name"]
+ min_value = basetype_content["value"].range.getlower()
+ max_value = basetype_content["value"].range.getupper()
+ datatype_def += [(basetype_name, (tagname, "base")),
+ (" (", ()),
+ ("%s"%min_value, (tagname, "lower")),
+ ("..", ()),
+ ("%s"%max_value, (tagname, "upper")),
+ (")",())]
+ # Data type is an enumerated type
+ elif basetype_content["name"] == "enum":
+ values = [[(value.getname(), (tagname, "value", i))]
+ for i, value in enumerate(basetype_content["value"].values.getvalue())]
+ datatype_def += [("(", ())]
+ datatype_def += JoinList([(", ", ())], values)
+ datatype_def += [(")", ())]
+ # Data type is an array
+ elif basetype_content["name"] == "array":
+ base_type = basetype_content["value"].baseType.getcontent()
+ # Array derived directly from a user defined type
+ if base_type["name"] == "derived":
+ basetype_name = base_type["value"].getname()
+ self.GenerateDataType(basetype_name)
+ # Array derived directly from a string type
+ elif base_type["name"] in ["string", "wstring"]:
+ basetype_name = base_type["name"].upper()
+ # Array derived directly from an elementary type
+ else:
+ basetype_name = base_type["name"]
+ dimensions = [[("%s"%dimension.getlower(), (tagname, "range", i, "lower")),
+ ("..", ()),
+ ("%s"%dimension.getupper(), (tagname, "range", i, "upper"))]
+ for i, dimension in enumerate(basetype_content["value"].getdimension())]
+ datatype_def += [("ARRAY [", ())]
+ datatype_def += JoinList([(",", ())], dimensions)
+ datatype_def += [("] OF " , ()),
+ (basetype_name, (tagname, "base"))]
+ # Data type is a structure
+ elif basetype_content["name"] == "struct":
+ elements = []
+ for i, element in enumerate(basetype_content["value"].getvariable()):
+ element_type = element.type.getcontent()
+ # Structure element derived directly from a user defined type
+ if element_type["name"] == "derived":
+ elementtype_name = element_type["value"].getname()
+ self.GenerateDataType(elementtype_name)
+ # Structure element derived directly from a string type
+ elif element_type["name"] in ["string", "wstring"]:
+ elementtype_name = element_type["name"].upper()
+ # Structure element derived directly from an elementary type
+ else:
+ elementtype_name = element_type["name"]
+ element_text = [("\n ", ()),
+ (element.getname(), (tagname, "struct", i, "name")),
+ (" : ", ()),
+ (elementtype_name, (tagname, "struct", i, "type"))]
+ if element.initialValue is not None:
+ element_text.extend([(" := ", ()),
+ (self.ComputeValue(element.initialValue.getvalue(), elementtype_name), (tagname, "struct", i, "initial value"))])
+ element_text.append((";", ()))
+ elements.append(element_text)
+ datatype_def += [("STRUCT", ())]
+ datatype_def += JoinList([("", ())], elements)
+ datatype_def += [("\n END_STRUCT", ())]
+ # Data type derived directly from a elementary type
+ else:
+ datatype_def += [(basetype_content["name"], (tagname, "base"))]
+ # Data type has an initial value
+ if datatype.initialValue is not None:
+ datatype_def += [(" := ", ()),
+ (self.ComputeValue(datatype.initialValue.getvalue(), datatype_name), (tagname, "initial value"))]
+ datatype_def += [(";\n", ())]
+ self.Program += datatype_def
+
+ # Generate a POU from its name
+ def GeneratePouProgram(self, pou_name):
+ # Verify that POU hasn't been generated yet
+ if not self.PouComputed.get(pou_name, True):
+ # If not mark POU as computed
+ self.PouComputed[pou_name] = True
+
+ # Getting POU model from project
+ pou = self.Project.getpou(pou_name)
+ pou_type = pou.getpouType()
+ # Verify that POU type exists
+ if pouTypeNames.has_key(pou_type):
+ # Create a POU program generator
+ pou_program = PouProgramGenerator(self, pou.getname(), pouTypeNames[pou_type], self.Errors, self.Warnings)
+ program = pou_program.GenerateProgram(pou)
+ self.Program += program
+ else:
+ raise PLCGenException, _("Undefined pou type \"%s\"")%pou_type
+
+ # Generate a POU defined and used in text
+ def GeneratePouProgramInText(self, text):
+ for pou_name in self.PouComputed.keys():
+ model = re.compile("(?:^|[^0-9^A-Z])%s(?:$|[^0-9^A-Z])"%pou_name.upper())
+ if model.search(text) is not None:
+ self.GeneratePouProgram(pou_name)
+
+ # Generate a configuration from its model
+ def GenerateConfiguration(self, configuration):
+ tagname = self.Controler.ComputeConfigurationName(configuration.getname())
+ config = [("\nCONFIGURATION ", ()),
+ (configuration.getname(), (tagname, "name")),
+ ("\n", ())]
+ var_number = 0
+ # Generate any global variable in configuration
+ for varlist in configuration.getglobalVars():
+ variable_type = errorVarTypes.get("VAR_GLOBAL", "var_local")
+ # Generate variable block with modifier
+ config += [(" VAR_GLOBAL", ())]
+ if varlist.getconstant():
+ config += [(" CONSTANT", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "constant"))]
+ elif varlist.getretain():
+ config += [(" RETAIN", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "retain"))]
+ elif varlist.getnonretain():
+ config += [(" NON_RETAIN", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "non_retain"))]
+ config += [("\n", ())]
+ # Generate any variable of this block
+ for var in varlist.getvariable():
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ var_type = vartype_content["value"].getname()
+ self.GenerateDataType(var_type)
+ else:
+ var_type = var.gettypeAsText()
+
+ config += [(" ", ()),
+ (var.getname(), (tagname, variable_type, var_number, "name")),
+ (" ", ())]
+ # Generate variable address if exists
+ address = var.getaddress()
+ if address:
+ config += [("AT ", ()),
+ (address, (tagname, variable_type, var_number, "location")),
+ (" ", ())]
+ config += [(": ", ()),
+ (var.gettypeAsText(), (tagname, variable_type, var_number, "type"))]
+ # Generate variable initial value if exists
+ initial = var.getinitialValue()
+ if initial:
+ config += [(" := ", ()),
+ (self.ComputeValue(initial.getvalue(), var_type), (tagname, variable_type, var_number, "initial value"))]
+ config += [(";\n", ())]
+ var_number += 1
+ config += [(" END_VAR\n", ())]
+ # Generate any resource in the configuration
+ for resource in configuration.getresource():
+ config += self.GenerateResource(resource, configuration.getname())
+ config += [("END_CONFIGURATION\n", ())]
+ return config
+
+ # Generate a resource from its model
+ def GenerateResource(self, resource, config_name):
+ tagname = self.Controler.ComputeConfigurationResourceName(config_name, resource.getname())
+ resrce = [("\n RESOURCE ", ()),
+ (resource.getname(), (tagname, "name")),
+ (" ON PLC\n", ())]
+ var_number = 0
+ # Generate any global variable in configuration
+ for varlist in resource.getglobalVars():
+ variable_type = errorVarTypes.get("VAR_GLOBAL", "var_local")
+ # Generate variable block with modifier
+ resrce += [(" VAR_GLOBAL", ())]
+ if varlist.getconstant():
+ resrce += [(" CONSTANT", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "constant"))]
+ elif varlist.getretain():
+ resrce += [(" RETAIN", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "retain"))]
+ elif varlist.getnonretain():
+ resrce += [(" NON_RETAIN", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "non_retain"))]
+ resrce += [("\n", ())]
+ # Generate any variable of this block
+ for var in varlist.getvariable():
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ var_type = vartype_content["value"].getname()
+ self.GenerateDataType(var_type)
+ else:
+ var_type = var.gettypeAsText()
+
+ resrce += [(" ", ()),
+ (var.getname(), (tagname, variable_type, var_number, "name")),
+ (" ", ())]
+ address = var.getaddress()
+ # Generate variable address if exists
+ if address:
+ resrce += [("AT ", ()),
+ (address, (tagname, variable_type, var_number, "location")),
+ (" ", ())]
+ resrce += [(": ", ()),
+ (var.gettypeAsText(), (tagname, variable_type, var_number, "type"))]
+ # Generate variable initial value if exists
+ initial = var.getinitialValue()
+ if initial:
+ resrce += [(" := ", ()),
+ (self.ComputeValue(initial.getvalue(), var_type), (tagname, variable_type, var_number, "initial value"))]
+ resrce += [(";\n", ())]
+ var_number += 1
+ resrce += [(" END_VAR\n", ())]
+ # Generate any task in the resource
+ tasks = resource.gettask()
+ task_number = 0
+ for task in tasks:
+ # Task declaration
+ resrce += [(" TASK ", ()),
+ (task.getname(), (tagname, "task", task_number, "name")),
+ ("(", ())]
+ args = []
+ single = task.getsingle()
+ # Single argument if exists
+ if single:
+ resrce += [("SINGLE := ", ()),
+ (single, (tagname, "task", task_number, "single")),
+ (",", ())]
+ # Interval argument if exists
+ interval = task.getinterval()
+ if interval:
+ resrce += [("INTERVAL := ", ()),
+ (interval, (tagname, "task", task_number, "interval")),
+ (",", ())]
+## resrce += [("INTERVAL := t#", ())]
+## if interval.hour != 0:
+## resrce += [("%dh"%interval.hour, (tagname, "task", task_number, "interval", "hour"))]
+## if interval.minute != 0:
+## resrce += [("%dm"%interval.minute, (tagname, "task", task_number, "interval", "minute"))]
+## if interval.second != 0:
+## resrce += [("%ds"%interval.second, (tagname, "task", task_number, "interval", "second"))]
+## if interval.microsecond != 0:
+## resrce += [("%dms"%(interval.microsecond / 1000), (tagname, "task", task_number, "interval", "millisecond"))]
+## resrce += [(",", ())]
+ # Priority argument
+ resrce += [("PRIORITY := ", ()),
+ ("%d"%task.getpriority(), (tagname, "task", task_number, "priority")),
+ (");\n", ())]
+ task_number += 1
+ instance_number = 0
+ # Generate any program assign to each task
+ for task in tasks:
+ for instance in task.getpouInstance():
+ resrce += [(" PROGRAM ", ()),
+ (instance.getname(), (tagname, "instance", instance_number, "name")),
+ (" WITH ", ()),
+ (task.getname(), (tagname, "instance", instance_number, "task")),
+ (" : ", ()),
+ (instance.gettypeName(), (tagname, "instance", instance_number, "type")),
+ (";\n", ())]
+ instance_number += 1
+ # Generate any program assign to no task
+ for instance in resource.getpouInstance():
+ resrce += [(" PROGRAM ", ()),
+ (instance.getname(), (tagname, "instance", instance_number, "name")),
+ (" : ", ()),
+ (instance.gettypeName(), (tagname, "instance", instance_number, "type")),
+ (";\n", ())]
+ instance_number += 1
+ resrce += [(" END_RESOURCE\n", ())]
+ return resrce
+
+ # Generate the entire program for current project
+ def GenerateProgram(self):
+ # Find all data types defined
+ for datatype in self.Project.getdataTypes():
+ self.DatatypeComputed[datatype.getname()] = False
+ # Find all data types defined
+ for pou in self.Project.getpous():
+ self.PouComputed[pou.getname()] = False
+ # Generate data type declaration structure if there is at least one data
+ # type defined
+ if len(self.DatatypeComputed) > 0:
+ self.Program += [("TYPE\n", ())]
+ # Generate every data types defined
+ for datatype_name in self.DatatypeComputed.keys():
+ self.GenerateDataType(datatype_name)
+ self.Program += [("END_TYPE\n\n", ())]
+ # Generate every POUs defined
+ for pou_name in self.PouComputed.keys():
+ self.GeneratePouProgram(pou_name)
+ # Generate every configurations defined
+ for config in self.Project.getconfigurations():
+ self.Program += self.GenerateConfiguration(config)
+
+ # Return generated program
+ def GetGeneratedProgram(self):
+ return self.Program
+
+
+#-------------------------------------------------------------------------------
+# Generator of POU programs
+#-------------------------------------------------------------------------------
+
+
+class PouProgramGenerator:
+
+ # Create a new POU program generator
+ def __init__(self, parent, name, type, errors, warnings):
+ # Keep Reference to the parent generator
+ self.ParentGenerator = parent
+ self.Name = name
+ self.Type = type
+ self.TagName = self.ParentGenerator.Controler.ComputePouName(name)
+ self.CurrentIndent = " "
+ self.ReturnType = None
+ self.Interface = []
+ self.InitialSteps = []
+ self.ComputedBlocks = {}
+ self.ComputedConnectors = {}
+ self.ConnectionTypes = {}
+ self.RelatedConnections = []
+ self.SFCNetworks = {"Steps":{}, "Transitions":{}, "Actions":{}}
+ self.SFCComputedBlocks = []
+ self.ActionNumber = 0
+ self.Program = []
+ self.Errors = errors
+ self.Warnings = warnings
+
+ def GetBlockType(self, type, inputs=None):
+ return self.ParentGenerator.Controler.GetBlockType(type, inputs)
+
+ def IndentLeft(self):
+ if len(self.CurrentIndent) >= 2:
+ self.CurrentIndent = self.CurrentIndent[:-2]
+
+ def IndentRight(self):
+ self.CurrentIndent += " "
+
+ # Generator of unique ID for inline actions
+ def GetActionNumber(self):
+ self.ActionNumber += 1
+ return self.ActionNumber
+
+ # Test if a variable has already been defined
+ def IsAlreadyDefined(self, name):
+ for list_type, option, located, vars in self.Interface:
+ for var_type, var_name, var_address, var_initial in vars:
+ if name == var_name:
+ return True
+ return False
+
+ # Return the type of a variable defined in interface
+ def GetVariableType(self, name):
+ parts = name.split('.')
+ current_type = None
+ if len(parts) > 0:
+ name = parts.pop(0)
+ for list_type, option, located, vars in self.Interface:
+ for var_type, var_name, var_address, var_initial in vars:
+ if name == var_name:
+ current_type = var_type
+ break
+ while current_type is not None and len(parts) > 0:
+ tagname = self.ParentGenerator.Controler.ComputeDataTypeName(current_type)
+ infos = self.ParentGenerator.Controler.GetDataTypeInfos(tagname)
+ name = parts.pop(0)
+ current_type = None
+ if infos is not None and infos["type"] == "Structure":
+ for element in infos["elements"]:
+ if element["Name"] == name:
+ current_type = element["Type"]
+ break
+ return current_type
+
+ # Return connectors linked by a connection to the given connector
+ def GetConnectedConnector(self, connector, body):
+ links = connector.getconnections()
+ if links and len(links) == 1:
+ return self.GetLinkedConnector(links[0], body)
+ return None
+
+ def GetLinkedConnector(self, link, body):
+ parameter = link.getformalParameter()
+ instance = body.getcontentInstance(link.getrefLocalId())
+ if isinstance(instance, (plcopen.fbdObjects_inVariable, plcopen.fbdObjects_inOutVariable, plcopen.commonObjects_continuation, plcopen.ldObjects_contact, plcopen.ldObjects_coil)):
+ return instance.connectionPointOut
+ elif isinstance(instance, plcopen.fbdObjects_block):
+ outputvariables = instance.outputVariables.getvariable()
+ if len(outputvariables) == 1:
+ return outputvariables[0].connectionPointOut
+ elif parameter:
+ for variable in outputvariables:
+ if variable.getformalParameter() == parameter:
+ return variable.connectionPointOut
+ else:
+ point = link.getposition()[-1]
+ for variable in outputvariables:
+ relposition = variable.connectionPointOut.getrelPositionXY()
+ blockposition = instance.getposition()
+ if point.x == blockposition.x + relposition[0] and point.y == blockposition.y + relposition[1]:
+ return variable.connectionPointOut
+ elif isinstance(instance, plcopen.ldObjects_leftPowerRail):
+ outputconnections = instance.getconnectionPointOut()
+ if len(outputconnections) == 1:
+ return outputconnections[0]
+ else:
+ point = link.getposition()[-1]
+ for outputconnection in outputconnections:
+ relposition = outputconnection.getrelPositionXY()
+ powerrailposition = instance.getposition()
+ if point.x == powerrailposition.x + relposition[0] and point.y == powerrailposition.y + relposition[1]:
+ return outputconnection
+ return None
+
+ def ExtractRelatedConnections(self, connection):
+ for i, related in enumerate(self.RelatedConnections):
+ if connection in related:
+ return self.RelatedConnections.pop(i)
+ return [connection]
+
+ def ComputeInterface(self, pou):
+ interface = pou.getinterface()
+ if interface is not None:
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ body_content = body.getcontent()
+ if self.Type == "FUNCTION":
+ returntype_content = interface.getreturnType().getcontent()
+ if returntype_content["name"] == "derived":
+ self.ReturnType = returntype_content["value"].getname()
+ elif returntype_content["name"] in ["string", "wstring"]:
+ self.ReturnType = returntype_content["name"].upper()
+ else:
+ self.ReturnType = returntype_content["name"]
+ for varlist in interface.getcontent():
+ variables = []
+ located = []
+ for var in varlist["value"].getvariable():
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ var_type = vartype_content["value"].getname()
+ blocktype = self.GetBlockType(var_type)
+ if blocktype is not None:
+ self.ParentGenerator.GeneratePouProgram(var_type)
+ if body_content["name"] in ["FBD", "LD", "SFC"]:
+ block = pou.getinstanceByName(var.getname())
+ else:
+ block = None
+ for variable in blocktype["initialise"](var_type, var.getname(), block):
+ if variable[2] is not None:
+ located.append(variable)
+ else:
+ variables.append(variable)
+ else:
+ self.ParentGenerator.GenerateDataType(var_type)
+ initial = var.getinitialValue()
+ if initial:
+ initial_value = initial.getvalue()
+ else:
+ initial_value = None
+ address = var.getaddress()
+ if address is not None:
+ located.append((vartype_content["value"].getname(), var.getname(), address, initial_value))
+ else:
+ variables.append((vartype_content["value"].getname(), var.getname(), None, initial_value))
+ else:
+ var_type = var.gettypeAsText()
+ initial = var.getinitialValue()
+ if initial:
+ initial_value = initial.getvalue()
+ else:
+ initial_value = None
+ address = var.getaddress()
+ if address is not None:
+ located.append((var_type, var.getname(), address, initial_value))
+ else:
+ variables.append((var_type, var.getname(), None, initial_value))
+ if varlist["value"].getconstant():
+ option = "CONSTANT"
+ elif varlist["value"].getretain():
+ option = "RETAIN"
+ elif varlist["value"].getnonretain():
+ option = "NON_RETAIN"
+ else:
+ option = None
+ if len(variables) > 0:
+ self.Interface.append((varTypeNames[varlist["name"]], option, False, variables))
+ if len(located) > 0:
+ self.Interface.append((varTypeNames[varlist["name"]], option, True, located))
+
+ def ComputeConnectionTypes(self, pou):
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ body_content = body.getcontent()
+ body_type = body_content["name"]
+ if body_type in ["FBD", "LD", "SFC"]:
+ undefined_blocks = []
+ for instance in body.getcontentInstances():
+ if isinstance(instance, (plcopen.fbdObjects_inVariable, plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
+ expression = instance.getexpression()
+ var_type = self.GetVariableType(expression)
+ if pou.getpouType() == "function" and expression == pou.getname():
+ returntype_content = pou.interface.getreturnType().getcontent()
+ if returntype_content["name"] == "derived":
+ var_type = returntype_content["value"].getname()
+ elif returntype_content["name"] in ["string", "wstring"]:
+ var_type = returntype_content["name"].upper()
+ else:
+ var_type = returntype_content["name"]
+ elif var_type is None:
+ parts = expression.split("#")
+ if len(parts) > 1:
+ var_type = parts[0]
+ elif REAL_MODEL.match(expression):
+ var_type = "ANY_REAL"
+ elif INTEGER_MODEL.match(expression):
+ var_type = "ANY_NUM"
+ elif expression.startswith("'"):
+ var_type = "STRING"
+ elif expression.startswith('"'):
+ var_type = "WSTRING"
+ if var_type is not None:
+ if isinstance(instance, (plcopen.fbdObjects_inVariable, plcopen.fbdObjects_inOutVariable)):
+ for connection in self.ExtractRelatedConnections(instance.connectionPointOut):
+ self.ConnectionTypes[connection] = var_type
+ if isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
+ self.ConnectionTypes[instance.connectionPointIn] = var_type
+ connected = self.GetConnectedConnector(instance.connectionPointIn, body)
+ if connected and not self.ConnectionTypes.has_key(connected):
+ for connection in self.ExtractRelatedConnections(connected):
+ self.ConnectionTypes[connection] = var_type
+ elif isinstance(instance, (plcopen.ldObjects_contact, plcopen.ldObjects_coil)):
+ for connection in self.ExtractRelatedConnections(instance.connectionPointOut):
+ self.ConnectionTypes[connection] = "BOOL"
+ self.ConnectionTypes[instance.connectionPointIn] = "BOOL"
+ connected = self.GetConnectedConnector(instance.connectionPointIn, body)
+ if connected and not self.ConnectionTypes.has_key(connected):
+ for connection in self.ExtractRelatedConnections(connected):
+ self.ConnectionTypes[connection] = "BOOL"
+ elif isinstance(instance, plcopen.ldObjects_leftPowerRail):
+ for connection in instance.getconnectionPointOut():
+ for related in self.ExtractRelatedConnections(connection):
+ self.ConnectionTypes[related] = "BOOL"
+ elif isinstance(instance, plcopen.ldObjects_rightPowerRail):
+ for connection in instance.getconnectionPointIn():
+ self.ConnectionTypes[connection] = "BOOL"
+ connected = self.GetConnectedConnector(connection, body)
+ if connected and not self.ConnectionTypes.has_key(connected):
+ for connection in self.ExtractRelatedConnections(connected):
+ self.ConnectionTypes[connection] = "BOOL"
+ elif isinstance(instance, plcopen.sfcObjects_transition):
+ content = instance.condition.getcontent()
+ if content["name"] == "connection" and len(content["value"]) == 1:
+ connected = self.GetLinkedConnector(content["value"][0], body)
+ if connected and not self.ConnectionTypes.has_key(connected):
+ for connection in self.ExtractRelatedConnections(connected):
+ self.ConnectionTypes[connection] = "BOOL"
+ elif isinstance(instance, plcopen.commonObjects_continuation):
+ name = instance.getname()
+ connector = None
+ var_type = "ANY"
+ for element in body.getcontentInstances():
+ if isinstance(element, plcopen.commonObjects_connector) and element.getname() == name:
+ if connector is not None:
+ raise PLCGenException, _("More than one connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
+ connector = element
+ if connector is not None:
+ undefined = [instance.connectionPointOut, connector.connectionPointIn]
+ connected = self.GetConnectedConnector(connector.connectionPointIn, body)
+ if connected:
+ undefined.append(connected)
+ related = []
+ for connection in undefined:
+ if self.ConnectionTypes.has_key(connection):
+ var_type = self.ConnectionTypes[connection]
+ else:
+ related.extend(self.ExtractRelatedConnections(connection))
+ if var_type.startswith("ANY") and len(related) > 0:
+ self.RelatedConnections.append(related)
+ else:
+ for connection in related:
+ self.ConnectionTypes[connection] = var_type
+ else:
+ raise PLCGenException, _("No connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
+ elif isinstance(instance, plcopen.fbdObjects_block):
+ block_infos = self.GetBlockType(instance.gettypeName(), "undefined")
+ if block_infos is not None:
+ self.ComputeBlockInputTypes(instance, block_infos, body)
+ else:
+ for variable in instance.inputVariables.getvariable():
+ connected = self.GetConnectedConnector(variable.connectionPointIn, body)
+ if connected is not None:
+ var_type = self.ConnectionTypes.get(connected, None)
+ if var_type is not None:
+ self.ConnectionTypes[variable.connectionPointIn] = var_type
+ else:
+ related = self.ExtractRelatedConnections(connected)
+ related.append(variable.connectionPointIn)
+ self.RelatedConnections.append(related)
+ undefined_blocks.append(instance)
+ for instance in undefined_blocks:
+ block_infos = self.GetBlockType(instance.gettypeName(), tuple([self.ConnectionTypes.get(variable.connectionPointIn, "ANY") for variable in instance.inputVariables.getvariable() if variable.getformalParameter() != "EN"]))
+ if block_infos is not None:
+ self.ComputeBlockInputTypes(instance, block_infos, body)
+ else:
+ raise PLCGenException, _("No informations found for \"%s\" block")%(instance.gettypeName())
+
+ def ComputeBlockInputTypes(self, instance, block_infos, body):
+ undefined = {}
+ for variable in instance.outputVariables.getvariable():
+ output_name = variable.getformalParameter()
+ if output_name == "ENO":
+ for connection in self.ExtractRelatedConnections(variable.connectionPointOut):
+ self.ConnectionTypes[connection] = "BOOL"
+ else:
+ for oname, otype, oqualifier in block_infos["outputs"]:
+ if output_name == oname:
+ if otype.startswith("ANY"):
+ if not undefined.has_key(otype):
+ undefined[otype] = []
+ undefined[otype].append(variable.connectionPointOut)
+ elif not self.ConnectionTypes.has_key(variable.connectionPointOut):
+ for connection in self.ExtractRelatedConnections(variable.connectionPointOut):
+ self.ConnectionTypes[connection] = otype
+ for variable in instance.inputVariables.getvariable():
+ input_name = variable.getformalParameter()
+ if input_name == "EN":
+ for connection in self.ExtractRelatedConnections(variable.connectionPointIn):
+ self.ConnectionTypes[connection] = "BOOL"
+ else:
+ for iname, itype, iqualifier in block_infos["inputs"]:
+ if input_name == iname:
+ connected = self.GetConnectedConnector(variable.connectionPointIn, body)
+ if itype.startswith("ANY"):
+ if not undefined.has_key(itype):
+ undefined[itype] = []
+ undefined[itype].append(variable.connectionPointIn)
+ if connected:
+ undefined[itype].append(connected)
+ else:
+ self.ConnectionTypes[variable.connectionPointIn] = itype
+ if connected and not self.ConnectionTypes.has_key(connected):
+ for connection in self.ExtractRelatedConnections(connected):
+ self.ConnectionTypes[connection] = itype
+ for var_type, connections in undefined.items():
+ related = []
+ for connection in connections:
+ if self.ConnectionTypes.has_key(connection):
+ var_type = self.ConnectionTypes[connection]
+ else:
+ related.extend(self.ExtractRelatedConnections(connection))
+ if var_type.startswith("ANY") and len(related) > 0:
+ self.RelatedConnections.append(related)
+ else:
+ for connection in related:
+ self.ConnectionTypes[connection] = var_type
+
+ def ComputeProgram(self, pou):
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ body_content = body.getcontent()
+ body_type = body_content["name"]
+ if body_type in ["IL","ST"]:
+ text = body_content["value"].gettext()
+ self.ParentGenerator.GeneratePouProgramInText(text.upper())
+ self.Program = [(ReIndentText(text, len(self.CurrentIndent)),
+ (self.TagName, "body", len(self.CurrentIndent)))]
+ elif body_type == "SFC":
+ self.IndentRight()
+ for instance in body.getcontentInstances():
+ if isinstance(instance, plcopen.sfcObjects_step):
+ self.GenerateSFCStep(instance, pou)
+ elif isinstance(instance, plcopen.commonObjects_actionBlock):
+ self.GenerateSFCStepActions(instance, pou)
+ elif isinstance(instance, plcopen.sfcObjects_transition):
+ self.GenerateSFCTransition(instance, pou)
+ elif isinstance(instance, plcopen.sfcObjects_jumpStep):
+ self.GenerateSFCJump(instance, pou)
+ if len(self.InitialSteps) > 0 and len(self.SFCComputedBlocks) > 0:
+ action_name = "COMPUTE_FUNCTION_BLOCKS"
+ action_infos = {"qualifier" : "S", "content" : action_name}
+ self.SFCNetworks["Steps"][self.InitialSteps[0]]["actions"].append(action_infos)
+ self.SFCNetworks["Actions"][action_name] = (self.SFCComputedBlocks, ())
+ self.Program = []
+ self.IndentLeft()
+ for initialstep in self.InitialSteps:
+ self.ComputeSFCStep(initialstep)
+ else:
+ otherInstances = {"outVariables&coils" : [], "blocks" : [], "connectors" : []}
+ orderedInstances = []
+ for instance in body.getcontentInstances():
+ if isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable, plcopen.fbdObjects_block)):
+ executionOrderId = instance.getexecutionOrderId()
+ if executionOrderId > 0:
+ orderedInstances.append((executionOrderId, instance))
+ elif isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
+ otherInstances["outVariables&coils"].append(instance)
+ elif isinstance(instance, plcopen.fbdObjects_block):
+ otherInstances["blocks"].append(instance)
+ elif isinstance(instance, plcopen.commonObjects_connector):
+ otherInstances["connectors"].append(instance)
+ elif isinstance(instance, plcopen.ldObjects_coil):
+ otherInstances["outVariables&coils"].append(instance)
+ orderedInstances.sort()
+ otherInstances["outVariables&coils"].sort(SortInstances)
+ otherInstances["blocks"].sort(SortInstances)
+ instances = [instance for (executionOrderId, instance) in orderedInstances]
+ instances.extend(otherInstances["connectors"] + otherInstances["outVariables&coils"] + otherInstances["blocks"])
+ for instance in instances:
+ if isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
+ connections = instance.connectionPointIn.getconnections()
+ if connections is not None:
+ expression = self.ComputeExpression(body, connections)
+ self.Program += [(self.CurrentIndent, ()),
+ (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
+ (" := ", ())]
+ self.Program += expression
+ self.Program += [(";\n", ())]
+ elif isinstance(instance, plcopen.fbdObjects_block):
+ block_type = instance.gettypeName()
+ self.ParentGenerator.GeneratePouProgram(block_type)
+ block_infos = self.GetBlockType(block_type, tuple([self.ConnectionTypes.get(variable.connectionPointIn, "ANY") for variable in instance.inputVariables.getvariable() if variable.getformalParameter() != "EN"]))
+ if block_infos is None:
+ block_infos = self.GetBlockType(block_type)
+ if block_infos is None:
+ raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
+ block_infos["generate"](self, instance, block_infos, body, None)
+ elif isinstance(instance, plcopen.commonObjects_connector):
+ connector = instance.getname()
+ if self.ComputedConnectors.get(connector, None):
+ continue
+ self.ComputedConnectors[connector] = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
+ elif isinstance(instance, plcopen.ldObjects_coil):
+ connections = instance.connectionPointIn.getconnections()
+ if connections is not None:
+ coil_info = (self.TagName, "coil", instance.getlocalId())
+ expression = self.ExtractModifier(instance, self.ComputeExpression(body, connections), coil_info)
+ self.Program += [(self.CurrentIndent, ())]
+ self.Program += [(instance.getvariable(), coil_info + ("reference",))]
+ self.Program += [(" := ", ())] + expression + [(";\n", ())]
+
+ def FactorizePaths(self, paths):
+ same_paths = {}
+ uncomputed_index = range(len(paths))
+ factorized_paths = []
+ for num, path in enumerate(paths):
+ if type(path) == ListType:
+ if len(path) > 1:
+ str_path = str(path[-1:])
+ same_paths.setdefault(str_path, [])
+ same_paths[str_path].append((path[:-1], num))
+ else:
+ factorized_paths.append(path)
+ uncomputed_index.remove(num)
+ for same_path, elements in same_paths.items():
+ if len(elements) > 1:
+ elements_paths = self.FactorizePaths([path for path, num in elements])
+ if len(elements_paths) > 1:
+ factorized_paths.append([tuple(elements_paths)] + eval(same_path))
+ else:
+ factorized_paths.append(elements_paths + eval(same_path))
+ for path, num in elements:
+ uncomputed_index.remove(num)
+ for num in uncomputed_index:
+ factorized_paths.append(paths[num])
+ factorized_paths.sort()
+ return factorized_paths
+
+ def GeneratePaths(self, connections, body, order = False, to_inout = False):
+ paths = []
+ for connection in connections:
+ localId = connection.getrefLocalId()
+ next = body.getcontentInstance(localId)
+ if isinstance(next, plcopen.ldObjects_leftPowerRail):
+ paths.append(None)
+ elif isinstance(next, (plcopen.fbdObjects_inVariable, plcopen.fbdObjects_inOutVariable)):
+ paths.append(str([(next.getexpression(), (self.TagName, "io_variable", localId, "expression"))]))
+ elif isinstance(next, plcopen.fbdObjects_block):
+ block_type = next.gettypeName()
+ self.ParentGenerator.GeneratePouProgram(block_type)
+ block_infos = self.GetBlockType(block_type, tuple([self.ConnectionTypes.get(variable.connectionPointIn, "ANY") for variable in next.inputVariables.getvariable() if variable.getformalParameter() != "EN"]))
+ if block_infos is None:
+ block_infos = self.GetBlockType(block_type)
+ if block_infos is None:
+ raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
+ paths.append(str(block_infos["generate"](self, next, block_infos, body, connection, order, to_inout)))
+ elif isinstance(next, plcopen.commonObjects_continuation):
+ name = next.getname()
+ computed_value = self.ComputedConnectors.get(name, None)
+ if computed_value != None:
+ paths.append(str(computed_value))
+ else:
+ connector = None
+ for instance in body.getcontentInstances():
+ if isinstance(instance, plcopen.commonObjects_connector) and instance.getname() == name:
+ if connector is not None:
+ raise PLCGenException, _("More than one connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
+ connector = instance
+ if connector is not None:
+ connections = connector.connectionPointIn.getconnections()
+ if connections is not None:
+ expression = self.ComputeExpression(body, connections, order)
+ self.ComputedConnectors[name] = expression
+ paths.append(str(expression))
+ else:
+ raise PLCGenException, _("No connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
+ elif isinstance(next, plcopen.ldObjects_contact):
+ contact_info = (self.TagName, "contact", next.getlocalId())
+ variable = str(self.ExtractModifier(next, [(next.getvariable(), contact_info + ("reference",))], contact_info))
+ result = self.GeneratePaths(next.connectionPointIn.getconnections(), body, order)
+ if len(result) > 1:
+ factorized_paths = self.FactorizePaths(result)
+ if len(factorized_paths) > 1:
+ paths.append([variable, tuple(factorized_paths)])
+ else:
+ paths.append([variable] + factorized_paths)
+ elif type(result[0]) == ListType:
+ paths.append([variable] + result[0])
+ elif result[0] is not None:
+ paths.append([variable, result[0]])
+ else:
+ paths.append(variable)
+ elif isinstance(next, plcopen.ldObjects_coil):
+ paths.append(str(self.GeneratePaths(next.connectionPointIn.getconnections(), body, order)))
+ return paths
+
+ def ComputePaths(self, paths, first = False):
+ if type(paths) == TupleType:
+ if None in paths:
+ return [("TRUE", ())]
+ else:
+ vars = [self.ComputePaths(path) for path in paths]
+ if first:
+ return JoinList([(" OR ", ())], vars)
+ else:
+ return [("(", ())] + JoinList([(" OR ", ())], vars) + [(")", ())]
+ elif type(paths) == ListType:
+ vars = [self.ComputePaths(path) for path in paths]
+ return JoinList([(" AND ", ())], vars)
+ elif paths is None:
+ return [("TRUE", ())]
+ else:
+ return eval(paths)
+
+ def ComputeExpression(self, body, connections, order = False, to_inout = False):
+ paths = self.GeneratePaths(connections, body, order, to_inout)
+ if len(paths) > 1:
+ factorized_paths = self.FactorizePaths(paths)
+ if len(factorized_paths) > 1:
+ paths = tuple(factorized_paths)
+ else:
+ paths = factorized_paths[0]
+ else:
+ paths = paths[0]
+ return self.ComputePaths(paths, True)
+
+ def ExtractModifier(self, variable, expression, var_info):
+ if variable.getnegated():
+ return [("NOT(", var_info + ("negated",))] + expression + [(")", ())]
+ else:
+ storage = variable.getstorage()
+ if storage in ["set", "reset"]:
+ self.Program += [(self.CurrentIndent + "IF ", var_info + (storage,))] + expression
+ self.Program += [(" THEN\n ", ())]
+ if storage == "set":
+ return [("TRUE; (*set*)\n" + self.CurrentIndent + "END_IF", ())]
+ else:
+ return [("FALSE; (*reset*)\n" + self.CurrentIndent + "END_IF", ())]
+ edge = variable.getedge()
+ if edge == "rising":
+ return self.AddTrigger("R_TRIG", expression, var_info + ("rising",))
+ elif edge == "falling":
+ return self.AddTrigger("F_TRIG", expression, var_info + ("falling",))
+ return expression
+
+ def AddTrigger(self, edge, expression, var_info):
+ if self.Interface[-1][0] != "VAR" or self.Interface[-1][1] is not None or self.Interface[-1][2]:
+ self.Interface.append(("VAR", None, False, []))
+ i = 1
+ name = "%s%d"%(edge, i)
+ while self.IsAlreadyDefined(name):
+ i += 1
+ name = "%s%d"%(edge, i)
+ self.Interface[-1][3].append((edge, name, None, None))
+ self.Program += [(self.CurrentIndent, ()), (name, var_info), ("(CLK := ", ())]
+ self.Program += expression
+ self.Program += [(");\n", ())]
+ return [("%s.Q"%name, var_info)]
+
+ def ExtractDivergenceInput(self, divergence, pou):
+ connectionPointIn = divergence.getconnectionPointIn()
+ if connectionPointIn:
+ connections = connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ instanceLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ return body.getcontentInstance(instanceLocalId)
+ return None
+
+ def ExtractConvergenceInputs(self, convergence, pou):
+ instances = []
+ for connectionPointIn in convergence.getconnectionPointIn():
+ connections = connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ instanceLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ instances.append(body.getcontentInstance(instanceLocalId))
+ return instances
+
+ def GenerateSFCStep(self, step, pou):
+ step_name = step.getname()
+ if step_name not in self.SFCNetworks["Steps"].keys():
+ if step.getinitialStep():
+ self.InitialSteps.append(step_name)
+ step_infos = {"id" : step.getlocalId(),
+ "initial" : step.getinitialStep(),
+ "transitions" : [],
+ "actions" : []}
+ if step.connectionPointIn:
+ instances = []
+ connections = step.connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ instanceLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ instance = body.getcontentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.sfcObjects_transition):
+ instances.append(instance)
+ elif isinstance(instance, plcopen.sfcObjects_selectionConvergence):
+ instances.extend(self.ExtractConvergenceInputs(instance, pou))
+ elif isinstance(instance, plcopen.sfcObjects_simultaneousDivergence):
+ transition = self.ExtractDivergenceInput(instance, pou)
+ if transition:
+ if isinstance(transition, plcopen.sfcObjects_transition):
+ instances.append(transition)
+ elif isinstance(transition, plcopen.sfcObjects_selectionConvergence):
+ instances.extend(self.ExtractConvergenceInputs(transition, pou))
+ for instance in instances:
+ self.GenerateSFCTransition(instance, pou)
+ if instance in self.SFCNetworks["Transitions"].keys():
+ target_info = (self.TagName, "transition", instance.getlocalId(), "to", step_infos["id"])
+ self.SFCNetworks["Transitions"][instance]["to"].append([(step_name, target_info)])
+ self.SFCNetworks["Steps"][step_name] = step_infos
+
+ def GenerateSFCJump(self, jump, pou):
+ jump_target = jump.gettargetName()
+ if jump.connectionPointIn:
+ instances = []
+ connections = jump.connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ instanceLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ instance = body.getcontentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.sfcObjects_transition):
+ instances.append(instance)
+ elif isinstance(instance, plcopen.sfcObjects_selectionConvergence):
+ instances.extend(self.ExtractConvergenceInputs(instance, pou))
+ elif isinstance(instance, plcopen.sfcObjects_simultaneousDivergence):
+ transition = self.ExtractDivergenceInput(instance, pou)
+ if transition:
+ if isinstance(transition, plcopen.sfcObjects_transition):
+ instances.append(transition)
+ elif isinstance(transition, plcopen.sfcObjects_selectionConvergence):
+ instances.extend(self.ExtractConvergenceInputs(transition, pou))
+ for instance in instances:
+ self.GenerateSFCTransition(instance, pou)
+ if instance in self.SFCNetworks["Transitions"].keys():
+ target_info = (self.TagName, "jump", jump.getlocalId(), "target")
+ self.SFCNetworks["Transitions"][instance]["to"].append([(jump_target, target_info)])
+
+ def GenerateSFCStepActions(self, actionBlock, pou):
+ connections = actionBlock.connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ stepLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ step = body.getcontentInstance(stepLocalId)
+ self.GenerateSFCStep(step, pou)
+ step_name = step.getname()
+ if step_name in self.SFCNetworks["Steps"].keys():
+ actions = actionBlock.getactions()
+ for i, action in enumerate(actions):
+ action_infos = {"id" : actionBlock.getlocalId(),
+ "qualifier" : action["qualifier"],
+ "content" : action["value"],
+ "num" : i}
+ if "duration" in action:
+ action_infos["duration"] = action["duration"]
+ if "indicator" in action:
+ action_infos["indicator"] = action["indicator"]
+ if action["type"] == "reference":
+ self.GenerateSFCAction(action["value"], pou)
+ else:
+ action_name = "%s_INLINE%d"%(step_name.upper(), self.GetActionNumber())
+ self.SFCNetworks["Actions"][action_name] = ([(self.CurrentIndent, ()),
+ (action["value"], (self.TagName, "action_block", action_infos["id"], "action", i, "inline")),
+ ("\n", ())], ())
+ action_infos["content"] = action_name
+ self.SFCNetworks["Steps"][step_name]["actions"].append(action_infos)
+
+ def GenerateSFCAction(self, action_name, pou):
+ if action_name not in self.SFCNetworks["Actions"].keys():
+ actionContent = pou.getaction(action_name)
+ if actionContent:
+ previous_tagname = self.TagName
+ self.TagName = self.ParentGenerator.Controler.ComputePouActionName(self.Name, action_name)
+ self.ComputeProgram(actionContent)
+ self.SFCNetworks["Actions"][action_name] = (self.Program, (self.TagName, "name"))
+ self.Program = []
+ self.TagName = previous_tagname
+
+ def GenerateSFCTransition(self, transition, pou):
+ if transition not in self.SFCNetworks["Transitions"].keys():
+ steps = []
+ connections = transition.connectionPointIn.getconnections()
+ if connections is not None and len(connections) == 1:
+ instanceLocalId = connections[0].getrefLocalId()
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ instance = body.getcontentInstance(instanceLocalId)
+ if isinstance(instance, plcopen.sfcObjects_step):
+ steps.append(instance)
+ elif isinstance(instance, plcopen.sfcObjects_selectionDivergence):
+ step = self.ExtractDivergenceInput(instance, pou)
+ if step:
+ if isinstance(step, plcopen.sfcObjects_step):
+ steps.append(step)
+ elif isinstance(step, plcopen.sfcObjects_simultaneousConvergence):
+ steps.extend(self.ExtractConvergenceInputs(step, pou))
+ elif isinstance(instance, plcopen.sfcObjects_simultaneousConvergence):
+ steps.extend(self.ExtractConvergenceInputs(instance, pou))
+ transition_infos = {"id" : transition.getlocalId(),
+ "priority": transition.getpriority(),
+ "from": [],
+ "to" : []}
+ transitionValues = transition.getconditionContent()
+ if transitionValues["type"] == "inline":
+ transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ()),
+ (transitionValues["value"], (self.TagName, "transition", transition.getlocalId(), "inline")),
+ (";\n", ())]
+ elif transitionValues["type"] == "reference":
+ transitionContent = pou.gettransition(transitionValues["value"])
+ transitionType = transitionContent.getbodyType()
+ transitionBody = transitionContent.getbody()
+ previous_tagname = self.TagName
+ self.TagName = self.ParentGenerator.Controler.ComputePouTransitionName(self.Name, transitionValues["value"])
+ if transitionType == "IL":
+ transition_infos["content"] = [(":\n", ()),
+ (ReIndentText(transitionBody.gettext(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
+ elif transitionType == "ST":
+ transition_infos["content"] = [("\n", ()),
+ (ReIndentText(transitionBody.gettext(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
+ else:
+ for instance in transitionBody.getcontentInstances():
+ if isinstance(instance, plcopen.fbdObjects_outVariable) and instance.getexpression() == transitionValues["value"]\
+ or isinstance(instance, plcopen.ldObjects_coil) and instance.getvariable() == transitionValues["value"]:
+ connections = instance.connectionPointIn.getconnections()
+ if connections is not None:
+ expression = self.ComputeExpression(transitionBody, connections)
+ transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+ self.SFCComputedBlocks += self.Program
+ self.Program = []
+ if not transition_infos.has_key("content"):
+ raise PLCGenException, _("Transition \"%s\" body must contain an output variable or coil referring to its name") % transitionValues["value"]
+ self.TagName = previous_tagname
+ elif transitionValues["type"] == "connection":
+ body = pou.getbody()
+ if isinstance(body, ListType):
+ body = body[0]
+ connections = transition.getconnections()
+ if connections is not None:
+ expression = self.ComputeExpression(body, connections)
+ transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+ self.SFCComputedBlocks += self.Program
+ self.Program = []
+ for step in steps:
+ self.GenerateSFCStep(step, pou)
+ step_name = step.getname()
+ if step_name in self.SFCNetworks["Steps"].keys():
+ transition_infos["from"].append([(step_name, (self.TagName, "transition", transition.getlocalId(), "from", step.getlocalId()))])
+ self.SFCNetworks["Steps"][step_name]["transitions"].append(transition)
+ self.SFCNetworks["Transitions"][transition] = transition_infos
+
+ def ComputeSFCStep(self, step_name):
+ if step_name in self.SFCNetworks["Steps"].keys():
+ step_infos = self.SFCNetworks["Steps"].pop(step_name)
+ self.Program += [(self.CurrentIndent, ())]
+ if step_infos["initial"]:
+ self.Program += [("INITIAL_", ())]
+ self.Program += [("STEP ", ()),
+ (step_name, (self.TagName, "step", step_infos["id"], "name")),
+ (":\n", ())]
+ actions = []
+ self.IndentRight()
+ for action_infos in step_infos["actions"]:
+ if action_infos.get("id", None) is not None:
+ action_info = (self.TagName, "action_block", action_infos["id"], "action", action_infos["num"])
+ else:
+ action_info = ()
+ actions.append(action_infos["content"])
+ self.Program += [(self.CurrentIndent, ()),
+ (action_infos["content"], action_info + ("reference",)),
+ ("(", ()),
+ (action_infos["qualifier"], action_info + ("qualifier",))]
+ if "duration" in action_infos:
+ self.Program += [(", ", ()),
+ (action_infos["duration"], action_info + ("duration",))]
+ if "indicator" in action_infos:
+ self.Program += [(", ", ()),
+ (action_infos["indicator"], action_info + ("indicator",))]
+ self.Program += [(");\n", ())]
+ self.IndentLeft()
+ self.Program += [("%sEND_STEP\n\n"%self.CurrentIndent, ())]
+ for action in actions:
+ self.ComputeSFCAction(action)
+ for transition in step_infos["transitions"]:
+ self.ComputeSFCTransition(transition)
+
+ def ComputeSFCAction(self, action_name):
+ if action_name in self.SFCNetworks["Actions"].keys():
+ action_content, action_info = self.SFCNetworks["Actions"].pop(action_name)
+ self.Program += [("%sACTION "%self.CurrentIndent, ()),
+ (action_name, action_info),
+ (" :\n", ())]
+ self.Program += action_content
+ self.Program += [("%sEND_ACTION\n\n"%self.CurrentIndent, ())]
+
+ def ComputeSFCTransition(self, transition):
+ if transition in self.SFCNetworks["Transitions"].keys():
+ transition_infos = self.SFCNetworks["Transitions"].pop(transition)
+ self.Program += [("%sTRANSITION"%self.CurrentIndent, ())]
+ if transition_infos["priority"] != None:
+ self.Program += [(" (PRIORITY := ", ()),
+ ("%d"%transition_infos["priority"], (self.TagName, "transition", transition_infos["id"], "priority")),
+ (")", ())]
+ self.Program += [(" FROM ", ())]
+ if len(transition_infos["from"]) > 1:
+ self.Program += [("(", ())]
+ self.Program += JoinList([(", ", ())], transition_infos["from"])
+ self.Program += [(")", ())]
+ elif len(transition_infos["from"]) == 1:
+ self.Program += transition_infos["from"][0]
+ else:
+ raise PLCGenException, _("Transition with content \"%s\" not connected to a previous step in \"%s\" POU")%(transition_infos["content"], self.Name)
+ self.Program += [(" TO ", ())]
+ if len(transition_infos["to"]) > 1:
+ self.Program += [("(", ())]
+ self.Program += JoinList([(", ", ())], transition_infos["to"])
+ self.Program += [(")", ())]
+ elif len(transition_infos["to"]) == 1:
+ self.Program += transition_infos["to"][0]
+ else:
+ raise PLCGenException, _("Transition with content \"%s\" not connected to a next step in \"%s\" POU")%(transition_infos["content"], self.Name)
+ self.Program += transition_infos["content"]
+ self.Program += [("%sEND_TRANSITION\n\n"%self.CurrentIndent, ())]
+ for [(step_name, step_infos)] in transition_infos["to"]:
+ self.ComputeSFCStep(step_name)
+
+ def GenerateProgram(self, pou):
+ self.ComputeInterface(pou)
+ self.ComputeConnectionTypes(pou)
+ self.ComputeProgram(pou)
+
+ program = [("%s "%self.Type, ()),
+ (self.Name, (self.TagName, "name"))]
+ if self.ReturnType:
+ program += [(" : ", ()),
+ (self.ReturnType, (self.TagName, "return"))]
+ program += [("\n", ())]
+ if len(self.Interface) == 0:
+ raise PLCGenException, _("No variable defined in \"%s\" POU")%self.Name
+ if len(self.Program) == 0 :
+ raise PLCGenException, _("No body defined in \"%s\" POU")%self.Name
+ var_number = 0
+ for list_type, option, located, variables in self.Interface:
+ variable_type = errorVarTypes.get(list_type, "var_local")
+ program += [(" %s"%list_type, ())]
+ if option is not None:
+ program += [(" %s"%option, (self.TagName, variable_type, (var_number, var_number + len(variables)), option.lower()))]
+ program += [("\n", ())]
+ for var_type, var_name, var_address, var_initial in variables:
+ program += [(" ", ())]
+ if var_name:
+ program += [(var_name, (self.TagName, variable_type, var_number, "name")),
+ (" ", ())]
+ if var_address != None:
+ program += [("AT ", ()),
+ (var_address, (self.TagName, variable_type, var_number, "location")),
+ (" ", ())]
+ program += [(": ", ()),
+ (var_type, (self.TagName, variable_type, var_number, "type"))]
+ if var_initial != None:
+ program += [(" := ", ()),
+ (self.ParentGenerator.ComputeValue(var_initial, var_type), (self.TagName, variable_type, var_number, "initial value"))]
+ program += [(";\n", ())]
+ var_number += 1
+ program += [(" END_VAR\n", ())]
+ program += [("\n", ())]
+ program += self.Program
+ program += [("END_%s\n\n"%self.Type, ())]
+ return program
+
+def GenerateCurrentProgram(controler, project, errors, warnings):
+ generator = ProgramGenerator(controler, project, errors, warnings)
+ generator.GenerateProgram()
+ return generator.GetGeneratedProgram()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/PLCOpenEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,608 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import os, sys, platform, time, traceback, getopt
+
+CWD = os.path.split(os.path.realpath(__file__))[0]
+
+from util.BitmapLibrary import AddBitmapFolder, GetBitmap
+AddBitmapFolder(os.path.join(CWD, "images"))
+
+from docutil import *
+
+__version__ = "$Revision: 1.130 $"
+
+if __name__ == '__main__':
+ # Usage message displayed when help request or when error detected in
+ # command line
+ def usage():
+ print "\nUsage of PLCOpenEditor.py :"
+ print "\n %s [Filepath]\n"%sys.argv[0]
+
+ # Parse options given to PLCOpenEditor in command line
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
+ except getopt.GetoptError:
+ # print help information and exit:
+ usage()
+ sys.exit(2)
+
+ # Extract if help has been requested
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ usage()
+ sys.exit()
+
+ # Extract the optional filename to open
+ fileOpen = None
+ if len(args) > 1:
+ usage()
+ sys.exit()
+ elif len(args) == 1:
+ fileOpen = args[0]
+
+ # Create wxApp (Need to create App before internationalization because of
+ # Windows)
+ app = wx.PySimpleApp()
+
+# Import module for internationalization
+import gettext
+import __builtin__
+
+# Get folder containing translation files
+localedir = os.path.join(CWD,"locale")
+# Get the default language
+langid = wx.LANGUAGE_DEFAULT
+# Define translation domain (name of translation files)
+domain = "Beremiz"
+
+# Define locale for wx
+loc = __builtin__.__dict__.get('loc', None)
+if loc is None:
+ test_loc = wx.Locale(langid)
+ test_loc.AddCatalogLookupPathPrefix(localedir)
+ if test_loc.AddCatalog(domain):
+ loc = wx.Locale(langid)
+ else:
+ loc = wx.Locale(wx.LANGUAGE_ENGLISH)
+ __builtin__.__dict__['loc'] = loc
+# Define location for searching translation files
+loc.AddCatalogLookupPathPrefix(localedir)
+# Define locale domain
+loc.AddCatalog(domain)
+
+if __name__ == '__main__':
+ __builtin__.__dict__['_'] = wx.GetTranslation
+
+from IDEFrame import IDEFrame, AppendMenu
+from IDEFrame import TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE, PAGETITLES
+from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
+from editors.Viewer import Viewer
+from PLCControler import PLCControler
+
+#-------------------------------------------------------------------------------
+# PLCOpenEditor Main Class
+#-------------------------------------------------------------------------------
+
+# Define PLCOpenEditor FileMenu extra items id
+[ID_PLCOPENEDITORFILEMENUGENERATE,
+] = [wx.NewId() for _init_coll_FileMenu_Items in range(1)]
+
+class PLCOpenEditor(IDEFrame):
+
+ # Compatibility function for wx versions < 2.6
+ if wx.VERSION < (2, 6, 0):
+ def Bind(self, event, function, id = None):
+ if id is not None:
+ event(self, id, function)
+ else:
+ event(self, function)
+
+ def _init_coll_FileMenu_Items(self, parent):
+ AppendMenu(parent, help='', id=wx.ID_NEW,
+ kind=wx.ITEM_NORMAL, text=_(u'New') +'\tCTRL+N')
+ AppendMenu(parent, help='', id=wx.ID_OPEN,
+ kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
+ AppendMenu(parent, help='', id=wx.ID_CLOSE,
+ kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
+ AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
+ kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_SAVE,
+ kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
+ AppendMenu(parent, help='', id=wx.ID_SAVEAS,
+ kind=wx.ITEM_NORMAL, text=_(u'Save As...') + '\tCTRL+SHIFT+S')
+ AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATE,
+ kind=wx.ITEM_NORMAL, text=_(u'Generate Program') + '\tCTRL+G')
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
+ kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
+ AppendMenu(parent, help='', id=wx.ID_PREVIEW,
+ kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
+ AppendMenu(parent, help='', id=wx.ID_PRINT,
+ kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_PROPERTIES,
+ kind=wx.ITEM_NORMAL, text=_(u'&Properties'))
+ parent.AppendSeparator()
+ AppendMenu(parent, help='', id=wx.ID_EXIT,
+ kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
+
+ self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
+ self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
+ self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
+ self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
+ self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
+ self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
+ self.Bind(wx.EVT_MENU, self.OnGenerateProgramMenu,
+ id=ID_PLCOPENEDITORFILEMENUGENERATE)
+ self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
+ self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
+ self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
+ self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES)
+ self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
+
+ self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
+ (wx.ID_OPEN, "open", _(u'Open'), None),
+ (wx.ID_SAVE, "save", _(u'Save'), None),
+ (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
+ (wx.ID_PRINT, "print", _(u'Print'), None)])
+
+ def _init_coll_HelpMenu_Items(self, parent):
+ AppendMenu(parent, help='', id=wx.ID_HELP,
+ kind=wx.ITEM_NORMAL, text=_(u'PLCOpenEditor') + '\tF1')
+ #AppendMenu(parent, help='', id=wx.ID_HELP_CONTENTS,
+ # kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
+ #AppendMenu(parent, help='', id=wx.ID_HELP_CONTEXT,
+ # kind=wx.ITEM_NORMAL, text=u'IEC 61131-3\tF3')
+ AppendMenu(parent, help='', id=wx.ID_ABOUT,
+ kind=wx.ITEM_NORMAL, text=_(u'About'))
+ self.Bind(wx.EVT_MENU, self.OnPLCOpenEditorMenu, id=wx.ID_HELP)
+ #self.Bind(wx.EVT_MENU, self.OnPLCOpenMenu, id=wx.ID_HELP_CONTENTS)
+ self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
+
+ ## Constructor of the PLCOpenEditor class.
+ # @param parent The parent window.
+ # @param controler The controler been used by PLCOpenEditor (default: None).
+ # @param fileOpen The filepath to open if no controler defined (default: None).
+ # @param debug The filepath to open if no controler defined (default: False).
+ def __init__(self, parent, fileOpen = None):
+ IDEFrame.__init__(self, parent)
+
+ result = None
+
+ # Open the filepath if defined
+ if fileOpen is not None:
+ fileOpen = DecodeFileSystemPath(fileOpen, False)
+ if os.path.isfile(fileOpen):
+ # Create a new controller
+ controler = PLCControler()
+ result = controler.OpenXMLFile(fileOpen)
+ if result is None:
+ self.Controler = controler
+ self.LibraryPanel.SetController(controler)
+ self.ProjectTree.Enable(True)
+ self.PouInstanceVariablesPanel.SetController(controler)
+ self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+
+ # Define PLCOpenEditor icon
+ self.SetIcon(wx.Icon(os.path.join(CWD, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
+
+ self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
+
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
+
+ if result is not None:
+ self.ShowErrorMessage(result)
+
+ def OnCloseFrame(self, event):
+ if self.Controler is None or self.CheckSaveBeforeClosing(_("Close Application")):
+ self.AUIManager.UnInit()
+
+ self.SaveLastState()
+
+ event.Skip()
+ else:
+ event.Veto()
+
+ def RefreshTitle(self):
+ name = _("PLCOpenEditor")
+ if self.Controler is not None:
+ self.SetTitle("%s - %s"%(name, self.Controler.GetFilename()))
+ else:
+ self.SetTitle(name)
+
+#-------------------------------------------------------------------------------
+# File Menu Functions
+#-------------------------------------------------------------------------------
+
+ def RefreshFileMenu(self):
+ MenuToolBar = self.Panes["MenuToolBar"]
+ if self.Controler is not None:
+ selected = self.TabsOpened.GetSelection()
+ if selected >= 0:
+ graphic_viewer = isinstance(self.TabsOpened.GetPage(selected), Viewer)
+ else:
+ graphic_viewer = False
+ if self.TabsOpened.GetPageCount() > 0:
+ self.FileMenu.Enable(wx.ID_CLOSE, True)
+ if graphic_viewer:
+ self.FileMenu.Enable(wx.ID_PREVIEW, True)
+ self.FileMenu.Enable(wx.ID_PRINT, True)
+ MenuToolBar.EnableTool(wx.ID_PRINT, True)
+ else:
+ self.FileMenu.Enable(wx.ID_PREVIEW, False)
+ self.FileMenu.Enable(wx.ID_PRINT, False)
+ MenuToolBar.EnableTool(wx.ID_PRINT, False)
+ else:
+ self.FileMenu.Enable(wx.ID_CLOSE, False)
+ self.FileMenu.Enable(wx.ID_PREVIEW, False)
+ self.FileMenu.Enable(wx.ID_PRINT, False)
+ MenuToolBar.EnableTool(wx.ID_PRINT, False)
+ self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
+ project_modified = not self.Controler.ProjectIsSaved()
+ self.FileMenu.Enable(wx.ID_SAVE, project_modified)
+ MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
+ self.FileMenu.Enable(wx.ID_PROPERTIES, True)
+ self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
+ self.FileMenu.Enable(wx.ID_SAVEAS, True)
+ MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
+ self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, True)
+ else:
+ self.FileMenu.Enable(wx.ID_CLOSE, False)
+ self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
+ self.FileMenu.Enable(wx.ID_PREVIEW, False)
+ self.FileMenu.Enable(wx.ID_PRINT, False)
+ MenuToolBar.EnableTool(wx.ID_PRINT, False)
+ self.FileMenu.Enable(wx.ID_SAVE, False)
+ MenuToolBar.EnableTool(wx.ID_SAVE, False)
+ self.FileMenu.Enable(wx.ID_PROPERTIES, False)
+ self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)
+ self.FileMenu.Enable(wx.ID_SAVEAS, False)
+ MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
+ self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, False)
+
+ def OnNewProjectMenu(self, event):
+ if self.Controler is not None and not self.CheckSaveBeforeClosing():
+ return
+ dialog = ProjectDialog(self)
+ if dialog.ShowModal() == wx.ID_OK:
+ properties = dialog.GetValues()
+ self.ResetView()
+ self.Controler = PLCControler()
+ self.Controler.CreateNewProject(properties)
+ self.LibraryPanel.SetController(self.Controler)
+ self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL,
+ LIBRARYTREE)
+
+ def OnOpenProjectMenu(self, event):
+ if self.Controler is not None and not self.CheckSaveBeforeClosing():
+ return
+ filepath = ""
+ if self.Controler is not None:
+ filepath = self.Controler.GetFilePath()
+ if filepath != "":
+ directory = os.path.dirname(filepath)
+ else:
+ directory = os.getcwd()
+
+ result = None
+
+ dialog = wx.FileDialog(self, _("Choose a file"), directory, "", _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
+ if dialog.ShowModal() == wx.ID_OK:
+ filepath = dialog.GetPath()
+ if os.path.isfile(filepath):
+ self.ResetView()
+ controler = PLCControler()
+ result = controler.OpenXMLFile(filepath)
+ if result is None:
+ self.Controler = controler
+ self.LibraryPanel.SetController(controler)
+ self.ProjectTree.Enable(True)
+ self.PouInstanceVariablesPanel.SetController(controler)
+ self.LoadProjectLayout()
+ self._Refresh(PROJECTTREE, LIBRARYTREE)
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
+ dialog.Destroy()
+
+ if result is not None:
+ self.ShowErrorMessage(result)
+
+ def OnCloseProjectMenu(self, event):
+ if not self.CheckSaveBeforeClosing():
+ return
+ self.SaveProjectLayout()
+ self.ResetView()
+ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
+
+ def OnSaveProjectMenu(self, event):
+ self.SaveProject()
+
+ def OnSaveProjectAsMenu(self, event):
+ self.SaveProjectAs()
+
+ def OnGenerateProgramMenu(self, event):
+ dialog = wx.FileDialog(self, _("Choose a file"), os.getcwd(), self.Controler.GetProgramFilePath(), _("ST files (*.st)|*.st|All files|*.*"), wx.SAVE|wx.CHANGE_DIR)
+ if dialog.ShowModal() == wx.ID_OK:
+ filepath = dialog.GetPath()
+ message_text = ""
+ header, icon = _("Done"), wx.ICON_INFORMATION
+ if os.path.isdir(os.path.dirname(filepath)):
+ program, errors, warnings = self.Controler.GenerateProgram(filepath)
+ message_text += "".join([_("warning: %s\n") for warning in warnings])
+ if len(errors) > 0:
+ message_text += "".join([_("error: %s\n") for error in errors])
+ message_text += _("Can't generate program to file %s!")%filepath
+ header, icon = _("Error"), wx.ICON_ERROR
+ else:
+ message_text += _("Program was successfully generated!")
+ else:
+ message_text += _("\"%s\" is not a valid folder!")%os.path.dirname(filepath)
+ header, icon = _("Error"), wx.ICON_ERROR
+ message = wx.MessageDialog(self, message_text, header, wx.OK|icon)
+ message.ShowModal()
+ message.Destroy()
+ dialog.Destroy()
+
+ def OnPLCOpenEditorMenu(self, event):
+ wx.MessageBox(_("No documentation available.\nComing soon."))
+
+ def OnPLCOpenMenu(self, event):
+ open_pdf(os.path.join(CWD, "plcopen", "TC6_XML_V101.pdf"))
+
+ def OnAboutMenu(self, event):
+ OpenHtmlFrame(self,_("About PLCOpenEditor"), os.path.join(CWD, "doc", "plcopen_about.html"), wx.Size(350, 350))
+
+ def SaveProject(self):
+ result = self.Controler.SaveXMLFile()
+ if not result:
+ self.SaveProjectAs()
+ else:
+ self._Refresh(TITLE, FILEMENU, PAGETITLES)
+
+ def SaveProjectAs(self):
+ filepath = self.Controler.GetFilePath()
+ if filepath != "":
+ directory, filename = os.path.split(filepath)
+ else:
+ directory, filename = os.getcwd(), "%(projectName)s.xml"%self.Controler.GetProjectProperties()
+ dialog = wx.FileDialog(self, _("Choose a file"), directory, filename, _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.SAVE|wx.OVERWRITE_PROMPT)
+ if dialog.ShowModal() == wx.ID_OK:
+ filepath = dialog.GetPath()
+ if os.path.isdir(os.path.dirname(filepath)):
+ result = self.Controler.SaveXMLFile(filepath)
+ if not result:
+ self.ShowErrorMessage(_("Can't save project to file %s!")%filepath)
+ else:
+ self.ShowErrorMessage(_("\"%s\" is not a valid folder!")%os.path.dirname(filepath))
+ self._Refresh(TITLE, FILEMENU, PAGETITLES)
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Debug Variables Panel
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+# Viewer Printout
+#-------------------------------------------------------------------------------
+
+UPPER_DIV = lambda x, y: (x / y) + {True : 0, False : 1}[(x % y) == 0]
+
+class GraphicPrintout(wx.Printout):
+ def __init__(self, viewer, page_size, margins, preview = False):
+ wx.Printout.__init__(self)
+ self.Viewer = viewer
+ self.PageSize = page_size
+ if self.PageSize[0] == 0 or self.PageSize[1] == 0:
+ self.PageSize = (1050, 1485)
+ self.Preview = preview
+ self.Margins = margins
+ self.FontSize = 5
+ self.TextMargin = 3
+
+ maxx, maxy = viewer.GetMaxSize()
+ self.PageGrid = (UPPER_DIV(maxx, self.PageSize[0]),
+ UPPER_DIV(maxy, self.PageSize[1]))
+
+ def GetPageNumber(self):
+ return self.PageGrid[0] * self.PageGrid[1]
+
+ def HasPage(self, page):
+ return page <= self.GetPageNumber()
+
+ def GetPageInfo(self):
+ page_number = self.GetPageNumber()
+ return (1, page_number, 1, page_number)
+
+ def OnBeginDocument(self, startPage, endPage):
+ dc = self.GetDC()
+ if not self.Preview and isinstance(dc, wx.PostScriptDC):
+ dc.SetResolution(720)
+ super(GraphicPrintout, self).OnBeginDocument(startPage, endPage)
+
+ def OnPrintPage(self, page):
+ dc = self.GetDC()
+ dc.SetUserScale(1.0, 1.0)
+ dc.SetDeviceOrigin(0, 0)
+ dc.printing = not self.Preview
+
+ # Get the size of the DC in pixels
+ ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
+ ppiScreenX, ppiScreenY = self.GetPPIScreen()
+ pw, ph = self.GetPageSizePixels()
+ dw, dh = dc.GetSizeTuple()
+ Xscale = (float(dw) * float(ppiPrinterX)) / (float(pw) * 25.4)
+ Yscale = (float(dh) * float(ppiPrinterY)) / (float(ph) * 25.4)
+
+ fontsize = self.FontSize * Yscale
+ text_margin = self.TextMargin * Yscale
+
+ margin_left = self.Margins[0].x * Xscale
+ margin_top = self.Margins[0].y * Yscale
+ area_width = dw - self.Margins[1].x * Xscale - margin_left
+ area_height = dh - self.Margins[1].y * Yscale - margin_top
+
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.DrawRectangle(margin_left, margin_top, area_width, area_height)
+
+ dc.SetFont(wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
+ dc.SetTextForeground(wx.BLACK)
+ block_name = " - ".join(self.Viewer.GetTagName().split("::")[1:])
+ text_width, text_height = dc.GetTextExtent(block_name)
+ dc.DrawText(block_name, margin_left, margin_top - text_height - self.TextMargin)
+ dc.DrawText(_("Page: %d") % page, margin_left, margin_top + area_height + self.TextMargin)
+
+ # Calculate the position on the DC for centering the graphic
+ posX = area_width * ((page - 1) % self.PageGrid[0])
+ posY = area_height * ((page - 1) / self.PageGrid[0])
+
+ scaleX = float(area_width) / float(self.PageSize[0])
+ scaleY = float(area_height) / float(self.PageSize[1])
+ scale = min(scaleX, scaleY)
+
+ # Set the scale and origin
+ dc.SetDeviceOrigin(-posX + margin_left, -posY + margin_top)
+ dc.SetClippingRegion(posX, posY, self.PageSize[0] * scale, self.PageSize[1] * scale)
+ dc.SetUserScale(scale, scale)
+
+ #-------------------------------------------
+
+ self.Viewer.DoDrawing(dc, True)
+
+ return True
+
+#-------------------------------------------------------------------------------
+# 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 has occurred.
+
+Click OK to save an error report.
+
+Please be kind enough to send this file to:
+edouard.tisserant@gmail.com
+
+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 = wx.MessageDialog(None, str(e_value), _("Error"), wx.OK|wx.ICON_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 AddExceptHook(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__':
+ wx.InitAllImageHandlers()
+
+ # Install a exception handle for bug reports
+ AddExceptHook(os.getcwd(),__version__)
+
+ frame = PLCOpenEditor(None, fileOpen=fileOpen)
+
+ frame.Show()
+ app.MainLoop()
+
--- a/ProjectController.py Wed Sep 05 11:17:52 2012 +0200
+++ b/ProjectController.py Fri Sep 07 16:45:55 2012 +0200
@@ -17,15 +17,14 @@
from util.misc import CheckPathPerm, GetClassImporter, IECCodeViewer
from util.MiniTextControler import MiniTextControler
from util.ProcessLogger import ProcessLogger
-from util.FileManagementPanel import FileManagementPanel
+from util.BitmapLibrary import GetBitmap
+from editors.FileManagementPanel import FileManagementPanel
+from editors.ProjectNodeEditor import ProjectNodeEditor
+from dialogs import DiscoveryDialog
from PLCControler import PLCControler
-from TextViewer import TextViewer
from plcopen.structures import IEC_KEYWORDS
from targets.typemapping import DebugTypesSize
-from util.discovery import DiscoveryDialog
from ConfigTreeNode import ConfigTreeNode
-from ProjectNodeEditor import ProjectNodeEditor
-from utils.BitmapLibrary import GetBitmap
base_folder = os.path.split(sys.path[0])[0]
--- a/ProjectNodeEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-
-import wx
-
-from controls import EditorPanel, ProjectPropertiesPanel
-from ConfTreeNodeEditor import ConfTreeNodeEditor, WINDOW_COLOUR
-
-class ProjectNodeEditor(ConfTreeNodeEditor):
-
- VARIABLE_PANEL_TYPE = "config"
- ENABLE_REQUIRED = True
-
- def _init_Editor(self, prnt):
- self.Editor = wx.ScrolledWindow(prnt, -1, size=wx.Size(-1, -1),
- style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER|wx.HSCROLL|wx.VSCROLL)
- self.Editor.SetBackgroundColour(WINDOW_COLOUR)
- self.Editor.Bind(wx.EVT_SIZE, self.OnWindowResize)
- self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
- self.ParamsEditor = self.Editor
-
- # Variable allowing disabling of Editor scroll when Popup shown
- self.ScrollingEnabled = True
-
- self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- self.ParamsEditorSizer.AddGrowableCol(0)
- self.ParamsEditorSizer.AddGrowableRow(1)
-
- self.Editor.SetSizer(self.ParamsEditorSizer)
-
-
- buttons_sizer = self.GenerateMethodButtonSizer()
- self.ParamsEditorSizer.AddSizer(buttons_sizer, 0, border=5,
- flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
-
- projectproperties_sizer = wx.BoxSizer(wx.HORIZONTAL)
- self.ParamsEditorSizer.AddSizer(projectproperties_sizer, 0, border=5,
- flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
-
- if self.SHOW_PARAMS:
- self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
- projectproperties_sizer.AddSizer(self.ConfNodeParamsSizer, 0, border=5,
- flag=wx.RIGHT)
- else:
- self.ConfNodeParamsSizer = None
-
- self.ProjectProperties = ProjectPropertiesPanel(self.Editor, self.Controler, self.ParentWindow, self.ENABLE_REQUIRED)
- projectproperties_sizer.AddWindow(self.ProjectProperties, 0, border=0, flag=0)
-
- def __init__(self, parent, controler, window):
- configuration = controler.GetProjectMainConfigurationName()
- if configuration is not None:
- tagname = controler.ComputeConfigurationName(configuration)
- else:
- tagname = ""
-
- ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
-
- def GetTagName(self):
- return self.Controler.CTNName()
-
- def GetTitle(self):
- fullname = self.Controler.CTNName()
- if self.Controler.CTNTestModified():
- return "~%s~" % fullname
- return fullname
-
- def RefreshView(self, variablepanel=True):
- EditorPanel.RefreshView(self, variablepanel)
- if self.ConfNodeParamsSizer is not None:
- self.RefreshConfNodeParamsSizer()
- self.ProjectProperties.RefreshView()
-
- def GetBufferState(self):
- return self.Controler.GetBufferState()
-
- def Undo(self):
- self.Controler.LoadPrevious()
- self.ParentWindow.CloseTabsWithoutModel()
-
- def Redo(self):
- self.Controler.LoadNext()
- self.ParentWindow.CloseTabsWithoutModel()
-
\ No newline at end of file
--- a/c_ext/CFileEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ b/c_ext/CFileEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -6,8 +6,8 @@
import wx.lib.buttons
from controls import CustomGrid, CustomTable
-from ConfTreeNodeEditor import ConfTreeNodeEditor
-from utils.BitmapLibrary import GetBitmap
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from util.BitmapLibrary import GetBitmap
if wx.Platform == '__WXMSW__':
faces = { 'times': 'Times New Roman',
--- a/canfestival/NetworkEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ b/canfestival/NetworkEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -3,7 +3,7 @@
from subindextable import EditingPanel
from networkedit import NetworkEditorTemplate
-from ConfTreeNodeEditor import ConfTreeNodeEditor
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
[ID_NETWORKEDITOR,
] = [wx.NewId() for _init_ctrls in range(1)]
--- a/canfestival/SlaveEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ b/canfestival/SlaveEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -3,7 +3,7 @@
from subindextable import EditingPanel
from nodeeditor import NodeEditorTemplate
-from ConfTreeNodeEditor import ConfTreeNodeEditor
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
[ID_SLAVEEDITORCONFNODEMENUNODEINFOS, ID_SLAVEEDITORCONFNODEMENUDS301PROFILE,
ID_SLAVEEDITORCONFNODEMENUDS302PROFILE, ID_SLAVEEDITORCONFNODEMENUDSOTHERPROFILE,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomEditableListBox.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.gizmos
+
+class CustomEditableListBox(wx.gizmos.EditableListBox):
+
+ def __init__(self, *args, **kwargs):
+ wx.gizmos.EditableListBox.__init__(self, *args, **kwargs)
+
+ listbox = self.GetListCtrl()
+ listbox.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+ listbox.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnLabelBeginEdit)
+ listbox.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnLabelEndEdit)
+
+ for button, tooltip, call_function in [
+ (self.GetEditButton(), _("Edit item"), "_OnEditButton"),
+ (self.GetNewButton(), _("New item"), "_OnNewButton"),
+ (self.GetDelButton(), _("Delete item"), "_OnDelButton"),
+ (self.GetUpButton(), _("Move up"), "_OnUpButton"),
+ (self.GetDownButton(), _("Move down"), "_OnDownButton")]:
+ button.SetToolTipString(tooltip)
+ button.Bind(wx.EVT_BUTTON, self.GetButtonPressedFunction(call_function))
+
+ self.Editing = False
+
+ def EnsureCurrentItemVisible(self):
+ listctrl = self.GetListCtrl()
+ listctrl.EnsureVisible(listctrl.GetFocusedItem())
+
+ def OnLabelBeginEdit(self, event):
+ self.Editing = True
+ func = getattr(self, "_OnLabelBeginEdit", None)
+ if func is not None:
+ func(event)
+ else:
+ event.Skip()
+
+ def OnLabelEndEdit(self, event):
+ self.Editing = False
+ func = getattr(self, "_OnLabelEndEdit", None)
+ if func is not None:
+ func(event)
+ else:
+ event.Skip()
+
+ def GetButtonPressedFunction(self, call_function):
+ def OnButtonPressed(event):
+ if wx.Platform != '__WXMSW__' or not self.Editing:
+ func = getattr(self, call_function, None)
+ if func is not None:
+ func(event)
+ wx.CallAfter(self.EnsureCurrentItemVisible)
+ else:
+ wx.CallAfter(self.EnsureCurrentItemVisible)
+ event.Skip()
+ return OnButtonPressed
+
+ def OnKeyDown(self, event):
+ button = None
+ keycode = event.GetKeyCode()
+ if keycode in (wx.WXK_ADD, wx.WXK_NUMPAD_ADD):
+ button = self.GetNewButton()
+ elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE):
+ button = self.GetDelButton()
+ elif keycode == wx.WXK_UP and event.ShiftDown():
+ button = self.GetUpButton()
+ elif keycode == wx.WXK_DOWN and event.ShiftDown():
+ button = self.GetDownButton()
+ elif keycode == wx.WXK_SPACE:
+ button = self.GetEditButton()
+ if button is not None and button.IsEnabled():
+ button.ProcessEvent(wx.CommandEvent(wx.EVT_BUTTON.typeId, button.GetId()))
+ else:
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomGrid.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.grid
+
+class CustomGrid(wx.grid.Grid):
+
+ def __init__(self, *args, **kwargs):
+ wx.grid.Grid.__init__(self, *args, **kwargs)
+
+ self.Editable = True
+
+ self.AddButton = None
+ self.DeleteButton = None
+ self.UpButton = None
+ self.DownButton = None
+
+ self.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False, 'Sans'))
+ self.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL, False, 'Sans'))
+ self.SetSelectionBackground(wx.WHITE)
+ self.SetSelectionForeground(wx.BLACK)
+ self.DisableDragRowSize()
+
+ self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelectCell)
+ self.Bind(wx.grid.EVT_GRID_EDITOR_HIDDEN, self.OnEditorHidden)
+ self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+
+ def SetDefaultValue(self, default_value):
+ self.DefaultValue = default_value
+
+ def SetEditable(self, editable=True):
+ self.Editable = editable
+ self.RefreshButtons()
+
+ def SetButtons(self, buttons):
+ for name in ["Add", "Delete", "Up", "Down"]:
+ button = buttons.get(name, None)
+ setattr(self, "%sButton" % name, button)
+ if button is not None:
+ button.Bind(wx.EVT_BUTTON, getattr(self, "On%sButton" % name))
+
+ def RefreshButtons(self):
+ if self:
+ rows = self.Table.GetNumberRows()
+ row = self.GetGridCursorRow()
+ if self.AddButton is not None:
+ self.AddButton.Enable(self.Editable)
+ if self.DeleteButton is not None:
+ self.DeleteButton.Enable(self.Editable and rows > 0)
+ if self.UpButton is not None:
+ self.UpButton.Enable(self.Editable and row > 0)
+ if self.DownButton is not None:
+ self.DownButton.Enable(self.Editable and 0 <= row < rows - 1)
+
+ def CloseEditControl(self):
+ row, col = self.GetGridCursorRow(), self.GetGridCursorCol()
+ if row != -1 and col != -1:
+ self.SetGridCursor(row, col)
+
+ def AddRow(self):
+ self.CloseEditControl()
+ new_row = self.GetGridCursorRow() + 1
+ col = max(self.GetGridCursorCol(), 0)
+ if getattr(self, "_AddRow", None) is not None:
+ new_row = self._AddRow(new_row)
+ else:
+ self.Table.InsertRow(new_row, self.DefaultValue.copy())
+ self.Table.ResetView(self)
+ self.SetGridCursor(new_row, col)
+ self.MakeCellVisible(new_row, col)
+ self.RefreshButtons()
+
+ def DeleteRow(self):
+ self.CloseEditControl()
+ row = self.GetGridCursorRow()
+ if row >= 0:
+ col = self.GetGridCursorCol()
+ if getattr(self, "_DeleteRow", None) is not None:
+ self._DeleteRow(row)
+ else:
+ self.Table.RemoveRow(row)
+ self.Table.ResetView(self)
+ new_row = min(row, self.Table.GetNumberRows() - 1)
+ self.SetGridCursor(new_row, col)
+ self.MakeCellVisible(new_row, col)
+ self.RefreshButtons()
+
+ def MoveRow(self, row, move):
+ self.CloseEditControl()
+ col = self.GetGridCursorCol()
+ if getattr(self, "_MoveRow", None) is not None:
+ new_row = self._MoveRow(row, move)
+ else:
+ new_row = self.Table.MoveRow(row, move)
+ if new_row != row:
+ self.Table.ResetView(self)
+ if new_row != row:
+ self.SetGridCursor(new_row, col)
+ self.MakeCellVisible(new_row, col)
+ self.RefreshButtons()
+
+ def OnAddButton(self, event):
+ self.AddRow()
+ self.SetFocus()
+ event.Skip()
+
+ def OnDeleteButton(self, event):
+ self.DeleteRow()
+ self.SetFocus()
+ event.Skip()
+
+ def OnUpButton(self, event):
+ self.MoveRow(self.GetGridCursorRow(), -1)
+ self.SetFocus()
+ event.Skip()
+
+ def OnDownButton(self, event):
+ self.MoveRow(self.GetGridCursorRow(), 1)
+ self.SetFocus()
+ event.Skip()
+
+ def OnSelectCell(self, event):
+ wx.CallAfter(self.RefreshButtons)
+ event.Skip()
+
+ def OnEditorHidden(self, event):
+ wx.CallAfter(self.SetFocus)
+ event.Skip()
+
+ def OnKeyDown(self, event):
+ key_handled = False
+ keycode = event.GetKeyCode()
+ if keycode == wx.WXK_TAB:
+ row = self.GetGridCursorRow()
+ col = self.GetGridCursorCol()
+ if event.ShiftDown():
+ if row < 0 or col == 0:
+ self.Navigate(wx.NavigationKeyEvent.IsBackward)
+ key_handled = True
+ elif row < 0 or col == self.Table.GetNumberCols() - 1:
+ self.Navigate(wx.NavigationKeyEvent.IsForward)
+ key_handled = True
+ elif keycode in (wx.WXK_ADD, wx.WXK_NUMPAD_ADD) and self.Editable:
+ self.AddRow()
+ key_handled = True
+ elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and self.Editable:
+ self.DeleteRow()
+ key_handled = True
+ elif keycode == wx.WXK_UP and event.ShiftDown() and self.Editable:
+ self.MoveRow(self.GetGridCursorRow(), -1)
+ key_handled = True
+ elif keycode == wx.WXK_DOWN and event.ShiftDown() and self.Editable:
+ self.MoveRow(self.GetGridCursorRow(), 1)
+ key_handled = True
+ if not key_handled:
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomTable.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.grid
+
+class CustomTable(wx.grid.PyGridTableBase):
+
+ """
+ A custom wx.grid.Grid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ wx.grid.PyGridTableBase.__init__(self)
+ self.data = data
+ self.colnames = colnames
+ self.Highlights = {}
+ 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, translate=True):
+ if col < len(self.colnames):
+ if translate:
+ return _(self.colnames[col])
+ return self.colnames[col]
+
+ def GetRowLabelValue(self, row, translate=True):
+ return row
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ return self.data[row].get(self.GetColLabelValue(col, False), "")
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.data[row][self.GetColLabelValue(col, False)] = value
+
+ def GetValueByName(self, row, colname):
+ if row < self.GetNumberRows():
+ return self.data[row].get(colname)
+
+ def SetValueByName(self, row, colname, value):
+ if row < self.GetNumberRows():
+ self.data[row][colname] = value
+
+ def ResetView(self, grid):
+ """
+ (wx.grid.Grid) -> Reset the grid view. Call this to
+ update the grid if rows and columns have been added or deleted
+ """
+ grid.CloseEditControl()
+ grid.BeginBatch()
+ for current, new, delmsg, addmsg in [
+ (self._rows, self.GetNumberRows(), wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
+ (self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED),
+ ]:
+ if new < current:
+ msg = wx.grid.GridTableMessage(self,delmsg,new,current-new)
+ grid.ProcessTableMessage(msg)
+ elif new > current:
+ msg = wx.grid.GridTableMessage(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 = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
+ grid.ProcessTableMessage(msg)
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+ for row in range(self.GetNumberRows()):
+ row_highlights = self.Highlights.get(row, {})
+ for col in range(self.GetNumberCols()):
+ colname = self.GetColLabelValue(col, False)
+
+ grid.SetReadOnly(row, col, True)
+ grid.SetCellEditor(row, col, None)
+ grid.SetCellRenderer(row, col, None)
+
+ highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
+ grid.SetCellBackgroundColour(row, col, highlight_colours[0])
+ grid.SetCellTextColour(row, col, highlight_colours[1])
+ self.ResizeRow(grid, row)
+
+ def ResizeRow(self, grid, row):
+ if wx.Platform == '__WXMSW__':
+ grid.SetRowMinimalHeight(row, 20)
+ else:
+ grid.SetRowMinimalHeight(row, 28)
+ grid.AutoSizeRow(row, False)
+
+ 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 InsertRow(self, index, row_content):
+ self.data.insert(index, row_content)
+
+ def MoveRow(self, row_index, move):
+ 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))
+ return new_index
+
+ def RemoveRow(self, row_index):
+ self.data.pop(row_index)
+
+ def GetRow(self, row_index):
+ return self.data[row_index]
+
+ def Empty(self):
+ self.data = []
+
+ def AddHighlight(self, infos, highlight_type):
+ row_highlights = self.Highlights.setdefault(infos[0], {})
+ col_highlights = row_highlights.setdefault(infos[1], [])
+ col_highlights.append(highlight_type)
+
+ def RemoveHighlight(self, infos, highlight_type):
+ row_highlights = self.Highlights.get(infos[0])
+ if row_highlights is not None:
+ col_highlights = row_highlights.get(infos[1])
+ if col_highlights is not None and highlight_type in col_highlights:
+ col_highlights.remove(highlight_type)
+ if len(col_highlights) == 0:
+ row_highlights.pop(infos[1])
+
+ def ClearHighlights(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ for row, row_highlights in self.Highlights.iteritems():
+ row_items = row_highlights.items()
+ for col, col_highlights in row_items:
+ if highlight_type in col_highlights:
+ col_highlights.remove(highlight_type)
+ if len(col_highlights) == 0:
+ row_highlights.pop(col)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomTree.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+class CustomTree(wx.TreeCtrl):
+
+ def __init__(self, *args, **kwargs):
+ wx.TreeCtrl.__init__(self, *args, **kwargs)
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+
+ self.BackgroundBitmap = None
+ self.BackgroundAlign = wx.ALIGN_LEFT|wx.ALIGN_TOP
+
+ self.AddMenu = None
+ self.Enabled = False
+
+ if wx.Platform == '__WXMSW__':
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+ else:
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+ self.Bind(wx.EVT_SCROLL, self.OnScroll)
+ self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
+
+ def SetBackgroundBitmap(self, bitmap, align):
+ self.BackgroundBitmap = bitmap
+ self.BackgroundAlign = align
+
+ def SetAddMenu(self, add_menu):
+ self.AddMenu = add_menu
+
+ def Enable(self, enabled):
+ self.Enabled = enabled
+
+ def GetBitmapRect(self):
+ client_size = self.GetClientSize()
+ bitmap_size = self.BackgroundBitmap.GetSize()
+
+ if self.BackgroundAlign & wx.ALIGN_RIGHT:
+ x = client_size[0] - bitmap_size[0]
+ elif self.BackgroundAlign & wx.ALIGN_CENTER_HORIZONTAL:
+ x = (client_size[0] - bitmap_size[0]) / 2
+ else:
+ x = 0
+
+ if self.BackgroundAlign & wx.ALIGN_BOTTOM:
+ y = client_size[1] - bitmap_size[1]
+ elif self.BackgroundAlign & wx.ALIGN_CENTER_VERTICAL:
+ y = (client_size[1] - bitmap_size[1]) / 2
+ else:
+ y = 0
+
+ return wx.Rect(x, y, bitmap_size[0], bitmap_size[1])
+
+ def RefreshBackground(self, refresh_base=False):
+ dc = wx.ClientDC(self)
+ dc.Clear()
+
+ bitmap_rect = self.GetBitmapRect()
+ dc.DrawBitmap(self.BackgroundBitmap, bitmap_rect.x, bitmap_rect.y)
+
+ if refresh_base:
+ self.Refresh(False)
+
+ def OnEraseBackground(self, event):
+ self.RefreshBackground(True)
+
+ def OnLeftUp(self, event):
+ if self.Enabled:
+ pos = event.GetPosition()
+ item, flags = self.HitTest(pos)
+
+ bitmap_rect = self.GetBitmapRect()
+ if (bitmap_rect.InsideXY(pos.x, pos.y) or
+ flags & wx.TREE_HITTEST_NOWHERE) and self.AddMenu is not None:
+ self.PopupMenuXY(self.AddMenu, pos.x, pos.y)
+ event.Skip()
+
+ def OnScroll(self, event):
+ self.RefreshBackground(True)
+ event.Skip()
+
+ def OnResize(self, event):
+ self.RefreshBackground(True)
+ event.Skip()
+
+ def OnPaint(self, event):
+ self.RefreshBackground()
+ event.Skip()
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,325 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+
+import wx
+import wx.lib.buttons
+
+from graphics import DebugDataConsumer, DebugViewer
+from controls import CustomGrid, CustomTable
+from dialogs.ForceVariableDialog import ForceVariableDialog
+from util.BitmapLibrary import GetBitmap
+
+def AppendMenu(parent, help, id, kind, text):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+
+def GetDebugVariablesTableColnames():
+ _ = lambda x : x
+ return [_("Variable"), _("Value")]
+
+class VariableTableItem(DebugDataConsumer):
+
+ def __init__(self, parent, variable, value):
+ DebugDataConsumer.__init__(self)
+ self.Parent = parent
+ self.Variable = variable
+ self.Value = value
+
+ def __del__(self):
+ self.Parent = None
+
+ def SetVariable(self, variable):
+ if self.Parent and self.Variable != variable:
+ self.Variable = variable
+ self.Parent.RefreshGrid()
+
+ def GetVariable(self):
+ return self.Variable
+
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ self.Parent.HasNewData = True
+
+ def SetValue(self, value):
+ if self.Value != value:
+ self.Value = value
+ self.Parent.HasNewData = True
+
+ def GetValue(self):
+ return self.Value
+
+class DebugVariableTable(CustomTable):
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ return self.GetValueByName(row, self.GetColLabelValue(col, False))
+ return ""
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ self.SetValueByName(row, self.GetColLabelValue(col, False), value)
+
+ def GetValueByName(self, row, colname):
+ if row < self.GetNumberRows():
+ if colname == "Variable":
+ return self.data[row].GetVariable()
+ elif colname == "Value":
+ return self.data[row].GetValue()
+ return ""
+
+ def SetValueByName(self, row, colname, value):
+ if row < self.GetNumberRows():
+ if colname == "Variable":
+ self.data[row].SetVariable(value)
+ elif colname == "Value":
+ self.data[row].SetValue(value)
+
+ def IsForced(self, row):
+ if row < self.GetNumberRows():
+ return self.data[row].IsForced()
+ return False
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ if self.GetColLabelValue(col, False) == "Value":
+ if self.IsForced(row):
+ grid.SetCellTextColour(row, col, wx.BLUE)
+ else:
+ grid.SetCellTextColour(row, col, wx.BLACK)
+ grid.SetReadOnly(row, col, True)
+ self.ResizeRow(grid, row)
+
+ def AppendItem(self, data):
+ self.data.append(data)
+
+ def InsertItem(self, idx, data):
+ self.data.insert(idx, data)
+
+ def RemoveItem(self, idx):
+ self.data.pop(idx)
+
+ def MoveItem(self, idx, new_idx):
+ self.data.insert(new_idx, self.data.pop(idx))
+
+ def GetItem(self, idx):
+ return self.data[idx]
+
+class DebugVariableDropTarget(wx.TextDropTarget):
+
+ def __init__(self, parent):
+ wx.TextDropTarget.__init__(self)
+ self.ParentWindow = parent
+
+ def OnDropText(self, x, y, data):
+ x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
+ row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize())
+ if row == wx.NOT_FOUND:
+ row = self.ParentWindow.Table.GetNumberRows()
+ message = None
+ try:
+ values = eval(data)
+ except:
+ message = _("Invalid value \"%s\" for debug variable")%data
+ values = None
+ if not isinstance(values, TupleType):
+ message = _("Invalid value \"%s\" for debug variable")%data
+ values = None
+ if values is not None and values[1] == "debug":
+ self.ParentWindow.InsertValue(values[0], row)
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ def ShowMessage(self, message):
+ dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+class DebugVariablePanel(wx.Panel, DebugViewer):
+
+ def __init__(self, parent, producer):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+ DebugViewer.__init__(self, producer, True)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(button_sizer, border=5,
+ flag=wx.ALIGN_RIGHT|wx.ALL)
+
+ for name, bitmap, help in [
+ ("DeleteButton", "remove_element", _("Remove debug variable")),
+ ("UpButton", "up", _("Move debug variable up")),
+ ("DownButton", "down", _("Move debug variable down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+
+ self.VariablesGrid = CustomGrid(self, size=wx.Size(0, 150), style=wx.VSCROLL)
+ self.VariablesGrid.SetDropTarget(DebugVariableDropTarget(self))
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK,
+ self.OnVariablesGridCellRightClick)
+ main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+
+ self.SetSizer(main_sizer)
+
+ self.HasNewData = False
+
+ self.Table = DebugVariableTable(self, [], GetDebugVariablesTableColnames())
+ self.VariablesGrid.SetTable(self.Table)
+ self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
+ "Up": self.UpButton,
+ "Down": self.DownButton})
+
+ def _AddVariable(new_row):
+ return self.VariablesGrid.GetGridCursorRow()
+ setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+
+ def _DeleteVariable(row):
+ item = self.Table.GetItem(row)
+ self.RemoveDataConsumer(item)
+ self.Table.RemoveItem(row)
+ self.RefreshGrid()
+ setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+
+ def _MoveVariable(row, move):
+ new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
+ if new_row != row:
+ self.Table.MoveItem(row, new_row)
+ self.RefreshGrid()
+ return new_row
+ setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+
+ self.VariablesGrid.SetRowLabelSize(0)
+
+ for col in range(self.Table.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
+ self.VariablesGrid.SetColAttr(col, attr)
+ self.VariablesGrid.SetColSize(col, 100)
+
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+
+ def RefreshNewData(self):
+ if self.HasNewData:
+ self.HasNewData = False
+ self.RefreshGrid()
+ DebugViewer.RefreshNewData(self)
+
+ def RefreshGrid(self):
+ self.Freeze()
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+ self.Thaw()
+
+ def UnregisterObsoleteData(self):
+ items = [(idx, item) for idx, item in enumerate(self.Table.GetData())]
+ items.reverse()
+ for idx, item in items:
+ iec_path = item.GetVariable().upper()
+ if self.GetDataType(iec_path) is None:
+ self.RemoveDataConsumer(item)
+ self.Table.RemoveItem(idx)
+ else:
+ self.AddDataConsumer(iec_path, item)
+ self.Freeze()
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+ self.Thaw()
+
+ def ResetGrid(self):
+ self.DeleteDataConsumers()
+ self.Table.Empty()
+ self.Freeze()
+ self.Table.ResetView(self.VariablesGrid)
+ self.VariablesGrid.RefreshButtons()
+ self.Thaw()
+
+ def GetForceVariableMenuFunction(self, iec_path, item):
+ iec_type = self.GetDataType(iec_path)
+ def ForceVariableFunction(event):
+ if iec_type is not None:
+ dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
+ if dialog.ShowModal() == wx.ID_OK:
+ self.ForceDataValue(iec_path, dialog.GetValue())
+ return ForceVariableFunction
+
+ def GetReleaseVariableMenuFunction(self, iec_path):
+ def ReleaseVariableFunction(event):
+ self.ReleaseDataValue(iec_path)
+ return ReleaseVariableFunction
+
+ def OnVariablesGridCellRightClick(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ if self.Table.GetColLabelValue(col, False) == "Value":
+ iec_path = self.Table.GetValueByName(row, "Variable").upper()
+
+ menu = wx.Menu(title='')
+
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Force value"))
+ self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.Table.GetItem(row)), id=new_id)
+
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Release value"))
+ self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), id=new_id)
+
+ if self.Table.IsForced(row):
+ menu.Enable(new_id, True)
+ else:
+ menu.Enable(new_id, False)
+
+ self.PopupMenu(menu)
+
+ menu.Destroy()
+ event.Skip()
+
+ def InsertValue(self, iec_path, idx = None, force=False):
+ if idx is None:
+ idx = self.Table.GetNumberRows()
+ for item in self.Table.GetData():
+ if iec_path == item.GetVariable():
+ return
+ item = VariableTableItem(self, iec_path, "")
+ result = self.AddDataConsumer(iec_path.upper(), item)
+ if result is not None or force:
+ self.Table.InsertItem(idx, item)
+ self.RefreshGrid()
+
+ def GetDebugVariables(self):
+ return [item.GetVariable() for item in self.Table.GetData()]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DurationCellEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from dialogs.DurationEditorDialog import DurationEditorDialog
+
+class DurationCellControl(wx.PyControl):
+
+ '''
+ Custom cell editor control with a text box and a button that launches
+ the DurationEditorDialog.
+ '''
+ def __init__(self, parent):
+ wx.Control.__init__(self, parent)
+
+ main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ # create location text control
+ self.Duration = wx.TextCtrl(self, size=wx.Size(0, -1),
+ style=wx.TE_PROCESS_ENTER)
+ self.Duration.Bind(wx.EVT_KEY_DOWN, self.OnDurationChar)
+ main_sizer.AddWindow(self.Duration, flag=wx.GROW)
+
+ # create browse button
+ self.EditButton = wx.Button(self, label='...', size=wx.Size(30, -1))
+ self.Bind(wx.EVT_BUTTON, self.OnEditButtonClick, self.EditButton)
+ main_sizer.AddWindow(self.EditButton, flag=wx.GROW)
+
+ self.Bind(wx.EVT_SIZE, self.OnSize)
+
+ self.SetSizer(main_sizer)
+
+ self.Default = None
+
+ def SetValue(self, value):
+ self.Default = value
+ self.Duration.SetValue(value)
+
+ def GetValue(self):
+ return self.Duration.GetValue()
+
+ def OnSize(self, event):
+ self.Layout()
+
+ def OnEditButtonClick(self, event):
+ # pop up the Duration Editor dialog
+ dialog = DurationEditorDialog(self)
+ dialog.SetDuration(self.Duration.GetValue())
+ if dialog.ShowModal() == wx.ID_OK:
+ # set the duration
+ self.Duration.SetValue(dialog.GetDuration())
+
+ dialog.Destroy()
+
+ self.Duration.SetFocus()
+
+ def OnDurationChar(self, event):
+ keycode = event.GetKeyCode()
+ if keycode in [wx.WXK_RETURN, wx.WXK_TAB]:
+ self.Parent.Parent.ProcessEvent(event)
+ elif keycode == wx.WXK_ESCAPE:
+ self.Duration.SetValue(self.Default)
+ self.Parent.Parent.CloseEditControl()
+ else:
+ event.Skip()
+
+ def SetInsertionPoint(self, i):
+ self.Duration.SetInsertionPoint(i)
+
+ def SetFocus(self):
+ self.Duration.SetFocus()
+
+class DurationCellEditor(wx.grid.PyGridCellEditor):
+ '''
+ Grid cell editor that uses DurationCellControl to display an edit button.
+ '''
+ def __init__(self, table):
+ wx.grid.PyGridCellEditor.__init__(self)
+
+ self.Table = table
+
+ def __del__(self):
+ self.CellControl = None
+
+ def Create(self, parent, id, evt_handler):
+ self.CellControl = DurationCellControl(parent)
+ self.SetControl(self.CellControl)
+ if evt_handler:
+ self.CellControl.PushEventHandler(evt_handler)
+
+ def BeginEdit(self, row, col, grid):
+ self.CellControl.Enable()
+ self.CellControl.SetValue(self.Table.GetValueByName(row, 'Interval'))
+ self.CellControl.SetFocus()
+
+ def EndEdit(self, row, col, grid):
+ duration = self.CellControl.GetValue()
+ old_duration = self.Table.GetValueByName(row, 'Interval')
+ changed = duration != old_duration
+ if changed:
+ self.Table.SetValueByName(row, 'Interval', duration)
+ self.CellControl.Disable()
+ return changed
+
+ def SetSize(self, rect):
+ self.CellControl.SetDimensions(rect.x + 1, rect.y,
+ rect.width, rect.height,
+ wx.SIZE_ALLOW_MINUS_ONE)
+
+ def Clone(self):
+ return DurationCellEditor(self.Table)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/LibraryPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,295 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+[CATEGORY, BLOCK] = range(2)
+
+#-------------------------------------------------------------------------------
+# Library Panel
+#-------------------------------------------------------------------------------
+
+class LibraryPanel(wx.Panel):
+
+ def __init__(self, parent, enable_drag=False):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ self.SearchCtrl = wx.SearchCtrl(self)
+ self.SearchCtrl.ShowSearchButton(True)
+ self.Bind(wx.EVT_TEXT, self.OnSearchCtrlChanged, self.SearchCtrl)
+ self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
+ self.OnSearchButtonClick, self.SearchCtrl)
+ search_textctrl = self.SearchCtrl.GetChildren()[0]
+ search_textctrl.Bind(wx.EVT_CHAR, self.OnKeyDown)
+ main_sizer.AddWindow(self.SearchCtrl, flag=wx.GROW)
+
+ splitter_window = wx.SplitterWindow(self)
+ splitter_window.SetSashGravity(1.0)
+ main_sizer.AddWindow(splitter_window, flag=wx.GROW)
+
+ self.Tree = wx.TreeCtrl(splitter_window,
+ size=wx.Size(0, 0),
+ style=wx.TR_HAS_BUTTONS|
+ wx.TR_SINGLE|
+ wx.SUNKEN_BORDER|
+ wx.TR_HIDE_ROOT|
+ wx.TR_LINES_AT_ROOT)
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemSelected, self.Tree)
+ self.Tree.Bind(wx.EVT_CHAR, self.OnKeyDown)
+ if enable_drag:
+ self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, self.Tree)
+
+ self.Comment = wx.TextCtrl(splitter_window, size=wx.Size(0, 80),
+ style=wx.TE_READONLY|wx.TE_MULTILINE)
+
+ splitter_window.SplitHorizontally(self.Tree, self.Comment, -80)
+
+ self.SetSizer(main_sizer)
+
+ self.Controller = None
+
+ self.BlockList = None
+
+ def __del__(self):
+ self.Controller = None
+
+ def SetController(self, controller):
+ self.Controller = controller
+
+ def SetBlockList(self, blocklist):
+ self.BlockList = blocklist
+ self.RefreshTree()
+
+ def SetFocus(self):
+ self.SearchCtrl.SetFocus()
+
+ def ResetTree(self):
+ self.SearchCtrl.SetValue("")
+ self.Tree.DeleteAllItems()
+ self.Comment.SetValue("")
+
+ def RefreshTree(self):
+ if self.Controller is not None:
+ to_delete = []
+ selected_name = None
+ selected = self.Tree.GetSelection()
+ if selected.IsOk():
+ selected_pydata = self.Tree.GetPyData(selected)
+ if selected_pydata is not None and selected_pydata["type"] != CATEGORY:
+ selected_name = self.Tree.GetItemText(selected)
+ if self.BlockList is not None:
+ blocktypes = self.BlockList
+ else:
+ blocktypes = self.Controller.GetBlockTypes()
+ root = self.Tree.GetRootItem()
+ if not root.IsOk():
+ root = self.Tree.AddRoot("")
+ category_item, root_cookie = self.Tree.GetFirstChild(root)
+ for category in blocktypes:
+ category_name = category["name"]
+ if not category_item.IsOk():
+ category_item = self.Tree.AppendItem(root, _(category_name))
+ if wx.Platform != '__WXMSW__':
+ category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ else:
+ self.Tree.SetItemText(category_item, _(category_name))
+ self.Tree.SetPyData(category_item, {"type" : CATEGORY})
+ blocktype_item, category_cookie = self.Tree.GetFirstChild(category_item)
+ for blocktype in category["list"]:
+ if not blocktype_item.IsOk():
+ blocktype_item = self.Tree.AppendItem(category_item, blocktype["name"])
+ if wx.Platform != '__WXMSW__':
+ blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
+ else:
+ self.Tree.SetItemText(blocktype_item, blocktype["name"])
+ block_data = {"type" : BLOCK,
+ "block_type" : blocktype["type"],
+ "inputs" : tuple([type for name, type, modifier in blocktype["inputs"]]),
+ "extension" : None}
+ if blocktype["extensible"]:
+ block_data["extension"] = len(blocktype["inputs"])
+ self.Tree.SetPyData(blocktype_item, block_data)
+ if selected_name == blocktype["name"]:
+ self.Tree.SelectItem(blocktype_item)
+ comment = blocktype["comment"]
+ self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
+ blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
+ while blocktype_item.IsOk():
+ to_delete.append(blocktype_item)
+ blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
+ category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ while category_item.IsOk():
+ to_delete.append(category_item)
+ category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ for item in to_delete:
+ self.Tree.Delete(item)
+
+ def GetSelectedBlock(self):
+ selected = self.Tree.GetSelection()
+ if (selected.IsOk() and
+ self.Tree.GetItemParent(selected) != self.Tree.GetRootItem() and
+ selected != self.Tree.GetRootItem()):
+ selected_data = self.Tree.GetPyData(selected)
+ return {"type": self.Tree.GetItemText(selected),
+ "inputs": selected_data["inputs"]}
+ return None
+
+ def SelectTreeItem(self, name, inputs):
+ item = self.FindTreeItem(self.Tree.GetRootItem(), name, inputs)
+ if item is not None and item.IsOk():
+ self.Tree.SelectItem(item)
+ self.Tree.EnsureVisible(item)
+
+ def FindTreeItem(self, root, name, inputs = None):
+ if root.IsOk():
+ pydata = self.Tree.GetPyData(root)
+ if pydata is not None:
+ type_inputs = pydata.get("inputs", None)
+ type_extension = pydata.get("extension", None)
+ if inputs is not None and type_inputs is not None:
+ if type_extension is not None:
+ same_inputs = type_inputs == inputs[:type_extension]
+ else:
+ same_inputs = type_inputs == inputs
+ else:
+ same_inputs = True
+ if pydata is not None and self.Tree.GetItemText(root) == name and same_inputs:
+ return root
+ else:
+ if wx.VERSION < (2, 6, 0):
+ item, root_cookie = self.Tree.GetFirstChild(root, 0)
+ else:
+ item, root_cookie = self.Tree.GetFirstChild(root)
+ while item.IsOk():
+ result = self.FindTreeItem(item, name, inputs)
+ if result:
+ return result
+ item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+ return None
+
+ def SearchInTree(self, value, mode="first"):
+ root = self.Tree.GetRootItem()
+ if not root.IsOk():
+ return False
+
+ if mode == "first":
+ item, item_cookie = self.Tree.GetFirstChild(root)
+ selected = None
+ else:
+ item = self.Tree.GetSelection()
+ selected = item
+ if not item.IsOk():
+ item, item_cookie = self.Tree.GetFirstChild(root)
+ while item.IsOk():
+ item_pydata = self.Tree.GetPyData(item)
+ if item_pydata["type"] == CATEGORY:
+ if mode == "previous":
+ child = self.Tree.GetLastChild(item)
+ else:
+ child, child_cookie = self.Tree.GetFirstChild(item)
+ if child.IsOk():
+ item = child
+ elif mode == "previous":
+ item = self.Tree.GetPrevSibling(item)
+ else:
+ item = self.Tree.GetNextSibling(item)
+ else:
+ name = self.Tree.GetItemText(item)
+ if name.upper().startswith(value.upper()) and item != selected:
+ child, child_cookie = self.Tree.GetFirstChild(root)
+ while child.IsOk():
+ self.Tree.CollapseAllChildren(child)
+ child, child_cookie = self.Tree.GetNextChild(root, child_cookie)
+ self.Tree.SelectItem(item)
+ self.Tree.EnsureVisible(item)
+ return True
+
+ elif mode == "previous":
+ previous = self.Tree.GetPrevSibling(item)
+ if previous.IsOk():
+ item = previous
+ else:
+ parent = self.Tree.GetItemParent(item)
+ item = self.Tree.GetPrevSibling(parent)
+
+ else:
+ next = self.Tree.GetNextSibling(item)
+ if next.IsOk():
+ item = next
+ else:
+ parent = self.Tree.GetItemParent(item)
+ item = self.Tree.GetNextSibling(parent)
+ return False
+
+ def OnSearchCtrlChanged(self, event):
+ self.SearchInTree(self.SearchCtrl.GetValue())
+ event.Skip()
+
+ def OnSearchButtonClick(self, event):
+ self.SearchInTree(self.SearchCtrl.GetValue(), "next")
+ event.Skip()
+
+ def OnTreeItemSelected(self, event):
+ selected = event.GetItem()
+ pydata = self.Tree.GetPyData(selected)
+ if pydata is not None and pydata["type"] != CATEGORY:
+ blocktype = self.Controller.GetBlockType(self.Tree.GetItemText(selected), pydata["inputs"])
+ if blocktype:
+ comment = blocktype["comment"]
+ self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
+ else:
+ self.Comment.SetValue("")
+ else:
+ self.Comment.SetValue("")
+ if getattr(self, "_OnTreeItemSelected", None) is not None:
+ self._OnTreeItemSelected(event)
+ event.Skip()
+
+ def OnTreeBeginDrag(self, event):
+ selected = event.GetItem()
+ pydata = self.Tree.GetPyData(selected)
+ if pydata is not None and pydata["type"] == BLOCK:
+ data = wx.TextDataObject(str((self.Tree.GetItemText(selected),
+ pydata["block_type"], "", pydata["inputs"])))
+ dragSource = wx.DropSource(self.Tree)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+
+ def OnKeyDown(self, event):
+ keycode = event.GetKeyCode()
+ search_value = self.SearchCtrl.GetValue()
+ if keycode == wx.WXK_UP and search_value != "":
+ self.SearchInTree(search_value, "previous")
+ elif keycode == wx.WXK_DOWN and search_value != "":
+ self.SearchInTree(search_value, "next")
+ else:
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/LocationCellEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from dialogs.BrowseLocationsDialog import BrowseLocationsDialog
+
+class LocationCellControl(wx.PyControl):
+
+ '''
+ Custom cell editor control with a text box and a button that launches
+ the BrowseLocationsDialog.
+ '''
+ def __init__(self, parent):
+ wx.Control.__init__(self, parent)
+
+ main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ # create location text control
+ self.Location = wx.TextCtrl(self, size=wx.Size(0, -1),
+ style=wx.TE_PROCESS_ENTER)
+ self.Location.Bind(wx.EVT_KEY_DOWN, self.OnLocationChar)
+ main_sizer.AddWindow(self.Location, flag=wx.GROW)
+
+ # create browse button
+ self.BrowseButton = wx.Button(self, label='...', size=wx.Size(30, -1))
+ self.BrowseButton.Bind(wx.EVT_BUTTON, self.OnBrowseButtonClick)
+ main_sizer.AddWindow(self.BrowseButton, flag=wx.GROW)
+
+ self.Bind(wx.EVT_SIZE, self.OnSize)
+
+ self.SetSizer(main_sizer)
+
+ self.Locations = None
+ self.VarType = None
+ self.Default = False
+
+ def SetLocations(self, locations):
+ self.Locations = locations
+
+ def SetVarType(self, vartype):
+ self.VarType = vartype
+
+ def SetValue(self, value):
+ self.Default = value
+ self.Location.SetValue(value)
+
+ def GetValue(self):
+ return self.Location.GetValue()
+
+ def OnSize(self, event):
+ self.Layout()
+
+ def OnBrowseButtonClick(self, event):
+ # pop up the location browser dialog
+ dialog = BrowseLocationsDialog(self, self.VarType, self.Locations)
+ if dialog.ShowModal() == wx.ID_OK:
+ infos = dialog.GetValues()
+
+ # set the location
+ self.Location.SetValue(infos["location"])
+
+ dialog.Destroy()
+
+ self.Location.SetFocus()
+
+ def OnLocationChar(self, event):
+ keycode = event.GetKeyCode()
+ if keycode in [wx.WXK_RETURN, wx.WXK_TAB]:
+ self.Parent.Parent.ProcessEvent(event)
+ elif keycode == wx.WXK_ESCAPE:
+ self.Location.SetValue(self.Default)
+ self.Parent.Parent.CloseEditControl()
+ else:
+ event.Skip()
+
+ def SetInsertionPoint(self, i):
+ self.Location.SetInsertionPoint(i)
+
+ def SetFocus(self):
+ self.Location.SetFocus()
+
+class LocationCellEditor(wx.grid.PyGridCellEditor):
+ '''
+ Grid cell editor that uses LocationCellControl to display a browse button.
+ '''
+ def __init__(self, table, controller):
+ wx.grid.PyGridCellEditor.__init__(self)
+
+ self.Table = table
+ self.Controller = controller
+
+ def __del__(self):
+ self.CellControl = None
+
+ def Create(self, parent, id, evt_handler):
+ self.CellControl = LocationCellControl(parent)
+ self.SetControl(self.CellControl)
+ if evt_handler:
+ self.CellControl.PushEventHandler(evt_handler)
+
+ def BeginEdit(self, row, col, grid):
+ self.CellControl.Enable()
+ self.CellControl.SetLocations(self.Controller.GetVariableLocationTree())
+ self.CellControl.SetValue(self.Table.GetValueByName(row, 'Location'))
+ if isinstance(self.CellControl, LocationCellControl):
+ self.CellControl.SetVarType(self.Controller.GetBaseType(self.Table.GetValueByName(row, 'Type')))
+ self.CellControl.SetFocus()
+
+ def EndEdit(self, row, col, grid):
+ loc = self.CellControl.GetValue()
+ old_loc = self.Table.GetValueByName(row, 'Location')
+ changed = loc != old_loc
+ if changed:
+ self.Table.SetValueByName(row, 'Location', loc)
+ self.CellControl.Disable()
+ return changed
+
+ def SetSize(self, rect):
+ self.CellControl.SetDimensions(rect.x + 1, rect.y,
+ rect.width, rect.height,
+ wx.SIZE_ALLOW_MINUS_ONE)
+
+ def Clone(self):
+ return LocationCellEditor(self.Table, self.Controller)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/PouInstanceVariablesPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,313 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.lib.buttons
+import wx.lib.agw.customtreectrl as CT
+
+from PLCControler import ITEMS_VARIABLE, ITEM_CONFIGURATION, ITEM_RESOURCE, ITEM_POU
+from util.BitmapLibrary import GetBitmap
+
+class PouInstanceVariablesPanel(wx.Panel):
+
+ def __init__(self, parent, window, controller, debug):
+ wx.Panel.__init__(self, name='PouInstanceTreePanel',
+ parent=parent, pos=wx.Point(0, 0),
+ size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
+
+ self.ParentButton = wx.lib.buttons.GenBitmapButton(self,
+ bitmap=GetBitmap("top"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ParentButton.SetToolTipString(_("Parent instance"))
+ self.Bind(wx.EVT_BUTTON, self.OnParentButtonClick,
+ self.ParentButton)
+
+ self.InstanceChoice = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnInstanceChoiceChanged,
+ self.InstanceChoice)
+ self.InstanceChoice.Bind(wx.EVT_LEFT_DOWN, self.OnInstanceChoiceLeftDown)
+
+ self.DebugButton = wx.lib.buttons.GenBitmapButton(self,
+ bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ParentButton.SetToolTipString(_("Debug instance"))
+ self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick,
+ self.DebugButton)
+
+ self.VariablesList = CT.CustomTreeCtrl(self,
+ style=wx.SUNKEN_BORDER,
+ agwStyle=CT.TR_NO_BUTTONS|
+ CT.TR_SINGLE|
+ CT.TR_HAS_VARIABLE_ROW_HEIGHT|
+ CT.TR_HIDE_ROOT|
+ CT.TR_NO_LINES|
+ getattr(CT, "TR_ALIGN_WINDOWS_RIGHT", CT.TR_ALIGN_WINDOWS))
+ self.VariablesList.SetIndent(0)
+ self.VariablesList.SetSpacing(5)
+ self.VariablesList.DoSelectItem = lambda *x,**y:True
+ self.VariablesList.Bind(CT.EVT_TREE_ITEM_ACTIVATED,
+ self.OnVariablesListItemActivated)
+ self.VariablesList.Bind(wx.EVT_LEFT_DOWN, self.OnVariablesListLeftDown)
+
+ buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
+ buttons_sizer.AddWindow(self.ParentButton)
+ buttons_sizer.AddWindow(self.InstanceChoice, flag=wx.GROW)
+ buttons_sizer.AddWindow(self.DebugButton)
+ buttons_sizer.AddGrowableCol(1)
+ buttons_sizer.AddGrowableRow(0)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ main_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
+ main_sizer.AddWindow(self.VariablesList, flag=wx.GROW)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ self.SetSizer(main_sizer)
+
+ self.ParentWindow = window
+ self.Controller = controller
+ self.Debug = debug
+ if not self.Debug:
+ self.DebugButton.Hide()
+
+ self.PouTagName = None
+ self.PouInfos = None
+ self.PouInstance = None
+
+ def __del__(self):
+ self.Controller = None
+
+ def SetTreeImageList(self, tree_image_list):
+ self.VariablesList.SetImageList(tree_image_list)
+
+ def SetController(self, controller):
+ self.Controller = controller
+
+ self.RefreshView()
+
+ def SetPouType(self, tagname, pou_instance=None):
+ self.PouTagName = tagname
+ if pou_instance is not None:
+ self.PouInstance = pou_instance
+
+ self.RefreshView()
+
+ def ResetView(self):
+ self.Controller = None
+
+ self.PouTagName = None
+ self.PouInfos = None
+ self.PouInstance = None
+
+ self.RefreshView()
+
+ def RefreshView(self):
+ self.VariablesList.DeleteAllItems()
+ self.InstanceChoice.Clear()
+ self.InstanceChoice.SetValue("")
+
+ if self.Controller is not None and self.PouTagName is not None:
+ self.PouInfos = self.Controller.GetPouVariables(self.PouTagName, self.Debug)
+ else:
+ self.PouInfos = None
+ if self.PouInfos is not None:
+ root = self.VariablesList.AddRoot("")
+ for var_infos in self.PouInfos["variables"]:
+ if var_infos.get("type", None) is not None:
+ text = "%(name)s (%(type)s)" % var_infos
+ else:
+ text = var_infos["name"]
+
+ panel = wx.Panel(self.VariablesList)
+
+ buttons = []
+ if var_infos["class"] in ITEMS_VARIABLE:
+ if (var_infos["debug"] and self.Debug and
+ (self.Controller.IsOfType(var_infos["type"], "ANY_NUM", True) or
+ self.Controller.IsOfType(var_infos["type"], "ANY_BIT", True))):
+ graph_button = wx.lib.buttons.GenBitmapButton(panel,
+ bitmap=GetBitmap("instance_graph"),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.Bind(wx.EVT_BUTTON, self.GenGraphButtonCallback(var_infos), graph_button)
+ buttons.append(graph_button)
+ elif var_infos["edit"]:
+ edit_button = wx.lib.buttons.GenBitmapButton(panel,
+ bitmap=GetBitmap("edit"),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.Bind(wx.EVT_BUTTON, self.GenEditButtonCallback(var_infos), edit_button)
+ buttons.append(edit_button)
+
+ if var_infos["debug"] and self.Debug:
+ debug_button = wx.lib.buttons.GenBitmapButton(panel,
+ bitmap=GetBitmap("debug_instance"),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.Bind(wx.EVT_BUTTON, self.GenDebugButtonCallback(var_infos), debug_button)
+ buttons.append(debug_button)
+
+ button_num = len(buttons)
+ if button_num > 0:
+ panel.SetSize(wx.Size(button_num * 32, 28))
+ panel.SetBackgroundColour(self.VariablesList.GetBackgroundColour())
+ panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ panel.SetSizer(panel_sizer)
+
+ for button in buttons:
+ panel_sizer.AddWindow(button, 0, border=4, flag=wx.LEFT)
+ panel_sizer.Layout()
+
+ else:
+ panel.Destroy()
+ panel = None
+
+ item = self.VariablesList.AppendItem(root, text, wnd=panel)
+ self.VariablesList.SetItemImage(item, self.ParentWindow.GetTreeImage(var_infos["class"]))
+ self.VariablesList.SetPyData(item, var_infos)
+
+ instances = self.Controller.SearchPouInstances(self.PouTagName, self.Debug)
+ for instance in instances:
+ self.InstanceChoice.Append(instance)
+ if len(instances) == 1:
+ self.PouInstance = instances[0]
+ if self.PouInfos["class"] in [ITEM_CONFIGURATION, ITEM_RESOURCE]:
+ self.PouInstance = None
+ self.InstanceChoice.SetSelection(0)
+ elif self.PouInstance in instances:
+ self.InstanceChoice.SetStringSelection(self.PouInstance)
+ else:
+ self.PouInstance = None
+ self.InstanceChoice.SetValue(_("Select an instance"))
+
+ self.RefreshButtons()
+
+ def RefreshButtons(self):
+ enabled = self.InstanceChoice.GetSelection() != -1
+ self.ParentButton.Enable(enabled and self.PouInfos["class"] != ITEM_CONFIGURATION)
+ self.DebugButton.Enable(enabled and self.PouInfos["debug"] and self.Debug)
+
+ root = self.VariablesList.GetRootItem()
+ if root is not None and root.IsOk():
+ item, item_cookie = self.VariablesList.GetFirstChild(root)
+ while item is not None and item.IsOk():
+ panel = self.VariablesList.GetItemWindow(item)
+ if panel is not None:
+ for child in panel.GetChildren():
+ if child.GetName() != "edit":
+ child.Enable(enabled)
+ item, item_cookie = self.VariablesList.GetNextChild(root, item_cookie)
+
+ def GenEditButtonCallback(self, infos):
+ def EditButtonCallback(event):
+ var_class = infos["class"]
+ if var_class == ITEM_RESOURCE:
+ tagname = self.Controller.ComputeConfigurationResourceName(
+ self.InstanceChoice.GetStringSelection(),
+ infos["name"])
+ else:
+ var_class = ITEM_POU
+ tagname = self.Controller.ComputePouName(infos["type"])
+ self.ParentWindow.EditProjectElement(var_class, tagname)
+ event.Skip()
+ return EditButtonCallback
+
+ def GenDebugButtonCallback(self, infos):
+ def DebugButtonCallback(event):
+ if self.InstanceChoice.GetSelection() != -1:
+ var_class = infos["class"]
+ var_path = "%s.%s" % (self.InstanceChoice.GetStringSelection(),
+ infos["name"])
+ if var_class in ITEMS_VARIABLE:
+ self.ParentWindow.AddDebugVariable(var_path, force=True)
+ else:
+ self.ParentWindow.OpenDebugViewer(
+ infos["class"],
+ var_path,
+ self.Controller.ComputePouName(infos["type"]))
+ event.Skip()
+ return DebugButtonCallback
+
+ def GenGraphButtonCallback(self, infos):
+ def GraphButtonCallback(event):
+ if self.InstanceChoice.GetSelection() != -1:
+ if infos["class"] in ITEMS_VARIABLE:
+ var_path = "%s.%s" % (self.InstanceChoice.GetStringSelection(),
+ infos["name"])
+ self.ParentWindow.OpenGraphicViewer(var_path)
+ event.Skip()
+ return GraphButtonCallback
+
+ def ShowInstanceChoicePopup(self):
+ self.InstanceChoice.SetFocusFromKbd()
+ size = self.InstanceChoice.GetSize()
+ event = wx.MouseEvent(wx.EVT_LEFT_DOWN._getEvtType())
+ event.m_x = size.width / 2
+ event.m_y = size.height / 2
+ event.SetEventObject(self.InstanceChoice)
+ #event = wx.KeyEvent(wx.EVT_KEY_DOWN._getEvtType())
+ #event.m_keyCode = wx.WXK_SPACE
+ self.InstanceChoice.GetEventHandler().ProcessEvent(event)
+
+ def OnParentButtonClick(self, event):
+ if self.InstanceChoice.GetSelection() != -1:
+ parent_path = self.InstanceChoice.GetStringSelection().rsplit(".", 1)[0]
+ tagname = self.Controller.GetPouInstanceTagName(parent_path, self.Debug)
+ if tagname is not None:
+ wx.CallAfter(self.SetPouType, tagname, parent_path)
+ wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, tagname)
+ event.Skip()
+
+ def OnInstanceChoiceChanged(self, event):
+ self.RefreshButtons()
+ event.Skip()
+
+ def OnDebugButtonClick(self, event):
+ if self.InstanceChoice.GetSelection() != -1:
+ self.ParentWindow.OpenDebugViewer(
+ self.PouInfos["class"],
+ self.InstanceChoice.GetStringSelection(),
+ self.PouTagName)
+ event.Skip()
+
+ def OnVariablesListItemActivated(self, event):
+ if self.InstanceChoice.GetSelection() != -1:
+ instance_path = self.InstanceChoice.GetStringSelection()
+ selected_item = event.GetItem()
+ if selected_item is not None and selected_item.IsOk():
+ item_infos = self.VariablesList.GetPyData(selected_item)
+ if item_infos is not None and item_infos["class"] not in ITEMS_VARIABLE:
+ if item_infos["class"] == ITEM_RESOURCE:
+ tagname = self.Controller.ComputeConfigurationResourceName(
+ instance_path,
+ item_infos["name"])
+ else:
+ tagname = self.Controller.ComputePouName(item_infos["type"])
+ item_path = "%s.%s" % (instance_path, item_infos["name"])
+ wx.CallAfter(self.SetPouType, tagname, item_path)
+ wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, tagname)
+ event.Skip()
+
+ def OnVariablesListLeftDown(self, event):
+ if self.InstanceChoice.GetSelection() == -1:
+ wx.CallAfter(self.ShowInstanceChoicePopup)
+ event.Skip()
+
+ def OnInstanceChoiceLeftDown(self, event):
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/ProjectPropertiesPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,358 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+REQUIRED_PARAMS = ["projectName", "productName", "productVersion", "companyName"]
+
+[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
+ POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
+] = range(10)
+
+#-------------------------------------------------------------------------------
+# Project Properties Panel
+#-------------------------------------------------------------------------------
+
+class ProjectPropertiesPanel(wx.Notebook):
+
+ def AddSizerParams(self, parent, sizer, params):
+ for idx, (name, label) in enumerate(params):
+ border = 0
+ if idx == 0:
+ border |= wx.TOP
+ elif idx == len(params) - 1:
+ border |= wx.BOTTOM
+
+ st = wx.StaticText(parent, label=label)
+ sizer.AddWindow(st, border=10,
+ flag=wx.ALIGN_CENTER_VERTICAL|border|wx.LEFT)
+
+ tc = wx.TextCtrl(parent, style=wx.TE_PROCESS_ENTER)
+ setattr(self, name, tc)
+ callback = self.GetTextCtrlChangedFunction(tc, name)
+ self.Bind(wx.EVT_TEXT_ENTER, callback, tc)
+ tc.Bind(wx.EVT_KILL_FOCUS, callback)
+ sizer.AddWindow(tc, border=10,
+ flag=wx.GROW|border|wx.RIGHT)
+
+ def __init__(self, parent, controller=None, window=None, enable_required=True):
+ wx.Notebook.__init__(self, parent, size=wx.Size(500, 300))
+
+ self.Controller = controller
+ self.ParentWindow = window
+ self.Values = None
+
+ # Project Panel elements
+
+ self.ProjectPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL)
+ projectpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=5, vgap=15)
+ projectpanel_sizer.AddGrowableCol(1)
+ self.ProjectPanel.SetSizer(projectpanel_sizer)
+
+ self.AddSizerParams(self.ProjectPanel, projectpanel_sizer,
+ [("projectName", _('Project Name (required):')),
+ ("projectVersion", _('Project Version (optional):')),
+ ("productName", _('Product Name (required):')),
+ ("productVersion", _('Product Version (required):')),
+ ("productRelease", _('Product Release (optional):'))])
+
+ self.AddPage(self.ProjectPanel, _("Project"))
+
+ # Author Panel elements
+
+ self.AuthorPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL)
+ authorpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=4, vgap=15)
+ authorpanel_sizer.AddGrowableCol(1)
+ self.AuthorPanel.SetSizer(authorpanel_sizer)
+
+ self.AddSizerParams(self.AuthorPanel, authorpanel_sizer,
+ [("companyName", _('Company Name (required):')),
+ ("companyURL", _('Company URL (optional):')),
+ ("authorName", _('Author Name (optional):')),
+ ("organization", _('Organization (optional):'))])
+
+ self.AddPage(self.AuthorPanel, _("Author"))
+
+ # Graphics Panel elements
+
+ self.GraphicsPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL)
+ graphicpanel_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=4, vgap=5)
+ graphicpanel_sizer.AddGrowableCol(0)
+ graphicpanel_sizer.AddGrowableRow(3)
+ self.GraphicsPanel.SetSizer(graphicpanel_sizer)
+
+ pageSize_st = wx.StaticText(self.GraphicsPanel,
+ label=_('Page Size (optional):'))
+ graphicpanel_sizer.AddWindow(pageSize_st, border=10,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ pageSize_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
+ pageSize_sizer.AddGrowableCol(1)
+ graphicpanel_sizer.AddSizer(pageSize_sizer, border=10,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ for name, label in [('PageWidth', _('Width:')),
+ ('PageHeight', _('Height:'))]:
+ st = wx.StaticText(self.GraphicsPanel, label=label)
+ pageSize_sizer.AddWindow(st, border=12,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT)
+
+ sp = wx.SpinCtrl(self.GraphicsPanel,
+ min=0, max=2**16, style=wx.TE_PROCESS_ENTER)
+ setattr(self, name, sp)
+ callback = self.GetPageSizeChangedFunction(sp, name)
+ self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
+ sp.Bind(wx.EVT_KILL_FOCUS, callback)
+ pageSize_sizer.AddWindow(sp, flag=wx.GROW)
+
+ scaling_st = wx.StaticText(self.GraphicsPanel,
+ label=_('Grid Resolution:'))
+ graphicpanel_sizer.AddWindow(scaling_st, border=10,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ scaling_nb = wx.Notebook(self.GraphicsPanel)
+ graphicpanel_sizer.AddWindow(scaling_nb, border=10,
+ flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.Scalings = {}
+ for language, translation in [("FBD",_("FBD")), ("LD",_("LD")), ("SFC",_("SFC"))]:
+ scaling_panel = wx.Panel(scaling_nb, style=wx.TAB_TRAVERSAL)
+ scalingpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
+ scalingpanel_sizer.AddGrowableCol(1)
+ scaling_panel.SetSizer(scalingpanel_sizer)
+
+ scaling_controls = []
+ for idx, (name, label) in enumerate([('XScale', _('Horizontal:')),
+ ('YScale', _('Vertical:'))]):
+ if idx == 0:
+ border = wx.TOP
+ else:
+ border = wx.BOTTOM
+
+ st = wx.StaticText(scaling_panel, label=label)
+ scalingpanel_sizer.AddWindow(st, border=10,
+ flag=wx.ALIGN_CENTER_VERTICAL|border|wx.LEFT)
+
+ sp = wx.SpinCtrl(scaling_panel,
+ min=0, max=2**16, style=wx.TE_PROCESS_ENTER)
+ scaling_controls.append(sp)
+ callback = self.GetScalingChangedFunction(sp, language, name)
+ self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
+ sp.Bind(wx.EVT_KILL_FOCUS, callback)
+ scalingpanel_sizer.AddWindow(sp, border=10,
+ flag=wx.GROW|border|wx.RIGHT)
+
+ self.Scalings[language] = scaling_controls
+ scaling_nb.AddPage(scaling_panel, translation)
+
+ self.AddPage(self.GraphicsPanel, _("Graphics"))
+
+ # Miscellaneous Panel elements
+
+ self.MiscellaneousPanel = wx.Panel(id=-1, parent=self,
+ name='MiscellaneousPanel', pos=wx.Point(0, 0),
+ size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
+ miscellaneouspanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=15)
+ miscellaneouspanel_sizer.AddGrowableCol(1)
+ miscellaneouspanel_sizer.AddGrowableRow(1)
+ self.MiscellaneousPanel.SetSizer(miscellaneouspanel_sizer)
+
+ language_label = wx.StaticText(self.MiscellaneousPanel,
+ label=_('Language (optional):'))
+ miscellaneouspanel_sizer.AddWindow(language_label, border=10,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.LEFT)
+
+ self.Language = wx.ComboBox(self.MiscellaneousPanel,
+ style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnLanguageChanged, self.Language)
+ miscellaneouspanel_sizer.AddWindow(self.Language, border=10,
+ flag=wx.GROW|wx.TOP|wx.RIGHT)
+
+ description_label = wx.StaticText(self.MiscellaneousPanel,
+ label=_('Content Description (optional):'))
+ miscellaneouspanel_sizer.AddWindow(description_label, border=10,
+ flag=wx.BOTTOM|wx.LEFT)
+
+ self.ContentDescription = wx.TextCtrl(self.MiscellaneousPanel,
+ style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnContentDescriptionChanged,
+ self.ContentDescription)
+ self.ContentDescription.Bind(wx.EVT_KILL_FOCUS,
+ self.OnContentDescriptionChanged)
+ miscellaneouspanel_sizer.AddWindow(self.ContentDescription, border=10,
+ flag=wx.GROW|wx.BOTTOM|wx.RIGHT)
+
+ self.AddPage(self.MiscellaneousPanel, _("Miscellaneous"))
+
+ for param in REQUIRED_PARAMS:
+ getattr(self, param).Enable(enable_required)
+
+ languages = ["", "en-US", "fr-FR", "zh-CN"]
+
+ for language in languages:
+ self.Language.Append(language)
+
+ def RefreshView(self):
+ if self.Controller is not None:
+ self.SetValues(self.Controller.GetProjectProperties())
+
+ def SetValues(self, values):
+ self.Values = values
+ for item, value in values.items():
+ if item == "language":
+ self.Language.SetStringSelection(value)
+ elif item == "contentDescription":
+ self.ContentDescription.SetValue(value)
+ elif item == "pageSize":
+ self.PageWidth.SetValue(value[0])
+ self.PageHeight.SetValue(value[1])
+ elif item == "scaling":
+ for language, (x, y) in value.items():
+ if language in self.Scalings:
+ self.Scalings[language][0].SetValue(x)
+ self.Scalings[language][1].SetValue(y)
+ else:
+ tc = getattr(self, item, None)
+ if tc is not None:
+ tc.SetValue(value)
+
+ def GetValues(self):
+ values = {}
+ for param in ["projectName", "projectVersion",
+ "productName", "productVersion",
+ "productRelease", "companyName",
+ "companyURL", "authorName",
+ "organization"]:
+ value = getattr(self, param).GetValue()
+ if param in REQUIRED_PARAMS or value != "":
+ values[param] = value
+ else:
+ values[param] = None
+ language = self.Language.GetStringSelection()
+ if language != "":
+ values["language"] = language
+ else:
+ values["language"] = None
+ content_description = self.ContentDescription.GetValue()
+ if content_description != "":
+ values["contentDescription"] = content_description
+ else:
+ values["contentDescription"] = None
+ values["pageSize"] = (self.PageWidth.GetValue(), self.PageHeight.GetValue())
+ values["scaling"] = {}
+ for language in ["FBD", "LD", "SFC"]:
+ values["scaling"][language] = (self.Scalings[language][0].GetValue(),
+ self.Scalings[language][1].GetValue())
+ return values
+
+ def GetTextCtrlChangedFunction(self, textctrl, name):
+ def TextCtrlChangedFunction(event):
+ if self.Controller is not None:
+ if self.Values is not None:
+ old_value = self.Values.get(name)
+ else:
+ old_value = None
+ new_value = textctrl.GetValue()
+ if name not in REQUIRED_PARAMS and new_value == "":
+ new_value = None
+ if old_value != new_value:
+ self.Controller.SetProjectProperties(properties={name: new_value})
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
+ PROJECTTREE, PAGETITLES)
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
+ return TextCtrlChangedFunction
+
+ def GetPageSizeChangedFunction(self, spinctrl, name):
+ def PageSizeChangedFunction(event):
+ if self.Controller is not None:
+ if self.Values is not None:
+ old_value = self.Values.get("pageSize")
+ else:
+ old_value = (0, 0)
+ if name == 'PageWidth':
+ new_value = (spinctrl.GetValue(), old_value[1])
+ else:
+ new_value = (old_value[0], spinctrl.GetValue())
+ if old_value != new_value:
+ self.Controller.SetProjectProperties(properties={"pageSize": new_value})
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
+ PAGETITLES, SCALING)
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
+ return PageSizeChangedFunction
+
+ def GetScalingChangedFunction(self, spinctrl, language, name):
+ def ScalingChangedFunction(event):
+ if self.Controller is not None:
+ old_value = (0, 0)
+ if self.Values is not None:
+ scaling = self.Values.get("scaling")
+ if scaling is not None:
+ old_value = scaling.get(language)
+ if name == 'XScale':
+ new_value = (spinctrl.GetValue(), old_value[1])
+ else:
+ new_value = (old_value[0], spinctrl.GetValue())
+ if old_value != new_value:
+ self.Controller.SetProjectProperties(properties={"scaling": {language: new_value}})
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
+ PAGETITLES, SCALING)
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
+ return ScalingChangedFunction
+
+ def OnLanguageChanged(self, event):
+ if self.Controller is not None:
+ if self.Values is not None:
+ old_value = self.Values.get("language")
+ else:
+ old_value = None
+ new_value = self.Language.GetStringSelection()
+ if new_value == "":
+ new_value = None
+ if old_value != new_value:
+ self.Controller.SetProjectProperties(properties={"language": new_value})
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
+
+ def OnContentDescriptionChanged(self, event):
+ if self.Controller is not None:
+ if self.Values is not None:
+ old_value = self.Values.get("contentDescription")
+ else:
+ old_value = None
+ new_value = self.ContentDescription.GetValue()
+ if new_value == "":
+ new_value = None
+ if old_value != new_value:
+ self.Controller.SetProjectProperties(properties={"contentDescription": new_value})
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/SearchResultPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import TupleType
+
+import wx
+import wx.lib.buttons
+import wx.lib.agw.customtreectrl as CT
+
+from PLCControler import *
+from util.BitmapLibrary import GetBitmap
+
+def GenerateName(infos):
+ if infos[0] in ["input", "output", "value"]:
+ return "%s %d:" % (infos[0], infos[1])
+ elif infos[0] == "range":
+ return "%s %d %s" % (infos[0], infos[1], infos[2])
+ elif infos[0] == "struct":
+ return "element %d %s" % (infos[1], infos[2])
+ return "%s:" % infos[0]
+
+#-------------------------------------------------------------------------------
+# Search Result Panel
+#-------------------------------------------------------------------------------
+
+[ID_SEARCHRESULTPANEL, ID_SEARCHRESULTPANELHEADERLABEL,
+ ID_SEARCHRESULTPANELSEARCHRESULTSTREE, ID_SEARCHRESULTPANELRESETBUTTON,
+] = [wx.NewId() for _init_ctrls in range(4)]
+
+class SearchResultPanel(wx.Panel):
+
+ if wx.VERSION < (2, 6, 0):
+ def Bind(self, event, function, id = None):
+ if id is not None:
+ event(self, id, function)
+ else:
+ event(self, function)
+
+ def _init_coll_MainSizer_Items(self, parent):
+ parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW)
+ parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
+
+ def _init_coll_MainSizer_Growables(self, parent):
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(1)
+
+ def _init_coll_HeaderSizer_Items(self, parent):
+ parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+ parent.AddWindow(self.ResetButton, 0, border=0, flag=0)
+
+ def _init_coll_HeaderSizer_Growables(self, parent):
+ parent.AddGrowableCol(0)
+
+ def _init_sizers(self):
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ self.HeaderSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self._init_coll_MainSizer_Items(self.MainSizer)
+ self._init_coll_MainSizer_Growables(self.MainSizer)
+ self._init_coll_HeaderSizer_Items(self.HeaderSizer)
+
+ self.SetSizer(self.MainSizer)
+
+ def _init_ctrls(self, prnt):
+ wx.Panel.__init__(self, id=ID_SEARCHRESULTPANEL,
+ name='SearchResultPanel', parent=prnt, pos=wx.Point(0, 0),
+ size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
+
+ self.HeaderLabel = wx.StaticText(id=ID_SEARCHRESULTPANELHEADERLABEL,
+ name='HeaderLabel', parent=self,
+ pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0)
+
+ search_results_tree_style = CT.TR_HAS_BUTTONS|CT.TR_NO_LINES|CT.TR_HAS_VARIABLE_ROW_HEIGHT
+ self.SearchResultsTree = CT.CustomTreeCtrl(id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE,
+ name="SearchResultsTree", parent=self,
+ pos=wx.Point(0, 0), style=search_results_tree_style)
+ if wx.VERSION >= (2, 8, 11):
+ self.SearchResultsTree.SetAGWWindowStyleFlag(search_results_tree_style)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnSearchResultsTreeItemActivated,
+ id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE)
+
+ self.ResetButton = wx.lib.buttons.GenBitmapButton(self,
+ bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ResetButton.SetToolTipString(_("Reset search result"))
+ self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
+
+ self._init_sizers()
+
+ def __init__(self, parent, window):
+ self.ParentWindow = window
+
+ self._init_ctrls(parent)
+
+ # Define Tree item icon list
+ self.TreeImageList = wx.ImageList(16, 16)
+ self.TreeImageDict = {}
+
+ # Icons for other items
+ for imgname, itemtype in [
+ #editables
+ ("PROJECT", ITEM_PROJECT),
+ ("TRANSITION", ITEM_TRANSITION),
+ ("ACTION", ITEM_ACTION),
+ ("CONFIGURATION", ITEM_CONFIGURATION),
+ ("RESOURCE", ITEM_RESOURCE),
+ ("DATATYPE", ITEM_DATATYPE),
+ ("ACTION", "action_block"),
+ ("IL", "IL"),
+ ("ST", "ST")]:
+ self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
+
+ for itemtype in ["function", "functionBlock", "program",
+ "comment", "block", "io_variable",
+ "connector", "contact", "coil",
+ "step", "transition", "jump",
+ "var_local", "var_input",
+ "var_inout", "var_output"]:
+ self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(itemtype.upper()))
+
+ # Assign icon list to TreeCtrl
+ self.SearchResultsTree.SetImageList(self.TreeImageList)
+
+ self.ResetSearchResults()
+
+ def SetSearchResults(self, criteria, search_results):
+ self.Criteria = criteria
+ self.SearchResults = {}
+ self.ElementsOrder = []
+
+ for infos, start, end, text in search_results:
+ if infos[0] not in self.ElementsOrder:
+ self.ElementsOrder.append(infos[0])
+
+ results = self.SearchResults.setdefault(infos[0], [])
+ results.append((infos, start, end, text))
+
+ self.RefreshView()
+
+ def ResetSearchResults(self):
+ self.Criteria = None
+ self.ElementsOrder = []
+ self.SearchResults = {}
+ self.RefreshView()
+
+ def RefreshView(self):
+ self.SearchResultsTree.DeleteAllItems()
+ if self.Criteria is None:
+ self.HeaderLabel.SetLabel(_("No search results available."))
+ self.ResetButton.Enable(False)
+ else:
+ matches_number = 0
+ search_results_tree_infos = {"name": _("Project '%s':") % self.ParentWindow.Controler.GetProjectName(),
+ "type": ITEM_PROJECT,
+ "data": None,
+ "text": None,
+ "matches": None,
+ }
+ search_results_tree_children = search_results_tree_infos.setdefault("children", [])
+ for tagname in self.ElementsOrder:
+ results = self.SearchResults.get(tagname, [])
+ matches_number += len(results)
+
+ words = tagname.split("::")
+
+ element_type = self.ParentWindow.Controler.GetElementType(tagname)
+ if element_type == ITEM_POU:
+ element_type = self.ParentWindow.Controler.GetPouType(words[1])
+
+ element_infos = {"name": words[-1],
+ "type": element_type,
+ "data": tagname,
+ "text": None,
+ "matches": len(results)}
+
+ children = element_infos.setdefault("children", [])
+ for infos, start, end, text in results:
+ if infos[1] == "name" or element_type == ITEM_DATATYPE:
+ child_name = GenerateName(infos[1:])
+ child_type = element_type
+ else:
+ if element_type == ITEM_RESOURCE:
+ child_type = element_type
+ else:
+ child_type = infos[1]
+ if child_type == "name":
+ child_name = "name"
+ elif child_type == "body":
+ child_name = "body"
+ if element_type == ITEM_TRANSITION:
+ child_type = self.ParentWindow.Controler.GetTransitionBodyType(words[1], words[2])
+ elif element_type == ITEM_ACTION:
+ child_type = self.ParentWindow.Controler.GetActionBodyType(words[1], words[2])
+ else:
+ child_type = self.ParentWindow.Controler.GetPouBodyType(words[1])
+ else:
+ child_name = GenerateName(infos[3:])
+ child_infos = {"name": child_name,
+ "type": child_type,
+ "data": (infos, start, end ,None),
+ "text": text,
+ "matches": 1,
+ "children": [],
+ }
+ children.append(child_infos)
+
+ if len(words) > 2:
+ for _element_infos in search_results_tree_children:
+ if _element_infos["name"] == words[1]:
+ _element_infos["matches"] += len(children)
+ _element_infos["children"].append(element_infos)
+ break
+ else:
+ search_results_tree_children.append(element_infos)
+
+ if matches_number < 2:
+ header_format = _("'%s' - %d match in project")
+ else:
+ header_format = _("'%s' - %d matches in project")
+
+ self.HeaderLabel.SetLabel(header_format % (self.Criteria["raw_pattern"], matches_number))
+ self.ResetButton.Enable(True)
+
+ if matches_number > 0:
+ root = self.SearchResultsTree.GetRootItem()
+ if root is None:
+ root = self.SearchResultsTree.AddRoot(search_results_tree_infos["name"])
+ self.GenerateSearchResultsTreeBranch(root, search_results_tree_infos)
+ self.SearchResultsTree.Expand(root)
+
+ def GetTextCtrlClickFunction(self, item):
+ def OnTextCtrlClick(event):
+ self.SearchResultsTree.SelectItem(item)
+ event.Skip()
+ return OnTextCtrlClick
+
+ def GetTextCtrlDClickFunction(self, item):
+ def OnTextCtrlDClick(event):
+ self.ShowSearchResults(item)
+ event.Skip()
+ return OnTextCtrlDClick
+
+ def GenerateSearchResultsTreeBranch(self, root, infos):
+ to_delete = []
+ if infos["name"] == "body":
+ item_name = "%d:" % infos["data"][1][0]
+ else:
+ item_name = infos["name"]
+
+ self.SearchResultsTree.SetItemText(root, item_name)
+ self.SearchResultsTree.SetPyData(root, infos["data"])
+ self.SearchResultsTree.SetItemBackgroundColour(root, wx.WHITE)
+ self.SearchResultsTree.SetItemTextColour(root, wx.BLACK)
+ if infos["type"] is not None:
+ if infos["type"] == ITEM_POU:
+ self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[self.ParentWindow.Controler.GetPouType(infos["name"])])
+ else:
+ self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[infos["type"]])
+
+ text = None
+ if infos["text"] is not None:
+ text = infos["text"]
+ start, end = infos["data"][1:3]
+ text_lines = infos["text"].splitlines()
+ start_idx = start[1]
+ end_idx = reduce(lambda x, y: x + y, map(lambda x: len(x) + 1, text_lines[:end[0] - start[0]]), end[1] + 1)
+ style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247))
+ elif infos["type"] is not None and infos["matches"] > 1:
+ text = _("(%d matches)") % infos["matches"]
+ start_idx, end_idx = 0, len(text)
+ style = wx.TextAttr(wx.Colour(0, 127, 174))
+
+ if text is not None:
+ text_ctrl_style = wx.BORDER_NONE|wx.TE_READONLY|wx.TE_RICH2
+ if wx.Platform != '__WXMSW__' or len(text.splitlines()) > 1:
+ text_ctrl_style |= wx.TE_MULTILINE
+ text_ctrl = wx.TextCtrl(id=-1, parent=self.SearchResultsTree, pos=wx.Point(0, 0),
+ value=text, style=text_ctrl_style)
+ width, height = text_ctrl.GetTextExtent(text)
+ text_ctrl.SetClientSize(wx.Size(width + 1, height))
+ text_ctrl.SetBackgroundColour(self.SearchResultsTree.GetBackgroundColour())
+ text_ctrl.Bind(wx.EVT_LEFT_DOWN, self.GetTextCtrlClickFunction(root))
+ text_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.GetTextCtrlDClickFunction(root))
+ text_ctrl.SetInsertionPoint(0)
+ text_ctrl.SetStyle(start_idx, end_idx, style)
+ self.SearchResultsTree.SetItemWindow(root, text_ctrl)
+
+ if wx.VERSION >= (2, 6, 0):
+ item, root_cookie = self.SearchResultsTree.GetFirstChild(root)
+ else:
+ item, root_cookie = self.SearchResultsTree.GetFirstChild(root, 0)
+ for child in infos["children"]:
+ if item is None:
+ item = self.SearchResultsTree.AppendItem(root, "")
+ item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
+ self.GenerateSearchResultsTreeBranch(item, child)
+ item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
+
+ def ShowSearchResults(self, item):
+ data = self.SearchResultsTree.GetPyData(item)
+ if isinstance(data, TupleType):
+ search_results = [data]
+ else:
+ search_results = self.SearchResults.get(data, [])
+ for infos, start, end, text in search_results:
+ self.ParentWindow.ShowSearchResult(infos, start, end)
+
+ def OnSearchResultsTreeItemActivated(self, event):
+ self.ShowSearchResults(event.GetItem())
+ event.Skip()
+
+ def OnResetButton(self, event):
+ self.ResetSearchResults()
+ self.ParentWindow.ClearSearchResults()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/TextCtrlAutoComplete.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import cPickle
+
+MAX_ITEM_COUNT = 10
+MAX_ITEM_SHOWN = 6
+if wx.Platform == '__WXMSW__':
+ ITEM_INTERVAL_HEIGHT = 3
+else:
+ ITEM_INTERVAL_HEIGHT = 6
+
+if wx.Platform == '__WXMSW__':
+ popupclass = wx.PopupTransientWindow
+else:
+ popupclass = wx.PopupWindow
+
+class PopupWithListbox(popupclass):
+
+ def __init__(self, parent, choices=[]):
+ popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
+
+ self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
+ if not wx.Platform == '__WXMSW__':
+ self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
+ self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
+
+ self.SetChoices(choices)
+
+ self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+
+ def SetChoices(self, choices):
+ max_text_width = 0
+ max_text_height = 0
+
+ self.ListBox.Clear()
+ for choice in choices:
+ self.ListBox.Append(choice)
+ w, h = self.ListBox.GetTextExtent(choice)
+ max_text_width = max(max_text_width, w)
+ max_text_height = max(max_text_height, h)
+
+ itemcount = min(len(choices), MAX_ITEM_SHOWN)
+ width = self.Parent.GetSize()[0]
+ height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
+ if max_text_width + 10 > width:
+ height += 15
+ size = wx.Size(width, height)
+ self.ListBox.SetSize(size)
+ self.SetClientSize(size)
+
+ def MoveSelection(self, direction):
+ selected = self.ListBox.GetSelection()
+ if selected == wx.NOT_FOUND:
+ if direction >= 0:
+ selected = 0
+ else:
+ selected = self.ListBox.GetCount() - 1
+ else:
+ selected = (selected + direction) % (self.ListBox.GetCount() + 1)
+ if selected == self.ListBox.GetCount():
+ selected = wx.NOT_FOUND
+ self.ListBox.SetSelection(selected)
+
+ def GetSelection(self):
+ return self.ListBox.GetStringSelection()
+
+ def ProcessLeftDown(self, event):
+ selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
+ if selected != wx.NOT_FOUND:
+ wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
+ return False
+
+ def OnListBoxClick(self, event):
+ selected = event.GetSelection()
+ if selected != wx.NOT_FOUND:
+ wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
+ event.Skip()
+
+ def OnKeyDown(self, event):
+ self.Parent.ProcessEvent(event)
+
+ def OnDismiss(self):
+ self.Parent.listbox = None
+ wx.CallAfter(self.Parent.DismissListBox)
+
+class TextCtrlAutoComplete(wx.TextCtrl):
+
+ def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
+ element_path=None, **therest):
+ """
+ Constructor works just like wx.TextCtrl except you can pass in a
+ list of choices. You can also change the choice list at any time
+ by calling setChoices.
+ """
+
+ therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
+
+ wx.TextCtrl.__init__(self, parent, **therest)
+ self.AppFrame = appframe
+
+ #Some variables
+ self._dropDownClick = dropDownClick
+ self._lastinsertionpoint = None
+
+ self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
+ self.element_path = element_path
+
+ self.listbox = None
+
+ self.SetChoices(choices)
+
+ #gp = self
+ #while ( gp != None ) :
+ # gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
+ # gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
+ # gp = gp.GetParent()
+
+ self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged)
+ self.Bind(wx.EVT_TEXT, self.OnEnteredText)
+ self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+
+ #If need drop down on left click
+ if dropDownClick:
+ self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
+ self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
+
+ def __del__(self):
+ self.AppFrame = None
+
+ def ChangeValue(self, value):
+ wx.TextCtrl.ChangeValue(self, value)
+ self.RefreshListBoxChoices()
+
+ def OnEnteredText(self, event):
+ wx.CallAfter(self.RefreshListBoxChoices)
+ event.Skip()
+
+ def OnKeyDown(self, event):
+ """ Do some work when the user press on the keys:
+ up and down: move the cursor
+ """
+ keycode = event.GetKeyCode()
+ if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
+ self.PopupListBox()
+ if keycode == wx.WXK_DOWN:
+ self.listbox.MoveSelection(1)
+ else:
+ self.listbox.MoveSelection(-1)
+ elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
+ self.SetValueFromSelected(self.listbox.GetSelection())
+ elif event.GetKeyCode() == wx.WXK_ESCAPE:
+ self.DismissListBox()
+ else:
+ event.Skip()
+
+ def OnClickToggleDown(self, event):
+ self._lastinsertionpoint = self.GetInsertionPoint()
+ event.Skip()
+
+ def OnClickToggleUp(self, event):
+ if self.GetInsertionPoint() == self._lastinsertionpoint:
+ wx.CallAfter(self.PopupListBox)
+ self._lastinsertionpoint = None
+ event.Skip()
+
+ def OnControlChanged(self, event):
+ res = self.GetValue()
+ config = wx.ConfigBase.Get()
+ listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
+ if res and res not in listentries:
+ listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
+ config.Write(self.element_path, cPickle.dumps(listentries))
+ config.Flush()
+ self.SetChoices(listentries)
+ self.DismissListBox()
+ event.Skip()
+
+ def SetChoices(self, choices):
+ self._choices = choices
+ self.RefreshListBoxChoices()
+
+ def GetChoices(self):
+ return self._choices
+
+ def SetValueFromSelected(self, selected):
+ """
+ Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
+ Will do nothing if no item is selected in the wx.ListCtrl.
+ """
+ if selected != "":
+ self.SetValue(selected)
+ self.DismissListBox()
+
+ def RefreshListBoxChoices(self):
+ if self.listbox is not None:
+ text = self.GetValue()
+ choices = [choice for choice in self._choices if choice.startswith(text)]
+ self.listbox.SetChoices(choices)
+
+ def PopupListBox(self):
+ if self.listbox is None:
+ self.listbox = PopupWithListbox(self)
+
+ # Show the popup right below or above the button
+ # depending on available screen space...
+ pos = self.ClientToScreen((0, 0))
+ sz = self.GetSize()
+ self.listbox.Position(pos, (0, sz[1]))
+
+ self.RefreshListBoxChoices()
+
+ if wx.Platform == '__WXMSW__':
+ self.listbox.Popup()
+ else:
+ self.listbox.Show()
+ self.AppFrame.EnableScrolling(False)
+
+ def DismissListBox(self):
+ if self.listbox is not None:
+ if wx.Platform == '__WXMSW__':
+ self.listbox.Dismiss()
+ else:
+ self.listbox.Destroy()
+ self.listbox = None
+ self.AppFrame.EnableScrolling(True)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/VariablePanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,845 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+import re
+from types import TupleType, StringType, UnicodeType
+
+import wx
+import wx.grid
+import wx.lib.buttons
+
+from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS
+from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT
+from dialogs.ArrayTypeDialog import ArrayTypeDialog
+from CustomGrid import CustomGrid
+from CustomTable import CustomTable
+from LocationCellEditor import LocationCellEditor
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+def AppendMenu(parent, help, id, kind, text):
+ if wx.VERSION >= (2, 6, 0):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+ else:
+ parent.Append(helpString=help, id=id, kind=kind, item=text)
+
+[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
+ POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
+] = range(10)
+
+def GetVariableTableColnames(location):
+ _ = lambda x : x
+ if location:
+ return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")]
+ return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")]
+
+def GetOptions(constant=True, retain=True, non_retain=True):
+ _ = lambda x : x
+ options = [""]
+ if constant:
+ options.append(_("Constant"))
+ if retain:
+ options.append(_("Retain"))
+ if non_retain:
+ options.append(_("Non-Retain"))
+ return options
+OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()])
+
+def GetFilterChoiceTransfer():
+ _ = lambda x : x
+ return {_("All"): _("All"), _("Interface"): _("Interface"),
+ _(" Input"): _("Input"), _(" Output"): _("Output"), _(" InOut"): _("InOut"),
+ _(" External"): _("External"), _("Variables"): _("Variables"), _(" Local"): _("Local"),
+ _(" Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")}
+VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()])
+VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()])
+
+CheckOptionForClass = {"Local": lambda x: x,
+ "Temp": lambda x: "",
+ "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
+ "InOut": lambda x: "",
+ "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
+ "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""),
+ "External": lambda x: {"Constant": "Constant"}.get(x, "")
+ }
+
+LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
+
+#-------------------------------------------------------------------------------
+# Variables Panel Table
+#-------------------------------------------------------------------------------
+
+class VariableTable(CustomTable):
+
+ """
+ A custom wx.grid.Grid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ CustomTable.__init__(self, parent, data, colnames)
+ self.old_value = None
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ if col == 0:
+ return self.data[row]["Number"]
+ colname = self.GetColLabelValue(col, False)
+ value = self.data[row].get(colname, "")
+ if colname == "Type" and isinstance(value, TupleType):
+ if value[0] == "array":
+ return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1])
+ if not isinstance(value, (StringType, UnicodeType)):
+ value = str(value)
+ if colname in ["Class", "Option"]:
+ return _(value)
+ return value
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Name":
+ self.old_value = self.data[row][colname]
+ elif colname == "Class":
+ value = VARIABLE_CLASSES_DICT[value]
+ self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option")))
+ if value == "External":
+ self.SetValueByName(row, "Initial Value", "")
+ elif colname == "Option":
+ value = OPTIONS_DICT[value]
+ self.data[row][colname] = value
+
+ def GetOldValue(self):
+ return self.old_value
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+ for row in range(self.GetNumberRows()):
+ var_class = self.GetValueByName(row, "Class")
+ var_type = self.GetValueByName(row, "Type")
+ row_highlights = self.Highlights.get(row, {})
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col, False)
+ if self.Parent.Debug:
+ grid.SetReadOnly(row, col, True)
+ else:
+ if colname == "Option":
+ options = GetOptions(constant = var_class in ["Local", "External", "Global"],
+ retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"],
+ non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"])
+ if len(options) > 1:
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(",".join(map(_, options)))
+ else:
+ grid.SetReadOnly(row, col, True)
+ elif col != 0 and self.GetValueByName(row, "Edit"):
+ grid.SetReadOnly(row, col, False)
+ if colname == "Name":
+ if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
+ grid.SetReadOnly(row, col, True)
+ else:
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ elif colname == "Initial Value":
+ if var_class not in ["External", "InOut"]:
+ if self.Parent.Controler.IsEnumeratedType(var_type):
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(",".join(self.Parent.Controler.GetEnumeratedDataValues(var_type)))
+ else:
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ else:
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Location":
+ if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type):
+ editor = LocationCellEditor(self, self.Parent.Controler)
+ renderer = wx.grid.GridCellStringRenderer()
+ else:
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Class":
+ if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
+ grid.SetReadOnly(row, col, True)
+ else:
+ editor = wx.grid.GridCellChoiceEditor()
+ excluded = []
+ if self.Parent.PouIsUsed:
+ excluded.extend(["Input","Output","InOut"])
+ if self.Parent.IsFunctionBlockType(var_type):
+ excluded.extend(["Local","Temp"])
+ editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
+ elif colname != "Documentation":
+ grid.SetReadOnly(row, col, True)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, "Location")) is None:
+ highlight_colours = ERROR_HIGHLIGHT
+ else:
+ highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
+ grid.SetCellBackgroundColour(row, col, highlight_colours[0])
+ grid.SetCellTextColour(row, col, highlight_colours[1])
+ self.ResizeRow(grid, row)
+
+#-------------------------------------------------------------------------------
+# Variable Panel Drop Target
+#-------------------------------------------------------------------------------
+
+class VariableDropTarget(wx.TextDropTarget):
+ '''
+ This allows dragging a variable location from somewhere to the Location
+ column of a variable row.
+
+ The drag source should be a TextDataObject containing a Python tuple like:
+ ('%ID0.0.0', 'location', 'REAL')
+
+ c_ext/CFileEditor.py has an example of this (you can drag a C extension
+ variable to the Location column of the variable panel).
+ '''
+ def __init__(self, parent):
+ wx.TextDropTarget.__init__(self)
+ self.ParentWindow = parent
+
+ def OnDropText(self, x, y, data):
+ self.ParentWindow.ParentWindow.Select()
+ x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
+ col = self.ParentWindow.VariablesGrid.XToCol(x)
+ row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize())
+ message = None
+ element_type = self.ParentWindow.ElementType
+ try:
+ values = eval(data)
+ except:
+ message = _("Invalid value \"%s\" for variable grid element")%data
+ values = None
+ if not isinstance(values, TupleType):
+ message = _("Invalid value \"%s\" for variable grid element")%data
+ values = None
+ if values is not None:
+ if col != wx.NOT_FOUND and row != wx.NOT_FOUND:
+ colname = self.ParentWindow.Table.GetColLabelValue(col, False)
+ if colname == "Location" and values[1] == "location":
+ if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
+ message = _("Can't give a location to a function block instance")
+ elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]:
+ message = _("Can only give a location to local or global variables")
+ else:
+ location = values[0]
+ variable_type = self.ParentWindow.Table.GetValueByName(row, "Type")
+ base_type = self.ParentWindow.Controler.GetBaseType(variable_type)
+ if location.startswith("%"):
+ if base_type != values[2]:
+ message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type)
+ else:
+ self.ParentWindow.Table.SetValue(row, col, location)
+ self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
+ self.ParentWindow.SaveValues()
+ else:
+ if location[0].isdigit() and base_type != "BOOL":
+ message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location
+ elif location[0] not in LOCATIONDATATYPES:
+ message = _("Unrecognized data size \"%s\"")%location[0]
+ elif base_type not in LOCATIONDATATYPES[location[0]]:
+ message = _("Incompatible size of data between \"%s\" and \"%s\"")%(location, variable_type)
+ else:
+ dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow,
+ _("Select a variable class:"), _("Variable class"),
+ ["Input", "Output", "Memory"],
+ wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ selected = dialog.GetSelection()
+ else:
+ selected = None
+ dialog.Destroy()
+ if selected is None:
+ return
+ if selected == 0:
+ location = "%I" + location
+ elif selected == 1:
+ location = "%Q" + location
+ else:
+ location = "%M" + location
+ self.ParentWindow.Table.SetValue(row, col, location)
+ self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
+ self.ParentWindow.SaveValues()
+ elif colname == "Initial Value" and values[1] == "Constant":
+ if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
+ message = _("Can't set an initial value to a function block instance")
+ else:
+ self.ParentWindow.Table.SetValue(row, col, values[0])
+ self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
+ self.ParentWindow.SaveValues()
+ elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
+ element_type in ["config", "resource"] and values[1] == "location"):
+ if values[1] == "location":
+ var_name = values[3]
+ else:
+ var_name = values[0]
+ tagname = self.ParentWindow.GetTagName()
+ if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+ message = _("\"%s\" pou already exists!")%var_name
+ elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+ var_infos = self.ParentWindow.DefaultValue.copy()
+ var_infos["Name"] = var_name
+ var_infos["Type"] = values[2]
+ if values[1] == "location":
+ var_infos["Class"] = "Global"
+ var_infos["Location"] = values[0]
+ else:
+ var_infos["Class"] = "External"
+ var_infos["Number"] = len(self.ParentWindow.Values)
+ self.ParentWindow.Values.append(var_infos)
+ self.ParentWindow.SaveValues()
+ self.ParentWindow.RefreshValues()
+
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ def ShowMessage(self, message):
+ message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+#-------------------------------------------------------------------------------
+# Variable Panel
+#-------------------------------------------------------------------------------
+
+class VariablePanel(wx.Panel):
+
+ def __init__(self, parent, window, controler, element_type, debug=False):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
+ self.MainSizer.AddGrowableCol(0)
+ self.MainSizer.AddGrowableRow(1)
+
+ controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
+ controls_sizer.AddGrowableCol(5)
+ controls_sizer.AddGrowableRow(0)
+ self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
+
+ self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
+ controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.ReturnType = wx.ComboBox(self,
+ size=wx.Size(145, -1), style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
+ controls_sizer.AddWindow(self.ReturnType)
+
+ self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
+ controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.Description = wx.TextCtrl(self,
+ size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
+ self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
+ controls_sizer.AddWindow(self.Description)
+
+ class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
+ controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.ClassFilter = wx.ComboBox(self,
+ size=wx.Size(145, -1), style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
+ controls_sizer.AddWindow(self.ClassFilter)
+
+ for name, bitmap, help in [
+ ("AddButton", "add_element", _("Add variable")),
+ ("DeleteButton", "remove_element", _("Remove variable")),
+ ("UpButton", "up", _("Move variable up")),
+ ("DownButton", "down", _("Move variable down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ controls_sizer.AddWindow(button)
+
+ self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
+ self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+ self.OnVariablesGridCellChange)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
+ self.OnVariablesGridCellLeftClick)
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
+ self.OnVariablesGridEditorShown)
+ self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+
+ self.SetSizer(self.MainSizer)
+
+ self.ParentWindow = window
+ self.Controler = controler
+ self.ElementType = element_type
+ self.Debug = debug
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer,
+ self.RefreshHighlightsTimer)
+
+ self.Filter = "All"
+ self.FilterChoices = []
+ self.FilterChoiceTransfer = GetFilterChoiceTransfer()
+
+ self.DefaultValue = {
+ "Name" : "",
+ "Class" : "",
+ "Type" : "INT",
+ "Location" : "",
+ "Initial Value" : "",
+ "Option" : "",
+ "Documentation" : "",
+ "Edit" : True
+ }
+
+ if element_type in ["config", "resource"]:
+ self.DefaultTypes = {"All" : "Global"}
+ else:
+ self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"}
+
+ if element_type in ["config", "resource"] \
+ or element_type in ["program", "transition", "action"]:
+ # this is an element that can have located variables
+ self.Table = VariableTable(self, [], GetVariableTableColnames(True))
+
+ if element_type in ["config", "resource"]:
+ self.FilterChoices = ["All", "Global"]#,"Access"]
+ else:
+ self.FilterChoices = ["All",
+ "Interface", " Input", " Output", " InOut", " External",
+ "Variables", " Local", " Temp"]#,"Access"]
+
+ # these condense the ColAlignements list
+ l = wx.ALIGN_LEFT
+ c = wx.ALIGN_CENTER
+
+ # Num Name Class Type Loc Init Option Doc
+ self.ColSizes = [40, 80, 70, 80, 80, 80, 100, 80]
+ self.ColAlignements = [c, l, l, l, l, l, l, l]
+
+ else:
+ # this is an element that cannot have located variables
+ self.Table = VariableTable(self, [], GetVariableTableColnames(False))
+
+ if element_type == "function":
+ self.FilterChoices = ["All",
+ "Interface", " Input", " Output", " InOut",
+ "Variables", " Local"]
+ else:
+ self.FilterChoices = ["All",
+ "Interface", " Input", " Output", " InOut", " External",
+ "Variables", " Local", " Temp"]
+
+ # these condense the ColAlignements list
+ l = wx.ALIGN_LEFT
+ c = wx.ALIGN_CENTER
+
+ # Num Name Class Type Init Option Doc
+ self.ColSizes = [40, 80, 70, 80, 80, 100, 160]
+ self.ColAlignements = [c, l, l, l, l, l, l]
+
+ for choice in self.FilterChoices:
+ self.ClassFilter.Append(_(choice))
+
+ reverse_transfer = {}
+ for filter, choice in self.FilterChoiceTransfer.items():
+ reverse_transfer[choice] = filter
+ self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
+ self.RefreshTypeList()
+
+ self.VariablesGrid.SetTable(self.Table)
+ self.VariablesGrid.SetButtons({"Add": self.AddButton,
+ "Delete": self.DeleteButton,
+ "Up": self.UpButton,
+ "Down": self.DownButton})
+ self.VariablesGrid.SetEditable(not self.Debug)
+
+ def _AddVariable(new_row):
+ if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
+ row_content = self.DefaultValue.copy()
+ if self.Filter in self.DefaultTypes:
+ row_content["Class"] = self.DefaultTypes[self.Filter]
+ else:
+ row_content["Class"] = self.Filter
+ if self.Filter == "All" and len(self.Values) > 0:
+ self.Values.insert(new_row, row_content)
+ else:
+ self.Values.append(row_content)
+ new_row = self.Table.GetNumberRows()
+ self.SaveValues()
+ self.RefreshValues()
+ return new_row
+ return self.VariablesGrid.GetGridCursorRow()
+ setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+
+ def _DeleteVariable(row):
+ if (self.Table.GetValueByName(row, "Edit") and
+ (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+ self.Values.remove(self.Table.GetRow(row))
+ self.SaveValues()
+ self.RefreshValues()
+ setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+
+ def _MoveVariable(row, move):
+ if (self.Filter == "All" and
+ (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+ new_row = max(0, min(row + move, len(self.Values) - 1))
+ if new_row != row:
+ self.Values.insert(new_row, self.Values.pop(row))
+ self.SaveValues()
+ self.RefreshValues()
+ return new_row
+ return row
+ setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+
+ def _RefreshButtons():
+ if self:
+ table_length = len(self.Table.data)
+ row_class = None
+ row_edit = True
+ row = 0
+ if table_length > 0:
+ row = self.VariablesGrid.GetGridCursorRow()
+ row_edit = self.Table.GetValueByName(row, "Edit")
+ if self.PouIsUsed:
+ row_class = self.Table.GetValueByName(row, "Class")
+ self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
+ self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
+ self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
+ self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
+ setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
+
+ self.VariablesGrid.SetRowLabelSize(0)
+ for col in range(self.Table.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
+ self.VariablesGrid.SetColAttr(col, attr)
+ self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col])
+ self.VariablesGrid.AutoSizeColumn(col, False)
+
+ def __del__(self):
+ self.RefreshHighlightsTimer.Stop()
+
+ def SetTagName(self, tagname):
+ self.TagName = tagname
+
+ def GetTagName(self):
+ return self.TagName
+
+ def IsFunctionBlockType(self, name):
+ bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
+ pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
+ if poutype != "function" and bodytype in ["ST", "IL"]:
+ return False
+ else:
+ return name in self.Controler.GetFunctionBlockTypes(self.TagName)
+
+ def RefreshView(self):
+ self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
+ returnType = None
+ description = None
+
+ words = self.TagName.split("::")
+ if self.ElementType == "config":
+ self.PouIsUsed = False
+ self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
+ elif self.ElementType == "resource":
+ self.PouIsUsed = False
+ self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
+ else:
+ if self.ElementType == "function":
+ self.ReturnType.Clear()
+ for base_type in self.Controler.GetDataTypes(self.TagName, True, debug=self.Debug):
+ self.ReturnType.Append(base_type)
+ returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
+ description = self.Controler.GetPouDescription(words[1])
+ self.PouIsUsed = self.Controler.PouIsUsed(words[1])
+ self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+
+ if returnType is not None:
+ self.ReturnType.SetStringSelection(returnType)
+ self.ReturnType.Enable(not self.Debug)
+ self.ReturnTypeLabel.Show()
+ self.ReturnType.Show()
+ else:
+ self.ReturnType.Enable(False)
+ self.ReturnTypeLabel.Hide()
+ self.ReturnType.Hide()
+
+ if description is not None:
+ self.Description.SetValue(description)
+ self.Description.Enable(not self.Debug)
+ self.DescriptionLabel.Show()
+ self.Description.Show()
+ else:
+ self.Description.Enable(False)
+ self.DescriptionLabel.Hide()
+ self.Description.Hide()
+
+ self.RefreshValues()
+ self.VariablesGrid.RefreshButtons()
+ self.MainSizer.Layout()
+
+ def OnReturnTypeChanged(self, event):
+ words = self.TagName.split("::")
+ self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
+ self.Controler.BufferProject()
+ self.ParentWindow.RefreshView(variablepanel = False)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ event.Skip()
+
+ def OnDescriptionChanged(self, event):
+ words = self.TagName.split("::")
+ old_description = self.Controler.GetPouDescription(words[1])
+ new_description = self.Description.GetValue()
+ if new_description != old_description:
+ self.Controler.SetPouDescription(words[1], new_description)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ event.Skip()
+
+ def OnClassFilter(self, event):
+ self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
+ self.RefreshTypeList()
+ self.RefreshValues()
+ self.VariablesGrid.RefreshButtons()
+ event.Skip()
+
+ def RefreshTypeList(self):
+ if self.Filter == "All":
+ self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[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 OnVariablesGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ colname = self.Table.GetColLabelValue(col, False)
+ value = self.Table.GetValue(row, col)
+ message = None
+
+ if colname == "Name" and value != "":
+ if not TestIdentifier(value):
+ message = _("\"%s\" is not a valid identifier!") % value
+ elif value.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % value
+ elif value.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % value
+ elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]:
+ message = _("A variable with \"%s\" as name already exists in this pou!") % value
+ else:
+ self.SaveValues(False)
+ old_value = self.Table.GetOldValue()
+ if old_value != "":
+ self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
+ self.Controler.BufferProject()
+ self.ParentWindow.RefreshView(variablepanel = False)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ event.Skip()
+ else:
+ self.SaveValues()
+ if colname == "Class":
+ self.ParentWindow.RefreshView(variablepanel = False)
+ elif colname == "Location":
+ wx.CallAfter(self.ParentWindow.RefreshView)
+
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def OnVariablesGridEditorShown(self, event):
+ row, col = event.GetRow(), event.GetCol()
+
+ label_value = self.Table.GetColLabelValue(col, False)
+ if label_value == "Type":
+ type_menu = wx.Menu(title='') # the root menu
+
+ # build a submenu containing standard IEC types
+ base_menu = wx.Menu(title='')
+ for base_type in self.Controler.GetBaseTypes():
+ new_id = wx.NewId()
+ AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
+
+ type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
+
+ # build a submenu containing user-defined types
+ datatype_menu = wx.Menu(title='')
+ datatypes = self.Controler.GetDataTypes(basetypes = False)
+ for datatype in datatypes:
+ new_id = wx.NewId()
+ AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
+
+ type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
+
+ for category in self.Controler.GetConfNodeDataTypes():
+
+ if len(category["list"]) > 0:
+ # build a submenu containing confnode types
+ confnode_datatype_menu = wx.Menu(title='')
+ for datatype in category["list"]:
+ new_id = wx.NewId()
+ AppendMenu(confnode_datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
+
+ type_menu.AppendMenu(wx.NewId(), category["name"], confnode_datatype_menu)
+
+ # build a submenu containing function block types
+ bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
+ pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
+ classtype = self.Table.GetValueByName(row, "Class")
+
+ new_id = wx.NewId()
+ AppendMenu(type_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array"))
+ self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id)
+
+ if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
+ poutype != "function" and bodytype in ["ST", "IL"]:
+ functionblock_menu = wx.Menu(title='')
+ fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
+ for functionblock_type in fbtypes:
+ new_id = wx.NewId()
+ AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
+ self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
+
+ type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
+
+ rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
+ corner_x = rect.x + rect.width
+ corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
+
+ # pop up this new menu
+ self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
+ type_menu.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def GetVariableTypeFunction(self, base_type):
+ def VariableTypeFunction(event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ self.Table.SetValueByName(row, "Type", base_type)
+ self.Table.ResetView(self.VariablesGrid)
+ self.SaveValues(False)
+ self.ParentWindow.RefreshView(variablepanel = False)
+ self.Controler.BufferProject()
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ return VariableTypeFunction
+
+ def VariableArrayTypeFunction(self, event):
+ row = self.VariablesGrid.GetGridCursorRow()
+ dialog = ArrayTypeDialog(self,
+ self.Controler.GetDataTypes(self.TagName),
+ self.Table.GetValueByName(row, "Type"))
+ if dialog.ShowModal() == wx.ID_OK:
+ self.Table.SetValueByName(row, "Type", dialog.GetValue())
+ self.Table.ResetView(self.VariablesGrid)
+ self.SaveValues(False)
+ self.ParentWindow.RefreshView(variablepanel = False)
+ self.Controler.BufferProject()
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+ dialog.Destroy()
+
+ def OnVariablesGridCellLeftClick(self, event):
+ row = event.GetRow()
+ if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")):
+ row = event.GetRow()
+ var_name = self.Table.GetValueByName(row, "Name")
+ var_class = self.Table.GetValueByName(row, "Class")
+ var_type = self.Table.GetValueByName(row, "Type")
+ data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName)))
+ dragSource = wx.DropSource(self.VariablesGrid)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+ event.Skip()
+
+ def RefreshValues(self):
+ data = []
+ for num, variable in enumerate(self.Values):
+ if variable["Class"] in self.ClassList:
+ variable["Number"] = num + 1
+ data.append(variable)
+ self.Table.SetData(data)
+ self.Table.ResetView(self.VariablesGrid)
+
+ def SaveValues(self, buffer = True):
+ words = self.TagName.split("::")
+ if self.ElementType == "config":
+ self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
+ elif self.ElementType == "resource":
+ self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
+ else:
+ if self.ReturnType.IsEnabled():
+ self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
+ self.Controler.SetPouInterfaceVars(words[1], self.Values)
+ if buffer:
+ self.Controler.BufferProject()
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.Table.ResetView(self.VariablesGrid)
+ event.Skip()
+
+ def AddVariableHighlight(self, infos, highlight_type):
+ if isinstance(infos[0], TupleType):
+ for i in xrange(*infos[0]):
+ self.Table.AddHighlight((i,) + infos[1:], highlight_type)
+ cell_visible = infos[0][0]
+ else:
+ self.Table.AddHighlight(infos, highlight_type)
+ cell_visible = infos[0]
+ colnames = [colname.lower() for colname in self.Table.colnames]
+ self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1]))
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def RemoveVariableHighlight(self, infos, highlight_type):
+ if isinstance(infos[0], TupleType):
+ for i in xrange(*infos[0]):
+ self.Table.RemoveHighlight((i,) + infos[1:], highlight_type)
+ else:
+ self.Table.RemoveHighlight(infos, highlight_type)
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ClearHighlights(self, highlight_type=None):
+ self.Table.ClearHighlights(highlight_type)
+ self.Table.ResetView(self.VariablesGrid)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialization
+
+from CustomEditableListBox import CustomEditableListBox
+from CustomGrid import CustomGrid
+from CustomTable import CustomTable
+from CustomTree import CustomTree
+from DebugVariablePanel import DebugVariablePanel
+from DurationCellEditor import DurationCellEditor
+from LibraryPanel import LibraryPanel
+from LocationCellEditor import LocationCellEditor
+from PouInstanceVariablesPanel import PouInstanceVariablesPanel
+from ProjectPropertiesPanel import ProjectPropertiesPanel
+from VariablePanel import VariablePanel
+from SearchResultPanel import SearchResultPanel
+from TextCtrlAutoComplete import TextCtrlAutoComplete
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/ActionBlockDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.grid
+import wx.lib.buttons
+
+from controls import CustomGrid, CustomTable
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+def GetActionTableColnames():
+ _ = lambda x: x
+ return [_("Qualifier"), _("Duration"), _("Type"), _("Value"), _("Indicator")]
+
+def GetTypeList():
+ _ = lambda x: x
+ return [_("Action"), _("Variable"), _("Inline")]
+
+#-------------------------------------------------------------------------------
+# Action Table
+#-------------------------------------------------------------------------------
+
+class ActionTable(CustomTable):
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ colname = self.GetColLabelValue(col, False)
+ name = str(self.data[row].get(colname, ""))
+ if colname == "Type":
+ return _(name)
+ return name
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Type":
+ value = self.Parent.TranslateType[value]
+ self.data[row][colname] = value
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ readonly = False
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Qualifier":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.QualifierList)
+ if colname == "Duration":
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ if self.Parent.DurationList[self.data[row]["Qualifier"]]:
+ readonly = False
+ else:
+ readonly = True
+ self.data[row]["Duration"] = ""
+ elif colname == "Type":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TypeList)
+ elif colname == "Value":
+ type = self.data[row]["Type"]
+ if type == "Action":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.ActionList)
+ elif type == "Variable":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+ elif type == "Inline":
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ elif colname == "Indicator":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+ grid.SetReadOnly(row, col, readonly)
+
+ grid.SetCellBackgroundColour(row, col, wx.WHITE)
+ self.ResizeRow(grid, row)
+
+#-------------------------------------------------------------------------------
+# Action Block Dialog
+#-------------------------------------------------------------------------------
+
+class ActionBlockDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(500, 300), title=_('Edit action block properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ top_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+ top_sizer.AddGrowableCol(0)
+ top_sizer.AddGrowableRow(0)
+ main_sizer.AddSizer(top_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ actions_label = wx.StaticText(self, label=_('Actions:'))
+ top_sizer.AddWindow(actions_label, flag=wx.ALIGN_BOTTOM)
+
+ for name, bitmap, help in [
+ ("AddButton", "add_element", _("Add action")),
+ ("DeleteButton", "remove_element", _("Remove action")),
+ ("UpButton", "up", _("Move action up")),
+ ("DownButton", "down", _("Move action down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ top_sizer.AddWindow(button)
+
+ self.ActionsGrid = CustomGrid(self, size=wx.Size(0, 0), style=wx.VSCROLL)
+ self.ActionsGrid.DisableDragGridSize()
+ self.ActionsGrid.EnableScrolling(False, True)
+ self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+ self.OnActionsGridCellChange)
+ main_sizer.AddSizer(self.ActionsGrid, border=20,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Table = ActionTable(self, [], GetActionTableColnames())
+ typelist = GetTypeList()
+ self.TypeList = ",".join(map(_,typelist))
+ self.TranslateType = dict([(_(value), value) for value in typelist])
+ self.ColSizes = [60, 90, 80, 110, 80]
+ self.ColAlignements = [wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+
+ self.ActionsGrid.SetTable(self.Table)
+ self.ActionsGrid.SetDefaultValue({"Qualifier" : "N",
+ "Duration" : "",
+ "Type" : "Action",
+ "Value" : "",
+ "Indicator" : ""})
+ self.ActionsGrid.SetButtons({"Add": self.AddButton,
+ "Delete": self.DeleteButton,
+ "Up": self.UpButton,
+ "Down": self.DownButton})
+ self.ActionsGrid.SetRowLabelSize(0)
+
+ for col in range(self.Table.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
+ self.ActionsGrid.SetColAttr(col, attr)
+ self.ActionsGrid.SetColMinimalWidth(col, self.ColSizes[col])
+ self.ActionsGrid.AutoSizeColumn(col, False)
+
+ self.Table.ResetView(self.ActionsGrid)
+ self.ActionsGrid.SetFocus()
+ self.ActionsGrid.RefreshButtons()
+
+ def OnOK(self, event):
+ self.ActionsGrid.CloseEditControl()
+ self.EndModal(wx.ID_OK)
+
+ def OnActionsGridCellChange(self, event):
+ wx.CallAfter(self.Table.ResetView, self.ActionsGrid)
+ event.Skip()
+
+ def SetQualifierList(self, list):
+ self.QualifierList = "," + ",".join(list)
+ self.DurationList = list
+
+ def SetVariableList(self, list):
+ self.VariableList = "," + ",".join([variable["Name"] for variable in list])
+
+ def SetActionList(self, list):
+ self.ActionList = "," + ",".join(list)
+
+ def SetValues(self, actions):
+ for action in actions:
+ row = {"Qualifier" : action["qualifier"], "Value" : action["value"]}
+ 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"]
+ else:
+ row["Duration"] = ""
+ if "indicator" in action:
+ row["Indicator"] = action["indicator"]
+ else:
+ row["Indicator"] = ""
+ self.Table.AppendRow(row)
+ self.Table.ResetView(self.ActionsGrid)
+ if len(actions) > 0:
+ self.ActionsGrid.SetGridCursor(0, 0)
+ self.ActionsGrid.RefreshButtons()
+
+ def GetValues(self):
+ values = []
+ for data in self.Table.GetData():
+ action = {"qualifier" : data["Qualifier"], "value" : data["Value"]}
+ if data["Type"] in ["Action", "Variable"]:
+ action["type"] = "reference"
+ else:
+ action["type"] = "inline"
+ if data["Duration"] != "":
+ action["duration"] = data["Duration"]
+ if data["Indicator"] != "":
+ action["indicator"] = data["Indicator"]
+ values.append(action)
+ return values
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/ArrayTypeDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+from types import TupleType
+
+import wx
+
+from controls import CustomEditableListBox
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+DIMENSION_MODEL = re.compile("([0-9]+)\.\.([0-9]+)$")
+
+#-------------------------------------------------------------------------------
+# Array Type Dialog
+#-------------------------------------------------------------------------------
+
+class ArrayTypeDialog(wx.Dialog):
+
+ def __init__(self, parent, datatypes, infos):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(500, 300), title=_('Edit array type properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ top_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(top_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ basetype_label = wx.StaticText(self, label=_('Base Type:'))
+ top_sizer.AddWindow(basetype_label, 1, flag=wx.ALIGN_BOTTOM)
+
+ self.BaseType = wx.ComboBox(self, style=wx.CB_READONLY)
+ top_sizer.AddWindow(self.BaseType, 1, flag=wx.GROW)
+
+ self.Dimensions = CustomEditableListBox(self, label=_("Dimensions:"),
+ style=wx.gizmos.EL_ALLOW_NEW|
+ wx.gizmos.EL_ALLOW_EDIT|
+ wx.gizmos.EL_ALLOW_DELETE)
+ for func in ["_OnLabelEndEdit",
+ "_OnAddButton",
+ "_OnDelButton",
+ "_OnUpButton",
+ "_OnDownButton"]:
+ setattr(self.Dimensions, func, self.OnDimensionsChanged)
+ main_sizer.AddSizer(self.Dimensions, border=20,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for datatype in datatypes:
+ self.BaseType.Append(datatype)
+
+ if isinstance(infos, TupleType) and infos[0] == "array":
+ self.BaseType.SetStringSelection(infos[1])
+ self.Dimensions.SetStrings(map(lambda x : "..".join(x), infos[2]))
+ elif infos in datatypes:
+ self.BaseType.SetStringSelection(infos)
+
+ self.BaseType.SetFocus()
+
+ def GetDimensions(self):
+ dimensions_list = []
+ for dimensions in self.Dimensions.GetStrings():
+ result = DIMENSION_MODEL.match(dimensions)
+ if result is None:
+ message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!")%dimensions, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ return None
+ bounds = result.groups()
+ if int(bounds[0]) >= int(bounds[1]):
+ message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!\nRight value must be greater than left value.")%dimensions, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ return None
+ dimensions_list.append(bounds)
+ return dimensions_list
+
+ def OnDimensionsChanged(self, event):
+ wx.CallAfter(self.GetDimensions)
+ event.Skip()
+
+ def OnOK(self, event):
+ if self.GetDimensions() is not None:
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self):
+ return "array", self.BaseType.GetStringSelection(), self.GetDimensions()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/BrowseLocationsDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,179 @@
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+
+import wx
+
+from plcopen.structures import LOCATIONDATATYPES
+from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+CWD = os.path.split(os.path.split(os.path.realpath(__file__))[0])[0]
+
+def GetDirChoiceOptions():
+ _ = lambda x : x
+ return [(_("All"), [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]),
+ (_("Input"), [LOCATION_VAR_INPUT]),
+ (_("Output"), [LOCATION_VAR_OUTPUT]),
+ (_("Memory"), [LOCATION_VAR_MEMORY])]
+DIRCHOICE_OPTIONS_FILTER = dict([(_(option), filter) for option, filter in GetDirChoiceOptions()])
+
+# turn LOCATIONDATATYPES inside-out
+LOCATION_SIZES = {}
+for size, types in LOCATIONDATATYPES.iteritems():
+ for type in types:
+ LOCATION_SIZES[type] = size
+
+#-------------------------------------------------------------------------------
+# Browse Locations Dialog
+#-------------------------------------------------------------------------------
+
+class BrowseLocationsDialog(wx.Dialog):
+
+ def __init__(self, parent, var_type, locations):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(600, 400), title=_('Browse Locations'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ locations_label = wx.StaticText(self, label=_('Locations available:'))
+ main_sizer.AddWindow(locations_label, border=20,
+ flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
+
+ self.LocationsTree = wx.TreeCtrl(self,
+ style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnLocationsTreeItemActivated,
+ self.LocationsTree)
+ main_sizer.AddWindow(self.LocationsTree, border=20,
+ flag=wx.LEFT|wx.RIGHT|wx.GROW)
+
+ button_gridsizer = wx.FlexGridSizer(cols=3, hgap=5, rows=1, vgap=0)
+ button_gridsizer.AddGrowableCol(2)
+ button_gridsizer.AddGrowableRow(0)
+ main_sizer.AddSizer(button_gridsizer, border=20,
+ flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.GROW)
+
+ direction_label = wx.StaticText(self, label=_('Direction:'))
+ button_gridsizer.AddWindow(direction_label,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.DirChoice = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnDirChoice, self.DirChoice)
+ button_gridsizer.AddWindow(self.DirChoice,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ button_gridsizer.AddWindow(button_sizer, flag=wx.ALIGN_RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.VarType = var_type
+ self.Locations = locations
+
+ # Define Tree item icon list
+ self.TreeImageList = wx.ImageList(16, 16)
+ self.TreeImageDict = {}
+
+ # Icons for items
+ for imgname, itemtype in [
+ ("CONFIGURATION", LOCATION_CONFNODE),
+ ("RESOURCE", LOCATION_MODULE),
+ ("PROGRAM", LOCATION_GROUP),
+ ("VAR_INPUT", LOCATION_VAR_INPUT),
+ ("VAR_OUTPUT", LOCATION_VAR_OUTPUT),
+ ("VAR_LOCAL", LOCATION_VAR_MEMORY)]:
+ self.TreeImageDict[itemtype]=self.TreeImageList.Add(wx.Bitmap(os.path.join(CWD, 'Images', '%s.png'%imgname)))
+
+ # Assign icon list to TreeCtrls
+ self.LocationsTree.SetImageList(self.TreeImageList)
+
+ # Set a options for the choice
+ for option, filter in GetDirChoiceOptions():
+ self.DirChoice.Append(_(option))
+ self.DirChoice.SetStringSelection(_("All"))
+ self.RefreshFilter()
+
+ self.RefreshLocationsTree()
+
+ def RefreshFilter(self):
+ self.Filter = DIRCHOICE_OPTIONS_FILTER[self.DirChoice.GetStringSelection()]
+
+ def RefreshLocationsTree(self):
+ root = self.LocationsTree.GetRootItem()
+ if not root.IsOk():
+ root = self.LocationsTree.AddRoot("")
+ self.GenerateLocationsTreeBranch(root, self.Locations)
+
+ def GenerateLocationsTreeBranch(self, root, locations):
+ to_delete = []
+ item, root_cookie = self.LocationsTree.GetFirstChild(root)
+ for loc_infos in locations:
+ infos = loc_infos.copy()
+ if infos["type"] in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP] or\
+ infos["type"] in self.Filter and (infos["IEC_type"] == self.VarType or
+ infos["IEC_type"] is None and LOCATION_SIZES[self.VarType] == infos["size"]):
+ children = [child for child in infos.pop("children")]
+ if not item.IsOk():
+ item = self.LocationsTree.AppendItem(root, infos["name"])
+ if wx.Platform != '__WXMSW__':
+ item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie)
+ else:
+ self.LocationsTree.SetItemText(item, infos["name"])
+ self.LocationsTree.SetPyData(item, infos)
+ self.LocationsTree.SetItemImage(item, self.TreeImageDict[infos["type"]])
+ self.GenerateLocationsTreeBranch(item, children)
+ item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie)
+ while item.IsOk():
+ to_delete.append(item)
+ item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie)
+ for item in to_delete:
+ self.LocationsTree.Delete(item)
+
+ def OnLocationsTreeItemActivated(self, event):
+ infos = self.LocationsTree.GetPyData(event.GetItem())
+ if infos["type"] not in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]:
+ wx.CallAfter(self.EndModal, wx.ID_OK)
+ event.Skip()
+
+ def OnDirChoice(self, event):
+ self.RefreshFilter()
+ self.RefreshLocationsTree()
+
+ def GetValues(self):
+ selected = self.LocationsTree.GetSelection()
+ return self.LocationsTree.GetPyData(selected)
+
+ def OnOK(self, event):
+ selected = self.LocationsTree.GetSelection()
+ var_infos = None
+ if selected.IsOk():
+ var_infos = self.LocationsTree.GetPyData(selected)
+ if var_infos is None or var_infos["type"] in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]:
+ dialog = wx.MessageDialog(self, _("A location must be selected!"), _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/BrowseValuesLibraryDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+
+class BrowseValuesLibraryDialog(wx.Dialog):
+ """
+ Modal dialog that helps in selecting predefined XML attributes sets out of hierarchically organized list
+ """
+
+ def __init__(self, parent, name, library, default=None):
+ wx.Dialog.__init__(self,
+ name='BrowseValueDialog', parent=parent,
+ size=wx.Size(600, 400), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
+ title=_('Browse %s values library') % name)
+
+ self.SetClientSize(wx.Size(600, 400))
+
+ self.staticText1 = wx.StaticText(
+ label=_('Choose a value for %s:') % name, name='staticText1', parent=self,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+
+ self.ValuesLibrary = wx.TreeCtrl(
+ name='ValuesLibrary', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(0, 0), style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT)
+
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+
+ self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId())
+
+ self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+
+ self.flexGridSizer1.AddWindow(self.staticText1, 0, border=20, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+ self.flexGridSizer1.AddWindow(self.ValuesLibrary, 0, border=20, flag=wx.GROW|wx.LEFT|wx.RIGHT)
+ self.flexGridSizer1.AddSizer(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.flexGridSizer1.AddGrowableCol(0)
+ self.flexGridSizer1.AddGrowableRow(1)
+
+ self.SetSizer(self.flexGridSizer1)
+
+ root = self.ValuesLibrary.AddRoot("")
+ self.GenerateValuesLibraryBranch(root, library, default)
+
+ def GenerateValuesLibraryBranch(self, root, children, default):
+ for infos in children:
+ item = self.ValuesLibrary.AppendItem(root, infos["name"])
+ self.ValuesLibrary.SetPyData(item, infos["infos"])
+ if infos["infos"] is not None and infos["infos"] == default:
+ self.ValuesLibrary.SelectItem(item)
+ self.ValuesLibrary.EnsureVisible(item)
+ self.GenerateValuesLibraryBranch(item, infos["children"], default)
+
+ def GetValueInfos(self):
+ selected = self.ValuesLibrary.GetSelection()
+ return self.ValuesLibrary.GetPyData(selected)
+
+ def OnOK(self, event):
+ selected = self.ValuesLibrary.GetSelection()
+ if not selected.IsOk() or self.ValuesLibrary.GetPyData(selected) is None:
+ message = wx.MessageDialog(self, _("No valid value selected!"), _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/ConnectionDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Create New Connection Dialog
+#-------------------------------------------------------------------------------
+
+class ConnectionDialog(wx.Dialog):
+
+ def __init__(self, parent, controller):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(350, 220), title=_('Connection Properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ type_label = wx.StaticText(self, label=_('Type:'))
+ left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+
+ self.ConnectorRadioButton = wx.RadioButton(self,
+ label=_('Connector'), style=wx.RB_GROUP)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectorRadioButton)
+ self.ConnectorRadioButton.SetValue(True)
+ left_gridsizer.AddWindow(self.ConnectorRadioButton, flag=wx.GROW)
+
+ self.ConnectionRadioButton = wx.RadioButton(self, label=_('Continuation'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
+ left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
+
+ name_label = wx.StaticText(self, label=_('Name:'))
+ left_gridsizer.AddWindow(name_label, flag=wx.GROW)
+
+ self.ConnectionName = wx.TextCtrl(self)
+ self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.ConnectionName)
+ left_gridsizer.AddWindow(self.ConnectionName, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Connection = None
+ self.MinConnectionSize = None
+
+ self.PouNames = []
+ self.PouElementNames = []
+
+ self.ConnectorRadioButton.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def SetMinConnectionSize(self, size):
+ self.MinConnectionSize = size
+
+ def SetValues(self, values):
+ for name, value in values.items():
+ if name == "type":
+ if value == CONNECTOR:
+ self.ConnectorRadioButton.SetValue(True)
+ elif value == CONTINUATION:
+ self.ConnectionRadioButton.SetValue(True)
+ elif name == "name":
+ self.ConnectionName.SetValue(value)
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = {}
+ if self.ConnectorRadioButton.GetValue():
+ values["type"] = CONNECTOR
+ else:
+ values["type"] = CONTINUATION
+ values["name"] = self.ConnectionName.GetValue()
+ values["width"], values["height"] = self.Connection.GetSize()
+ return values
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetPouElementNames(self, element_names):
+ self.PouElementNames = [element_name.upper() for element_name in element_names]
+
+ def OnOK(self, event):
+ message = None
+ connection_name = self.ConnectionName.GetValue()
+ if connection_name == "":
+ message = _("Form isn't complete. Name must be filled!")
+ elif not TestIdentifier(connection_name):
+ message = _("\"%s\" is not a valid identifier!") % connection_name
+ elif connection_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % connection_name
+ elif connection_name.upper() in self.PouNames:
+ message = _("\"%s\" pou already exists!") % connection_name
+ elif connection_name.upper() in self.PouElementNames:
+ message = _("\"%s\" element for this pou already exists!") % connection_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def OnTypeChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ if self.ConnectorRadioButton.GetValue():
+ self.Connection = FBD_Connector(self.Preview, CONNECTOR, self.ConnectionName.GetValue())
+ else:
+ self.Connection = FBD_Connector(self.Preview, CONTINUATION, self.ConnectionName.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()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/DiscoveryDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,249 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import socket
+import wx
+import wx.lib.mixins.listctrl as listmix
+from util.Zeroconf import *
+
+import connectors
+
+service_type = '_PYRO._tcp.local.'
+
+class AutoWidthListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
+ def __init__(self, parent, id, name, pos=wx.DefaultPosition,
+ size=wx.DefaultSize, style=0):
+ wx.ListCtrl.__init__(self, parent, id, pos, size, style, name=name)
+ listmix.ListCtrlAutoWidthMixin.__init__(self)
+
+[ID_DISCOVERYDIALOG, ID_DISCOVERYDIALOGSTATICTEXT1,
+ ID_DISCOVERYDIALOGSERVICESLIST, ID_DISCOVERYDIALOGREFRESHBUTTON,
+ ID_DISCOVERYDIALOGLOCALBUTTON, ID_DISCOVERYDIALOGIPBUTTON,
+] = [wx.NewId() for _init_ctrls in range(6)]
+
+class DiscoveryDialog(wx.Dialog, listmix.ColumnSorterMixin):
+
+ def _init_coll_MainSizer_Items(self, parent):
+ parent.AddWindow(self.staticText1, 0, border=20, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
+ parent.AddWindow(self.ServicesList, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.GROW)
+ parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+
+ def _init_coll_MainSizer_Growables(self, parent):
+ parent.AddGrowableCol(0)
+ parent.AddGrowableRow(1)
+
+ def _init_coll_ButtonGridSizer_Items(self, parent):
+ parent.AddWindow(self.RefreshButton, 0, border=0, flag=0)
+ parent.AddWindow(self.LocalButton, 0, border=0, flag=0)
+ parent.AddWindow(self.IpButton, 0, border=0, flag=0)
+ parent.AddSizer(self.ButtonSizer, 0, border=0, flag=0)
+
+ def _init_coll_ButtonGridSizer_Growables(self, parent):
+ parent.AddGrowableCol(0)
+ parent.AddGrowableCol(1)
+ parent.AddGrowableRow(1)
+
+ def _init_sizers(self):
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+ self.ButtonGridSizer = wx.FlexGridSizer(cols=4, hgap=5, rows=1, vgap=0)
+
+ self._init_coll_MainSizer_Items(self.MainSizer)
+ self._init_coll_MainSizer_Growables(self.MainSizer)
+ self._init_coll_ButtonGridSizer_Items(self.ButtonGridSizer)
+ self._init_coll_ButtonGridSizer_Growables(self.ButtonGridSizer)
+
+ self.SetSizer(self.MainSizer)
+
+ def _init_ctrls(self, prnt):
+ wx.Dialog.__init__(self, id=ID_DISCOVERYDIALOG,
+ name='DiscoveryDialog', parent=prnt,
+ size=wx.Size(600, 600), style=wx.DEFAULT_DIALOG_STYLE,
+ title='Service Discovery')
+
+ self.staticText1 = wx.StaticText(id=ID_DISCOVERYDIALOGSTATICTEXT1,
+ label=_('Services available:'), name='staticText1', parent=self,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+
+ # Set up list control
+ self.ServicesList = AutoWidthListCtrl(id=ID_DISCOVERYDIALOGSERVICESLIST,
+ name='ServicesList', parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 0),
+ style=wx.LC_REPORT|wx.LC_EDIT_LABELS|wx.LC_SORT_ASCENDING|wx.LC_SINGLE_SEL)
+ self.ServicesList.InsertColumn(0, 'NAME')
+ self.ServicesList.InsertColumn(1, 'TYPE')
+ self.ServicesList.InsertColumn(2, 'IP')
+ self.ServicesList.InsertColumn(3, 'PORT')
+ self.ServicesList.SetColumnWidth(0, 150)
+ self.ServicesList.SetColumnWidth(1, 150)
+ self.ServicesList.SetColumnWidth(2, 150)
+ self.ServicesList.SetColumnWidth(3, 150)
+ self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, id=ID_DISCOVERYDIALOGSERVICESLIST)
+ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, id=ID_DISCOVERYDIALOGSERVICESLIST)
+
+ listmix.ColumnSorterMixin.__init__(self, 4)
+
+ self.RefreshButton = wx.Button(id=ID_DISCOVERYDIALOGREFRESHBUTTON,
+ label=_('Refresh'), name='RefreshButton', parent=self,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+ self.Bind(wx.EVT_BUTTON, self.OnRefreshButton, id=ID_DISCOVERYDIALOGREFRESHBUTTON)
+
+ self.LocalButton = wx.Button(id=ID_DISCOVERYDIALOGLOCALBUTTON,
+ label=_('Local'), name='LocalButton', parent=self,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+ self.Bind(wx.EVT_BUTTON, self.OnLocalButton, id=ID_DISCOVERYDIALOGLOCALBUTTON)
+
+ self.IpButton = wx.Button(id=ID_DISCOVERYDIALOGIPBUTTON,
+ label=_('Add IP'), name='IpButton', parent=self,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+ self.Bind(wx.EVT_BUTTON, self.OnIpButton, id=ID_DISCOVERYDIALOGIPBUTTON)
+
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTER)
+
+ self._init_sizers()
+
+ def __init__(self, parent):
+ self._init_ctrls(parent)
+
+ self.itemDataMap = {}
+ self.nextItemId = 0
+
+ self.URI = None
+ self.Browser = None
+
+ self.ZeroConfInstance = Zeroconf()
+ self.RefreshList()
+ self.LatestSelection=None
+
+ def __del__(self):
+ if self.Browser is not None : self.Browser.cancel()
+ self.ZeroConfInstance.close()
+
+ def RefreshList(self):
+ if self.Browser is not None : self.Browser.cancel()
+ self.Browser = ServiceBrowser(self.ZeroConfInstance, service_type, self)
+
+ def OnRefreshButton(self, event):
+ self.ServicesList.DeleteAllItems()
+ self.RefreshList()
+
+ def OnLocalButton(self, event):
+ self.URI = "LOCAL://"
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+ def OnIpButton(self, event):
+ if self.LatestSelection is not None:
+ l = lambda col : self.getColumnText(self.LatestSelection,col)
+ self.URI = "%s://%s:%s"%tuple(map(l,(1,2,3)))
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+ # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+ def GetListCtrl(self):
+ return self.ServicesList
+
+ def getColumnText(self, index, col):
+ item = self.ServicesList.GetItem(index, col)
+ return item.GetText()
+
+ def OnItemSelected(self, event):
+ self.SetURI(event.m_itemIndex)
+ event.Skip()
+
+ def OnItemActivated(self, event):
+ self.SetURI(event.m_itemIndex)
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+# def SetURI(self, idx):
+# connect_type = self.getColumnText(idx, 1)
+# connect_address = self.getColumnText(idx, 2)
+# connect_port = self.getColumnText(idx, 3)
+#
+# self.URI = "%s://%s:%s"%(connect_type, connect_address, connect_port)
+
+ def SetURI(self, idx):
+ self.LatestSelection = idx
+ svcname = self.getColumnText(idx, 0)
+ connect_type = self.getColumnText(idx, 1)
+ self.URI = "%s://%s"%(connect_type, svcname + '.' + service_type)
+
+ def GetURI(self):
+ return self.URI
+
+ def removeService(self, zeroconf, _type, name):
+ wx.CallAfter(self._removeService, name)
+
+
+ def _removeService(self, name):
+ '''
+ called when a service with the desired type goes offline.
+ '''
+
+ # loop through the list items looking for the service that went offline
+ for idx in xrange(self.ServicesList.GetItemCount()):
+ # this is the unique identifier assigned to the item
+ item_id = self.ServicesList.GetItemData(idx)
+
+ # this is the full typename that was received by addService
+ item_name = self.itemDataMap[item_id][4]
+
+ if item_name == name:
+ self.ServicesList.DeleteItem(idx)
+ break
+
+ def addService(self, zeroconf, _type, name):
+ wx.CallAfter(self._addService, _type, name)
+
+ def _addService(self, _type, name):
+ '''
+ called when a service with the desired type is discovered.
+ '''
+ info = self.ZeroConfInstance.getServiceInfo(_type, name)
+
+ svcname = name.split(".")[0]
+ typename = _type.split(".")[0][1:]
+ ip = str(socket.inet_ntoa(info.getAddress()))
+ port = info.getPort()
+
+ num_items = self.ServicesList.GetItemCount()
+
+ # display the new data in the list
+ new_item = self.ServicesList.InsertStringItem(num_items, svcname)
+ self.ServicesList.SetStringItem(new_item, 1, "%s" % typename)
+ self.ServicesList.SetStringItem(new_item, 2, "%s" % ip)
+ self.ServicesList.SetStringItem(new_item, 3, "%s" % port)
+
+ # record the new data for the ColumnSorterMixin
+ # we assign every list item a unique id (that won't change when items
+ # are added or removed)
+ self.ServicesList.SetItemData(new_item, self.nextItemId)
+
+ # the value of each column has to be stored in the itemDataMap
+ # so that ColumnSorterMixin knows how to sort the column.
+
+ # "name" is included at the end so that self.removeService
+ # can access it.
+ self.itemDataMap[self.nextItemId] = [ svcname, typename, ip, port, name ]
+
+ self.nextItemId += 1
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/DurationEditorDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+MICROSECONDS = 0.001
+MILLISECONDS = 1
+SECOND = 1000
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+DAY = 24 * HOUR
+
+IEC_TIME_MODEL = re.compile("(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M(?!S)_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?" % {"float": "[0-9]+(?:\.[0-9]+)?"})
+
+CONTROLS = [
+ ("Days", _('Days:')),
+ ("Hours", _('Hours:')),
+ ("Minutes", _('Minutes:')),
+ ("Seconds", _('Seconds:')),
+ ("Milliseconds", _('Milliseconds:')),
+ ("Microseconds", _('Microseconds:')),
+]
+
+#-------------------------------------------------------------------------------
+# Edit Duration Value Dialog
+#-------------------------------------------------------------------------------
+
+class DurationEditorDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(700, 200), title=_('Edit Duration'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ controls_sizer = wx.FlexGridSizer(cols=len(CONTROLS), hgap=10, rows=2, vgap=10)
+ main_sizer.AddSizer(controls_sizer, border=20,
+ flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
+
+ controls = []
+ for i, (name, label) in enumerate(CONTROLS):
+ controls_sizer.AddGrowableCol(i)
+
+ st = wx.StaticText(self, label=label)
+ txtctrl = wx.TextCtrl(self, value='0', style=wx.TE_PROCESS_ENTER)
+ self.Bind(wx.EVT_TEXT_ENTER,
+ self.GetControlValueTestFunction(txtctrl),
+ txtctrl)
+ setattr(self, name, txtctrl)
+
+ controls.append((st, txtctrl))
+
+ for st, txtctrl in controls:
+ controls_sizer.AddWindow(st, flag=wx.GROW)
+
+ for st, txtctrl in controls:
+ controls_sizer.AddWindow(txtctrl, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Days.SetFocus()
+
+ def SetDuration(self, value):
+ result = IEC_TIME_MODEL.match(value.upper())
+ if result is not None:
+ values = result.groups()
+ for control, index in [(self.Days, 1), (self.Hours, 2),
+ (self.Minutes, 3), (self.Seconds, 4)]:
+ value = values[index]
+ if value is not None:
+ control.SetValue(value)
+ else:
+ control.SetValue("0")
+ milliseconds = values[5]
+ if milliseconds is not None:
+ self.Milliseconds.SetValue("%d" % int(float(milliseconds)))
+ self.Microseconds.SetValue("%.3f" % ((float(milliseconds) % MILLISECONDS) / MICROSECONDS))
+ else:
+ self.Milliseconds.SetValue("0")
+ self.Microseconds.SetValue("0")
+
+ def GetControlValueTestFunction(self, control):
+ def OnValueChanged(event):
+ try:
+ value = float(control.GetValue())
+ except ValueError, e:
+ message = wx.MessageDialog(self, _("Invalid value!\nYou must fill a numeric value."), _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Skip()
+ return OnValueChanged
+
+ def GetDuration(self):
+ milliseconds = 0
+ for control, factor in [(self.Days, DAY), (self.Hours, HOUR),
+ (self.Minutes, MINUTE), (self.Seconds, SECOND),
+ (self.Milliseconds, MILLISECONDS), (self.Microseconds, MICROSECONDS)]:
+
+ milliseconds += float(control.GetValue()) * factor
+
+ not_null = False
+ duration = "T#"
+ for value, format in [(int(milliseconds) / DAY, "%dd"),
+ ((int(milliseconds) % DAY) / HOUR, "%dh"),
+ ((int(milliseconds) % HOUR) / MINUTE, "%dm"),
+ ((int(milliseconds) % MINUTE) / SECOND, "%ds")]:
+
+ if value > 0 or not_null:
+ duration += format % value
+ not_null = True
+
+ duration += "%gms" % (milliseconds % SECOND)
+ return duration
+
+ def OnOK(self, event):
+ errors = []
+ for control, name in [(self.Days, "days"), (self.Hours, "hours"),
+ (self.Minutes, "minutes"), (self.Seconds, "seconds"),
+ (self.Milliseconds, "milliseconds")]:
+ try:
+ value = float(control.GetValue())
+ except ValueError, e:
+ errors.append(name)
+ if len(errors) > 0:
+ if len(errors) == 1:
+ message = _("Field %s hasn't a valid value!") % errors[0]
+ else:
+ message = _("Fields %s haven't a valid value!") % ",".join(errors)
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/FBDBlockDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+from controls.LibraryPanel import LibraryPanel
+
+#-------------------------------------------------------------------------------
+# Create New Block Dialog
+#-------------------------------------------------------------------------------
+
+class FBDBlockDialog(wx.Dialog):
+
+ def __init__(self, parent, controller):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(600, 450), title=_('Block Properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ type_staticbox = wx.StaticBox(self, label=_('Type:'))
+ left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL)
+ column_sizer.AddSizer(left_staticboxsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ self.LibraryPanel = LibraryPanel(self)
+ self.LibraryPanel.SetController(controller)
+ setattr(self.LibraryPanel, "_OnTreeItemSelected",
+ self.OnLibraryTreeItemSelected)
+ left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5,
+ flag=wx.GROW|wx.TOP)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(2)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5)
+ top_right_gridsizer.AddGrowableCol(1)
+ right_gridsizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
+
+ name_label = wx.StaticText(self, label=_('Name:'))
+ top_right_gridsizer.AddWindow(name_label,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.BlockName = wx.TextCtrl(self)
+ self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.BlockName)
+ top_right_gridsizer.AddWindow(self.BlockName, flag=wx.GROW)
+
+ inputs_label = wx.StaticText(self, label=_('Inputs:'))
+ top_right_gridsizer.AddWindow(inputs_label,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.Inputs = wx.SpinCtrl(self, min=2, max=20,
+ style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, self.Inputs)
+ top_right_gridsizer.AddWindow(self.Inputs, flag=wx.GROW)
+
+ execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
+ top_right_gridsizer.AddWindow(execution_order_label,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
+ top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+
+ execution_control_label = wx.StaticText(self, label=_('Execution Control:'))
+ top_right_gridsizer.AddWindow(execution_control_label,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.ExecutionControl = wx.CheckBox(self)
+ self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, self.ExecutionControl)
+ top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "GetBlockType", controller.GetBlockType)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Controller = controller
+
+ self.BlockName.SetValue("")
+ self.BlockName.Enable(False)
+ self.Inputs.Enable(False)
+ self.Block = None
+ self.MinBlockSize = None
+ self.First = True
+
+ self.PouNames = []
+ self.PouElementNames = []
+
+ self.LibraryPanel.SetFocus()
+
+ def __del__(self):
+ self.Controller = None
+
+ def SetBlockList(self, blocklist):
+ self.LibraryPanel.SetBlockList(blocklist)
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def OnOK(self, event):
+ message = None
+ selected = self.LibraryPanel.GetSelectedBlock()
+ block_name = self.BlockName.GetValue()
+ name_enabled = self.BlockName.IsEnabled()
+ if selected is None:
+ message = _("Form isn't complete. Valid block type must be selected!")
+ elif name_enabled and block_name == "":
+ message = _("Form isn't complete. Name must be filled!")
+ elif name_enabled and not TestIdentifier(block_name):
+ message = _("\"%s\" is not a valid identifier!") % block_name
+ elif name_enabled and block_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % block_name
+ elif name_enabled and block_name.upper() in self.PouNames:
+ message = _("\"%s\" pou already exists!") % block_name
+ elif name_enabled and block_name.upper() in self.PouElementNames:
+ message = _("\"%s\" element for this pou already exists!") % block_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def SetMinBlockSize(self, size):
+ self.MinBlockSize = size
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetPouElementNames(self, element_names):
+ self.PouElementNames = [element_name.upper() for element_name in element_names]
+
+ def SetValues(self, values):
+ blocktype = values.get("type", None)
+ if blocktype is not None:
+ self.LibraryPanel.SelectTreeItem(blocktype, values.get("inputs", None))
+ for name, value in values.items():
+ if name == "name":
+ self.BlockName.SetValue(value)
+ elif name == "extension":
+ self.Inputs.SetValue(value)
+ elif name == "executionOrder":
+ self.ExecutionOrder.SetValue(value)
+ elif name == "executionControl":
+ self.ExecutionControl.SetValue(value)
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = self.LibraryPanel.GetSelectedBlock()
+ if self.BlockName.IsEnabled() and self.BlockName.GetValue() != "":
+ values["name"] = self.BlockName.GetValue()
+ values["width"], values["height"] = self.Block.GetSize()
+ values["extension"] = self.Inputs.GetValue()
+ values["executionOrder"] = self.ExecutionOrder.GetValue()
+ values["executionControl"] = self.ExecutionControl.GetValue()
+ return values
+
+ def OnLibraryTreeItemSelected(self, event):
+ values = self.LibraryPanel.GetSelectedBlock()
+ if values is not None:
+ blocktype = self.Controller.GetBlockType(values["type"], values["inputs"])
+ else:
+ blocktype = None
+ if blocktype is not None:
+ self.Inputs.SetValue(len(blocktype["inputs"]))
+ self.Inputs.Enable(blocktype["extensible"])
+ self.BlockName.Enable(blocktype["type"] != "function")
+ wx.CallAfter(self.RefreshPreview)
+ else:
+ self.BlockName.Enable(False)
+ self.Inputs.Enable(False)
+ self.Inputs.SetValue(2)
+ wx.CallAfter(self.ErasePreview)
+
+ def OnNameChanged(self, event):
+ if self.BlockName.IsEnabled():
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnInputsChanged(self, event):
+ if self.Inputs.IsEnabled():
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnExecutionOrderChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnExecutionControlChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def ErasePreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.Clear()
+ self.Block = None
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ values = self.LibraryPanel.GetSelectedBlock()
+ if values is not None:
+ if self.BlockName.IsEnabled():
+ blockname = self.BlockName.GetValue()
+ else:
+ blockname = ""
+ self.Block = FBD_Block(self.Preview, values["type"],
+ blockname,
+ extension = self.Inputs.GetValue(),
+ inputs = values["inputs"],
+ executionControl = self.ExecutionControl.GetValue(),
+ executionOrder = self.ExecutionOrder.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)
+ else:
+ self.Block = None
+
+ def OnPaint(self, event):
+ if self.Block is not None:
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/FBDVariableDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,259 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+VARIABLE_CLASSES_DICT = {INPUT : _("Input"),
+ INOUT : _("InOut"),
+ OUTPUT : _("Output")}
+VARIABLE_CLASSES_DICT_REVERSE = dict(
+ [(value, key) for key, value in VARIABLE_CLASSES_DICT.iteritems()])
+
+#-------------------------------------------------------------------------------
+# Create New Variable Dialog
+#-------------------------------------------------------------------------------
+
+class FBDVariableDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, transition = ""):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(400, 380), title=_('Variable Properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(2)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ class_label = wx.StaticText(self, label=_('Class:'))
+ left_gridsizer.AddWindow(class_label, flag=wx.GROW)
+
+ self.Class = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnClassChanged, self.Class)
+ left_gridsizer.AddWindow(self.Class, flag=wx.GROW)
+
+ expression_label = wx.StaticText(self, label=_('Expression:'))
+ left_gridsizer.AddWindow(expression_label, flag=wx.GROW)
+
+ self.Expression = wx.TextCtrl(self)
+ self.Bind(wx.EVT_TEXT, self.OnExpressionChanged, self.Expression)
+ left_gridsizer.AddWindow(self.Expression, flag=wx.GROW)
+
+ execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
+ left_gridsizer.AddWindow(execution_order_label, flag=wx.GROW)
+
+ self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
+ left_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ name_label = wx.StaticText(self, label=_('Name:'))
+ right_gridsizer.AddWindow(name_label, flag=wx.GROW)
+
+ self.VariableName = wx.ListBox(self, size=wx.Size(0, 0),
+ style=wx.LB_SINGLE|wx.LB_SORT)
+ self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
+ right_gridsizer.AddWindow(self.VariableName, flag=wx.GROW)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ main_sizer.AddWindow(preview_label, border=20,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ main_sizer.AddWindow(self.Preview, border=20,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Transition = transition
+ self.Variable = None
+ self.VarList = []
+ self.MinVariableSize = None
+
+ for choice in VARIABLE_CLASSES_DICT.itervalues():
+ self.Class.Append(choice)
+ self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[INPUT])
+
+ self.RefreshNameList()
+ self.Class.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def RefreshNameList(self):
+ selected = self.VariableName.GetStringSelection()
+ var_class = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+ self.VariableName.Clear()
+ self.VariableName.Append("")
+ for name, var_type, value_type in self.VarList:
+ if var_type != "Input" or var_class == INPUT:
+ self.VariableName.Append(name)
+ if selected != "" and self.VariableName.FindString(selected) != wx.NOT_FOUND:
+ self.VariableName.SetStringSelection(selected)
+ self.Expression.Enable(False)
+ else:
+ self.VariableName.SetStringSelection("")
+ #self.Expression.Enable(var_class == INPUT)
+ self.VariableName.Enable(self.VariableName.GetCount() > 0)
+
+ def SetMinVariableSize(self, size):
+ self.MinVariableSize = size
+
+ def SetVariables(self, vars):
+ self.VarList = vars
+ self.RefreshNameList()
+
+ def SetValues(self, values):
+ value_type = values.get("type", None)
+ value_name = values.get("name", None)
+ if value_type:
+ self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[value_type])
+ self.RefreshNameList()
+ if value_name:
+ if self.VariableName.FindString(value_name) != wx.NOT_FOUND:
+ self.VariableName.SetStringSelection(value_name)
+ self.Expression.Enable(False)
+ else:
+ self.Expression.SetValue(value_name)
+ self.VariableName.Enable(False)
+ if "executionOrder" in values:
+ self.ExecutionOrder.SetValue(values["executionOrder"])
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = {}
+ values["type"] = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+ expression = self.Expression.GetValue()
+ if self.Expression.IsEnabled() and expression != "":
+ values["name"] = expression
+ else:
+ values["name"] = self.VariableName.GetStringSelection()
+ values["value_type"] = None
+ 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()
+ values["executionOrder"] = self.ExecutionOrder.GetValue()
+ return values
+
+ def OnOK(self, event):
+ message = None
+ expression = self.Expression.GetValue()
+ if self.Expression.IsEnabled():
+ value = expression
+ else:
+ value = self.VariableName.GetStringSelection()
+ if value == "":
+ message = _("At least a variable or an expression must be selected!")
+ elif value.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % value
+ if message is not None:
+ message = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def OnClassChanged(self, event):
+ self.RefreshNameList()
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ if self.VariableName.GetStringSelection() != "":
+ self.Expression.Enable(False)
+ elif VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()] == INPUT:
+ self.Expression.Enable(True)
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnExpressionChanged(self, event):
+ if self.Expression.GetValue() != "":
+ self.VariableName.Enable(False)
+ else:
+ self.VariableName.Enable(True)
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnExecutionOrderChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ expression = self.Expression.GetValue()
+ if self.Expression.IsEnabled() and expression != "":
+ name = expression
+ else:
+ name = self.VariableName.GetStringSelection()
+ type = ""
+ for var_name, var_type, value_type in self.VarList:
+ if var_name == name:
+ type = value_type
+ classtype = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+ self.Variable = FBD_Variable(self.Preview, classtype, name, type, executionOrder = self.ExecutionOrder.GetValue())
+ 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()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/FindInPouDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+class FindInPouDialog(wx.Frame):
+
+ def __init__(self, parent):
+ wx.Frame.__init__(self, parent, title=_("Find"),
+ size=wx.Size(400, 250), style=wx.CAPTION|
+ wx.CLOSE_BOX|
+ wx.CLIP_CHILDREN|
+ wx.RESIZE_BORDER|
+ wx.STAY_ON_TOP)
+
+ panel = wx.Panel(self, style=wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ controls_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.AddSizer(controls_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ patterns_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
+ patterns_sizer.AddGrowableCol(1)
+ controls_sizer.AddSizer(patterns_sizer, border=5, flag=wx.GROW|wx.BOTTOM)
+
+ find_label = wx.StaticText(panel, label=_("Find:"))
+ patterns_sizer.AddWindow(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.FindPattern = wx.TextCtrl(panel)
+ self.Bind(wx.EVT_TEXT, self.OnFindPatternChanged, self.FindPattern)
+ patterns_sizer.AddWindow(self.FindPattern, flag=wx.GROW)
+
+ params_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ controls_sizer.AddSizer(params_sizer, border=5, flag=wx.GROW|wx.BOTTOM)
+
+ direction_staticbox = wx.StaticBox(panel, label=_("Direction"))
+ direction_staticboxsizer = wx.StaticBoxSizer(
+ direction_staticbox, wx.VERTICAL)
+ params_sizer.AddSizer(direction_staticboxsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ self.Forward = wx.RadioButton(panel, label=_("Forward"),
+ style=wx.RB_GROUP)
+ direction_staticboxsizer.AddWindow(self.Forward, border=5,
+ flag=wx.ALL|wx.GROW)
+
+ self.Backward = wx.RadioButton(panel, label=_("Backward"))
+ direction_staticboxsizer.AddWindow(self.Backward, border=5,
+ flag=wx.ALL|wx.GROW)
+
+ options_staticbox = wx.StaticBox(panel, label=_("Options"))
+ options_staticboxsizer = wx.StaticBoxSizer(
+ options_staticbox, wx.VERTICAL)
+ params_sizer.AddSizer(options_staticboxsizer, 1, flag=wx.GROW)
+
+ self.CaseSensitive = wx.CheckBox(panel, label=_("Case sensitive"))
+ self.CaseSensitive.SetValue(True)
+ options_staticboxsizer.AddWindow(self.CaseSensitive, border=5,
+ flag=wx.ALL|wx.GROW)
+
+ self.WrapSearch = wx.CheckBox(panel, label=_("Wrap search"))
+ self.WrapSearch.SetValue(True)
+ options_staticboxsizer.AddWindow(self.WrapSearch, border=5,
+ flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+
+ self.RegularExpressions = wx.CheckBox(panel, label=_("Regular expressions"))
+ options_staticboxsizer.AddWindow(self.RegularExpressions, border=5,
+ flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+
+ buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(buttons_sizer, border=20,
+ flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT)
+
+ self.FindButton = wx.Button(panel, label=_("Find"))
+ self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
+ buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
+
+ self.CloseButton = wx.Button(panel, label=("Close"))
+ self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
+ buttons_sizer.AddWindow(self.CloseButton)
+
+ panel.SetSizer(main_sizer)
+
+ self.ParentWindow = parent
+
+ self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
+
+ self.RefreshButtonsState()
+
+ def RefreshButtonsState(self):
+ find_pattern = self.FindPattern.GetValue()
+ self.FindButton.Enable(find_pattern != "")
+
+ def OnCloseFrame(self, event):
+ self.Hide()
+ event.Veto()
+
+ def OnCloseButton(self, event):
+ self.Hide()
+ event.Skip()
+
+ def OnFindPatternChanged(self, event):
+ self.RefreshButtonsState()
+ event.Skip()
+
+ def OnFindButton(self, event):
+ infos = {
+ "find_pattern": self.FindPattern.GetValue(),
+ "wrap": self.WrapSearch.GetValue(),
+ "case_sensitive": self.CaseSensitive.GetValue(),
+ "regular_expression": self.RegularExpressions.GetValue()}
+ wx.CallAfter(self.ParentWindow.FindInPou,
+ {True: 1, False:-1}[self.Forward.GetValue()],
+ infos)
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/ForceVariableDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+import datetime
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+LOCATIONDATATYPES = {"X" : ["BOOL"],
+ "B" : ["SINT", "USINT", "BYTE", "STRING"],
+ "W" : ["INT", "UINT", "WORD", "WSTRING"],
+ "D" : ["DINT", "UDINT", "REAL", "DWORD"],
+ "L" : ["LINT", "ULINT", "LREAL", "LWORD"]}
+
+def gen_get_function(f):
+ def get_function(v):
+ try:
+ return f(v)
+ except:
+ return None
+ return get_function
+
+getinteger = gen_get_function(int)
+getfloat = gen_get_function(float)
+getstring = gen_get_function(str)
+
+SECOND = 1000000
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+DAY = 24 * HOUR
+
+IEC_TIME_MODEL = re.compile("(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M(?!S)_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?" % {"float": "[0-9]+(?:\.[0-9]+)?"})
+IEC_DATE_MODEL = re.compile("(?:(?:D|DATE)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})")
+IEC_DATETIME_MODEL = re.compile("(?:(?:DT|DATE_AND_TIME)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)")
+IEC_TIMEOFDAY_MODEL = re.compile("(?:(?:TOD|TIME_OF_DAY)#)?([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)")
+
+def gettime(v):
+ result = IEC_TIME_MODEL.match(v.upper())
+ if result is not None:
+ negative, days, hours, minutes, seconds, milliseconds = result.groups()
+ microseconds = 0
+ not_null = False
+ for value, factor in [(days, DAY),
+ (hours, HOUR),
+ (minutes, MINUTE),
+ (seconds, SECOND),
+ (milliseconds, 1000)]:
+ if value is not None:
+ microseconds += float(value) * factor
+ not_null = True
+ if not not_null:
+ return None
+ if negative is not None:
+ microseconds = -microseconds
+ return datetime.timedelta(microseconds=microseconds)
+
+ else:
+ return None
+
+def getdate(v):
+ result = IEC_DATE_MODEL.match(v.upper())
+ if result is not None:
+ year, month, day = result.groups()
+ try:
+ date = datetime.datetime(int(year), int(month), int(day))
+ except ValueError, e:
+ return None
+ base_date = datetime.datetime(1970, 1, 1)
+ return date - base_date
+ else:
+ return None
+
+def getdatetime(v):
+ result = IEC_DATETIME_MODEL.match(v.upper())
+ if result is not None:
+ year, month, day, hours, minutes, seconds = result.groups()
+ try:
+ date = datetime.datetime(int(year), int(month), int(day), int(hours), int(minutes), int(float(seconds)), int((float(second) * SECOND) % SECOND))
+ except ValueError, e:
+ return None
+ base_date = datetime.datetime(1970, 1, 1)
+ return date - base_date
+ else:
+ return None
+
+def gettimeofday(v):
+ result = IEC_TIMEOFDAY_MODEL.match(v.upper())
+ if result is not None:
+ hours, minutes, seconds = result.groups()
+ microseconds = 0
+ for value, factor in [(hours, HOUR),
+ (minutes, MINUTE),
+ (seconds, SECOND)]:
+ microseconds += float(value) * factor
+ return datetime.timedelta(microseconds=microseconds)
+ else:
+ return None
+
+GetTypeValue = {"BOOL": lambda x: {"TRUE": True, "FALSE": False, "0": False, "1": True}.get(x.upper(), None),
+ "SINT": getinteger,
+ "INT": getinteger,
+ "DINT": getinteger,
+ "LINT": getinteger,
+ "USINT": getinteger,
+ "UINT": getinteger,
+ "UDINT": getinteger,
+ "ULINT": getinteger,
+ "BYTE": getinteger,
+ "WORD": getinteger,
+ "DWORD": getinteger,
+ "LWORD": getinteger,
+ "REAL": getfloat,
+ "LREAL": getfloat,
+ "STRING": getstring,
+ "WSTRING": getstring,
+ "TIME": gettime,
+ "DATE": getdate,
+ "DT": getdatetime,
+ "TOD": gettimeofday}
+
+#-------------------------------------------------------------------------------
+# Force Variable Dialog
+#-------------------------------------------------------------------------------
+
+class ForceVariableDialog(wx.TextEntryDialog):
+
+ def __init__(self, parent, iec_type, defaultValue=""):
+ wx.TextEntryDialog.__init__(self, parent, message = _("Forcing Variable Value"),
+ caption = _("Please enter value for a \"%s\" variable:") % iec_type, defaultValue = defaultValue,
+ style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition)
+
+ self.IEC_Type = iec_type
+
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+
+ def OnOK(self, event):
+ message = None
+ value = self.GetSizer().GetItem(1).GetWindow().GetValue()
+ if value == "":
+ message = _("You must type a value!")
+ elif GetTypeValue[self.IEC_Type](value) is None:
+ message = _("Invalid value \"%s\" for \"%s\" variable!") % (value, self.IEC_Type)
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+ def GetValue(self):
+ return GetTypeValue[self.IEC_Type](wx.TextEntryDialog.GetValue(self))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/LDElementDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Edit Ladder Element Properties Dialog
+#-------------------------------------------------------------------------------
+
+class LDElementDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, type):
+ if type == "contact":
+ wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
+ title=_("Edit Contact Values"))
+ else:
+ wx.Dialog.__init__(self, parent, size=wx.Size(350, 310),
+ title=_("Edit Coil Values"))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ if type == "contact":
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=0)
+ else:
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=9, vgap=0)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ modifier_label = wx.StaticText(self, label=_('Modifier:'))
+ left_gridsizer.AddWindow(modifier_label, border=5, flag=wx.GROW|wx.BOTTOM)
+
+ self.Normal = wx.RadioButton(self, label=_("Normal"), style=wx.RB_GROUP)
+ self.Normal.SetValue(True)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Normal)
+ left_gridsizer.AddWindow(self.Normal, flag=wx.GROW)
+
+ self.Negated = wx.RadioButton(self, label=_("Negated"))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Negated)
+ left_gridsizer.AddWindow(self.Negated, flag=wx.GROW)
+
+ if type != "contact":
+ self.Set = wx.RadioButton(self, label=_("Set"))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Set)
+ left_gridsizer.AddWindow(self.Set, flag=wx.GROW)
+
+ self.Reset = wx.RadioButton(self, label=_("Reset"))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Reset)
+ left_gridsizer.AddWindow(self.Reset, flag=wx.GROW)
+
+ self.RisingEdge = wx.RadioButton(self, label=_("Rising Edge"))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RisingEdge)
+ left_gridsizer.AddWindow(self.RisingEdge, flag=wx.GROW)
+
+ self.FallingEdge = wx.RadioButton(self, label=_("Falling Edge"))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.FallingEdge)
+ left_gridsizer.AddWindow(self.FallingEdge, flag=wx.GROW)
+
+ element_name_label = wx.StaticText(self, label=_('Name:'))
+ left_gridsizer.AddWindow(element_name_label, border=5, flag=wx.GROW|wx.TOP)
+
+ self.ElementName = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnNameChanged, self.ElementName)
+ left_gridsizer.AddWindow(self.ElementName, border=5, flag=wx.GROW|wx.TOP)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ if type == "contact":
+ self.Element = LD_Contact(self.Preview, CONTACT_NORMAL, "")
+ else:
+ self.Element = LD_Coil(self.Preview, COIL_NORMAL, "")
+
+ self.Type = type
+
+ self.Normal.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def SetElementSize(self, size):
+ min_width, min_height = self.Element.GetMinSize()
+ width, height = max(min_width, size[0]), max(min_height, size[1])
+ self.Element.SetSize(width, height)
+
+ def SetVariables(self, vars):
+ self.ElementName.Clear()
+ for name in vars:
+ self.ElementName.Append(name)
+ self.ElementName.Enable(self.ElementName.GetCount() > 0)
+
+ def SetValues(self, values):
+ for name, value in values.items():
+ if name == "name":
+ self.Element.SetName(value)
+ self.ElementName.SetStringSelection(value)
+ elif name == "type":
+ self.Element.SetType(value)
+ if self.Type == "contact":
+ if value == CONTACT_NORMAL:
+ self.Normal.SetValue(True)
+ elif value == CONTACT_REVERSE:
+ self.Negated.SetValue(True)
+ elif value == CONTACT_RISING:
+ self.RisingEdge.SetValue(True)
+ elif value == CONTACT_FALLING:
+ self.FallingEdge.SetValue(True)
+ elif self.Type == "coil":
+ if value == COIL_NORMAL:
+ self.Normal.SetValue(True)
+ elif value == COIL_REVERSE:
+ self.Negated.SetValue(True)
+ elif value == COIL_SET:
+ self.Set.SetValue(True)
+ elif value == COIL_RESET:
+ self.Reset.SetValue(True)
+ elif value == COIL_RISING:
+ self.RisingEdge.SetValue(True)
+ elif value == COIL_FALLING:
+ self.FallingEdge.SetValue(True)
+
+ def GetValues(self):
+ values = {}
+ values["name"] = self.Element.GetName()
+ values["type"] = self.Element.GetType()
+ values["width"], values["height"] = self.Element.GetSize()
+ return values
+
+ def OnTypeChanged(self, event):
+ if self.Type == "contact":
+ if self.Normal.GetValue():
+ self.Element.SetType(CONTACT_NORMAL)
+ elif self.Negated.GetValue():
+ self.Element.SetType(CONTACT_REVERSE)
+ elif self.RisingEdge.GetValue():
+ self.Element.SetType(CONTACT_RISING)
+ elif self.FallingEdge.GetValue():
+ self.Element.SetType(CONTACT_FALLING)
+ elif self.Type == "coil":
+ if self.Normal.GetValue():
+ self.Element.SetType(COIL_NORMAL)
+ elif self.Negated.GetValue():
+ self.Element.SetType(COIL_REVERSE)
+ elif self.Set.GetValue():
+ self.Element.SetType(COIL_SET)
+ elif self.Reset.GetValue():
+ self.Element.SetType(COIL_RESET)
+ elif self.RisingEdge.GetValue():
+ self.Element.SetType(COIL_RISING)
+ elif self.FallingEdge.GetValue():
+ self.Element.SetType(COIL_FALLING)
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.Element.SetName(self.ElementName.GetStringSelection())
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ clientsize = self.Preview.GetClientSize()
+ width, height = self.Element.GetSize()
+ self.Element.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
+ self.Element.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/LDPowerRailDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Edit Ladder Power Rail Properties Dialog
+#-------------------------------------------------------------------------------
+
+class LDPowerRailDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, type = LEFTRAIL, number = 1):
+ wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
+ title=_('Power Rail Properties'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ type_label = wx.StaticText(self, label=_('Type:'))
+ left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+
+ self.LeftPowerRail = wx.RadioButton(self,
+ label=_('Left PowerRail'), style=wx.RB_GROUP)
+ self.LeftPowerRail.SetValue(True)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.LeftPowerRail)
+ left_gridsizer.AddWindow(self.LeftPowerRail, flag=wx.GROW)
+
+ self.RightPowerRail = wx.RadioButton(self, label=_('Right PowerRail'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RightPowerRail)
+ left_gridsizer.AddWindow(self.RightPowerRail, flag=wx.GROW)
+
+ pin_number_label = wx.StaticText(self, label=_('Pin number:'))
+ left_gridsizer.AddWindow(pin_number_label, flag=wx.GROW)
+
+ self.PinNumber = wx.SpinCtrl(self, min=1, max=50,
+ style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_SPINCTRL, self.OnPinNumberChanged, self.PinNumber)
+ left_gridsizer.AddWindow(self.PinNumber, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Type = type
+ if type == LEFTRAIL:
+ self.LeftPowerRail.SetValue(True)
+ elif type == RIGHTRAIL:
+ self.RightPowerRail.SetValue(True)
+ self.PinNumber.SetValue(number)
+
+ self.PowerRailMinSize = (0, 0)
+ self.PowerRail = None
+
+ self.LeftPowerRail.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def SetMinSize(self, size):
+ self.PowerRailMinSize = size
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = {}
+ values["type"] = self.Type
+ values["number"] = self.PinNumber.GetValue()
+ values["width"], values["height"] = self.PowerRail.GetSize()
+ return values
+
+ def OnTypeChanged(self, event):
+ if self.LeftPowerRail.GetValue():
+ self.Type = LEFTRAIL
+ elif self.RightPowerRail.GetValue():
+ self.Type = RIGHTRAIL
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnPinNumberChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ self.PowerRail = LD_PowerRail(self.Preview, self.Type, connectors = self.PinNumber.GetValue())
+ min_width, min_height = 2, LD_LINE_SIZE * self.PinNumber.GetValue()
+ width, height = max(min_width, self.PowerRailMinSize[0]), max(min_height, self.PowerRailMinSize[1])
+ self.PowerRail.SetSize(width, height)
+ clientsize = self.Preview.GetClientSize()
+ self.PowerRail.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
+ self.PowerRail.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/PouActionDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,128 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+
+def GetActionLanguages():
+ _ = lambda x : x
+ return [_("IL"), _("ST"), _("LD"), _("FBD")]
+ACTION_LANGUAGES_DICT = dict([(_(language), language) for language in GetActionLanguages()])
+
+class PouActionDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent, size=wx.Size(320, 160),
+ title=_('Create a new action'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
+ infos_sizer.AddGrowableCol(1)
+ main_sizer.AddSizer(infos_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ actionname_label = wx.StaticText(self, label=_('Action Name:'))
+ infos_sizer.AddWindow(actionname_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.ActionName = wx.TextCtrl(self)
+ infos_sizer.AddWindow(self.ActionName, flag=wx.GROW)
+
+ language_label = wx.StaticText(self, label=_('Language:'))
+ infos_sizer.AddWindow(language_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
+ infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for option in GetActionLanguages():
+ self.Language.Append(_(option))
+
+ self.PouNames = []
+ self.PouElementNames = []
+
+ def OnOK(self, event):
+ error = []
+ action_name = self.ActionName.GetValue()
+ if action_name == "":
+ error.append(_("Action Name"))
+ if self.Language.GetSelection() == -1:
+ error.append(_("Language"))
+ message = None
+ 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 = _("Form isn't complete. %s must be filled!") % text
+ elif not TestIdentifier(action_name):
+ message = _("\"%s\" is not a valid identifier!") % action_name
+ elif action_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % action_name
+ elif action_name.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % action_name
+ elif action_name.upper() in self.PouElementNames:
+ message = _("\"%s\" element for this pou already exists!") % action_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetPouElementNames(self, element_names):
+ self.PouElementNames = [element_name.upper() for element_name in element_names]
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "actionName":
+ self.ActionName.SetValue(value)
+ elif item == "language":
+ self.Language.SetStringSelection(_(value))
+
+ def GetValues(self):
+ values = {}
+ values["actionName"] = self.ActionName.GetValue()
+ values["language"] = ACTION_LANGUAGES_DICT[self.Language.GetStringSelection()]
+ return values
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/PouDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,171 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+
+def GetPouTypes():
+ _ = lambda x : x
+ return [_("function"), _("functionBlock"), _("program")]
+POU_TYPES_DICT = dict([(_(pou_type), pou_type) for pou_type in GetPouTypes()])
+
+def GetPouLanguages():
+ _ = lambda x : x
+ return [_("IL"), _("ST"), _("LD"), _("FBD"), _("SFC")]
+POU_LANGUAGES_DICT = dict([(_(language), language) for language in GetPouLanguages()])
+
+class PouDialog(wx.Dialog):
+
+ def __init__(self, parent, pou_type = None):
+ wx.Dialog.__init__(self, id=-1, parent=parent,
+ name='PouDialog', title=_('Create a new POU'),
+ size=wx.Size(300, 200), style=wx.DEFAULT_DIALOG_STYLE)
+ self.SetClientSize(wx.Size(300, 200))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
+ infos_sizer.AddGrowableCol(1)
+ main_sizer.AddSizer(infos_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ pouname_label = wx.StaticText(self, label=_('POU Name:'))
+ infos_sizer.AddWindow(pouname_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.PouName = wx.TextCtrl(self)
+ infos_sizer.AddWindow(self.PouName, flag=wx.GROW)
+
+ poutype_label = wx.StaticText(self, label=_('POU Type:'))
+ infos_sizer.AddWindow(poutype_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.PouType = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnTypeChanged, self.PouType)
+ infos_sizer.AddWindow(self.PouType, flag=wx.GROW)
+
+ language_label = wx.StaticText(self, label=_('Language:'))
+ infos_sizer.AddWindow(language_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
+ infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for option in GetPouTypes():
+ self.PouType.Append(_(option))
+ if pou_type is not None:
+ self.PouType.SetStringSelection(_(pou_type))
+ self.RefreshLanguage()
+
+ self.PouNames = []
+ self.PouElementNames = []
+
+ def OnOK(self, event):
+ error = []
+ pou_name = self.PouName.GetValue()
+ if pou_name == "":
+ error.append(_("POU Name"))
+ if self.PouType.GetSelection() == -1:
+ error.append(_("POU Type"))
+ if self.Language.GetSelection() == -1:
+ error.append(_("Language"))
+ message = None
+ question = False
+ 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 = _("Form isn't complete. %s must be filled!") % text
+ elif not TestIdentifier(pou_name):
+ message = _("\"%s\" is not a valid identifier!") % pou_name
+ elif pou_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % pou_name
+ elif pou_name.upper() in self.PouNames:
+ message = _("\"%s\" pou already exists!") % pou_name
+ elif pou_name.upper() in self.PouElementNames:
+ message = _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?") % pou_name
+ question = True
+ if message is not None:
+ if question:
+ dialog = wx.MessageDialog(self, message, _("Warning"), wx.YES_NO|wx.ICON_EXCLAMATION)
+ result = dialog.ShowModal()
+ dialog.Destroy()
+ if result == wx.ID_YES:
+ self.EndModal(wx.ID_OK)
+ else:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def RefreshLanguage(self):
+ selection = POU_LANGUAGES_DICT.get(self.Language.GetStringSelection(), "")
+ self.Language.Clear()
+ for language in GetPouLanguages():
+ if language != "SFC" or POU_TYPES_DICT[self.PouType.GetStringSelection()] != "function":
+ self.Language.Append(_(language))
+ if self.Language.FindString(_(selection)) != wx.NOT_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 SetPouElementNames(self, element_names):
+ self.PouElementNames = [element_name.upper() for element_name in element_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"] = POU_TYPES_DICT[self.PouType.GetStringSelection()]
+ values["language"] = POU_LANGUAGES_DICT[self.Language.GetStringSelection()]
+ return values
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/PouNameDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+#-------------------------------------------------------------------------------
+# POU Name Dialog
+#-------------------------------------------------------------------------------
+
+class PouNameDialog(wx.TextEntryDialog):
+
+ def __init__(self, parent, message, caption = "Please enter text", defaultValue = "",
+ style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
+ wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
+
+ self.PouNames = []
+
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+
+ def OnOK(self, event):
+ message = None
+ step_name = self.GetSizer().GetItem(1).GetWindow().GetValue()
+ if step_name == "":
+ message = _("You must type a name!")
+ elif not TestIdentifier(step_name):
+ message = _("\"%s\" is not a valid identifier!") % step_name
+ elif step_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % step_name
+ elif step_name.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % step_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/PouTransitionDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,130 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+
+#-------------------------------------------------------------------------------
+# POU Transition Dialog
+#-------------------------------------------------------------------------------
+
+def GetTransitionLanguages():
+ _ = lambda x : x
+ return [_("IL"), _("ST"), _("LD"), _("FBD")]
+TRANSITION_LANGUAGES_DICT = dict([(_(language), language) for language in GetTransitionLanguages()])
+
+class PouTransitionDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent, size=wx.Size(350, 160),
+ title=_('Create a new transition'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
+ infos_sizer.AddGrowableCol(1)
+ main_sizer.AddSizer(infos_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ transitionname_label = wx.StaticText(self, label=_('Transition Name:'))
+ infos_sizer.AddWindow(transitionname_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.TransitionName = wx.TextCtrl(self)
+ infos_sizer.AddWindow(self.TransitionName, flag=wx.GROW)
+
+ language_label = wx.StaticText(self, label=_('Language:'))
+ infos_sizer.AddWindow(language_label, border=4,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP)
+
+ self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
+ infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for language in GetTransitionLanguages():
+ self.Language.Append(_(language))
+
+ self.PouNames = []
+ self.PouElementNames = []
+
+ def OnOK(self, event):
+ error = []
+ transition_name = self.TransitionName.GetValue()
+ if self.TransitionName.GetValue() == "":
+ error.append(_("Transition Name"))
+ if self.Language.GetSelection() == -1:
+ error.append(_("Language"))
+ message = None
+ 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 = _("Form isn't complete. %s must be filled!") % text
+ elif not TestIdentifier(transition_name):
+ message = _("\"%s\" is not a valid identifier!") % transition_name
+ elif transition_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % transition_name
+ elif transition_name.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % transition_name
+ elif transition_name.upper() in self.PouElementNames:
+ message = _("\"%s\" element for this pou already exists!") % transition_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetPouElementNames(self, pou_names):
+ self.PouElementNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetValues(self, values):
+ for item, value in values.items():
+ if item == "transitionName":
+ self.TransitionName.SetValue(value)
+ elif item == "language":
+ self.Language.SetSelection(_(value))
+
+ def GetValues(self):
+ values = {}
+ values["transitionName"] = self.TransitionName.GetValue()
+ values["language"] = TRANSITION_LANGUAGES_DICT[self.Language.GetStringSelection()]
+ return values
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/ProjectDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,81 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from controls import ProjectPropertiesPanel
+
+class ProjectDialog(wx.Dialog):
+
+ def __init__(self, parent, enable_required=True):
+ wx.Dialog.__init__(self, parent, title=_('Project properties'),
+ size=wx.Size(500, 350), style=wx.DEFAULT_DIALOG_STYLE)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ self.ProjectProperties = ProjectPropertiesPanel(self,
+ enable_required=enable_required)
+ main_sizer.AddWindow(self.ProjectProperties, flag=wx.GROW)
+
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ self.ButtonSizer.GetAffirmativeButton())
+ main_sizer.AddSizer(self.ButtonSizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ def OnOK(self, event):
+ values = self.ProjectProperties.GetValues()
+ error = []
+ for param, name in [("projectName", "Project Name"),
+ ("productName", "Product Name"),
+ ("productVersion", "Product Version"),
+ ("companyName", "Company Name")]:
+ if values[param] == "":
+ error.append(name)
+ 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
+ dialog = wx.MessageDialog(self,
+ _("Form isn't complete. %s must be filled!") % text,
+ _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def SetValues(self, values):
+ self.ProjectProperties.SetValues(values)
+
+ def GetValues(self):
+ return self.ProjectProperties.GetValues()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/SFCDivergenceDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Create New Divergence Dialog
+#-------------------------------------------------------------------------------
+
+class SFCDivergenceDialog(wx.Dialog):
+
+ def __init__(self, parent, controller):
+ wx.Dialog.__init__(self, parent, size=wx.Size(500, 300),
+ title=_('Create a new divergence or convergence'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ type_label = wx.StaticText(self, label=_('Type:'))
+ left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+
+ self.SelectionDivergence = wx.RadioButton(self,
+ label=_('Selection Divergence'), style=wx.RB_GROUP)
+ self.SelectionDivergence.SetValue(True)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
+ self.SelectionDivergence)
+ left_gridsizer.AddWindow(self.SelectionDivergence, flag=wx.GROW)
+
+ self.SelectionConvergence = wx.RadioButton(self,
+ label=_('Selection Convergence'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
+ self.SelectionConvergence)
+ left_gridsizer.AddWindow(self.SelectionConvergence, flag=wx.GROW)
+
+ self.SimultaneousDivergence = wx.RadioButton(self,
+ label=_('Simultaneous Divergence'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
+ self.SimultaneousDivergence)
+ left_gridsizer.AddWindow(self.SimultaneousDivergence, flag=wx.GROW)
+
+ self.SimultaneousConvergence = wx.RadioButton(self,
+ label=_('Simultaneous Convergence'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged,
+ self.SimultaneousConvergence)
+ left_gridsizer.AddWindow(self.SimultaneousConvergence, flag=wx.GROW)
+
+ sequences_label = wx.StaticText(self,
+ label=_('Number of sequences:'))
+ left_gridsizer.AddWindow(sequences_label, flag=wx.GROW)
+
+ self.Sequences = wx.SpinCtrl(self, min=2, max=20)
+ self.Bind(wx.EVT_SPINCTRL, self.OnSequencesChanged, self.Sequences)
+ left_gridsizer.AddWindow(self.Sequences, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self, style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ main_sizer.AddSizer(button_sizer, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Divergence = None
+ self.MinSize = (0, 0)
+
+ self.SelectionDivergence.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def GetValues(self):
+ values = {}
+ if self.SelectionDivergence.GetValue():
+ values["type"] = SELECTION_DIVERGENCE
+ elif self.SelectionConvergence.GetValue():
+ values["type"] = SELECTION_CONVERGENCE
+ elif self.SimultaneousDivergence.GetValue():
+ values["type"] = SIMULTANEOUS_DIVERGENCE
+ else:
+ values["type"] = SIMULTANEOUS_CONVERGENCE
+ values["number"] = self.Sequences.GetValue()
+ return values
+
+ def SetMinSize(self, size):
+ self.MinSize = size
+
+ def OnTypeChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnSequencesChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ if self.SelectionDivergence.GetValue():
+ self.Divergence = SFC_Divergence(self.Preview, SELECTION_DIVERGENCE, self.Sequences.GetValue())
+ elif self.SelectionConvergence.GetValue():
+ self.Divergence = SFC_Divergence(self.Preview, SELECTION_CONVERGENCE, self.Sequences.GetValue())
+ elif self.SimultaneousDivergence.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()
+ min_width, min_height = max(width, self.MinSize[0]), max(height, self.MinSize[1])
+ self.Divergence.SetSize(min_width, min_height)
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - min_width) / 2
+ y = (clientsize.height - min_height) / 2
+ self.Divergence.SetPosition(x, y)
+ self.Divergence.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/SFCStepDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Edit Step Content Dialog
+#-------------------------------------------------------------------------------
+
+class SFCStepDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, initial = False):
+ wx.Dialog.__init__(self, parent, title=_('Edit Step'),
+ size=wx.Size(400, 250))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ name_label = wx.StaticText(self, label=_('Name:'))
+ left_gridsizer.AddWindow(name_label, flag=wx.GROW)
+
+ self.StepName = wx.TextCtrl(self)
+ self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.StepName)
+ left_gridsizer.AddWindow(self.StepName, flag=wx.GROW)
+
+ connectors_label = wx.StaticText(self, label=_('Connectors:'))
+ left_gridsizer.AddWindow(connectors_label, flag=wx.GROW)
+
+ self.Input = wx.CheckBox(self, label=_("Input"))
+ self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Input)
+ left_gridsizer.AddWindow(self.Input, flag=wx.GROW)
+
+ self.Output = wx.CheckBox(self, label=_("Output"))
+ self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Output)
+ left_gridsizer.AddWindow(self.Output, flag=wx.GROW)
+
+ self.Action = wx.CheckBox(self, label=_("Action"))
+ self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Action)
+ left_gridsizer.AddWindow(self.Action, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "RefreshStepModel", lambda x:None)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(
+ wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Step = None
+ self.Initial = initial
+ self.MinStepSize = None
+
+ self.PouNames = []
+ self.Variables = []
+ self.StepNames = []
+
+ self.StepName.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def OnOK(self, event):
+ message = None
+ step_name = self.StepName.GetValue()
+ if step_name == "":
+ message = _("You must type a name!")
+ elif not TestIdentifier(step_name):
+ message = _("\"%s\" is not a valid identifier!") % step_name
+ elif step_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % step_name
+ elif step_name.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % step_name
+ elif step_name.upper() in self.Variables:
+ message = _("A variable with \"%s\" as name already exists in this pou!") % step_name
+ elif step_name.upper() in self.StepNames:
+ message = _("\"%s\" step already exists!") % step_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def SetMinStepSize(self, size):
+ self.MinStepSize = size
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetVariables(self, variables):
+ self.Variables = [var["Name"].upper() for var in variables]
+
+ def SetStepNames(self, step_names):
+ self.StepNames = [step_name.upper() for step_name in step_names]
+
+ def SetValues(self, values):
+ value_name = values.get("name", None)
+ if value_name:
+ self.StepName.SetValue(value_name)
+ else:
+ self.StepName.SetValue("")
+ self.Input.SetValue(values.get("input", False))
+ self.Output.SetValue(values.get("output", False))
+ self.Action.SetValue(values.get("action", False))
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = {}
+ values["name"] = self.StepName.GetValue()
+ values["input"] = self.Input.IsChecked()
+ values["output"] = self.Output.IsChecked()
+ values["action"] = self.Action.IsChecked()
+ values["width"], values["height"] = self.Step.GetSize()
+ return values
+
+ def OnConnectorsChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnNameChanged(self, event):
+ self.RefreshPreview()
+ event.Skip()
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ self.Step = SFC_Step(self.Preview, self.StepName.GetValue(), self.Initial)
+ if self.Input.IsChecked():
+ self.Step.AddInput()
+ else:
+ self.Step.RemoveInput()
+ if self.Output.IsChecked():
+ self.Step.AddOutput()
+ else:
+ self.Step.RemoveOutput()
+ if self.Action.IsChecked():
+ self.Step.AddAction()
+ else:
+ self.Step.RemoveAction()
+ width, height = self.MinStepSize
+ min_width, min_height = self.Step.GetMinSize()
+ width, height = max(min_width, width), max(min_height, height)
+ self.Step.SetSize(width, height)
+ clientsize = self.Preview.GetClientSize()
+ x = (clientsize.width - width) / 2
+ y = (clientsize.height - height) / 2
+ self.Step.SetPosition(x, y)
+ self.Step.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/SFCStepNameDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Edit Step Name Dialog
+#-------------------------------------------------------------------------------
+
+class SFCStepNameDialog(wx.TextEntryDialog):
+
+ def __init__(self, parent, message, caption = "Please enter text", defaultValue = "",
+ style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
+ wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
+
+ self.PouNames = []
+ self.Variables = []
+ self.StepNames = []
+
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+
+ def OnOK(self, event):
+ message = None
+ step_name = self.GetSizer().GetItem(1).GetWindow().GetValue()
+ if step_name == "":
+ message = _("You must type a name!")
+ elif not TestIdentifier(step_name):
+ message = _("\"%s\" is not a valid identifier!") % step_name
+ elif step_name.upper() in IEC_KEYWORDS:
+ message = _("\"%s\" is a keyword. It can't be used!") % step_name
+ elif step_name.upper() in self.PouNames:
+ message = _("A POU named \"%s\" already exists!") % step_name
+ elif step_name.upper() in self.Variables:
+ message = _("A variable with \"%s\" as name already exists in this pou!") % step_name
+ elif step_name.upper() in self.StepNames:
+ message = _("\"%s\" step already exists!") % step_name
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+ event.Skip()
+
+ def SetPouNames(self, pou_names):
+ self.PouNames = [pou_name.upper() for pou_name in pou_names]
+
+ def SetVariables(self, variables):
+ self.Variables = [var["Name"].upper() for var in variables]
+
+ def SetStepNames(self, step_names):
+ self.StepNames = [step_name.upper() for step_name in step_names]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/SFCTransitionDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from graphics import *
+
+#-------------------------------------------------------------------------------
+# Edit Transition Content Dialog
+#-------------------------------------------------------------------------------
+
+class SFCTransitionDialog(wx.Dialog):
+
+ def __init__(self, parent, controller, connection):
+ self.Connection = connection
+
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(350, 300), title=_('Edit transition'))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ main_sizer.AddSizer(column_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=8, vgap=5)
+ left_gridsizer.AddGrowableCol(0)
+ column_sizer.AddSizer(left_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.RIGHT)
+
+ type_label = wx.StaticText(self, label=_('Type:'))
+ left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+
+ self.ReferenceRadioButton = wx.RadioButton(self,
+ label=_('Reference'), style=wx.RB_GROUP)
+ self.ReferenceRadioButton.SetValue(True)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ReferenceRadioButton)
+ left_gridsizer.AddWindow(self.ReferenceRadioButton, flag=wx.GROW)
+
+ self.Reference = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnReferenceChanged, self.Reference)
+ left_gridsizer.AddWindow(self.Reference, flag=wx.GROW)
+
+ self.InlineRadioButton = wx.RadioButton(self, label=_('Inline'))
+ self.InlineRadioButton.SetValue(False)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.InlineRadioButton)
+ left_gridsizer.AddWindow(self.InlineRadioButton, flag=wx.GROW)
+
+ self.Inline = wx.TextCtrl(self)
+ self.Inline.Enable(False)
+ self.Bind(wx.EVT_TEXT, self.OnInlineChanged, self.Inline)
+ left_gridsizer.AddWindow(self.Inline, flag=wx.GROW)
+
+ self.ConnectionRadioButton = wx.RadioButton(self, label=_('Connection'))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
+ self.ConnectionRadioButton.SetValue(False)
+ if not self.Connection:
+ self.ConnectionRadioButton.Hide()
+ left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
+
+ priority_label = wx.StaticText(self, label=_('Priority:'))
+ left_gridsizer.AddWindow(priority_label, flag=wx.GROW)
+
+ self.Priority = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
+ self.Bind(wx.EVT_TEXT, self.OnPriorityChanged, self.Priority)
+ left_gridsizer.AddWindow(self.Priority, flag=wx.GROW)
+
+ right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ right_gridsizer.AddGrowableCol(0)
+ right_gridsizer.AddGrowableRow(1)
+ column_sizer.AddSizer(right_gridsizer, 1, border=5,
+ flag=wx.GROW|wx.LEFT)
+
+ preview_label = wx.StaticText(self, label=_('Preview:'))
+ right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+
+ self.Preview = wx.Panel(self,
+ style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
+ self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
+ setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+ setattr(self.Preview, "RefreshTransitionModel", lambda x:None)
+ setattr(self.Preview, "GetScaling", lambda:None)
+ setattr(self.Preview, "IsOfType", controller.IsOfType)
+ self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+ right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ self.Bind(wx.EVT_BUTTON, self.OnOK,
+ button_sizer.GetAffirmativeButton())
+ main_sizer.AddSizer(button_sizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.Transition = None
+ self.MinTransitionSize = None
+
+ self.Element = SFC_Transition(self.Preview)
+
+ self.ReferenceRadioButton.SetFocus()
+
+ def SetPreviewFont(self, font):
+ self.Preview.SetFont(font)
+
+ def SetElementSize(self, size):
+ min_width, min_height = self.Element.GetMinSize()
+ width, height = max(min_width, size[0]), max(min_height, size[1])
+ self.Element.SetSize(width, height)
+
+ def OnOK(self, event):
+ error = []
+ if self.ReferenceRadioButton.GetValue() and self.Reference.GetStringSelection() == "":
+ error.append(_("Reference"))
+ if self.InlineRadioButton.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
+ dialog = wx.MessageDialog(self, _("Form isn't complete. %s must be filled!")%text, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
+
+ def OnTypeChanged(self, event):
+ if self.ReferenceRadioButton.GetValue():
+ self.Element.SetType("reference", self.Reference.GetStringSelection())
+ self.Reference.Enable(True)
+ self.Inline.Enable(False)
+ elif self.InlineRadioButton.GetValue():
+ self.Element.SetType("inline", self.Inline.GetValue())
+ self.Reference.Enable(False)
+ self.Inline.Enable(True)
+ else:
+ self.Element.SetType("connection")
+ self.Reference.Enable(False)
+ self.Inline.Enable(False)
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnReferenceChanged(self, event):
+ self.Element.SetType("reference", self.Reference.GetStringSelection())
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnInlineChanged(self, event):
+ self.Element.SetType("inline", self.Inline.GetValue())
+ self.RefreshPreview()
+ event.Skip()
+
+ def OnPriorityChanged(self, event):
+ self.Element.SetPriority(int(self.Priority.GetValue()))
+ self.RefreshPreview()
+ event.Skip()
+
+ def SetTransitions(self, transitions):
+ self.Reference.Append("")
+ for transition in transitions:
+ self.Reference.Append(transition)
+
+ def SetValues(self, values):
+ if values["type"] == "reference":
+ self.ReferenceRadioButton.SetValue(True)
+ self.InlineRadioButton.SetValue(False)
+ self.ConnectionRadioButton.SetValue(False)
+ self.Reference.Enable(True)
+ self.Inline.Enable(False)
+ self.Reference.SetStringSelection(values["value"])
+ self.Element.SetType("reference", values["value"])
+ elif values["type"] == "inline":
+ self.ReferenceRadioButton.SetValue(False)
+ self.InlineRadioButton.SetValue(True)
+ self.ConnectionRadioButton.SetValue(False)
+ self.Reference.Enable(False)
+ self.Inline.Enable(True)
+ self.Inline.SetValue(values["value"])
+ self.Element.SetType("inline", values["value"])
+ elif values["type"] == "connection" and self.Connection:
+ self.ReferenceRadioButton.SetValue(False)
+ self.InlineRadioButton.SetValue(False)
+ self.ConnectionRadioButton.SetValue(True)
+ self.Reference.Enable(False)
+ self.Inline.Enable(False)
+ self.Element.SetType("connection")
+ self.Priority.SetValue(values["priority"])
+ self.Element.SetPriority(values["priority"])
+ self.RefreshPreview()
+
+ def GetValues(self):
+ values = {"priority" : int(self.Priority.GetValue())}
+ if self.ReferenceRadioButton.GetValue():
+ values["type"] = "reference"
+ values["value"] = self.Reference.GetStringSelection()
+ elif self.InlineRadioButton.GetValue():
+ values["type"] = "inline"
+ values["value"] = self.Inline.GetValue()
+ else:
+ values["type"] = "connection"
+ values["value"] = None
+ return values
+
+ def RefreshPreview(self):
+ dc = wx.ClientDC(self.Preview)
+ dc.SetFont(self.Preview.GetFont())
+ dc.Clear()
+ clientsize = self.Preview.GetClientSize()
+ posx, posy = self.Element.GetPosition()
+ rect = self.Element.GetBoundingBox()
+ diffx, diffy = posx - rect.x, posy - rect.y
+ self.Element.SetPosition((clientsize.width - rect.width) / 2 + diffx, (clientsize.height - rect.height) / 2 + diffy)
+ self.Element.Draw(dc)
+
+ def OnPaint(self, event):
+ self.RefreshPreview()
+ event.Skip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/SearchInProjectDialog.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+
+import wx
+
+RE_ESCAPED_CHARACTERS = ".*+()[]?:|{}^$<>=-,"
+
+def EscapeText(text):
+ text = text.replace('\\', '\\\\')
+ for c in RE_ESCAPED_CHARACTERS:
+ text = text.replace(c, '\\' + c)
+ return text
+
+#-------------------------------------------------------------------------------
+# Search In Project Dialog
+#-------------------------------------------------------------------------------
+
+def GetElementsChoices():
+ _ = lambda x: x
+ return [("datatype", _("Data Type")),
+ ("function", _("Function")),
+ ("functionBlock", _("Function Block")),
+ ("program", _("Program")),
+ ("configuration", _("Configuration"))]
+
+class SearchInProjectDialog(wx.Dialog):
+
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent, title=_('Search in Project'),
+ size=wx.Size(600, 350))
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ pattern_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
+ pattern_sizer.AddGrowableCol(0)
+ main_sizer.AddSizer(pattern_sizer, border=20,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ pattern_label = wx.StaticText(self, label=_('Pattern to search:'))
+ pattern_sizer.AddWindow(pattern_label, flag=wx.ALIGN_BOTTOM)
+
+ self.CaseSensitive = wx.CheckBox(self, label=_('Case sensitive'))
+ pattern_sizer.AddWindow(self.CaseSensitive, flag=wx.GROW)
+
+ self.Pattern = wx.TextCtrl(self)
+ pattern_sizer.AddWindow(self.Pattern, flag=wx.GROW)
+
+ self.RegularExpression = wx.CheckBox(self, label=_('Regular expression'))
+ pattern_sizer.AddWindow(self.RegularExpression, flag=wx.GROW)
+
+ scope_staticbox = wx.StaticBox(self, label=_('Scope'))
+ scope_sizer = wx.StaticBoxSizer(scope_staticbox, wx.HORIZONTAL)
+ main_sizer.AddSizer(scope_sizer, border=20,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ scope_selection_sizer = wx.BoxSizer(wx.VERTICAL)
+ scope_sizer.AddSizer(scope_selection_sizer, 1, border=5,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.BOTTOM)
+
+ self.WholeProject = wx.RadioButton(self, label=_('Whole Project'),
+ size=wx.Size(0, 24), style=wx.RB_GROUP)
+ self.WholeProject.SetValue(True)
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.WholeProject)
+ scope_selection_sizer.AddWindow(self.WholeProject, border=5,
+ flag=wx.GROW|wx.BOTTOM)
+
+ self.OnlyElements = wx.RadioButton(self,
+ label=_('Only Elements'), size=wx.Size(0, 24))
+ self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.OnlyElements)
+ self.OnlyElements.SetValue(False)
+ scope_selection_sizer.AddWindow(self.OnlyElements, flag=wx.GROW)
+
+ self.ElementsList = wx.CheckListBox(self)
+ self.ElementsList.Enable(False)
+ scope_sizer.AddWindow(self.ElementsList, 1, border=5,
+ flag=wx.GROW|wx.TOP|wx.RIGHT|wx.BOTTOM)
+
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ ok_button = self.ButtonSizer.GetAffirmativeButton()
+ ok_button.SetLabel(_('Search'))
+ self.Bind(wx.EVT_BUTTON, self.OnOK, ok_button)
+ main_sizer.AddSizer(self.ButtonSizer, border=20,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for name, label in GetElementsChoices():
+ self.ElementsList.Append(_(label))
+
+ self.Pattern.SetFocus()
+
+ def GetCriteria(self):
+ raw_pattern = pattern = self.Pattern.GetValue()
+ if not self.CaseSensitive.GetValue():
+ pattern = pattern.upper()
+ if not self.RegularExpression.GetValue():
+ pattern = EscapeText(pattern)
+ criteria = {
+ "raw_pattern": raw_pattern,
+ "pattern": re.compile(pattern),
+ "case_sensitive": self.CaseSensitive.GetValue(),
+ "regular_expression": self.RegularExpression.GetValue(),
+ }
+ if self.WholeProject.GetValue():
+ criteria["filter"] = "all"
+ elif self.OnlyElements.GetValue():
+ criteria["filter"] = []
+ for index, (name, label) in enumerate(GetElementsChoices()):
+ if self.ElementsList.IsChecked(index):
+ criteria["filter"].append(name)
+ return criteria
+
+ def OnScopeChanged(self, event):
+ self.ElementsList.Enable(self.OnlyElements.GetValue())
+ event.Skip()
+
+ def OnOK(self, event):
+ message = None
+ if self.Pattern.GetValue() == "":
+ message = _("Form isn't complete. Pattern to search must be filled!")
+ else:
+ wrong_pattern = False
+ if self.RegularExpression.GetValue():
+ try:
+ re.compile(self.Pattern.GetValue())
+ except:
+ wrong_pattern = True
+ if wrong_pattern:
+ message = _("Syntax error in regular expression of pattern to search!")
+
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ else:
+ self.EndModal(wx.ID_OK)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialization
+
+from ConnectionDialog import ConnectionDialog
+from ActionBlockDialog import ActionBlockDialog
+from FBDBlockDialog import FBDBlockDialog
+from FBDVariableDialog import FBDVariableDialog
+from LDElementDialog import LDElementDialog
+from LDPowerRailDialog import LDPowerRailDialog
+from SFCStepDialog import SFCStepDialog
+from SFCStepNameDialog import SFCStepNameDialog
+from SFCTransitionDialog import SFCTransitionDialog
+from SFCDivergenceDialog import SFCDivergenceDialog
+from ForceVariableDialog import ForceVariableDialog
+from ArrayTypeDialog import ArrayTypeDialog
+from DurationEditorDialog import DurationEditorDialog
+from SearchInProjectDialog import SearchInProjectDialog
+from BrowseLocationsDialog import BrowseLocationsDialog
+from ProjectDialog import ProjectDialog
+from PouDialog import PouDialog
+from PouTransitionDialog import PouTransitionDialog
+from PouActionDialog import PouActionDialog
+from FindInPouDialog import FindInPouDialog
+from BrowseValuesLibraryDialog import BrowseValuesLibraryDialog
+from DiscoveryDialog import DiscoveryDialog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/plcopen_about.html Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,13 @@
+<HTML>
+<BODY>
+<CENTER>
+<IMG SRC="../images/aboutlogo.png">
+<BR><BR>
+<font size="3">The PLCopen Editor saves and loads XML projects,<BR>accordingly to PLCopen TC6-XML Schemes.</font>
+<BR><BR>
+More informations on :
+<a href="http://www.beremiz.org/">http://www.beremiz.org/</a>
+<BR><BR>
+</CENTER>
+</BODY>
+</HTML>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docutil/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from dochtml import *
+from docpdf import *
+from docsvg import *
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docutil/dochtml.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx, os, wx.html, subprocess
+
+HtmlFrameOpened = []
+
+def OpenHtmlFrame(self, title, file, size):
+ if title not in HtmlFrameOpened:
+ HtmlFrameOpened.append(title)
+ window = HtmlFrame(self, HtmlFrameOpened)
+ window.SetTitle(title)
+ window.SetHtmlPage(file)
+ window.SetClientSize(size)
+ window.Show()
+
+[ID_HTMLFRAME, ID_HTMLFRAMEHTMLCONTENT] = [wx.NewId() for _init_ctrls in range(2)]
+EVT_HTML_URL_CLICK = wx.NewId()
+
+class HtmlWindowUrlClick(wx.PyEvent):
+ def __init__(self, linkinfo):
+ wx.PyEvent.__init__(self)
+ self.SetEventType(EVT_HTML_URL_CLICK)
+ self.linkinfo = (linkinfo.GetHref(), linkinfo.GetTarget())
+
+class UrlClickHtmlWindow(wx.html.HtmlWindow):
+ """ HTML window that generates and OnLinkClicked event.
+
+ Use this to avoid having to override HTMLWindow
+ """
+ def OnLinkClicked(self, linkinfo):
+ wx.PostEvent(self, HtmlWindowUrlClick(linkinfo))
+
+ def Bind(self, event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY):
+ if event == HtmlWindowUrlClick:
+ self.Connect(-1, -1, EVT_HTML_URL_CLICK, handler)
+ else:
+ wx.html.HtmlWindow.Bind(event, handler, source=source, id=id, id2=id2)
+
+class HtmlFrame(wx.Frame):
+ def _init_ctrls(self, prnt):
+ wx.Frame.__init__(self, id=ID_HTMLFRAME, name='HtmlFrame',
+ parent=prnt, pos=wx.Point(320, 231), size=wx.Size(853, 616),
+ style=wx.DEFAULT_FRAME_STYLE, title='')
+ self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
+
+ self.HtmlContent = UrlClickHtmlWindow(id=ID_HTMLFRAMEHTMLCONTENT,
+ name='HtmlContent', parent=self, pos=wx.Point(0, 0),
+ size=wx.Size(-1, -1), style=wx.html.HW_SCROLLBAR_AUTO|wx.html.HW_NO_SELECTION)
+ self.HtmlContent.Bind(HtmlWindowUrlClick, self.OnLinkClick)
+
+ def __init__(self, parent, opened):
+ self._init_ctrls(parent)
+ self.HtmlFrameOpened = opened
+
+ def SetHtmlCode(self, htmlcode):
+ self.HtmlContent.SetPage(htmlcode)
+
+ def SetHtmlPage(self, htmlpage):
+ self.HtmlContent.LoadPage(htmlpage)
+
+ def OnCloseFrame(self, event):
+ self.HtmlFrameOpened.remove(self.GetTitle())
+ event.Skip()
+
+ def OnLinkClick(self, event):
+ url = event.linkinfo[0]
+ try:
+ if wx.Platform == '__WXMSW__':
+ import webbrowser
+ webbrowser.open(url)
+ elif subprocess.call("firefox %s"%url, shell=True) != 0:
+ wx.MessageBox("""Firefox browser not found.\nPlease point your browser at :\n%s""" % url)
+ except ImportError:
+ wx.MessageBox('Please point your browser at: %s' % url)
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docutil/docpdf.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx, os
+
+readerexepath = None
+
+def get_acroversion():
+ " Return version of Adobe Acrobat executable or None"
+ import _winreg
+ adobesoft = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\Adobe')
+ for index in range(_winreg.QueryInfoKey(adobesoft)[0]):
+ key = _winreg.EnumKey(adobesoft, index)
+ if "acrobat" in key.lower():
+ acrokey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'Software\\Adobe\\%s' % key)
+ for index in range(_winreg.QueryInfoKey(acrokey)[0]):
+ numver = _winreg.EnumKey(acrokey, index)
+ try:
+ res = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, 'Software\\Adobe\\%s\\%s\\InstallPath' % (key, numver))
+ return res
+ except:
+ pass
+ return None
+
+def open_win_pdf(readerexepath, pdffile, pagenum = None):
+ if pagenum != None :
+ os.spawnl(os.P_DETACH, readerexepath, "AcroRd32.exe", "/A", "page=%d=OpenActions" % pagenum, '"%s"'%pdffile)
+ else:
+ os.spawnl(os.P_DETACH, readerexepath, "AcroRd32.exe", '"%s"'%pdffile)
+
+def open_lin_pdf(readerexepath, pdffile, pagenum = None):
+ if pagenum == None :
+ os.system("%s -remote DS301 %s &"%(readerexepath, pdffile))
+ else:
+ print "Open pdf %s at page %d"%(pdffile, pagenum)
+ os.system("%s -remote DS301 %s %d &"%(readerexepath, pdffile, pagenum))
+
+def open_pdf(pdffile, pagenum = None):
+ if wx.Platform == '__WXMSW__' :
+ try:
+ readerpath = get_acroversion()
+ except:
+ wx.MessageBox("Acrobat Reader is not found or installed !")
+ return None
+
+ readerexepath = os.path.join(readerpath, "AcroRd32.exe")
+ if(os.path.isfile(readerexepath)):
+ open_win_pdf(readerexepath, pdffile, pagenum)
+ else:
+ return None
+ else:
+ readerexepath = os.path.join("/usr/bin","xpdf")
+ if(os.path.isfile(readerexepath)):
+ open_lin_pdf(readerexepath, pdffile, pagenum)
+ else:
+ wx.MessageBox("xpdf is not found or installed !")
+ return None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docutil/docsvg.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx, os, subprocess
+
+def get_inkscape_path():
+ """ Return the Inkscape path """
+ import _winreg
+ svgexepath = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE,
+ 'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
+ svgexepath = svgexepath.replace('"%1"', '')
+ return svgexepath.replace('"', '')
+
+def open_win_svg(svgexepath, svgfile):
+ """ Open Inkscape on Windows platform """
+ popenargs = [svgexepath]
+ if svgfile is not None :
+ popenargs.append(svgfile)
+ subprocess.Popen(popenargs).pid
+
+def open_lin_svg(svgexepath, svgfile):
+ """ Open Inkscape on Linux platform """
+ if os.path.isfile("/usr/bin/inkscape"):
+ os.system("%s %s &"%(svgexepath , svgfile))
+
+def open_svg(svgfile):
+ """ Generic function to open SVG file """
+ if wx.Platform == '__WXMSW__' :
+ try:
+ open_win_svg(get_inkscape_path(), svgfile)
+ except:
+ wx.MessageBox("Inkscape is not found or installed !")
+ return None
+ else:
+ svgexepath = os.path.join("/usr/bin","inkscape")
+ if(os.path.isfile(svgexepath)):
+ open_lin_svg(svgexepath, svgfile)
+ else:
+ wx.MessageBox("Inkscape is not found or installed !")
+ return None
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/ConfTreeNodeEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,573 @@
+
+import os
+import types
+
+import wx
+import wx.lib.buttons
+
+from EditorPanel import EditorPanel
+
+from IDEFrame import TITLE, FILEMENU, PROJECTTREE, PAGETITLES
+
+from controls import TextCtrlAutoComplete
+from dialogs import BrowseValuesLibraryDialog
+from util.BitmapLibrary import GetBitmap
+
+if wx.Platform == '__WXMSW__':
+ faces = { 'times': 'Times New Roman',
+ 'mono' : 'Courier New',
+ 'helv' : 'Arial',
+ 'other': 'Comic Sans MS',
+ 'size' : 16,
+ }
+else:
+ faces = { 'times': 'Times',
+ 'mono' : 'Courier',
+ 'helv' : 'Helvetica',
+ 'other': 'new century schoolbook',
+ 'size' : 18,
+ }
+
+SCROLLBAR_UNIT = 10
+WINDOW_COLOUR = wx.Colour(240, 240, 240)
+
+CWD = os.path.split(os.path.realpath(__file__))[0]
+
+def Bpath(*args):
+ return os.path.join(CWD,*args)
+
+# Some helpers to tweak GenBitmapTextButtons
+# TODO: declare customized classes instead.
+gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
+gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,))
+
+def make_genbitmaptogglebutton_flat(button):
+ button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button)
+ button.labelDelta = 0
+ button.SetBezelWidth(0)
+ button.SetUseFocusIndicator(False)
+
+# Patch wx.lib.imageutils so that gray is supported on alpha images
+import wx.lib.imageutils
+from wx.lib.imageutils import grayOut as old_grayOut
+def grayOut(anImage):
+ if anImage.HasAlpha():
+ AlphaData = anImage.GetAlphaData()
+ else :
+ AlphaData = None
+
+ old_grayOut(anImage)
+
+ if AlphaData is not None:
+ anImage.SetAlphaData(AlphaData)
+
+wx.lib.imageutils.grayOut = grayOut
+
+class GenBitmapTextButton(wx.lib.buttons.GenBitmapTextButton):
+ def _GetLabelSize(self):
+ """ used internally """
+ w, h = self.GetTextExtent(self.GetLabel())
+ if not self.bmpLabel:
+ return w, h, False # if there isn't a bitmap use the size of the text
+
+ w_bmp = self.bmpLabel.GetWidth()+2
+ h_bmp = self.bmpLabel.GetHeight()+2
+ height = h + h_bmp
+ if w_bmp > w:
+ width = w_bmp
+ else:
+ width = w
+ return width, height, False
+
+ def DrawLabel(self, dc, width, height, dw=0, dy=0):
+ bmp = self.bmpLabel
+ if bmp != None: # if the bitmap is used
+ if self.bmpDisabled and not self.IsEnabled():
+ bmp = self.bmpDisabled
+ if self.bmpFocus and self.hasFocus:
+ bmp = self.bmpFocus
+ if self.bmpSelected and not self.up:
+ bmp = self.bmpSelected
+ bw,bh = bmp.GetWidth(), bmp.GetHeight()
+ if not self.up:
+ dw = dy = self.labelDelta
+ hasMask = bmp.GetMask() != None
+ else:
+ bw = bh = 0 # no bitmap -> size is zero
+
+ dc.SetFont(self.GetFont())
+ if self.IsEnabled():
+ dc.SetTextForeground(self.GetForegroundColour())
+ else:
+ dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
+
+ label = self.GetLabel()
+ tw, th = dc.GetTextExtent(label) # size of text
+ if not self.up:
+ dw = dy = self.labelDelta
+
+ pos_x = (width-bw)/2+dw # adjust for bitmap and text to centre
+ pos_y = (height-bh-th)/2+dy
+ if bmp !=None:
+ dc.DrawBitmap(bmp, pos_x, pos_y, hasMask) # draw bitmap if available
+ pos_x = (width-tw)/2+dw # adjust for bitmap and text to centre
+ pos_y += bh + 2
+
+ dc.DrawText(label, pos_x, pos_y) # draw the text
+
+
+class GenStaticBitmap(wx.lib.statbmp.GenStaticBitmap):
+ """ Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32,
+ and accept image name as __init__ parameter, fail silently if file do not exist"""
+ def __init__(self, parent, ID, bitmapname,
+ pos = wx.DefaultPosition, size = wx.DefaultSize,
+ style = 0,
+ name = "genstatbmp"):
+
+ wx.lib.statbmp.GenStaticBitmap.__init__(self, parent, ID,
+ GetBitmap(bitmapname),
+ pos, size,
+ style,
+ name)
+
+ def OnPaint(self, event):
+ dc = wx.PaintDC(self)
+ colour = self.GetParent().GetBackgroundColour()
+ dc.SetPen(wx.Pen(colour))
+ dc.SetBrush(wx.Brush(colour ))
+ dc.DrawRectangle(0, 0, *dc.GetSizeTuple())
+ if self._bitmap:
+ dc.DrawBitmap(self._bitmap, 0, 0, True)
+
+class ConfTreeNodeEditor(EditorPanel):
+
+ SHOW_BASE_PARAMS = True
+ SHOW_PARAMS = True
+
+ def _init_ConfNodeEditor(self, prnt):
+ self.ConfNodeEditor = None
+
+ def _init_Editor(self, parent):
+ self.Editor = wx.SplitterWindow(parent,
+ style=wx.SUNKEN_BORDER|wx.SP_3D)
+ self.SetNeedUpdating(True)
+ self.SetMinimumPaneSize(1)
+
+ if self.SHOW_PARAMS:
+ self.ParamsEditor = wx.ScrolledWindow(self.Editor,
+ style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER|wx.HSCROLL|wx.VSCROLL)
+ self.ParamsEditor.SetBackgroundColour(WINDOW_COLOUR)
+ self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnWindowResize)
+ self.ParamsEditor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+
+ # Variable allowing disabling of ParamsEditor scroll when Popup shown
+ self.ScrollingEnabled = True
+
+ if self.SHOW_BASE_PARAMS:
+ self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ self.ParamsEditorSizer.AddGrowableCol(0)
+ self.ParamsEditorSizer.AddGrowableRow(1)
+
+ self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
+
+ baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.ParamsEditorSizer.AddSizer(baseparamseditor_sizer, border=5,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
+
+ self.FullIECChannel = wx.StaticText(self.ParamsEditor, -1)
+ self.FullIECChannel.SetFont(
+ wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL,
+ wx.BOLD, faceName = faces["helv"]))
+ baseparamseditor_sizer.AddWindow(self.FullIECChannel,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ updownsizer = wx.BoxSizer(wx.VERTICAL)
+ baseparamseditor_sizer.AddSizer(updownsizer, border=5,
+ flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
+
+ self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.ParamsEditor,
+ bitmap=GetBitmap('IECCDown'), size=wx.Size(16, 16), style=wx.NO_BORDER)
+ self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
+ self.IECCUpButton)
+ updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
+
+ self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.ParamsEditor,
+ bitmap=GetBitmap('IECCUp'), size=wx.Size(16, 16), style=wx.NO_BORDER)
+ self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1),
+ self.IECCDownButton)
+ updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
+
+ self.ConfNodeName = wx.TextCtrl(self.ParamsEditor,
+ size=wx.Size(150, 25), style=wx.NO_BORDER)
+ self.ConfNodeName.SetFont(
+ wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL,
+ wx.BOLD, faceName = faces["helv"]))
+ self.ConfNodeName.Bind(wx.EVT_TEXT,
+ self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True),
+ self.ConfNodeName)
+ baseparamseditor_sizer.AddWindow(self.ConfNodeName, border=5,
+ flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+
+ buttons_sizer = self.GenerateMethodButtonSizer()
+ baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
+
+ else:
+ self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
+ self.ParamsEditorSizer.AddGrowableCol(0)
+ self.ParamsEditorSizer.AddGrowableRow(0)
+
+ self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
+ self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
+ flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
+
+ self.RefreshConfNodeParamsSizer()
+ else:
+ self.ParamsEditor = None
+
+ self._init_ConfNodeEditor(self.Editor)
+
+ if self.ConfNodeEditor is not None:
+ if self.ParamsEditor is not None:
+ min_size = self.ParamsEditorSizer.GetMinSize()
+ self.Editor.SplitHorizontally(self.ParamsEditor,
+ self.ConfNodeEditor,
+ min(min_size.height, 200))
+ else:
+ self.Editor.Initialize(self.ConfNodeEditor)
+ elif self.ParamsEditor is not None:
+ self.Editor.Initialize(self.ParamsEditor)
+
+ def __init__(self, parent, controler, window, tagname=""):
+ EditorPanel.__init__(self, parent, tagname, window, controler)
+
+ icon_name = self.Controler.GetIconName()
+ if icon_name is not None:
+ self.SetIcon(GetBitmap(icon_name))
+ else:
+ self.SetIcon(GetBitmap("Extension"))
+
+ def __del__(self):
+ self.Controler.OnCloseEditor(self)
+
+ def GetTagName(self):
+ return self.Controler.CTNFullName()
+
+ def GetTitle(self):
+ fullname = self.Controler.CTNFullName()
+ if self.Controler.CTNTestModified():
+ return "~%s~" % fullname
+ return fullname
+
+ def HasNoModel(self):
+ return False
+
+ def GetBufferState(self):
+ return False, False
+
+ def Undo(self):
+ pass
+
+ def Redo(self):
+ pass
+
+ def RefreshView(self):
+ EditorPanel.RefreshView(self)
+ if self.ParamsEditor is not None:
+ if self.SHOW_BASE_PARAMS:
+ self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
+ self.RefreshIECChannelControlsState()
+ self.RefreshConfNodeParamsSizer()
+
+ def EnableScrolling(self, enable):
+ self.ScrollingEnabled = enable
+
+ def RefreshIECChannelControlsState(self):
+ self.FullIECChannel.SetLabel(self.Controler.GetFullIEC_Channel())
+ self.IECCDownButton.Enable(self.Controler.BaseParams.getIEC_Channel() > 0)
+
+ def RefreshConfNodeParamsSizer(self):
+ self.Freeze()
+ self.ConfNodeParamsSizer.Clear(True)
+
+ confnode_infos = self.Controler.GetParamsAttributes()
+ if len(confnode_infos) > 0:
+ self.GenerateSizerElements(self.ConfNodeParamsSizer, confnode_infos, None, False)
+
+ self.ParamsEditorSizer.Layout()
+ self.Thaw()
+
+ def GenerateMethodButtonSizer(self):
+ normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"])
+ mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"])
+
+ msizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ for confnode_method in self.Controler.ConfNodeMethods:
+ if "method" in confnode_method and confnode_method.get("shown",True):
+ button = GenBitmapTextButton(self.ParamsEditor,
+ bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")),
+ label=confnode_method["name"], style=wx.NO_BORDER)
+ button.SetFont(normal_bt_font)
+ button.SetToolTipString(confnode_method["tooltip"])
+ if confnode_method.get("push", False):
+ button.Bind(wx.EVT_LEFT_DOWN, self.GetButtonCallBackFunction(confnode_method["method"], True))
+ else:
+ button.Bind(wx.EVT_BUTTON, self.GetButtonCallBackFunction(confnode_method["method"]), button)
+ # a fancy underline on mouseover
+ def setFontStyle(b, s):
+ def fn(event):
+ b.SetFont(s)
+ b.Refresh()
+ event.Skip()
+ return fn
+ button.Bind(wx.EVT_ENTER_WINDOW, setFontStyle(button, mouseover_bt_font))
+ button.Bind(wx.EVT_LEAVE_WINDOW, setFontStyle(button, normal_bt_font))
+ #hack to force size to mini
+ if not confnode_method.get("enabled",True):
+ button.Disable()
+ msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
+ return msizer
+
+ def GenerateSizerElements(self, sizer, elements, path, clean = True):
+ if clean:
+ sizer.Clear(True)
+ first = True
+ for element_infos in elements:
+ if path:
+ element_path = "%s.%s"%(path, element_infos["name"])
+ else:
+ element_path = element_infos["name"]
+ if element_infos["type"] == "element":
+ label = element_infos["name"]
+ staticbox = wx.StaticBox(self.ParamsEditor,
+ label=_(label), size=wx.Size(10, 0))
+ staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
+ if first:
+ sizer.AddSizer(staticboxsizer, border=5,
+ flag=wx.GROW|wx.TOP|wx.BOTTOM)
+ else:
+ sizer.AddSizer(staticboxsizer, border=5,
+ flag=wx.GROW|wx.BOTTOM)
+ self.GenerateSizerElements(staticboxsizer,
+ element_infos["children"],
+ element_path)
+ else:
+ boxsizer = wx.FlexGridSizer(cols=3, rows=1)
+ boxsizer.AddGrowableCol(1)
+ if first:
+ sizer.AddSizer(boxsizer, border=5,
+ flag=wx.GROW|wx.ALL)
+ else:
+ sizer.AddSizer(boxsizer, border=5,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
+
+ staticbitmap = GenStaticBitmap(ID=-1, bitmapname=element_infos["name"],
+ name="%s_bitmap"%element_infos["name"], parent=self.ParamsEditor,
+ pos=wx.Point(0, 0), size=wx.Size(24, 24), style=0)
+ boxsizer.AddWindow(staticbitmap, border=5, flag=wx.RIGHT)
+
+ statictext = wx.StaticText(self.ParamsEditor,
+ label="%s:"%_(element_infos["name"]))
+ boxsizer.AddWindow(statictext, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT)
+
+ if isinstance(element_infos["type"], types.ListType):
+ if isinstance(element_infos["value"], types.TupleType):
+ browse_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
+ boxsizer.AddSizer(browse_boxsizer)
+
+ textctrl = wx.TextCtrl(self.ParamsEditor,
+ size=wx.Size(275, 25), style=wx.TE_READONLY)
+ if element_infos["value"] is not None:
+ textctrl.SetValue(element_infos["value"][0])
+ value_infos = element_infos["value"][1]
+ else:
+ value_infos = None
+ browse_boxsizer.AddWindow(textctrl)
+
+ button = wx.Button(self.ParamsEditor,
+ label="...", size=wx.Size(25, 25))
+ browse_boxsizer.AddWindow(button)
+ button.Bind(wx.EVT_BUTTON,
+ self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"],
+ value_infos, element_path),
+ button)
+ else:
+ combobox = wx.ComboBox(self.ParamsEditor,
+ size=wx.Size(300, 28), style=wx.CB_READONLY)
+ boxsizer.AddWindow(combobox)
+
+ if element_infos["use"] == "optional":
+ combobox.Append("")
+ if len(element_infos["type"]) > 0 and isinstance(element_infos["type"][0], types.TupleType):
+ for choice, xsdclass in element_infos["type"]:
+ combobox.Append(choice)
+ name = element_infos["name"]
+ value = element_infos["value"]
+
+ staticbox = wx.StaticBox(self.ParamsEditor,
+ label="%s - %s"%(_(name), _(value)), size=wx.Size(10, 0))
+ staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
+ sizer.AddSizer(staticboxsizer, border=5, flag=wx.GROW|wx.BOTTOM)
+ self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
+ callback = self.GetChoiceContentCallBackFunction(combobox, staticboxsizer, element_path)
+ else:
+ for choice in element_infos["type"]:
+ combobox.Append(choice)
+ callback = self.GetChoiceCallBackFunction(combobox, element_path)
+ if element_infos["value"] is None:
+ combobox.SetStringSelection("")
+ else:
+ combobox.SetStringSelection(element_infos["value"])
+ combobox.Bind(wx.EVT_COMBOBOX, callback, combobox)
+
+ elif isinstance(element_infos["type"], types.DictType):
+ scmin = -(2**31)
+ scmax = 2**31-1
+ if "min" in element_infos["type"]:
+ scmin = element_infos["type"]["min"]
+ if "max" in element_infos["type"]:
+ scmax = element_infos["type"]["max"]
+ spinctrl = wx.SpinCtrl(self.ParamsEditor,
+ size=wx.Size(300, 25), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
+ spinctrl.SetRange(scmin, scmax)
+ boxsizer.AddWindow(spinctrl)
+ if element_infos["value"] is not None:
+ spinctrl.SetValue(element_infos["value"])
+ spinctrl.Bind(wx.EVT_SPINCTRL,
+ self.GetTextCtrlCallBackFunction(spinctrl, element_path),
+ spinctrl)
+
+ else:
+ if element_infos["type"] == "boolean":
+ checkbox = wx.CheckBox(self.ParamsEditor, size=wx.Size(17, 25))
+ boxsizer.AddWindow(checkbox)
+ if element_infos["value"] is not None:
+ checkbox.SetValue(element_infos["value"])
+ checkbox.Bind(wx.EVT_CHECKBOX,
+ self.GetCheckBoxCallBackFunction(checkbox, element_path),
+ checkbox)
+
+ elif element_infos["type"] in ["unsignedLong", "long","integer"]:
+ if element_infos["type"].startswith("unsigned"):
+ scmin = 0
+ else:
+ scmin = -(2**31)
+ scmax = 2**31-1
+ spinctrl = wx.SpinCtrl(self.ParamsEditor,
+ size=wx.Size(300, 25), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
+ spinctrl.SetRange(scmin, scmax)
+ boxsizer.AddWindow(spinctrl)
+ if element_infos["value"] is not None:
+ spinctrl.SetValue(element_infos["value"])
+ spinctrl.Bind(wx.EVT_SPINCTRL,
+ self.GetTextCtrlCallBackFunction(spinctrl, element_path),
+ spinctrl)
+
+ else:
+ choices = self.ParentWindow.GetConfigEntry(element_path, [""])
+ textctrl = TextCtrlAutoComplete(name=element_infos["name"],
+ parent=self.ParamsEditor,
+ appframe=self,
+ choices=choices,
+ element_path=element_path,
+ size=wx.Size(300, 25))
+
+ boxsizer.AddWindow(textctrl)
+ if element_infos["value"] is not None:
+ textctrl.ChangeValue(str(element_infos["value"]))
+ textctrl.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(textctrl, element_path))
+ first = False
+
+
+ def GetItemChannelChangedFunction(self, dir):
+ def OnConfNodeTreeItemChannelChanged(event):
+ confnode_IECChannel = self.Controler.BaseParams.getIEC_Channel()
+ res = self.SetConfNodeParamsAttribute("BaseParams.IEC_Channel", confnode_IECChannel + dir)
+ wx.CallAfter(self.RefreshIECChannelControlsState)
+ wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE)
+ wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
+ event.Skip()
+ return OnConfNodeTreeItemChannelChanged
+
+ def SetConfNodeParamsAttribute(self, *args, **kwargs):
+ res, StructChanged = self.Controler.SetParamsAttribute(*args, **kwargs)
+ if StructChanged:
+ wx.CallAfter(self.RefreshConfNodeParamsSizer)
+ wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU)
+ return res
+
+ def GetButtonCallBackFunction(self, method, push=False):
+ """ Generate the callbackfunc for a given confnode method"""
+ def OnButtonClick(event):
+ # Disable button to prevent re-entrant call
+ event.GetEventObject().Disable()
+ # Call
+ getattr(self.Controler,method)()
+ # Re-enable button
+ event.GetEventObject().Enable()
+
+ if not push:
+ event.Skip()
+ return OnButtonClick
+
+ def GetChoiceCallBackFunction(self, choicectrl, path):
+ def OnChoiceChanged(event):
+ res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
+ choicectrl.SetStringSelection(res)
+ event.Skip()
+ return OnChoiceChanged
+
+ def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, path):
+ def OnChoiceContentChanged(event):
+ res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
+ wx.CallAfter(self.RefreshConfNodeParamsSizer)
+ event.Skip()
+ return OnChoiceContentChanged
+
+ def GetTextCtrlCallBackFunction(self, textctrl, path, refresh=False):
+ def OnTextCtrlChanged(event):
+ res = self.SetConfNodeParamsAttribute(path, textctrl.GetValue())
+ if res != textctrl.GetValue():
+ textctrl.ChangeValue(res)
+ if refresh:
+ wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE, PAGETITLES)
+ wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
+ event.Skip()
+ return OnTextCtrlChanged
+
+ def GetCheckBoxCallBackFunction(self, chkbx, path):
+ def OnCheckBoxChanged(event):
+ res = self.SetConfNodeParamsAttribute(path, chkbx.IsChecked())
+ chkbx.SetValue(res)
+ event.Skip()
+ return OnCheckBoxChanged
+
+ def GetBrowseCallBackFunction(self, name, textctrl, library, value_infos, path):
+ infos = [value_infos]
+ def OnBrowseButton(event):
+ dialog = BrowseValuesLibraryDialog(self, name, library, infos[0])
+ if dialog.ShowModal() == wx.ID_OK:
+ value, value_infos = self.SetConfNodeParamsAttribute(path, dialog.GetValueInfos())
+ textctrl.ChangeValue(value)
+ infos[0] = value_infos
+ dialog.Destroy()
+ event.Skip()
+ return OnBrowseButton
+
+ def OnWindowResize(self, event):
+ self.ParamsEditor.GetBestSize()
+ xstart, ystart = self.ParamsEditor.GetViewStart()
+ window_size = self.ParamsEditor.GetClientSize()
+ maxx, maxy = self.ParamsEditorSizer.GetMinSize()
+ posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
+ posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
+ self.ParamsEditor.Scroll(posx, posy)
+ self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
+ maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
+ event.Skip()
+
+ def OnMouseWheel(self, event):
+ if self.ScrollingEnabled:
+ event.Skip()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/DataTypeEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,791 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+
+import wx
+import wx.grid
+import wx.lib.buttons
+
+from plcopen.structures import IEC_KEYWORDS, TestIdentifier
+from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD
+from controls import CustomEditableListBox, CustomGrid, CustomTable
+from EditorPanel import EditorPanel
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+# Helpers
+#-------------------------------------------------------------------------------
+
+DIMENSION_MODEL = re.compile("([0-9]+)\.\.([0-9]+)$")
+
+def AppendMenu(parent, help, id, kind, text):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+
+def GetElementsTableColnames():
+ _ = lambda x : x
+ return ["#", _("Name"), _("Type"), _("Initial Value")]
+
+def GetDatatypeTypes():
+ _ = lambda x : x
+ return [_("Directly"), _("Subrange"), _("Enumerated"), _("Array"), _("Structure")]
+DATATYPE_TYPES_DICT = dict([(_(datatype), datatype) for datatype in GetDatatypeTypes()])
+
+#-------------------------------------------------------------------------------
+# Structure Elements Table
+#-------------------------------------------------------------------------------
+
+class ElementsTable(CustomTable):
+
+ """
+ A custom wx.grid.Grid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ CustomTable.__init__(self, parent, data, colnames)
+ self.old_value = None
+
+ def GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ if col == 0:
+ return row + 1
+ name = str(self.data[row].get(self.GetColLabelValue(col, False), ""))
+ return name
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Name":
+ self.old_value = self.data[row][colname]
+ self.data[row][colname] = value
+
+ def GetOldValue(self):
+ return self.old_value
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> update the column attributes to add the
+ appropriate renderer given the column name.
+
+ Otherwise default to the default renderer.
+ """
+
+ for row in range(self.GetNumberRows()):
+ row_highlights = self.Highlights.get(row, {})
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col, False)
+ if col != 0:
+ grid.SetReadOnly(row, col, False)
+ if colname == "Name":
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ elif colname == "Initial Value":
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ elif colname == "Type":
+ editor = wx.grid.GridCellTextEditor()
+ else:
+ grid.SetReadOnly(row, col, True)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
+ grid.SetCellBackgroundColour(row, col, highlight_colours[0])
+ grid.SetCellTextColour(row, col, highlight_colours[1])
+ self.ResizeRow(grid, row)
+
+ def AddHighlight(self, infos, highlight_type):
+ row_highlights = self.Highlights.setdefault(infos[0], {})
+ if infos[1] == "initial":
+ col_highlights = row_highlights.setdefault("initial value", [])
+ else:
+ col_highlights = row_highlights.setdefault(infos[1], [])
+ col_highlights.append(highlight_type)
+
+#-------------------------------------------------------------------------------
+# Datatype Editor class
+#-------------------------------------------------------------------------------
+
+class DataTypeEditor(EditorPanel):
+
+ def _init_Editor(self, parent):
+ self.Editor = wx.Panel(parent, style=wx.SUNKEN_BORDER)
+
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ self.MainSizer.AddGrowableCol(0)
+ self.MainSizer.AddGrowableRow(1)
+
+ top_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.MainSizer.AddSizer(top_sizer, border=5,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ derivation_type_label = wx.StaticText(self.Editor, label=_('Derivation Type:'))
+ top_sizer.AddWindow(derivation_type_label, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT)
+
+ self.DerivationType = wx.ComboBox(self.Editor,
+ size=wx.Size(200, -1), style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnDerivationTypeChanged, self.DerivationType)
+ top_sizer.AddWindow(self.DerivationType, border=5, flag=wx.GROW|wx.RIGHT)
+
+ typeinfos_staticbox = wx.StaticBox(self.Editor, label=_('Type infos:'))
+ typeinfos_sizer = wx.StaticBoxSizer(typeinfos_staticbox, wx.HORIZONTAL)
+ self.MainSizer.AddSizer(typeinfos_sizer, border=5,
+ flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ # Panel for Directly derived data types
+
+ self.DirectlyPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
+ typeinfos_sizer.AddWindow(self.DirectlyPanel, 1)
+
+ directly_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ directly_basetype_label = wx.StaticText(self.DirectlyPanel,
+ label=_('Base Type:'))
+ directly_panel_sizer.AddWindow(directly_basetype_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.DirectlyBaseType = wx.ComboBox(self.DirectlyPanel, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.DirectlyPanel)
+ directly_panel_sizer.AddWindow(self.DirectlyBaseType, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ directly_initialvalue_label = wx.StaticText(self.DirectlyPanel,
+ label=_('Initial Value:'))
+ directly_panel_sizer.AddWindow(directly_initialvalue_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.DirectlyInitialValue = wx.TextCtrl(self.DirectlyPanel,
+ style=wx.TAB_TRAVERSAL|wx.TE_PROCESS_ENTER|wx.TE_RICH)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.DirectlyInitialValue)
+ directly_panel_sizer.AddWindow(self.DirectlyInitialValue, 1, border=5,
+ flag=wx.ALL)
+
+ self.DirectlyPanel.SetSizer(directly_panel_sizer)
+
+ # Panel for Subrange data types
+
+ self.SubrangePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
+ typeinfos_sizer.AddWindow(self.SubrangePanel, 1)
+
+ subrange_panel_sizer = wx.GridSizer(cols=4, hgap=5, rows=3, vgap=0)
+
+ subrange_basetype_label = wx.StaticText(self.SubrangePanel,
+ label=_('Base Type:'))
+ subrange_panel_sizer.AddWindow(subrange_basetype_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.SubrangeBaseType = wx.ComboBox(self.SubrangePanel, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnSubrangeBaseTypeChanged,
+ self.SubrangeBaseType)
+ subrange_panel_sizer.AddWindow(self.SubrangeBaseType, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ subrange_initialvalue_label = wx.StaticText(self.SubrangePanel,
+ label=_('Initial Value:'))
+ subrange_panel_sizer.AddWindow(subrange_initialvalue_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.SubrangeInitialValue = wx.SpinCtrl(self.SubrangePanel,
+ style=wx.TAB_TRAVERSAL)
+ self.Bind(wx.EVT_SPINCTRL, self.OnInfosChanged, self.SubrangeInitialValue)
+ subrange_panel_sizer.AddWindow(self.SubrangeInitialValue, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ subrange_minimum_label = wx.StaticText(self.SubrangePanel, label=_('Minimum:'))
+ subrange_panel_sizer.AddWindow(subrange_minimum_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.SubrangeMinimum = wx.SpinCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
+ self.Bind(wx.EVT_SPINCTRL, self.OnSubrangeMinimumChanged, self.SubrangeMinimum)
+ subrange_panel_sizer.AddWindow(self.SubrangeMinimum, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ for i in xrange(2):
+ subrange_panel_sizer.AddWindow(wx.Size(0, 0), 1)
+
+ subrange_maximum_label = wx.StaticText(self.SubrangePanel,
+ label=_('Maximum:'))
+ subrange_panel_sizer.AddWindow(subrange_maximum_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.SubrangeMaximum = wx.SpinCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
+ self.Bind(wx.EVT_SPINCTRL, self.OnSubrangeMaximumChanged, self.SubrangeMaximum)
+
+ subrange_panel_sizer.AddWindow(self.SubrangeMaximum, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ self.SubrangePanel.SetSizer(subrange_panel_sizer)
+
+ # Panel for Enumerated data types
+
+ self.EnumeratedPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
+ typeinfos_sizer.AddWindow(self.EnumeratedPanel, 1)
+
+ enumerated_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self.EnumeratedValues = CustomEditableListBox(self.EnumeratedPanel,
+ label=_("Values:"), style=wx.gizmos.EL_ALLOW_NEW|
+ wx.gizmos.EL_ALLOW_EDIT|
+ wx.gizmos.EL_ALLOW_DELETE)
+ setattr(self.EnumeratedValues, "_OnLabelEndEdit", self.OnEnumeratedValueEndEdit)
+ for func in ["_OnAddButton", "_OnDelButton", "_OnUpButton", "_OnDownButton"]:
+ setattr(self.EnumeratedValues, func, self.OnEnumeratedValuesChanged)
+ enumerated_panel_sizer.AddWindow(self.EnumeratedValues, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ enumerated_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
+ enumerated_panel_sizer.AddSizer(enumerated_panel_rightsizer, 1)
+
+ enumerated_initialvalue_label = wx.StaticText(self.EnumeratedPanel,
+ label=_('Initial Value:'))
+ enumerated_panel_rightsizer.AddWindow(enumerated_initialvalue_label, 1,
+ border=5, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.EnumeratedInitialValue = wx.ComboBox(self.EnumeratedPanel,
+ style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.EnumeratedInitialValue)
+ enumerated_panel_rightsizer.AddWindow(self.EnumeratedInitialValue, 1,
+ border=5, flag=wx.ALL)
+
+ self.EnumeratedPanel.SetSizer(enumerated_panel_sizer)
+
+ # Panel for Array data types
+
+ self.ArrayPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
+ typeinfos_sizer.AddWindow(self.ArrayPanel, 1)
+
+ array_panel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=0)
+ array_panel_sizer.AddGrowableCol(0)
+ array_panel_sizer.AddGrowableCol(1)
+ array_panel_sizer.AddGrowableRow(1)
+
+ array_panel_leftSizer = wx.BoxSizer(wx.HORIZONTAL)
+ array_panel_sizer.AddSizer(array_panel_leftSizer, flag=wx.GROW)
+
+ array_basetype_label = wx.StaticText(self.ArrayPanel, label=_('Base Type:'))
+ array_panel_leftSizer.AddWindow(array_basetype_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.ArrayBaseType = wx.ComboBox(self.ArrayPanel, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.ArrayBaseType)
+ array_panel_leftSizer.AddWindow(self.ArrayBaseType, 1, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ array_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
+ array_panel_sizer.AddSizer(array_panel_rightsizer, flag=wx.GROW)
+
+ array_initialvalue_label = wx.StaticText(self.ArrayPanel,
+ label=_('Initial Value:'))
+ array_panel_rightsizer.AddWindow(array_initialvalue_label, 1, border=5,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+
+ self.ArrayInitialValue = wx.TextCtrl(self.ArrayPanel,
+ style=wx.TAB_TRAVERSAL|wx.TE_PROCESS_ENTER|wx.TE_RICH)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.ArrayInitialValue)
+ array_panel_rightsizer.AddWindow(self.ArrayInitialValue, 1, border=5,
+ flag=wx.ALL)
+
+ self.ArrayDimensions = CustomEditableListBox(self.ArrayPanel,
+ label=_("Dimensions:"), style=wx.gizmos.EL_ALLOW_NEW|
+ wx.gizmos.EL_ALLOW_EDIT|
+ wx.gizmos.EL_ALLOW_DELETE)
+ for func in ["_OnLabelEndEdit", "_OnAddButton", "_OnDelButton",
+ "_OnUpButton", "_OnDownButton"]:
+ setattr(self.ArrayDimensions, func, self.OnDimensionsChanged)
+ array_panel_sizer.AddWindow(self.ArrayDimensions, 0, border=5,
+ flag=wx.GROW|wx.ALL)
+
+ self.ArrayPanel.SetSizer(array_panel_sizer)
+
+ # Panel for Structure data types
+
+ self.StructurePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
+ typeinfos_sizer.AddWindow(self.StructurePanel, 1)
+
+ structure_panel_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ structure_panel_sizer.AddGrowableCol(0)
+ structure_panel_sizer.AddGrowableRow(1)
+
+ structure_button_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+ structure_button_sizer.AddGrowableCol(0)
+ structure_button_sizer.AddGrowableRow(0)
+ structure_panel_sizer.AddSizer(structure_button_sizer, 0, border=5,
+ flag=wx.ALL|wx.GROW)
+
+ structure_elements_label = wx.StaticText(self.StructurePanel,
+ label=_('Elements :'))
+ structure_button_sizer.AddWindow(structure_elements_label, flag=wx.ALIGN_BOTTOM)
+
+ for name, bitmap, help in [
+ ("StructureAddButton", "add_element", _("Add element")),
+ ("StructureDeleteButton", "remove_element", _("Remove element")),
+ ("StructureUpButton", "up", _("Move element up")),
+ ("StructureDownButton", "down", _("Move element down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self.StructurePanel,
+ bitmap=GetBitmap(bitmap), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ structure_button_sizer.AddWindow(button)
+
+ self.StructureElementsGrid = CustomGrid(self.StructurePanel,
+ size=wx.Size(0, 150), style=wx.VSCROLL)
+ self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+ self.OnStructureElementsGridCellChange)
+ self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
+ self.OnStructureElementsGridEditorShown)
+ structure_panel_sizer.AddWindow(self.StructureElementsGrid, flag=wx.GROW)
+
+ self.StructurePanel.SetSizer(structure_panel_sizer)
+
+ self.Editor.SetSizer(self.MainSizer)
+
+ def __init__(self, parent, tagname, window, controler):
+ EditorPanel.__init__(self, parent, tagname, window, controler)
+
+ self.StructureElementDefaultValue = {"Name" : "", "Type" : "INT", "Initial Value" : ""}
+ self.StructureElementsTable = ElementsTable(self, [], GetElementsTableColnames())
+ self.StructureColSizes = [40, 150, 100, 250]
+ self.StructureColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+
+ self.StructureElementsGrid.SetTable(self.StructureElementsTable)
+ self.StructureElementsGrid.SetButtons({"Add": self.StructureAddButton,
+ "Delete": self.StructureDeleteButton,
+ "Up": self.StructureUpButton,
+ "Down": self.StructureDownButton})
+
+ def _AddStructureElement(new_row):
+ self.StructureElementsTable.InsertRow(new_row, self.StructureElementDefaultValue.copy())
+ self.RefreshTypeInfos()
+ self.StructureElementsTable.ResetView(self.StructureElementsGrid)
+ return new_row
+ setattr(self.StructureElementsGrid, "_AddRow", _AddStructureElement)
+
+ def _DeleteStructureElement(row):
+ self.StructureElementsTable.RemoveRow(row)
+ self.RefreshTypeInfos()
+ self.StructureElementsTable.ResetView(self.StructureElementsGrid)
+ setattr(self.StructureElementsGrid, "_DeleteRow", _DeleteStructureElement)
+
+ def _MoveStructureElement(row, move):
+ new_row = self.StructureElementsTable.MoveRow(row, move)
+ if new_row != row:
+ self.RefreshTypeInfos()
+ self.StructureElementsTable.ResetView(self.StructureElementsGrid)
+ return new_row
+ setattr(self.StructureElementsGrid, "_MoveRow", _MoveStructureElement)
+
+ self.StructureElementsGrid.SetRowLabelSize(0)
+ for col in range(self.StructureElementsTable.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(self.StructureColAlignements[col], wx.ALIGN_CENTRE)
+ self.StructureElementsGrid.SetColAttr(col, attr)
+ self.StructureElementsGrid.SetColMinimalWidth(col, self.StructureColSizes[col])
+ self.StructureElementsGrid.AutoSizeColumn(col, False)
+ self.StructureElementsGrid.RefreshButtons()
+
+ for datatype in GetDatatypeTypes():
+ self.DerivationType.Append(_(datatype))
+ self.SubrangePanel.Hide()
+ self.EnumeratedPanel.Hide()
+ self.ArrayPanel.Hide()
+ self.StructurePanel.Hide()
+ self.CurrentPanel = "Directly"
+ self.Highlights = []
+ self.Initializing = False
+
+ self.HighlightControls = {
+ ("Directly", "base"): self.DirectlyBaseType,
+ ("Directly", "initial"): self.DirectlyInitialValue,
+ ("Subrange", "base"): self.SubrangeBaseType,
+ ("Subrange", "lower"): self.SubrangeMinimum,
+ ("Subrange", "upper"): self.SubrangeMaximum,
+ ("Subrange", "initial"): self.SubrangeInitialValue,
+ ("Enumerated", "value"): self.EnumeratedValues,
+ ("Enumerated", "initial"): self.EnumeratedInitialValue,
+ ("Array", "initial"): self.ArrayInitialValue,
+ ("Array", "base"): self.ArrayBaseType,
+ ("Array", "range"): self.ArrayDimensions,
+ }
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+
+ def __del__(self):
+ self.RefreshHighlightsTimer.Stop()
+
+ def GetBufferState(self):
+ return self.Controler.GetBufferState()
+
+ def Undo(self):
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def HasNoModel(self):
+ return self.Controler.GetEditedElement(self.TagName) is None
+
+ def RefreshView(self):
+ self.Initializing = True
+ self.DirectlyBaseType.Clear()
+ self.ArrayBaseType.Clear()
+ for datatype in self.Controler.GetDataTypes(self.TagName):
+ self.DirectlyBaseType.Append(datatype)
+ self.ArrayBaseType.Append(datatype)
+ self.DirectlyBaseType.SetSelection(0)
+ self.SubrangeBaseType.Clear()
+ words = self.TagName.split("::")
+ for base_type in self.Controler.GetSubrangeBaseTypes(words[1]):
+ self.SubrangeBaseType.Append(base_type)
+ self.SubrangeBaseType.SetSelection(0)
+ self.RefreshBoundsRange()
+ type_infos = self.Controler.GetDataTypeInfos(self.TagName)
+ if type_infos is not None:
+ datatype = type_infos["type"]
+ self.DerivationType.SetStringSelection(_(datatype))
+ if type_infos["type"] == "Directly":
+ self.DirectlyBaseType.SetStringSelection(type_infos["base_type"])
+ self.DirectlyInitialValue.SetValue(type_infos["initial"])
+ elif type_infos["type"] == "Subrange":
+ self.SubrangeBaseType.SetStringSelection(type_infos["base_type"])
+ self.RefreshBoundsRange()
+ self.SubrangeMinimum.SetValue(int(type_infos["min"]))
+ self.SubrangeMaximum.SetValue(int(type_infos["max"]))
+ self.RefreshSubrangeInitialValueRange()
+ if type_infos["initial"] != "":
+ self.SubrangeInitialValue.SetValue(int(type_infos["initial"]))
+ else:
+ self.SubrangeInitialValue.SetValue(type_infos["min"])
+ elif type_infos["type"] == "Enumerated":
+ self.EnumeratedValues.SetStrings(type_infos["values"])
+ self.RefreshEnumeratedValues()
+ self.EnumeratedInitialValue.SetStringSelection(type_infos["initial"])
+ elif type_infos["type"] == "Array":
+ self.ArrayBaseType.SetStringSelection(type_infos["base_type"])
+ self.ArrayDimensions.SetStrings(map(lambda x : "..".join(x), type_infos["dimensions"]))
+ self.ArrayInitialValue.SetValue(type_infos["initial"])
+ elif type_infos["type"] == "Structure":
+ self.StructureElementsTable.SetData(type_infos["elements"])
+ self.RefreshDisplayedInfos()
+ self.ShowHighlights()
+ self.StructureElementsTable.ResetView(self.StructureElementsGrid)
+ self.StructureElementsGrid.RefreshButtons()
+ self.Initializing = False
+
+ def OnDerivationTypeChanged(self, event):
+ wx.CallAfter(self.RefreshDisplayedInfos)
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+
+ def OnReturnKeyPressed(self, event):
+ self.RefreshTypeInfos()
+
+ def OnInfosChanged(self, event):
+ self.RefreshTypeInfos()
+ event.Skip()
+
+ def OnSubrangeBaseTypeChanged(self, event):
+ self.RefreshBoundsRange()
+ self.RefreshTypeInfos()
+ event.Skip()
+
+ def OnSubrangeMinimumChanged(self, event):
+ if not self.Initializing:
+ wx.CallAfter(self.SubrangeMinimum.SetValue, min(self.SubrangeMaximum.GetValue(), self.SubrangeMinimum.GetValue()))
+ wx.CallAfter(self.RefreshSubrangeInitialValueRange)
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+
+ def OnSubrangeMaximumChanged(self, event):
+ if not self.Initializing:
+ wx.CallAfter(self.SubrangeMaximum.SetValue, max(self.SubrangeMinimum.GetValue(), self.SubrangeMaximum.GetValue()))
+ wx.CallAfter(self.RefreshSubrangeInitialValueRange)
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+
+ def OnDimensionsChanged(self, event):
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+
+ def OnEnumeratedValueEndEdit(self, event):
+ text = event.GetText()
+ values = self.EnumeratedValues.GetStrings()
+ index = event.GetIndex()
+ if index >= len(values) or values[index].upper() != text.upper():
+ if text.upper() in [value.upper() for value in values]:
+ message = wx.MessageDialog(self, _("\"%s\" value already defined!")%text, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+ elif text.upper() in IEC_KEYWORDS:
+ message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!")%text, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ initial_selected = None
+ if index < len(values) and self.EnumeratedInitialValue.GetStringSelection() == values[index]:
+ initial_selected = text
+ wx.CallAfter(self.RefreshEnumeratedValues, initial_selected)
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+ else:
+ event.Skip()
+
+ def OnEnumeratedValuesChanged(self, event):
+ wx.CallAfter(self.RefreshEnumeratedValues)
+ wx.CallAfter(self.RefreshTypeInfos)
+ event.Skip()
+
+ def OnStructureElementsGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ colname = self.StructureElementsTable.GetColLabelValue(col)
+ value = self.StructureElementsTable.GetValue(row, col)
+ if colname == "Name":
+ if not TestIdentifier(value):
+ message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+ elif value.upper() in IEC_KEYWORDS:
+ message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+## elif value.upper() in self.PouNames:
+## message = wx.MessageDialog(self, "A pou with \"%s\" as name exists!"%value, "Error", wx.OK|wx.ICON_ERROR)
+## message.ShowModal()
+## message.Destroy()
+## event.Veto()
+ elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.StructureElementsTable.GetData()) if idx != row]:
+ message = wx.MessageDialog(self, _("An element named \"%s\" already exists in this structure!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+ else:
+ self.RefreshTypeInfos()
+ wx.CallAfter(self.StructureElementsTable.ResetView, self.StructureElementsGrid)
+## old_value = self.Table.GetOldValue()
+## if old_value != "":
+## self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
+## self.Controler.BufferProject()
+ event.Skip()
+ else:
+ self.RefreshTypeInfos()
+ wx.CallAfter(self.StructureElementsTable.ResetView, self.StructureElementsGrid)
+ event.Skip()
+
+ def OnStructureElementsGridSelectCell(self, event):
+ wx.CallAfter(self.RefreshStructureButtons)
+ event.Skip()
+
+ def OnStructureElementsGridEditorShown(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ if self.StructureElementsTable.GetColLabelValue(col) == "Type":
+ type_menu = wx.Menu(title='')
+ base_menu = wx.Menu(title='')
+ for base_type in self.Controler.GetBaseTypes():
+ new_id = wx.NewId()
+ AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
+ self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(base_type), id=new_id)
+ type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
+ datatype_menu = wx.Menu(title='')
+ for datatype in self.Controler.GetDataTypes(self.TagName, False):
+ new_id = wx.NewId()
+ AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
+ self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(datatype), id=new_id)
+ type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
+## functionblock_menu = wx.Menu(title='')
+## bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
+## pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
+## if classtype in ["Input","Output","InOut","External","Global"] or poutype != "function" and bodytype in ["ST", "IL"]:
+## for functionblock_type in self.Controler.GetFunctionBlockTypes(self.TagName):
+## new_id = wx.NewId()
+## AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
+## self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
+## type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
+ rect = self.StructureElementsGrid.BlockToDeviceRect((row, col), (row, col))
+ self.StructureElementsGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
+ type_menu.Destroy()
+ event.Veto()
+ else:
+ event.Skip()
+
+ def GetElementTypeFunction(self, base_type):
+ def ElementTypeFunction(event):
+ row = self.StructureElementsGrid.GetGridCursorRow()
+ self.StructureElementsTable.SetValueByName(row, "Type", base_type)
+ self.RefreshTypeInfos()
+ self.StructureElementsTable.ResetView(self.StructureElementsGrid)
+ return ElementTypeFunction
+
+ def RefreshDisplayedInfos(self):
+ selected = DATATYPE_TYPES_DICT[self.DerivationType.GetStringSelection()]
+ if selected != self.CurrentPanel:
+ if self.CurrentPanel == "Directly":
+ self.DirectlyPanel.Hide()
+ elif self.CurrentPanel == "Subrange":
+ self.SubrangePanel.Hide()
+ elif self.CurrentPanel == "Enumerated":
+ self.EnumeratedPanel.Hide()
+ elif self.CurrentPanel == "Array":
+ self.ArrayPanel.Hide()
+ elif self.CurrentPanel == "Structure":
+ self.StructurePanel.Hide()
+ self.CurrentPanel = selected
+ if selected == "Directly":
+ self.DirectlyPanel.Show()
+ elif selected == "Subrange":
+ self.SubrangePanel.Show()
+ elif selected == "Enumerated":
+ self.EnumeratedPanel.Show()
+ elif selected == "Array":
+ self.ArrayPanel.Show()
+ elif selected == "Structure":
+ self.StructurePanel.Show()
+ self.MainSizer.Layout()
+
+ def RefreshEnumeratedValues(self, initial_selected=None):
+ if initial_selected is None:
+ initial_selected = self.EnumeratedInitialValue.GetStringSelection()
+ self.EnumeratedInitialValue.Clear()
+ self.EnumeratedInitialValue.Append("")
+ for value in self.EnumeratedValues.GetStrings():
+ self.EnumeratedInitialValue.Append(value)
+ self.EnumeratedInitialValue.SetStringSelection(initial_selected)
+
+ def RefreshBoundsRange(self):
+ range = self.Controler.GetDataTypeRange(self.SubrangeBaseType.GetStringSelection())
+ if range is not None:
+ min_value, max_value = range
+ self.SubrangeMinimum.SetRange(min_value, max_value)
+ self.SubrangeMinimum.SetValue(min(max(min_value, self.SubrangeMinimum.GetValue()), max_value))
+ self.SubrangeMaximum.SetRange(min_value, max_value)
+ self.SubrangeMaximum.SetValue(min(max(min_value, self.SubrangeMaximum.GetValue()), max_value))
+
+ def RefreshSubrangeInitialValueRange(self):
+ self.SubrangeInitialValue.SetRange(self.SubrangeMinimum.GetValue(), self.SubrangeMaximum.GetValue())
+
+ def RefreshTypeInfos(self):
+ selected = DATATYPE_TYPES_DICT[self.DerivationType.GetStringSelection()]
+ infos = {"type" : selected}
+ if selected == "Directly":
+ infos["base_type"] = self.DirectlyBaseType.GetStringSelection()
+ infos["initial"] = self.DirectlyInitialValue.GetValue()
+ elif selected == "Subrange":
+ infos["base_type"] = self.SubrangeBaseType.GetStringSelection()
+ infos["min"] = str(self.SubrangeMinimum.GetValue())
+ infos["max"] = str(self.SubrangeMaximum.GetValue())
+ initial_value = self.SubrangeInitialValue.GetValue()
+ if initial_value == infos["min"]:
+ infos["initial"] = ""
+ else:
+ infos["initial"] = str(initial_value)
+ elif selected == "Enumerated":
+ infos["values"] = self.EnumeratedValues.GetStrings()
+ infos["initial"] = self.EnumeratedInitialValue.GetStringSelection()
+ elif selected == "Array":
+ infos["base_type"] = self.ArrayBaseType.GetStringSelection()
+ infos["dimensions"] = []
+ for dimensions in self.ArrayDimensions.GetStrings():
+ result = DIMENSION_MODEL.match(dimensions)
+ if result is None:
+ message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!")%dimensions, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ self.RefreshView()
+ return
+ bounds = result.groups()
+ if int(bounds[0]) >= int(bounds[1]):
+ message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!\nRight value must be greater than left value.")%dimensions, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ self.RefreshView()
+ return
+ infos["dimensions"].append(bounds)
+ infos["initial"] = self.ArrayInitialValue.GetValue()
+ elif selected == "Structure":
+ infos["elements"] = self.StructureElementsTable.GetData()
+ infos["initial"] = ""
+ self.Controler.SetDataTypeInfos(self.TagName, infos)
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def ClearHighlights(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = []
+ else:
+ self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type]
+ for control in self.HighlightControls.itervalues():
+ if isinstance(control, (wx.ComboBox, wx.SpinCtrl)):
+ control.SetBackgroundColour(wx.NullColour)
+ control.SetForegroundColour(wx.NullColour)
+ elif isinstance(control, wx.TextCtrl):
+ value = control.GetValue()
+ control.SetStyle(0, len(value), wx.TextAttr(wx.NullColour))
+ elif isinstance(control, wx.gizmos.EditableListBox):
+ listctrl = control.GetListCtrl()
+ for i in xrange(listctrl.GetItemCount()):
+ listctrl.SetItemBackgroundColour(i, wx.NullColour)
+ listctrl.SetItemTextColour(i, wx.NullColour)
+ self.StructureElementsTable.ClearHighlights(highlight_type)
+ self.RefreshView()
+
+ def AddHighlight(self, infos, start, end ,highlight_type):
+ self.Highlights.append((infos, start, end, highlight_type))
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ShowHighlights(self):
+ type_infos = self.Controler.GetDataTypeInfos(self.TagName)
+ for infos, start, end, highlight_type in self.Highlights:
+ if infos[0] == "struct":
+ self.StructureElementsTable.AddHighlight(infos[1:], highlight_type)
+ else:
+ control = self.HighlightControls.get((type_infos["type"], infos[0]), None)
+ if control is not None:
+ if isinstance(control, (wx.ComboBox, wx.SpinCtrl)):
+ control.SetBackgroundColour(highlight_type[0])
+ control.SetForegroundColour(highlight_type[1])
+ elif isinstance(control, wx.TextCtrl):
+ control.SetStyle(start[1], end[1] + 1, wx.TextAttr(highlight_type[1], highlight_type[0]))
+ elif isinstance(control, wx.gizmos.EditableListBox):
+ listctrl = control.GetListCtrl()
+ listctrl.SetItemBackgroundColour(infos[1], highlight_type[0])
+ listctrl.SetItemTextColour(infos[1], highlight_type[1])
+ listctrl.Select(listctrl.FocusedItem, False)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/EditorPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from controls import VariablePanel
+
+class EditorPanel(wx.SplitterWindow):
+
+ VARIABLE_PANEL_TYPE = None
+
+ def _init_Editor(self, prnt):
+ self.Editor = None
+
+ def _init_MenuItems(self):
+ self.MenuItems = []
+
+ def _init_ctrls(self, parent):
+ wx.SplitterWindow.__init__(self, parent,
+ style=wx.SUNKEN_BORDER|wx.SP_3D)
+ self.SetNeedUpdating(True)
+ self.SetMinimumPaneSize(1)
+
+ self._init_MenuItems()
+
+ if self.VARIABLE_PANEL_TYPE is not None:
+ self.VariableEditor = VariablePanel(self, self, self.Controler, self.VARIABLE_PANEL_TYPE, self.Debug)
+ self.VariableEditor.SetTagName(self.TagName)
+ else:
+ self.VariableEditor = None
+
+ self._init_Editor(self)
+
+ if self.Editor is not None and self.VariableEditor is not None:
+ self.SplitHorizontally(self.VariableEditor, self.Editor, 200)
+ elif self.VariableEditor is not None:
+ self.Initialize(self.VariableEditor)
+ elif self.Editor is not None:
+ self.Initialize(self.Editor)
+
+ def __init__(self, parent, tagname, window, controler, debug=False):
+ self.ParentWindow = window
+ self.Controler = controler
+ self.TagName = tagname
+ self.Icon = None
+ self.Debug = debug
+
+ self._init_ctrls(parent)
+
+ def SetTagName(self, tagname):
+ self.TagName = tagname
+ if self.VARIABLE_PANEL_TYPE is not None:
+ self.VariableEditor.SetTagName(tagname)
+
+ def GetTagName(self):
+ return self.TagName
+
+ def Select(self):
+ self.ParentWindow.EditProjectElement(None, self.GetTagName(), True)
+
+ def GetTitle(self):
+ return "-".join(self.TagName.split("::")[1:])
+
+ def GetIcon(self):
+ return self.Icon
+
+ def SetIcon(self, icon):
+ self.Icon = icon
+
+ def GetState(self):
+ return None
+
+ def SetState(self, state):
+ pass
+
+ def IsViewing(self, tagname):
+ return self.GetTagName() == tagname
+
+ def IsDebugging(self):
+ return self.Debug
+
+ def SetMode(self, mode):
+ pass
+
+ def ResetBuffer(self):
+ pass
+
+ def IsModified(self):
+ return False
+
+ def CheckSaveBeforeClosing(self):
+ return True
+
+ def Save(self):
+ pass
+
+ def SaveAs(self):
+ pass
+
+ def GetBufferState(self):
+ if self.Controler is not None:
+ return self.Controler.GetBufferState()
+ return False, False
+
+ def Undo(self):
+ if self.Controler is not None:
+ self.Controler.LoadPrevious()
+ self.RefreshView()
+
+ def Redo(self):
+ if self.Controler is not None:
+ self.Controler.LoadNext()
+ self.RefreshView()
+
+ def Find(self, direction, search_params):
+ pass
+
+ def HasNoModel(self):
+ return False
+
+ def RefreshView(self, variablepanel=True):
+ if variablepanel:
+ self.RefreshVariablePanel()
+
+ def RefreshVariablePanel(self):
+ if self.VariableEditor is not None:
+ self.VariableEditor.RefreshView()
+
+ def GetConfNodeMenuItems(self):
+ return self.MenuItems
+
+ def RefreshConfNodeMenu(self, confnode_menu):
+ pass
+
+ def _Refresh(self, *args):
+ self.ParentWindow._Refresh(*args)
+
+ def RefreshScaling(self, refresh=True):
+ pass
+
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if self.VariableEditor is not None and infos[0] in ["var_local", "var_input", "var_output", "var_inout"]:
+ self.VariableEditor.AddVariableHighlight(infos[1:], highlight_type)
+
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if self.VariableEditor is not None and infos[0] in ["var_local", "var_input", "var_output", "var_inout"]:
+ self.VariableEditor.RemoveVariableHighlight(infos[1:], highlight_type)
+
+ def ClearHighlights(self, highlight_type=None):
+ if self.VariableEditor is not None:
+ self.VariableEditor.ClearHighlights(highlight_type)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/FileManagementPanel.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,432 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of Beremiz, a Integrated Development Environment for
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+import shutil
+
+import wx
+
+from EditorPanel import EditorPanel
+from util.BitmapLibrary import GetBitmap
+
+DRIVE, FOLDER, FILE = range(3)
+
+FILTER = _("All files (*.*)|*.*|CSV files (*.csv)|*.csv")
+
+def sort_folder(x, y):
+ if x[1] == y[1]:
+ return cmp(x[0], y[0])
+ elif x[1] != FILE:
+ return -1
+ else:
+ return 1
+
+def splitpath(path):
+ head, tail = os.path.split(path)
+ if head == "":
+ return [tail]
+ elif tail == "":
+ return splitpath(head)
+ return splitpath(head) + [tail]
+
+class FolderTree(wx.Panel):
+
+ def __init__(self, parent, folder, filter, editable=True):
+ wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.Tree = wx.TreeCtrl(self,
+ style=wx.TR_HAS_BUTTONS|
+ wx.TR_SINGLE|
+ wx.SUNKEN_BORDER|
+ wx.TR_HIDE_ROOT|
+ wx.TR_LINES_AT_ROOT|
+ wx.TR_EDIT_LABELS)
+ if wx.Platform == '__WXMSW__':
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnTreeItemExpanded, self.Tree)
+ self.Tree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeLeftDown)
+ else:
+ self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnTreeItemExpanded, self.Tree)
+ self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnTreeItemCollapsed, self.Tree)
+ self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeBeginLabelEdit, self.Tree)
+ self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeEndLabelEdit, self.Tree)
+ main_sizer.AddWindow(self.Tree, 1, flag=wx.GROW)
+
+ self.Filter = wx.ComboBox(self, style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnFilterChanged, self.Filter)
+ main_sizer.AddWindow(self.Filter, flag=wx.GROW)
+
+ self.SetSizer(main_sizer)
+
+ self.Folder = folder
+ self.Editable = editable
+
+ self.TreeImageList = wx.ImageList(16, 16)
+ self.TreeImageDict = {}
+ for item_type, bitmap in [(DRIVE, "tree_drive"),
+ (FOLDER, "tree_folder"),
+ (FILE, "tree_file")]:
+ self.TreeImageDict[item_type] = self.TreeImageList.Add(GetBitmap(bitmap))
+ self.Tree.SetImageList(self.TreeImageList)
+
+ self.Filters = {}
+ filter_parts = filter.split("|")
+ for idx in xrange(0, len(filter_parts), 2):
+ if filter_parts[idx + 1] == "*.*":
+ self.Filters[filter_parts[idx]] = ""
+ else:
+ self.Filters[filter_parts[idx]] = filter_parts[idx + 1].replace("*", "")
+ self.Filter.Append(filter_parts[idx])
+ if idx == 0:
+ self.Filter.SetStringSelection(filter_parts[idx])
+
+ self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
+
+ def _GetFolderChildren(self, folderpath, recursive=True):
+ items = []
+ if wx.Platform == '__WXMSW__' and folderpath == "/":
+ for c in xrange(ord('a'), ord('z')):
+ drive = os.path.join("%s:\\" % chr(c))
+ if os.path.exists(drive):
+ items.append((drive, DRIVE, self._GetFolderChildren(drive, False)))
+ else:
+ try:
+ files = os.listdir(folderpath)
+ except:
+ return []
+ for filename in files:
+ if not filename.startswith("."):
+ filepath = os.path.join(folderpath, filename)
+ if os.path.isdir(filepath):
+ if recursive:
+ children = len(self._GetFolderChildren(filepath, False))
+ else:
+ children = 0
+ items.append((filename, FOLDER, children))
+ elif (self.CurrentFilter == "" or
+ os.path.splitext(filename)[1] == self.CurrentFilter):
+ items.append((filename, FILE, None))
+ if recursive:
+ items.sort(sort_folder)
+ return items
+
+ def GetTreeCtrl(self):
+ return self.Tree
+
+ def RefreshTree(self):
+ root = self.Tree.GetRootItem()
+ if not root.IsOk():
+ root = self.Tree.AddRoot("")
+ self.GenerateTreeBranch(root, self.Folder)
+
+ def GenerateTreeBranch(self, root, folderpath):
+ item, item_cookie = self.Tree.GetFirstChild(root)
+ for idx, (filename, item_type, children) in enumerate(self._GetFolderChildren(folderpath)):
+ if not item.IsOk():
+ item = self.Tree.AppendItem(root, filename, self.TreeImageDict[item_type])
+ if wx.Platform != '__WXMSW__':
+ item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
+ elif self.Tree.GetItemText(item) != filename:
+ item = self.Tree.InsertItemBefore(root, idx, filename, self.TreeImageDict[item_type])
+ filepath = os.path.join(folderpath, filename)
+ if item_type != FILE:
+ if self.Tree.IsExpanded(item):
+ self.GenerateTreeBranch(item, filepath)
+ elif children > 0:
+ self.Tree.SetItemHasChildren(item)
+ item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
+ to_delete = []
+ while item.IsOk():
+ to_delete.append(item)
+ item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
+ for item in to_delete:
+ self.Tree.Delete(item)
+
+ def ExpandItem(self, item):
+ self.GenerateTreeBranch(item, self.GetPath(item))
+ self.Tree.Expand(item)
+
+ def OnTreeItemActivated(self, event):
+ self.ExpandItem(event.GetItem())
+ event.Skip()
+
+ def OnTreeLeftDown(self, event):
+ item, flags = self.Tree.HitTest(event.GetPosition())
+ if flags & wx.TREE_HITTEST_ONITEMBUTTON and not self.Tree.IsExpanded(item):
+ self.ExpandItem(item)
+ else:
+ event.Skip()
+
+ def OnTreeItemExpanded(self, event):
+ item = event.GetItem()
+ self.GenerateTreeBranch(item, self.GetPath(item))
+ event.Skip()
+
+ def OnTreeItemCollapsed(self, event):
+ item = event.GetItem()
+ self.Tree.DeleteChildren(item)
+ self.Tree.SetItemHasChildren(item)
+ event.Skip()
+
+ def OnTreeBeginLabelEdit(self, event):
+ item = event.GetItem()
+ if self.Editable and not self.Tree.ItemHasChildren(item):
+ event.Skip()
+ else:
+ event.Veto()
+
+ def OnTreeEndLabelEdit(self, event):
+ old_filepath = self.GetPath(event.GetItem())
+ new_filepath = os.path.join(os.path.split(old_filepath)[0], event.GetLabel())
+ if new_filepath != old_filepath:
+ if not os.path.exists(new_filepath):
+ os.rename(old_filepath, new_filepath)
+ event.Skip()
+ else:
+ message = wx.MessageDialog(self,
+ _("File '%s' already exists!") % event.GetLabel(),
+ _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ event.Veto()
+
+ def OnFilterChanged(self, event):
+ self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
+ self.RefreshTree()
+ event.Skip()
+
+ def _SelectItem(self, root, parts):
+ if len(parts) == 0:
+ self.Tree.SelectItem(root)
+ else:
+ item, item_cookie = self.Tree.GetFirstChild(root)
+ while item.IsOk():
+ if self.Tree.GetItemText(item) == parts[0]:
+ if (self.Tree.ItemHasChildren(item) and
+ not self.Tree.IsExpanded(item)):
+ self.Tree.Expand(item)
+ wx.CallAfter(self._SelectItem, item, parts[1:])
+ else:
+ self._SelectItem(item, parts[1:])
+ return
+ item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
+
+ def SetPath(self, path):
+ if path.startswith(self.Folder):
+ root = self.Tree.GetRootItem()
+ if root.IsOk():
+ relative_path = path.replace(os.path.join(self.Folder, ""), "")
+ self._SelectItem(root, splitpath(relative_path))
+
+ def GetPath(self, item=None):
+ if item is None:
+ item = self.Tree.GetSelection()
+ if item.IsOk():
+ filepath = self.Tree.GetItemText(item)
+ parent = self.Tree.GetItemParent(item)
+ while parent.IsOk() and parent != self.Tree.GetRootItem():
+ filepath = os.path.join(self.Tree.GetItemText(parent), filepath)
+ parent = self.Tree.GetItemParent(parent)
+ return os.path.join(self.Folder, filepath)
+ return self.Folder
+
+class FileManagementPanel(EditorPanel):
+
+ def _init_Editor(self, parent):
+ self.Editor = wx.Panel(parent)
+
+ main_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ left_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.AddSizer(left_sizer, 1, border=5, flag=wx.GROW|wx.ALL)
+
+ managed_dir_label = wx.StaticText(self.Editor, label=self.TagName + ":")
+ left_sizer.AddWindow(managed_dir_label, border=5, flag=wx.GROW|wx.BOTTOM)
+
+ self.ManagedDir = FolderTree(self.Editor, self.Folder, FILTER)
+ left_sizer.AddWindow(self.ManagedDir, 1, flag=wx.GROW)
+
+ managed_treectrl = self.ManagedDir.GetTreeCtrl()
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, managed_treectrl)
+ if self.EnableDragNDrop:
+ self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, managed_treectrl)
+
+ button_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.AddSizer(button_sizer, border=5,
+ flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL)
+
+ for idx, (name, bitmap, help) in enumerate([
+ ("DeleteButton", "remove_element", _("Remove file from left folder")),
+ ("LeftCopyButton", "LeftCopy", _("Copy file from right folder to left")),
+ ("RightCopyButton", "RightCopy", _("Copy file from left folder to right")),
+ ("EditButton", "edit", _("Edit file"))]):
+ button = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ if idx > 0:
+ flag = wx.TOP
+ else:
+ flag = 0
+ self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
+ button_sizer.AddWindow(button, border=20, flag=flag)
+
+ right_sizer = wx.BoxSizer(wx.VERTICAL)
+ main_sizer.AddSizer(right_sizer, 1, border=5, flag=wx.GROW|wx.ALL)
+
+ if wx.Platform == '__WXMSW__':
+ system_dir_label = wx.StaticText(self.Editor, label=_("My Computer:"))
+ else:
+ system_dir_label = wx.StaticText(self.Editor, label=_("Home Directory:"))
+ right_sizer.AddWindow(system_dir_label, border=5, flag=wx.GROW|wx.BOTTOM)
+
+ self.SystemDir = FolderTree(self.Editor, self.HomeDirectory, FILTER, False)
+ right_sizer.AddWindow(self.SystemDir, 1, flag=wx.GROW)
+
+ system_treectrl = self.SystemDir.GetTreeCtrl()
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, system_treectrl)
+
+ self.Editor.SetSizer(main_sizer)
+
+ def __init__(self, parent, controler, name, folder, enable_dragndrop=False):
+ self.Folder = os.path.realpath(folder)
+ self.EnableDragNDrop = enable_dragndrop
+
+ if wx.Platform == '__WXMSW__':
+ self.HomeDirectory = "/"
+ else:
+ self.HomeDirectory = os.path.expanduser("~")
+
+ EditorPanel.__init__(self, parent, name, None, None)
+
+ self.Controler = controler
+
+ self.EditableFileExtensions = []
+ self.EditButton.Hide()
+
+ self.SetIcon(GetBitmap("FOLDER"))
+
+ def __del__(self):
+ self.Controler.OnCloseEditor(self)
+
+ def GetTitle(self):
+ return self.TagName
+
+ def SetEditableFileExtensions(self, extensions):
+ self.EditableFileExtensions = extensions
+ if len(self.EditableFileExtensions) > 0:
+ self.EditButton.Show()
+
+ def RefreshView(self):
+ self.ManagedDir.RefreshTree()
+ self.SystemDir.RefreshTree()
+ self.RefreshButtonsState()
+
+ def RefreshButtonsState(self):
+ managed_filepath = self.ManagedDir.GetPath()
+ system_filepath = self.SystemDir.GetPath()
+
+ self.DeleteButton.Enable(os.path.isfile(managed_filepath))
+ self.LeftCopyButton.Enable(os.path.isfile(system_filepath))
+ self.RightCopyButton.Enable(os.path.isfile(managed_filepath))
+ if len(self.EditableFileExtensions) > 0:
+ self.EditButton.Enable(
+ os.path.isfile(managed_filepath) and
+ os.path.splitext(managed_filepath)[1] in self.EditableFileExtensions)
+
+ def OnTreeItemChanged(self, event):
+ self.RefreshButtonsState()
+ event.Skip()
+
+ def OnDeleteButton(self, event):
+ filepath = self.ManagedDir.GetPath()
+ if os.path.isfile(filepath):
+ folder, filename = os.path.split(filepath)
+
+ dialog = wx.MessageDialog(self,
+ _("Do you really want to delete the file '%s'?") % filename,
+ _("Delete File"), wx.YES_NO|wx.ICON_QUESTION)
+ remove = dialog.ShowModal() == wx.ID_YES
+ dialog.Destroy()
+
+ if remove:
+ os.remove(filepath)
+ self.ManagedDir.RefreshTree()
+ event.Skip()
+
+ def OnEditButton(self, event):
+ filepath = self.ManagedDir.GetPath()
+ if (os.path.isfile(filepath) and
+ os.path.splitext(filepath)[1] in self.EditableFileExtensions):
+ self.Controler._OpenView(filepath + "::")
+ event.Skip()
+
+ def CopyFile(self, src, dst):
+ if os.path.isfile(src):
+ src_folder, src_filename = os.path.split(src)
+ if os.path.isfile(dst):
+ dst_folder, dst_filename = os.path.split(dst)
+ else:
+ dst_folder = dst
+
+ dst_filepath = os.path.join(dst_folder, src_filename)
+ if os.path.isfile(dst_filepath):
+ dialog = wx.MessageDialog(self,
+ _("The file '%s' already exist.\nDo you want to replace it?") % src_filename,
+ _("Replace File"), wx.YES_NO|wx.ICON_QUESTION)
+ copy = dialog.ShowModal() == wx.ID_YES
+ dialog.Destroy()
+ else:
+ copy = True
+
+ if copy:
+ shutil.copyfile(src, dst_filepath)
+ return dst_filepath
+ return None
+
+ def OnLeftCopyButton(self, event):
+ filepath = self.CopyFile(self.SystemDir.GetPath(), self.ManagedDir.GetPath())
+ if filepath is not None:
+ self.ManagedDir.RefreshTree()
+ self.ManagedDir.SetPath(filepath)
+ event.Skip()
+
+ def OnRightCopyButton(self, event):
+ filepath = self.CopyFile(self.ManagedDir.GetPath(), self.SystemDir.GetPath())
+ if filepath is not None:
+ self.SystemDir.RefreshTree()
+ self.SystemDir.SetPath(filepath)
+ event.Skip()
+
+ def OnTreeBeginDrag(self, event):
+ filepath = self.ManagedDir.GetPath()
+ if os.path.isfile(filepath):
+ relative_filepath = filepath.replace(os.path.join(self.Folder, ""), "")
+ data = wx.TextDataObject(str(("'%s'" % relative_filepath, "Constant")))
+ dragSource = wx.DropSource(self)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/GraphicViewer.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,542 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import numpy
+import math
+
+import wx
+import wx.lib.plot as plot
+import wx.lib.buttons
+
+from graphics.GraphicCommons import DebugViewer, MODE_SELECTION, MODE_MOTION
+from EditorPanel import EditorPanel
+from util.BitmapLibrary import GetBitmap
+
+colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan',
+ 'pink', 'grey']
+markers = ['circle', 'dot', 'square', 'triangle', 'triangle_down', 'cross', 'plus', 'circle']
+
+
+#-------------------------------------------------------------------------------
+# Debug Variable Graphic Viewer class
+#-------------------------------------------------------------------------------
+
+SECOND = 1000000000
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+
+ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
+RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
+TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
+ [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
+ [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
+
+class GraphicViewer(EditorPanel, DebugViewer):
+
+ def _init_Editor(self, prnt):
+ self.Editor = wx.Panel(prnt)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ self.Canvas = plot.PlotCanvas(self.Editor, name='Canvas')
+ def _axisInterval(spec, lower, upper):
+ if spec == 'border':
+ if lower == upper:
+ return lower - 0.5, upper + 0.5
+ else:
+ border = (upper - lower) * 0.05
+ return lower - border, upper + border
+ else:
+ return plot.PlotCanvas._axisInterval(self.Canvas, spec, lower, upper)
+ self.Canvas._axisInterval = _axisInterval
+ self.Canvas.SetYSpec('border')
+ self.Canvas.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown)
+ self.Canvas.canvas.Bind(wx.EVT_LEFT_UP, self.OnCanvasLeftUp)
+ self.Canvas.canvas.Bind(wx.EVT_MIDDLE_DOWN, self.OnCanvasMiddleDown)
+ self.Canvas.canvas.Bind(wx.EVT_MIDDLE_UP, self.OnCanvasMiddleUp)
+ self.Canvas.canvas.Bind(wx.EVT_MOTION, self.OnCanvasMotion)
+ self.Canvas.canvas.Bind(wx.EVT_SIZE, self.OnCanvasResize)
+ main_sizer.AddWindow(self.Canvas, 0, border=0, flag=wx.GROW)
+
+ range_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=0)
+ range_sizer.AddGrowableCol(5)
+ range_sizer.AddGrowableRow(0)
+ main_sizer.AddSizer(range_sizer, 0, border=5, flag=wx.GROW|wx.ALL)
+
+ range_label = wx.StaticText(self.Editor, label=_('Range:'))
+ range_sizer.AddWindow(range_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.CanvasRange = wx.ComboBox(self.Editor,
+ size=wx.Size(100, 28), style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
+ range_sizer.AddWindow(self.CanvasRange, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ zoom_label = wx.StaticText(self.Editor, label=_('Zoom:'))
+ range_sizer.AddWindow(zoom_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.CanvasZoom = wx.ComboBox(self.Editor,
+ size=wx.Size(70, 28), style=wx.CB_READONLY)
+ self.Bind(wx.EVT_COMBOBOX, self.OnZoomChanged, self.CanvasZoom)
+ range_sizer.AddWindow(self.CanvasZoom, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ position_label = wx.StaticText(self.Editor, label=_('Position:'))
+ range_sizer.AddWindow(position_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+
+ self.CanvasPosition = wx.ScrollBar(self.Editor,
+ size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
+ self.CanvasPosition.SetScrollbar(0, 10, 100, 10)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP,
+ self.OnPositionChanging, self.CanvasPosition)
+ self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN,
+ self.OnPositionChanging, self.CanvasPosition)
+ range_sizer.AddWindow(self.CanvasPosition, 0, border=5, flag=wx.GROW|wx.ALL)
+
+ self.ResetButton = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ResetButton.SetToolTipString(_("Clear the graph values"))
+ self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
+ range_sizer.AddWindow(self.ResetButton, 0, border=0, flag=0)
+
+ self.CurrentButton = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap("current"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.CurrentButton.SetToolTipString(_("Go to current value"))
+ self.Bind(wx.EVT_BUTTON, self.OnCurrentButton, self.CurrentButton)
+ range_sizer.AddWindow(self.CurrentButton, 0, border=0, flag=0)
+
+ self.ResetZoomOffsetButton = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap("fit"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ResetZoomOffsetButton.SetToolTipString(_("Reset zoom and offset"))
+ self.Bind(wx.EVT_BUTTON, self.OnResetZoomOffsetButton,
+ self.ResetZoomOffsetButton)
+ range_sizer.AddWindow(self.ResetZoomOffsetButton, 0, border=0, flag=0)
+
+ self.ExportGraphButton = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap("export_graph"), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ self.ExportGraphButton.SetToolTipString(_("Export graph values to clipboard"))
+ self.Bind(wx.EVT_BUTTON, self.OnExportGraphButtonClick,
+ self.ExportGraphButton)
+ range_sizer.AddWindow(self.ExportGraphButton, 0, border=0, flag=0)
+
+ self.Editor.SetSizer(main_sizer)
+
+ self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnCanvasMouseWheel)
+
+ def __init__(self, parent, window, producer, instancepath = ""):
+ EditorPanel.__init__(self, parent, "", window, None)
+ DebugViewer.__init__(self, producer, True, False)
+
+ self.InstancePath = instancepath
+ self.RangeValues = None
+ self.CursorIdx = None
+ self.LastCursor = None
+ self.CurrentMousePos = None
+ self.CurrentMotionValue = None
+ self.Dragging = False
+
+ # Initialize Viewer mode to Selection mode
+ self.Mode = MODE_SELECTION
+
+ self.Datas = []
+ self.StartTick = 0
+ self.StartIdx = 0
+ self.EndIdx = 0
+ self.MinValue = None
+ self.MaxValue = None
+ self.YCenter = 0
+ self.CurrentZoom = 1.0
+ self.Fixed = False
+ self.Ticktime = self.DataProducer.GetTicktime()
+ self.RefreshCanvasRange()
+
+ for zoom_txt, zoom in ZOOM_VALUES:
+ self.CanvasZoom.Append(zoom_txt)
+ self.CanvasZoom.SetSelection(0)
+
+ self.AddDataConsumer(self.InstancePath.upper(), self)
+
+ def __del__(self):
+ DebugViewer.__del__(self)
+ self.RemoveDataConsumer(self)
+
+ def GetTitle(self):
+ if len(self.InstancePath) > 15:
+ return "..." + self.InstancePath[-12:]
+ return self.InstancePath
+
+ # Changes Viewer mode
+ def SetMode(self, mode):
+ if self.Mode != mode or mode == MODE_SELECTION:
+ if self.Mode == MODE_MOTION:
+ wx.CallAfter(self.Canvas.canvas.SetCursor, wx.NullCursor)
+ self.Mode = mode
+ if self.Mode == MODE_MOTION:
+ wx.CallAfter(self.Canvas.canvas.SetCursor, wx.StockCursor(wx.CURSOR_HAND))
+
+ def ResetView(self, register=False):
+ self.Datas = []
+ self.StartTick = 0
+ self.StartIdx = 0
+ self.EndIdx = 0
+ self.MinValue = None
+ self.MaxValue = None
+ self.CursorIdx = None
+ self.Fixed = False
+ self.Ticktime = self.DataProducer.GetTicktime()
+ if register:
+ self.AddDataConsumer(self.InstancePath.upper(), self)
+ self.ResetLastCursor()
+ self.RefreshCanvasRange()
+ self.RefreshView()
+
+ def RefreshNewData(self, *args, **kwargs):
+ self.RefreshView(*args, **kwargs)
+ DebugViewer.RefreshNewData(self)
+
+ def GetNearestData(self, tick, adjust):
+ ticks = numpy.array(zip(*self.Datas)[0])
+ new_cursor = numpy.argmin(abs(ticks - tick))
+ if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0:
+ new_cursor -= 1
+ elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(self.Datas):
+ new_cursor += 1
+ return new_cursor
+
+ def GetBounds(self):
+ if self.StartIdx is None or self.EndIdx is None:
+ self.StartIdx = self.GetNearestData(self.StartTick, -1)
+ self.EndIdx = self.GetNearestData(self.StartTick + self.CurrentRange, 1)
+
+ def ResetBounds(self):
+ self.StartIdx = None
+ self.EndIdx = None
+
+ def RefreshCanvasRange(self):
+ if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES:
+ self.RangeValues = RANGE_VALUES
+ self.CanvasRange.Clear()
+ for text, value in RANGE_VALUES:
+ self.CanvasRange.Append(text)
+ self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0])
+ self.CurrentRange = RANGE_VALUES[0][1]
+ elif self.RangeValues != TIME_RANGE_VALUES:
+ self.RangeValues = TIME_RANGE_VALUES
+ self.CanvasRange.Clear()
+ for text, value in TIME_RANGE_VALUES:
+ self.CanvasRange.Append(text)
+ self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0])
+ self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime
+
+ def RefreshView(self, force=False):
+ self.Freeze()
+ if force or not self.Fixed or (len(self.Datas) > 0 and self.StartTick + self.CurrentRange > self.Datas[-1][0]):
+ if (self.MinValue is not None and
+ self.MaxValue is not None and
+ self.MinValue != self.MaxValue):
+ Yrange = float(self.MaxValue - self.MinValue) / self.CurrentZoom
+ else:
+ Yrange = 2. / self.CurrentZoom
+
+ if not force and not self.Fixed and len(self.Datas) > 0:
+ self.YCenter = max(self.Datas[-1][1] - Yrange / 2,
+ min(self.YCenter,
+ self.Datas[-1][1] + Yrange / 2))
+
+ var_name = self.InstancePath.split(".")[-1]
+
+ self.GetBounds()
+ self.VariableGraphic = plot.PolyLine(self.Datas[self.StartIdx:self.EndIdx + 1],
+ legend=var_name, colour=colours[0])
+ self.GraphicsObject = plot.PlotGraphics([self.VariableGraphic], _("%s Graphics") % var_name, _("Tick"), _("Values"))
+ self.Canvas.Draw(self.GraphicsObject,
+ xAxis=(self.StartTick, self.StartTick + self.CurrentRange),
+ yAxis=(self.YCenter - Yrange * 1.1 / 2., self.YCenter + Yrange * 1.1 / 2.))
+
+ # Reset and draw cursor
+ self.ResetLastCursor()
+ self.RefreshCursor()
+
+ self.RefreshScrollBar()
+
+ self.Thaw()
+
+ def GetInstancePath(self):
+ return self.InstancePath
+
+ def IsViewing(self, tagname):
+ return self.InstancePath == tagname
+
+ def NewValue(self, tick, value, forced=False):
+ value = {True:1., False:0.}.get(value, float(value))
+ self.Datas.append((float(tick), value))
+ if self.MinValue is None:
+ self.MinValue = value
+ else:
+ self.MinValue = min(self.MinValue, value)
+ if self.MaxValue is None:
+ self.MaxValue = value
+ else:
+ self.MaxValue = max(self.MaxValue, value)
+ if not self.Fixed or tick < self.StartTick + self.CurrentRange:
+ self.GetBounds()
+ while int(self.Datas[self.StartIdx][0]) < tick - self.CurrentRange:
+ self.StartIdx += 1
+ self.EndIdx += 1
+ self.StartTick = self.Datas[self.StartIdx][0]
+ self.NewDataAvailable()
+
+ def RefreshScrollBar(self):
+ if len(self.Datas) > 0:
+ self.GetBounds()
+ pos = int(self.Datas[self.StartIdx][0] - self.Datas[0][0])
+ range = int(self.Datas[-1][0] - self.Datas[0][0])
+ else:
+ pos = 0
+ range = 0
+ self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
+
+ def RefreshRange(self):
+ if len(self.Datas) > 0:
+ if self.Fixed and self.Datas[-1][0] - self.Datas[0][0] < self.CurrentRange:
+ self.Fixed = False
+ self.ResetBounds()
+ if self.Fixed:
+ self.StartTick = min(self.StartTick, self.Datas[-1][0] - self.CurrentRange)
+ else:
+ self.StartTick = max(self.Datas[0][0], self.Datas[-1][0] - self.CurrentRange)
+ self.RefreshView(True)
+
+ def OnRangeChanged(self, event):
+ try:
+ if self.Ticktime == 0:
+ self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
+ else:
+ self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] / self.Ticktime
+ except ValueError, e:
+ self.CanvasRange.SetValue(str(self.CurrentRange))
+ wx.CallAfter(self.RefreshRange)
+ event.Skip()
+
+ def OnZoomChanged(self, event):
+ self.CurrentZoom = ZOOM_VALUES[self.CanvasZoom.GetSelection()][1]
+ wx.CallAfter(self.RefreshView, True)
+ event.Skip()
+
+ def OnPositionChanging(self, event):
+ if len(self.Datas) > 0:
+ self.ResetBounds()
+ self.StartTick = self.Datas[0][0] + event.GetPosition()
+ self.Fixed = True
+ self.NewDataAvailable(True)
+ event.Skip()
+
+ def OnResetButton(self, event):
+ self.Fixed = False
+ self.ResetView()
+ event.Skip()
+
+ def OnCurrentButton(self, event):
+ if len(self.Datas) > 0:
+ self.ResetBounds()
+ self.StartTick = max(self.Datas[0][0], self.Datas[-1][0] - self.CurrentRange)
+ self.Fixed = False
+ self.NewDataAvailable(True)
+ event.Skip()
+
+ def OnResetZoomOffsetButton(self, event):
+ if len(self.Datas) > 0:
+ self.YCenter = (self.MaxValue + self.MinValue) / 2
+ else:
+ self.YCenter = 0.0
+ self.CurrentZoom = 1.0
+ self.CanvasZoom.SetSelection(0)
+ wx.CallAfter(self.RefreshView, True)
+ event.Skip()
+
+ def OnExportGraphButtonClick(self, event):
+ data_copy = self.Datas[:]
+ text = "tick;%s;\n" % self.InstancePath
+ for tick, value in data_copy:
+ text += "%d;%.3f;\n" % (tick, value)
+ self.ParentWindow.SetCopyBuffer(text)
+ event.Skip()
+
+ def OnCanvasLeftDown(self, event):
+ self.Fixed = True
+ self.Canvas.canvas.CaptureMouse()
+ if len(self.Datas) > 0:
+ if self.Mode == MODE_SELECTION:
+ self.Dragging = True
+ pos = self.Canvas.PositionScreenToUser(event.GetPosition())
+ self.CursorIdx = self.GetNearestData(pos[0], -1)
+ self.RefreshCursor()
+ elif self.Mode == MODE_MOTION:
+ self.GetBounds()
+ self.CurrentMousePos = event.GetPosition()
+ self.CurrentMotionValue = self.Datas[self.StartIdx][0]
+ event.Skip()
+
+ def OnCanvasLeftUp(self, event):
+ self.Dragging = False
+ if self.Mode == MODE_MOTION:
+ self.CurrentMousePos = None
+ self.CurrentMotionValue = None
+ if self.Canvas.canvas.HasCapture():
+ self.Canvas.canvas.ReleaseMouse()
+ event.Skip()
+
+ def OnCanvasMiddleDown(self, event):
+ self.Fixed = True
+ self.Canvas.canvas.CaptureMouse()
+ if len(self.Datas) > 0:
+ self.GetBounds()
+ self.CurrentMousePos = event.GetPosition()
+ self.CurrentMotionValue = self.Datas[self.StartIdx][0]
+ event.Skip()
+
+ def OnCanvasMiddleUp(self, event):
+ self.CurrentMousePos = None
+ self.CurrentMotionValue = None
+ if self.Canvas.canvas.HasCapture():
+ self.Canvas.canvas.ReleaseMouse()
+ event.Skip()
+
+ def OnCanvasMotion(self, event):
+ if self.Mode == MODE_SELECTION and self.Dragging:
+ pos = self.Canvas.PositionScreenToUser(event.GetPosition())
+ graphics, xAxis, yAxis = self.Canvas.last_draw
+ self.CursorIdx = self.GetNearestData(max(xAxis[0], min(pos[0], xAxis[1])), -1)
+ self.RefreshCursor()
+ elif self.CurrentMousePos is not None and len(self.Datas) > 0:
+ oldpos = self.Canvas.PositionScreenToUser(self.CurrentMousePos)
+ newpos = self.Canvas.PositionScreenToUser(event.GetPosition())
+ self.CurrentMotionValue += oldpos[0] - newpos[0]
+ self.YCenter += oldpos[1] - newpos[1]
+ self.ResetBounds()
+ self.StartTick = max(self.Datas[0][0], min(self.CurrentMotionValue, self.Datas[-1][0] - self.CurrentRange))
+ self.CurrentMousePos = event.GetPosition()
+ self.NewDataAvailable(True)
+ event.Skip()
+
+ def OnCanvasMouseWheel(self, event):
+ if self.CurrentMousePos is None:
+ rotation = event.GetWheelRotation() / event.GetWheelDelta()
+ if event.ShiftDown():
+ current = self.CanvasRange.GetSelection()
+ new = max(0, min(current - rotation, len(self.RangeValues) - 1))
+ if new != current:
+ if self.Ticktime == 0:
+ self.CurrentRange = self.RangeValues[new][1]
+ else:
+ self.CurrentRange = self.RangeValues[new][1] / self.Ticktime
+ self.CanvasRange.SetStringSelection(self.RangeValues[new][0])
+ wx.CallAfter(self.RefreshRange)
+ else:
+ current = self.CanvasZoom.GetSelection()
+ new = max(0, min(current + rotation, len(ZOOM_VALUES) - 1))
+ if new != current:
+ self.CurrentZoom = ZOOM_VALUES[new][1]
+ self.CanvasZoom.SetStringSelection(ZOOM_VALUES[new][0])
+ wx.CallAfter(self.RefreshView, True)
+ event.Skip()
+
+ def OnCanvasResize(self, event):
+ self.ResetLastCursor()
+ wx.CallAfter(self.RefreshCursor)
+ event.Skip()
+
+ ## Reset the last cursor
+ def ResetLastCursor(self):
+ self.LastCursor = None
+
+ ## Draw the cursor on graphic
+ # @param dc The draw canvas
+ # @param cursor The cursor parameters
+ def DrawCursor(self, dc, cursor, value):
+ if self.StartTick <= cursor <= self.StartTick + self.CurrentRange:
+ # Prepare temporary dc for drawing
+ width = self.Canvas._Buffer.GetWidth()
+ height = self.Canvas._Buffer.GetHeight()
+ tmp_Buffer = wx.EmptyBitmap(width, height)
+ dcs = wx.MemoryDC()
+ dcs.SelectObject(tmp_Buffer)
+ dcs.Clear()
+ dcs.BeginDrawing()
+
+ dcs.SetPen(wx.Pen(wx.RED))
+ dcs.SetBrush(wx.Brush(wx.RED, wx.SOLID))
+ dcs.SetFont(self.Canvas._getFont(self.Canvas._fontSizeAxis))
+
+ # Calculate clipping region
+ graphics, xAxis, yAxis = self.Canvas.last_draw
+ p1 = numpy.array([xAxis[0], yAxis[0]])
+ p2 = numpy.array([xAxis[1], yAxis[1]])
+ cx, cy, cwidth, cheight = self.Canvas._point2ClientCoord(p1, p2)
+
+ px, py = self.Canvas.PositionUserToScreen((float(cursor), 0.))
+
+ # Draw line cross drawing for diaplaying time cursor
+ dcs.DrawLine(px, cy + 1, px, cy + cheight - 1)
+
+ lines = ("X:%d\nY:%f" % (cursor, value)).splitlines()
+
+ wtext = 0
+ for line in lines:
+ w, h = dcs.GetTextExtent(line)
+ wtext = max(wtext, w)
+
+ offset = 0
+ for line in lines:
+ # Draw time cursor date
+ dcs.DrawText(line, min(px + 3, cx + cwidth - wtext), cy + 3 + offset)
+ w, h = dcs.GetTextExtent(line)
+ offset += h
+
+ dcs.EndDrawing()
+
+ #this will erase if called twice
+ dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst
+
+ ## Refresh the variable cursor.
+ # @param dc The draw canvas
+ def RefreshCursor(self, dc=None):
+ if self:
+ if dc is None:
+ dc = wx.BufferedDC(wx.ClientDC(self.Canvas.canvas), self.Canvas._Buffer)
+
+ # Erase previous time cursor if drawn
+ if self.LastCursor is not None:
+ self.DrawCursor(dc, *self.LastCursor)
+
+ # Draw new time cursor
+ if self.CursorIdx is not None:
+ self.LastCursor = self.Datas[self.CursorIdx]
+ self.DrawCursor(dc, *self.LastCursor)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/LDViewer.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import time
+from types 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, stops):
+ branch_size = 0
+ stop_list = stops
+ for stop in stops:
+ 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")
+ remove_stops = {"start":[], "stop":[]}
+ for element, values in element_tree.items():
+ if "stop" in values["children"]:
+ removed = []
+ for child in values["children"]:
+ if child != "stop":
+## if child in elements:
+## RemoveElement(child, element_tree)
+## removed.append(child)
+ if "start" in element_tree[child]["parents"]:
+ if element not in remove_stops["stop"]:
+ remove_stops["stop"].append(element)
+ if child not in remove_stops["start"]:
+ remove_stops["start"].append(child)
+ for child in removed:
+ values["children"].remove(child)
+ for element in remove_stops["start"]:
+ element_tree[element]["parents"].remove("start")
+ for element in remove_stops["stop"]:
+ element_tree[element]["children"].remove("stop")
+ for element, values in element_tree.items():
+ if values and "stop" in values["children"]:
+ CalcWeight(element, element_tree)
+ if values["weight"]:
+ branch_size += values["weight"]
+ else:
+ return 1
+ #print branch_size
+ 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.pop(remove)
+## 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"]
+ element_tree[element]["children"].append("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, tagname, window, controler, debug = False, instancepath = ""):
+ Viewer.__init__(self, parent, tagname, window, controler, debug, instancepath)
+ self.Rungs = []
+ self.RungComments = []
+ self.CurrentLanguage = "LD"
+
+#-------------------------------------------------------------------------------
+# Refresh functions
+#-------------------------------------------------------------------------------
+
+ def ResetView(self):
+ self.Rungs = []
+ self.RungComments = []
+ Viewer.ResetView(self)
+
+ def RefreshView(self, variablepanel=True, selection=None):
+ Viewer.RefreshView(self, variablepanel, selection)
+ wx.CallAfter(self.Refresh)
+ for i, rung in enumerate(self.Rungs):
+ bbox = rung.GetBoundingBox()
+ if i < len(self.RungComments):
+ if self.RungComments[i]:
+ pos = self.RungComments[i].GetPosition()
+ if pos[1] > bbox.y:
+ self.RungComments.insert(i, None)
+ else:
+ self.RungComments.insert(i, None)
+
+ def loadInstance(self, instance, ids, selection):
+ Viewer.loadInstance(self, instance, ids, selection)
+ if self.GetDrawingMode() != FREEDRAWING_MODE:
+ 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["inputs"]:
+ 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"])
+ element_connectors = element.GetConnectors()
+ self.Rungs[rungs[0]].SelectElement(element)
+ for connector in element_connectors["inputs"]:
+ for wire, num in connector.GetWires():
+ self.Rungs[rungs[0]].SelectElement(wire)
+ wx.CallAfter(self.RefreshPosition, element)
+ elif instance["type"] in ["contact", "coil"]:
+ rungs = []
+ for link in instance["inputs"][0]["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"])
+ element_connectors = element.GetConnectors()
+ self.Rungs[rungs[0]].SelectElement(element)
+ for wire, num in element_connectors["inputs"][0].GetWires():
+ self.Rungs[rungs[0]].SelectElement(wire)
+ wx.CallAfter(self.RefreshPosition, element)
+ elif instance["type"] == "comment":
+ element = self.FindElementById(instance["id"])
+ pos = element.GetPosition()
+ i = 0
+ inserted = False
+ while i < len(self.RungComments) and not inserted:
+ ipos = self.RungComments[i].GetPosition()
+ if pos[1] < ipos[1]:
+ self.RungComments.insert(i, element)
+ inserted = True
+ i += 1
+ if not inserted:
+ self.RungComments.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, event, exclude_group = False, connectors = True):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ return Viewer.FindElement(self, event, exclude_group, connectors)
+
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ if self.SelectedElement and not isinstance(self.SelectedElement, (Graphic_Group, Wire)):
+ if self.SelectedElement.HitTest(pos, connectors) or self.SelectedElement.TestHandle(pos) != (0, 0):
+ return self.SelectedElement
+ elements = []
+ for element in self.GetElements(sort_wires=True):
+ if element.HitTest(pos, connectors) or element.TestHandle(event) != (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 self.IsBlock(element):
+ return element
+ group.SelectElement(element)
+ return group
+ return None
+
+ def SearchElements(self, bbox):
+ elements = []
+ for element in self.Blocks.values() + self.Comments.values():
+ if element.IsInSelection(bbox):
+ elements.append(element)
+ return elements
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerLeftDown(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerLeftDown(self, event)
+ elif self.Mode == MODE_SELECTION:
+ element = self.FindElement(event)
+ if self.SelectedElement:
+ if not isinstance(self.SelectedElement, Graphic_Group):
+ if self.SelectedElement != element:
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(None)
+ else:
+ self.SelectedElement.SetSelected(False)
+ else:
+ self.SelectedElement = None
+ elif element and isinstance(element, Graphic_Group):
+ if self.SelectedElement.GetElements() != element.GetElements():
+ for elt in self.SelectedElement.GetElements():
+ if self.IsWire(elt):
+ elt.SetSelectedSegment(None)
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ else:
+ for elt in self.SelectedElement.GetElements():
+ if self.IsWire(elt):
+ elt.SetSelectedSegment(None)
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ if element:
+ self.SelectedElement = element
+ self.SelectedElement.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerLeftUp(self, event)
+ elif self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ if len(elements) > 0:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(elements)
+ self.SelectedElement.SetSelected(True)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ dc = self.GetLogicalDC()
+ if not isinstance(self.SelectedElement, Graphic_Group):
+ if self.IsWire(self.SelectedElement):
+ result = self.SelectedElement.TestSegment(event.GetLogicalPosition(dc), True)
+ if result and result[1] in [EAST, WEST]:
+ self.SelectedElement.SetSelectedSegment(result[0])
+ else:
+ self.SelectedElement.OnLeftUp(event, dc, self.Scaling)
+ else:
+ for element in self.SelectedElement.GetElements():
+ if self.IsWire(element):
+ result = element.TestSegment(event.GetLogicalPosition(dc), True)
+ if result and result[1] in [EAST, WEST]:
+ element.SetSelectedSegment(result[0])
+ else:
+ element.OnLeftUp(event, dc, self.Scaling)
+ self.SelectedElement.Refresh()
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerRightUp(self, event)
+ else:
+ element = self.FindElement(event)
+ if element:
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = element
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.OnRightUp(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnChar(self, event)
+ else:
+ xpos, ypos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
+ xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
+ ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
+ keycode = event.GetKeyCode()
+ if keycode == wx.WXK_DELETE and self.SelectedElement:
+ if self.IsBlock(self.SelectedElement):
+ self.SelectedElement.Delete()
+ elif self.IsWire(self.SelectedElement):
+ self.DeleteWire(self.SelectedElement)
+ elif isinstance(self.SelectedElement, Graphic_Group):
+ all_wires = True
+ for element in self.SelectedElement.GetElements():
+ all_wires &= self.IsWire(element)
+ if all_wires:
+ self.DeleteWire(self.SelectedElement)
+ else:
+ self.SelectedElement.Delete()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ elif keycode == wx.WXK_LEFT:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(0, ypos)
+ elif event.ControlDown():
+ self.Scroll(max(0, xpos - 1), ypos)
+ elif keycode == wx.WXK_RIGHT:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xmax, ypos)
+ elif event.ControlDown():
+ self.Scroll(min(xpos + 1, xmax), ypos)
+ elif keycode == wx.WXK_UP:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xpos, 0)
+ elif event.ControlDown():
+ self.Scroll(xpos, max(0, ypos - 1))
+ elif keycode == wx.WXK_DOWN:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xpos, ymax)
+ elif event.ControlDown():
+ self.Scroll(xpos, min(ypos + 1, ymax))
+ else:
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Model adding functions from Drop Target
+#-------------------------------------------------------------------------------
+
+ def AddVariableBlock(self, x, y, scaling, var_class, var_name, var_type):
+ if var_type == "BOOL":
+ id = self.GetNewId()
+ if var_class == INPUT:
+ contact = LD_Contact(self, CONTACT_NORMAL, var_name, id)
+ width, height = contact.GetMinSize()
+ if scaling is not None:
+ x = round(float(x) / float(scaling[0])) * scaling[0]
+ y = round(float(y) / float(scaling[1])) * scaling[1]
+ width = round(float(width) / float(scaling[0]) + 0.5) * scaling[0]
+ height = round(float(height) / float(scaling[1]) + 0.5) * scaling[1]
+ contact.SetPosition(x, y)
+ contact.SetSize(width, height)
+ self.AddBlock(contact)
+ self.Controler.AddEditedElementContact(self.GetTagName(), id)
+ self.RefreshContactModel(contact)
+ else:
+ coil = LD_Coil(self, COIL_NORMAL, var_name, id)
+ width, height = coil.GetMinSize()
+ if scaling is not None:
+ x = round(float(x) / float(scaling[0])) * scaling[0]
+ y = round(float(y) / float(scaling[1])) * scaling[1]
+ width = round(float(width) / float(scaling[0]) + 0.5) * scaling[0]
+ height = round(float(height) / float(scaling[1]) + 0.5) * scaling[1]
+ coil.SetPosition(x, y)
+ coil.SetSize(width, height)
+ self.AddBlock(coil)
+ self.Controler.AddEditedElementCoil(self.GetTagName(), id)
+ self.RefreshCoilModel(coil)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+ else:
+ Viewer.AddVariableBlock(self, x, y, scaling, var_class, var_name, var_type)
+
+#-------------------------------------------------------------------------------
+# Adding element functions
+#-------------------------------------------------------------------------------
+
+ def AddLadderRung(self):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Class"] != "Input" and var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetEditedElementName(self.TagName))
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":COIL_NORMAL})
+ if dialog.ShowModal() == wx.ID_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, _("Comment"), id)
+ comment.SetPosition(startx, starty)
+ comment.SetSize(LD_COMMENT_DEFAULTSIZE[0], LD_COMMENT_DEFAULTSIZE[1])
+ self.AddComment(comment)
+ self.RungComments.append(comment)
+ self.Controler.AddEditedElementComment(self.TagName, 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)
+ leftpowerrail_connectors = leftpowerrail.GetConnectors()
+ self.AddBlock(leftpowerrail)
+ rung.SelectElement(leftpowerrail)
+ self.Controler.AddEditedElementPowerRail(self.TagName, 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)
+ coil_connectors = coil.GetConnectors()
+ self.AddBlock(coil)
+ rung.SelectElement(coil)
+ self.Controler.AddEditedElementCoil(self.TagName, id)
+
+ # Create Wire between LeftPowerRail and Coil
+ wire = Wire(self)
+ start_connector = coil_connectors["inputs"][0]
+ end_connector = leftpowerrail_connectors["outputs"][0]
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ self.AddWire(wire)
+ rung.SelectElement(wire)
+
+ # Create RightPowerRail
+ id = self.GetNewId()
+ rightpowerrail = LD_PowerRail(self, RIGHTRAIL, id)
+ rightpowerrail.SetPosition(startx, starty)
+ rightpowerrail_connectors = rightpowerrail.GetConnectors()
+ self.AddBlock(rightpowerrail)
+ rung.SelectElement(rightpowerrail)
+ self.Controler.AddEditedElementPowerRail(self.TagName, id, RIGHTRAIL)
+
+ # Create Wire between LeftPowerRail and Coil
+ wire = Wire(self)
+ start_connector = rightpowerrail_connectors["inputs"][0]
+ end_connector = coil_connectors["outputs"][0]
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, start_connector)
+ wire.ConnectEndPoint(None, end_connector)
+ self.AddWire(wire)
+ rung.SelectElement(wire)
+ self.RefreshPosition(coil)
+ self.Rungs.append(rung)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+
+ def AddLadderContact(self):
+ wires = []
+ if self.IsWire(self.SelectedElement):
+ left_element = self.SelectedElement.EndConnected
+ if not isinstance(left_element.GetParentBlock(), LD_Coil):
+ wires.append(self.SelectedElement)
+ elif self.SelectedElement and isinstance(self.SelectedElement,Graphic_Group):
+ if False not in [self.IsWire(element) for element in self.SelectedElement.GetElements()]:
+ for element in self.SelectedElement.GetElements():
+ wires.append(element)
+ if len(wires) > 0:
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Class"] != "Output" and var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":CONTACT_NORMAL})
+ if dialog.ShowModal() == wx.ID_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.AddBlock(contact)
+ self.Controler.AddEditedElementContact(self.TagName, 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.RemoveWire(wire)
+ wires = []
+ right_wires = []
+ for i, left_element in enumerate(left_elements):
+ wire = Wire(self)
+ wires.append(wire)
+ connectors["inputs"][0].Connect((wire, 0), False)
+ left_element.InsertConnect(left_index[i], (wire, -1), False)
+ wire.ConnectStartPoint(None, connectors["inputs"][0])
+ 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["outputs"][0].Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, right_element)
+ wire.ConnectEndPoint(None, connectors["outputs"][0])
+ right_wires.reverse()
+ for wire in wires:
+ self.AddWire(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.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+ else:
+ message = wx.MessageDialog(self, _("You must select the wire where a contact should be added!"), _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+ def AddLadderBranch(self):
+ blocks = []
+ if self.IsBlock(self.SelectedElement):
+ blocks = [self.SelectedElement]
+ elif isinstance(self.SelectedElement, Graphic_Group):
+ elements = self.SelectedElement.GetElements()
+ for element in elements:
+ 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 = {"lefts":[],"rights":[]}
+ block_infos.update(connectors)
+ 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(right_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:
+ wires = []
+ if left_powerrail:
+ powerrail = left_elements[0].GetParentBlock()
+ index = 0
+ for left_element in left_elements:
+ index = max(index, powerrail.GetConnectorIndex(left_element))
+ powerrail.InsertConnector(index + 1)
+ powerrail.RefreshModel()
+ connectors = powerrail.GetConnectors()
+ right_elements.reverse()
+ for i, right_element in enumerate(right_elements):
+ new_wire = Wire(self)
+ wires.append(new_wire)
+ right_element.InsertConnect(right_index[i] + 1, (new_wire, 0), False)
+ connectors["outputs"][index + 1].Connect((new_wire, -1), False)
+ new_wire.ConnectStartPoint(None, right_element)
+ new_wire.ConnectEndPoint(None, connectors["outputs"][index + 1])
+ right_elements.reverse()
+ elif right_powerrail:
+ dialog = LDElementDialog(self.ParentWindow, self.Controleur, "coil")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Class"] != "Input" and var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetEditedElementName(self.TagName))
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":COIL_NORMAL})
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ powerrail = right_elements[0].GetParentBlock()
+ index = 0
+ for right_element in right_elements:
+ index = max(index, powerrail.GetConnectorIndex(right_element))
+ powerrail.InsertConnector(index + 1)
+ powerrail.RefreshModel()
+ connectors = powerrail.GetConnectors()
+
+ # Create Coil
+ id = self.GetNewId()
+ coil = LD_Coil(self, values["type"], values["name"], id)
+ pos = blocks[0].GetPosition()
+ coil.SetPosition(pos[0], pos[1] + LD_LINE_SIZE)
+ self.AddBlock(coil)
+ rung.SelectElement(coil)
+ self.Controler.AddEditedElementCoil(self.TagName, id)
+ coil_connectors = coil.GetConnectors()
+
+ # Create Wire between LeftPowerRail and Coil
+ wire = Wire(self)
+ connectors["inputs"][index + 1].Connect((wire, 0), False)
+ coil_connectors["outputs"][0].Connect((wire, -1), False)
+ wire.ConnectStartPoint(None, connectors["inputs"][index + 1])
+ wire.ConnectEndPoint(None, coil_connectors["outputs"][0])
+ self.AddWire(wire)
+ rung.SelectElement(wire)
+ left_elements.reverse()
+
+ for i, left_element in enumerate(left_elements):
+ # Create Wire between LeftPowerRail and Coil
+ new_wire = Wire(self)
+ wires.append(new_wire)
+ coil_connectors["inputs"][0].Connect((new_wire, 0), False)
+ left_element.InsertConnect(left_index[i] + 1, (new_wire, -1), False)
+ new_wire.ConnectStartPoint(None, coil_connectors["inputs"][0])
+ new_wire.ConnectEndPoint(None, left_element)
+
+ self.RefreshPosition(coil)
+ else:
+ left_elements.reverse()
+ right_elements.reverse()
+ 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.AddWire(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.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+ else:
+ message = wx.MessageDialog(self, _("The group of block must be coherent!"), _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ else:
+ message = wx.MessageDialog(self, _("You must select the block or group of blocks around which a branch should be added!"), _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+ def AddLadderBlock(self):
+ message = wx.MessageDialog(self, _("This option isn't available yet!"), _("Warning"), wx.OK|wx.ICON_EXCLAMATION)
+ message.ShowModal()
+ message.Destroy()
+
+#-------------------------------------------------------------------------------
+# Delete element functions
+#-------------------------------------------------------------------------------
+
+ def DeleteContact(self, contact):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteContact(self, contact)
+ else:
+ rungindex = self.FindRung(contact)
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ connectors = contact.GetConnectors()
+ input_wires = [wire for wire, handle in connectors["inputs"][0].GetWires()]
+ output_wires = [wire for wire, handle in connectors["outputs"][0].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.RemoveWire(wire)
+ for wire in output_wires:
+ wire.Clean()
+ rung.SelectElement(wire)
+ self.RemoveWire(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.RemoveWire(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.AddWire(wire)
+ rung.SelectElement(wire)
+ right_elements.reverse()
+ for right_element, right_index in right_elements:
+ self.RefreshPosition(right_element.GetParentBlock())
+ self.RemoveBlock(contact)
+ self.Controler.RemoveEditedElementInstance(self.TagName, contact.GetId())
+ rung.RefreshBoundingBox()
+ new_bbox = rung.GetBoundingBox()
+ self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+ self.SelectedElement = None
+
+ def RecursiveDeletion(self, element, rung):
+ connectors = element.GetConnectors()
+ input_wires = [wire for wire, handle in connectors["inputs"][0].GetWires()]
+ left_elements = [wire.EndConnected for wire in input_wires]
+ rung.SelectElement(element)
+ element.Clean()
+ for wire in input_wires:
+ wire.Clean()
+ self.RemoveWire(wire)
+ rung.SelectElement(wire)
+ self.RemoveBlock(element)
+ self.Controler.RemoveEditedElementInstance(self.TagName, element.GetId())
+ for left_element in left_elements:
+ block = left_element.GetParentBlock()
+ if len(left_element.GetWires()) == 0:
+ self.RecursiveDeletion(block, rung)
+ else:
+ self.RefreshPosition(block)
+
+ def DeleteCoil(self, coil):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteContact(self, coil)
+ else:
+ rungindex = self.FindRung(coil)
+ rung = self.Rungs[rungindex]
+ old_bbox = rung.GetBoundingBox()
+ nbcoils = 0
+ for element in rung.GetElements():
+ if isinstance(element, LD_Coil):
+ nbcoils += 1
+ if nbcoils > 1:
+ connectors = coil.GetConnectors()
+ output_wires = [wire for wire, handle in connectors["outputs"][0].GetWires()]
+ right_elements = [wire.StartConnected for wire in output_wires]
+ for wire in output_wires:
+ wire.Clean()
+ self.Wires.remove(wire)
+ self.Elements.remove(wire)
+ rung.SelectElement(wire)
+ for right_element in right_elements:
+ right_block = right_element.GetParentBlock()
+ if isinstance(right_block, LD_PowerRail):
+ if len(right_element.GetWires()) == 0:
+ index = right_block.GetConnectorIndex(right_element)
+ right_block.DeleteConnector(index)
+ powerrail_connectors = right_block.GetConnectors()
+ for connector in powerrail_connectors["inputs"]:
+ for wire, handle in connector.GetWires():
+ block = wire.EndConnected.GetParentBlock()
+ endpoint = wire.EndConnected.GetPosition(False)
+ startpoint = connector.GetPosition(False)
+ block.Move(0, startpoint.y - endpoint.y)
+ self.RefreshPosition(block)
+ self.RecursiveDeletion(coil, rung)
+ else:
+ for element in rung.GetElements():
+ if self.IsWire(element):
+ element.Clean()
+ self.RemoveWire(element)
+ for element in rung.GetElements():
+ if self.IsBlock(element):
+ self.Controler.RemoveEditedElementInstance(self.TagName, element.GetId())
+ self.RemoveBlock(element)
+ self.Controler.RemoveEditedElementInstance(self.TagName, self.Comments[rungindex].GetId())
+ self.RemoveComment(self.RungComments[rungindex])
+ self.RungComments.pop(rungindex)
+ self.Rungs.pop(rungindex)
+ if rungindex < len(self.Rungs):
+ next_bbox = self.Rungs[rungindex].GetBoundingBox()
+ self.RefreshRungs(old_bbox.y - next_bbox.y, rungindex)
+ self.SelectedElement = None
+
+ def DeleteWire(self, wire):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteWire(self, wire)
+ else:
+ wires = []
+ left_elements = []
+ right_elements = []
+ if self.IsWire(wire):
+ wires = [wire]
+ elif isinstance(wire, Graphic_Group):
+ for element in wire.GetElements():
+ if self.IsWire(element):
+ 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.RemoveWire(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()
+ for connector in connectors["outputs"]:
+ 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, recursive=True):
+ # If element is LeftPowerRail, no need to update position
+ if isinstance(element, LD_PowerRail) and element.GetType() == LEFTRAIL:
+ element.RefreshModel()
+ return
+
+ # Extract max position of the elements connected to input
+ connectors = element.GetConnectors()
+ position = element.GetPosition()
+ maxx = 0
+ onlyone = []
+ for connector in connectors["inputs"]:
+ 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()
+ maxx = max(maxx, pos[0] + size[0])
+
+ # Refresh position of element
+ if isinstance(element, LD_Coil):
+ interval = LD_WIRECOIL_SIZE
+ else:
+ interval = LD_WIRE_SIZE
+ if False in onlyone:
+ interval += LD_WIRE_SIZE
+ movex = maxx + interval - position[0]
+ element.Move(movex, 0)
+ position = element.GetPosition()
+
+ # Extract blocks connected to inputs
+ blocks = []
+ for i, connector in enumerate(connectors["inputs"]):
+ for j, (wire, handle) in enumerate(connector.GetWires()):
+ blocks.append(wire.EndConnected.GetParentBlock())
+
+ for i, connector in enumerate(connectors["inputs"]):
+ startpoint = connector.GetPosition(False)
+ previous_blocks = []
+ block_list = []
+ start_offset = 0
+ if not onlyone[i]:
+ middlepoint = maxx + LD_WIRE_SIZE
+ for j, (wire, handle) in enumerate(connector.GetWires()):
+ block = wire.EndConnected.GetParentBlock()
+ if isinstance(element, LD_PowerRail):
+ pos = block.GetPosition()
+ size = leftblock.GetSize()
+ movex = position[0] - LD_WIRE_SIZE - size[0] - pos[0]
+ block.Move(movex, 0)
+ 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, blocks)
+ if block in block_list:
+ wires = wire.EndConnected.GetWires()
+ endmiddlepoint = wires[0][0].StartConnected.GetPosition(False)[0] - LD_WIRE_SIZE
+ points = [startpoint, wx.Point(middlepoint, startpoint.y),
+ wx.Point(middlepoint, startpoint.y + offset),
+ wx.Point(endmiddlepoint, startpoint.y + offset),
+ wx.Point(endmiddlepoint, endpoint.y), endpoint]
+ else:
+ if startpoint.y + offset != endpoint.y:
+ if isinstance(element, LD_PowerRail):
+ element.MoveConnector(connector, startpoint.y - endpoint.y)
+ elif isinstance(block, LD_PowerRail):
+ block.MoveConnector(wire.EndConnected, startpoint.y - endpoint.y)
+ else:
+ block.Move(0, startpoint.y + offset - endpoint.y)
+ self.RefreshPosition(block, False)
+ endpoint = wire.EndConnected.GetPosition(False)
+ if not onlyone[i]:
+ points = [startpoint, wx.Point(middlepoint, startpoint.y),
+ wx.Point(middlepoint, endpoint.y), endpoint]
+ else:
+ points = [startpoint, endpoint]
+ wire.SetPoints(points, False)
+ previous_blocks.append(block)
+ blocks.remove(block)
+ ExtractNextBlocks(block, block_list)
+
+ element.RefreshModel(False)
+ if recursive:
+ for connector in connectors["outputs"]:
+ 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.RungComments[i].Move(0, movey)
+ self.RungComments[i].RefreshModel()
+ self.Rungs[i].Move(0, movey)
+ for element in self.Rungs[i].GetElements():
+ if self.IsBlock(element):
+ self.RefreshPosition(element)
+
+#-------------------------------------------------------------------------------
+# Edit element content functions
+#-------------------------------------------------------------------------------
+
+ def EditPowerRailContent(self, powerrail):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.EditPowerRailContent(self, powerrail)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/ProjectNodeEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,83 @@
+
+import wx
+
+from controls import ProjectPropertiesPanel
+from EditorPanel import EditorPanel
+from ConfTreeNodeEditor import ConfTreeNodeEditor, WINDOW_COLOUR
+
+class ProjectNodeEditor(ConfTreeNodeEditor):
+
+ VARIABLE_PANEL_TYPE = "config"
+ ENABLE_REQUIRED = True
+
+ def _init_Editor(self, prnt):
+ self.Editor = wx.ScrolledWindow(prnt, -1, size=wx.Size(-1, -1),
+ style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER|wx.HSCROLL|wx.VSCROLL)
+ self.Editor.SetBackgroundColour(WINDOW_COLOUR)
+ self.Editor.Bind(wx.EVT_SIZE, self.OnWindowResize)
+ self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+ self.ParamsEditor = self.Editor
+
+ # Variable allowing disabling of Editor scroll when Popup shown
+ self.ScrollingEnabled = True
+
+ self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ self.ParamsEditorSizer.AddGrowableCol(0)
+ self.ParamsEditorSizer.AddGrowableRow(1)
+
+ self.Editor.SetSizer(self.ParamsEditorSizer)
+
+
+ buttons_sizer = self.GenerateMethodButtonSizer()
+ self.ParamsEditorSizer.AddSizer(buttons_sizer, 0, border=5,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
+
+ projectproperties_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.ParamsEditorSizer.AddSizer(projectproperties_sizer, 0, border=5,
+ flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
+
+ if self.SHOW_PARAMS:
+ self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
+ projectproperties_sizer.AddSizer(self.ConfNodeParamsSizer, 0, border=5,
+ flag=wx.RIGHT)
+ else:
+ self.ConfNodeParamsSizer = None
+
+ self.ProjectProperties = ProjectPropertiesPanel(self.Editor, self.Controler, self.ParentWindow, self.ENABLE_REQUIRED)
+ projectproperties_sizer.AddWindow(self.ProjectProperties, 0, border=0, flag=0)
+
+ def __init__(self, parent, controler, window):
+ configuration = controler.GetProjectMainConfigurationName()
+ if configuration is not None:
+ tagname = controler.ComputeConfigurationName(configuration)
+ else:
+ tagname = ""
+
+ ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
+
+ def GetTagName(self):
+ return self.Controler.CTNName()
+
+ def GetTitle(self):
+ fullname = self.Controler.CTNName()
+ if self.Controler.CTNTestModified():
+ return "~%s~" % fullname
+ return fullname
+
+ def RefreshView(self, variablepanel=True):
+ EditorPanel.RefreshView(self, variablepanel)
+ if self.ConfNodeParamsSizer is not None:
+ self.RefreshConfNodeParamsSizer()
+ self.ProjectProperties.RefreshView()
+
+ def GetBufferState(self):
+ return self.Controler.GetBufferState()
+
+ def Undo(self):
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/ResourceEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,465 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+import wx.lib.buttons
+import wx.grid
+
+from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD
+from controls import CustomGrid, CustomTable, DurationCellEditor
+from EditorPanel import EditorPanel
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+# Configuration Editor class
+#-------------------------------------------------------------------------------
+
+[ID_CONFIGURATIONEDITOR,
+] = [wx.NewId() for _init_ctrls in range(1)]
+
+class ConfigurationEditor(EditorPanel):
+
+ ID = ID_CONFIGURATIONEDITOR
+ VARIABLE_PANEL_TYPE = "config"
+
+ def GetBufferState(self):
+ return self.Controler.GetBufferState()
+
+ def Undo(self):
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def HasNoModel(self):
+ return self.Controler.GetEditedElement(self.TagName) is None
+
+
+#-------------------------------------------------------------------------------
+# Resource Editor class
+#-------------------------------------------------------------------------------
+
+def GetTasksTableColnames():
+ _ = lambda x : x
+ return [_("Name"), _("Triggering"), _("Single"), _("Interval"), _("Priority")]
+
+def GetTaskTriggeringOptions():
+ _ = lambda x : x
+ return [_("Interrupt"), _("Cyclic")]
+TASKTRIGGERINGOPTIONS_DICT = dict([(_(option), option) for option in GetTaskTriggeringOptions()])
+
+def GetInstancesTableColnames():
+ _ = lambda x : x
+ return [_("Name"), _("Type"), _("Task")]
+
+class ResourceTable(CustomTable):
+
+ """
+ A custom wx.grid.Grid Table using user supplied data
+ """
+ def __init__(self, parent, data, colnames):
+ # The base class must be initialized *first*
+ CustomTable.__init__(self, parent, data, colnames)
+ self.ColAlignements = []
+ self.ColSizes = []
+
+ 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 GetValue(self, row, col):
+ if row < self.GetNumberRows():
+ colname = self.GetColLabelValue(col, False)
+ value = str(self.data[row].get(colname, ""))
+ if colname == "Triggering":
+ return _(value)
+ return value
+
+ def SetValue(self, row, col, value):
+ if col < len(self.colnames):
+ colname = self.GetColLabelValue(col, False)
+ if colname == "Triggering":
+ value = TASKTRIGGERINGOPTIONS_DICT[value]
+ self.data[row][colname] = value
+
+ def _updateColAttrs(self, grid):
+ """
+ wx.grid.Grid -> 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 = wx.grid.GridCellAttr()
+ attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
+ grid.SetColAttr(col, attr)
+ grid.SetColSize(col, self.ColSizes[col])
+
+ for row in range(self.GetNumberRows()):
+ row_highlights = self.Highlights.get(row, {})
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col, False)
+ grid.SetReadOnly(row, col, False)
+ if colname == "Name":
+ editor = wx.grid.GridCellTextEditor()
+ renderer = wx.grid.GridCellStringRenderer()
+ elif colname == "Interval":
+ editor = DurationCellEditor(self)
+ renderer = wx.grid.GridCellStringRenderer()
+ if self.GetValueByName(row, "Triggering") != "Cyclic":
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Single":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.VariableList)
+ if self.GetValueByName(row, "Triggering") != "Interrupt":
+ grid.SetReadOnly(row, col, True)
+ elif colname == "Triggering":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(",".join([""] + map(_, GetTaskTriggeringOptions())))
+ elif colname == "Type":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TypeList)
+ elif colname == "Priority":
+ editor = wx.grid.GridCellNumberEditor()
+ editor.SetParameters("0,65535")
+ elif colname == "Task":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters(self.Parent.TaskList)
+
+ grid.SetCellEditor(row, col, editor)
+ grid.SetCellRenderer(row, col, renderer)
+
+ highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
+ grid.SetCellBackgroundColour(row, col, highlight_colours[0])
+ grid.SetCellTextColour(row, col, highlight_colours[1])
+ self.ResizeRow(grid, row)
+
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def AddHighlight(self, infos, highlight_type):
+ row_highlights = self.Highlights.setdefault(infos[0], {})
+ col_highlights = row_highlights.setdefault(infos[1], [])
+ col_highlights.append(highlight_type)
+
+ def ClearHighlights(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ for row, row_highlights in self.Highlights.iteritems():
+ row_items = row_highlights.items()
+ for col, col_highlights in row_items:
+ if highlight_type in col_highlights:
+ col_highlights.remove(highlight_type)
+ if len(col_highlights) == 0:
+ row_highlights.pop(col)
+
+
+
+class ResourceEditor(EditorPanel):
+
+ VARIABLE_PANEL_TYPE = "resource"
+
+ def _init_Editor(self, parent):
+ self.Editor = wx.Panel(parent, style=wx.SUNKEN_BORDER|wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+ main_sizer.AddGrowableRow(1)
+
+ tasks_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ tasks_sizer.AddGrowableCol(0)
+ tasks_sizer.AddGrowableRow(1)
+ main_sizer.AddSizer(tasks_sizer, border=5,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ tasks_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+ tasks_buttons_sizer.AddGrowableCol(0)
+ tasks_buttons_sizer.AddGrowableRow(0)
+ tasks_sizer.AddSizer(tasks_buttons_sizer, flag=wx.GROW)
+
+ tasks_label = wx.StaticText(self.Editor, label=_(u'Tasks:'))
+ tasks_buttons_sizer.AddWindow(tasks_label, flag=wx.ALIGN_BOTTOM)
+
+ for name, bitmap, help in [
+ ("AddTaskButton", "add_element", _("Add task")),
+ ("DeleteTaskButton", "remove_element", _("Remove task")),
+ ("UpTaskButton", "up", _("Move task up")),
+ ("DownTaskButton", "down", _("Move task down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap(bitmap), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ tasks_buttons_sizer.AddWindow(button)
+
+ self.TasksGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
+ self.TasksGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnTasksGridCellChange)
+ tasks_sizer.AddWindow(self.TasksGrid, flag=wx.GROW)
+
+ instances_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ instances_sizer.AddGrowableCol(0)
+ instances_sizer.AddGrowableRow(1)
+ main_sizer.AddSizer(instances_sizer, border=5,
+ flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ instances_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
+ instances_buttons_sizer.AddGrowableCol(0)
+ instances_buttons_sizer.AddGrowableRow(0)
+ instances_sizer.AddSizer(instances_buttons_sizer, flag=wx.GROW)
+
+ instances_label = wx.StaticText(self.Editor, label=_(u'Instances:'))
+ instances_buttons_sizer.AddWindow(instances_label, flag=wx.ALIGN_BOTTOM)
+
+ for name, bitmap, help in [
+ ("AddInstanceButton", "add_element", _("Add instance")),
+ ("DeleteInstanceButton", "remove_element", _("Remove instance")),
+ ("UpInstanceButton", "up", _("Move instance up")),
+ ("DownInstanceButton", "down", _("Move instance down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self.Editor,
+ bitmap=GetBitmap(bitmap), size=wx.Size(28, 28), style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ instances_buttons_sizer.AddWindow(button)
+
+ self.InstancesGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
+ self.InstancesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+ self.OnInstancesGridCellChange)
+ instances_sizer.AddWindow(self.InstancesGrid, flag=wx.GROW)
+
+ self.Editor.SetSizer(main_sizer)
+
+ def __init__(self, parent, tagname, window, controler):
+ EditorPanel.__init__(self, parent, tagname, window, controler)
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+
+ self.TasksDefaultValue = {"Name" : "", "Triggering" : "", "Single" : "", "Interval" : "", "Priority" : 0}
+ self.TasksTable = ResourceTable(self, [], GetTasksTableColnames())
+ self.TasksTable.SetColAlignements([wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT])
+ self.TasksTable.SetColSizes([200, 100, 100, 150, 100])
+ self.TasksGrid.SetTable(self.TasksTable)
+ self.TasksGrid.SetButtons({"Add": self.AddTaskButton,
+ "Delete": self.DeleteTaskButton,
+ "Up": self.UpTaskButton,
+ "Down": self.DownTaskButton})
+
+ def _AddTask(new_row):
+ self.TasksTable.InsertRow(new_row, self.TasksDefaultValue.copy())
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.TasksGrid, "_AddRow", _AddTask)
+
+ def _DeleteTask(row):
+ self.TasksTable.RemoveRow(row)
+ self.RefreshModel()
+ self.RefreshView()
+ setattr(self.TasksGrid, "_DeleteRow", _DeleteTask)
+
+ def _MoveTask(row, move):
+ new_row = self.TasksTable.MoveRow(row, move)
+ if new_row != row:
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.TasksGrid, "_MoveRow", _MoveTask)
+
+ self.TasksGrid.SetRowLabelSize(0)
+ self.TasksTable.ResetView(self.TasksGrid)
+ self.TasksGrid.RefreshButtons()
+
+ self.InstancesDefaultValue = {"Name" : "", "Type" : "", "Task" : ""}
+ self.InstancesTable = ResourceTable(self, [], GetInstancesTableColnames())
+ self.InstancesTable.SetColAlignements([wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT])
+ self.InstancesTable.SetColSizes([200, 150, 150])
+ self.InstancesGrid.SetTable(self.InstancesTable)
+ self.InstancesGrid.SetButtons({"Add": self.AddInstanceButton,
+ "Delete": self.DeleteInstanceButton,
+ "Up": self.UpInstanceButton,
+ "Down": self.DownInstanceButton})
+
+ def _AddInstance(new_row):
+ self.InstancesTable.InsertRow(new_row, self.InstancesDefaultValue.copy())
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.InstancesGrid, "_AddRow", _AddInstance)
+
+ def _DeleteInstance(row):
+ self.InstancesTable.RemoveRow(row)
+ self.RefreshModel()
+ self.RefreshView()
+ setattr(self.InstancesGrid, "_DeleteRow", _DeleteInstance)
+
+ def _MoveInstance(row, move):
+ new_row = max(0, min(row + move, self.InstancesTable.GetNumberRows() - 1))
+ if new_row != row:
+ if self.InstancesTable.GetValueByName(row, "Task") != self.InstancesTable.GetValueByName(new_row, "Task"):
+ return row
+ self.InstancesTable.MoveRow(row, move)
+ self.RefreshModel()
+ self.RefreshView()
+ return new_row
+ setattr(self.InstancesGrid, "_MoveRow", _MoveInstance)
+
+ def _RefreshInstanceButtons():
+ if self:
+ rows = self.InstancesTable.GetNumberRows()
+ row = self.InstancesGrid.GetGridCursorRow()
+ self.DeleteInstanceButton.Enable(rows > 0)
+ self.UpInstanceButton.Enable(row > 0 and
+ self.InstancesTable.GetValueByName(row, "Task") == self.InstancesTable.GetValueByName(row - 1, "Task"))
+ self.DownInstanceButton.Enable(0 <= row < rows - 1 and
+ self.InstancesTable.GetValueByName(row, "Task") == self.InstancesTable.GetValueByName(row + 1, "Task"))
+ setattr(self.InstancesGrid, "RefreshButtons", _RefreshInstanceButtons)
+
+ self.InstancesGrid.SetRowLabelSize(0)
+ self.InstancesTable.ResetView(self.InstancesGrid)
+ self.InstancesGrid.RefreshButtons()
+
+ self.TasksGrid.SetFocus()
+
+ def __del__(self):
+ self.RefreshHighlightsTimer.Stop()
+
+ 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.GetEditedResourceVariables(self.TagName):
+ self.VariableList += ",%s"%variable
+
+ def RefreshModel(self):
+ self.Controler.SetEditedResourceInfos(self.TagName, self.TasksTable.GetData(), self.InstancesTable.GetData())
+ self.RefreshBuffer()
+
+ # Buffer the last model state
+ def RefreshBuffer(self):
+ self.Controler.BufferProject()
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+ def GetBufferState(self):
+ return self.Controler.GetBufferState()
+
+ def Undo(self):
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def HasNoModel(self):
+ return self.Controler.GetEditedElement(self.TagName) is None
+
+ def RefreshView(self, variablepanel=True):
+ EditorPanel.RefreshView(self, variablepanel)
+
+ tasks, instances = self.Controler.GetEditedResourceInfos(self.TagName)
+ self.TasksTable.SetData(tasks)
+ self.InstancesTable.SetData(instances)
+ self.RefreshTypeList()
+ self.RefreshTaskList()
+ self.RefreshVariableList()
+ self.TasksTable.ResetView(self.TasksGrid)
+ self.InstancesTable.ResetView(self.InstancesGrid)
+ self.TasksGrid.RefreshButtons()
+ self.InstancesGrid.RefreshButtons()
+
+ def OnTasksGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ if self.TasksTable.GetColLabelValue(col) == "Name":
+ tasklist = [name for name in self.TaskList.split(",") if name != ""]
+ for i in xrange(self.TasksTable.GetNumberRows()):
+ task = self.TasksTable.GetValueByName(i, "Name")
+ if task in tasklist:
+ tasklist.remove(task)
+ 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()
+ colname = self.TasksTable.GetColLabelValue(col, False)
+ if colname in ["Triggering", "Name"]:
+ wx.CallAfter(self.RefreshView, False)
+ event.Skip()
+
+ def OnInstancesGridCellChange(self, event):
+ self.RefreshModel()
+ self.ParentWindow.RefreshPouInstanceVariablesPanel()
+ self.InstancesGrid.RefreshButtons()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "task":
+ self.TasksTable.AddHighlight(infos[1:], highlight_type)
+ elif infos[0] == "instance":
+ self.InstancesTable.AddHighlight(infos[1:], highlight_type)
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ClearHighlights(self, highlight_type=None):
+ EditorPanel.ClearHighlights(self, highlight_type)
+
+ self.TasksTable.ClearHighlights(highlight_type)
+ self.InstancesTable.ClearHighlights(highlight_type)
+ self.TasksTable.ResetView(self.TasksGrid)
+ self.InstancesTable.ResetView(self.InstancesGrid)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/SFCViewer.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1016 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from types import *
+
+import wx
+
+from Viewer import *
+
+class SFC_Viewer(Viewer):
+
+ def __init__(self, parent, tagname, window, controler, debug = False, instancepath = ""):
+ Viewer.__init__(self, parent, tagname, window, controler, debug, instancepath)
+ self.CurrentLanguage = "SFC"
+
+ def ConnectConnectors(self, start, end):
+ startpoint = [start.GetPosition(False), start.GetDirection()]
+ endpoint = [end.GetPosition(False), end.GetDirection()]
+ wire = Wire(self, startpoint, endpoint)
+ self.AddWire(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", "", 0, 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([wx.Point(pos.x, pos.y + GetWireSize(previous)), wx.Point(pos.x, pos.y)])
+ self.AddBlock(transition)
+ self.Controler.AddEditedElementTransition(self.TagName, 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([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ transition.RefreshOutputModel(True)
+ 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.RemoveWire(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.RemoveWire(output_wire)
+ transition.Clean()
+ self.RemoveBlock(transition)
+ self.Controler.RemoveEditedElementInstance(self.TagName, 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([wx.Point(pos.x, pos.y + GetWireSize(previous)), wx.Point(pos.x, pos.y)])
+ self.AddBlock(step)
+ self.Controler.AddEditedElementStep(self.TagName, 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([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ step.RefreshOutputModel(True)
+ 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.RemoveWire(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.RemoveWire(output_wire)
+ else:
+ next = None
+ action = step.GetActionConnected()
+ if action:
+ self.DeleteActionBlock(action.GetParentBlock())
+ step.Clean()
+ self.RemoveBlock(step)
+ self.Controler.RemoveEditedElementInstance(self.TagName, 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.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerLeftDown(self, event)
+ elif self.Mode == MODE_SELECTION:
+ if event.ShiftDown() and not event.ControlDown() and self.SelectedElement is not None:
+ element = self.FindElement(event, True)
+ if element and not self.IsWire(element):
+ 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]
+ self.SelectedElement.SetSelected(True)
+ else:
+ element = self.FindElement(event)
+ if self.SelectedElement and self.SelectedElement != element:
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(None)
+ else:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ if element:
+ self.SelectedElement = element
+ self.SelectedElement.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ elif self.Mode == MODE_COMMENT:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerLeftUp(self, event)
+ elif self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ if len(elements) > 0:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(elements)
+ self.SelectedElement.SetSelected(True)
+ elif self.Mode == MODE_COMMENT:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ wx.CallAfter(self.AddComment, bbox)
+ elif self.Mode == MODE_INITIALSTEP:
+ wx.CallAfter(self.AddInitialStep, GetScaledEventPosition(event, self.GetLogicalDC(), self.Scaling))
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.RefreshModel()
+ self.SelectedElement.SetSelected(True)
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerRightUp(self, event)
+ else:
+ element = self.FindElement(event)
+ if element:
+ if self.SelectedElement and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = element
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.OnRightUp(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerLeftDClick(self, event)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.OnLeftDClick(event, self.GetLogicalDC(), self.Scaling)
+ self.Refresh(False)
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnViewerMotion(self, event)
+ else:
+ if self.rubberBand.IsShown():
+ self.rubberBand.OnMotion(event, self.GetLogicalDC(), self.Scaling)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement:
+ if not self.IsWire(self.SelectedElement) and not isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.OnMotion(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ elif self.Mode == MODE_WIRE and self.SelectedElement:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.Refresh()
+ self.UpdateScrollPos(event)
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ def OnChar(self, event):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.OnChar(self, event)
+ else:
+ xpos, ypos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
+ xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
+ ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
+ keycode = event.GetKeyCode()
+ if self.Scaling:
+ scaling = self.Scaling
+ else:
+ scaling = (8, 8)
+ if keycode == wx.WXK_DELETE and self.SelectedElement:
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ elif keycode == wx.WXK_LEFT:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(0, ypos)
+ elif event.ControlDown():
+ event.Skip()
+ elif self.SelectedElement:
+ self.SelectedElement.Move(-scaling[0], 0)
+ self.SelectedElement.RefreshModel()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(-scaling[0], 0)), False)
+ elif keycode == wx.WXK_RIGHT:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xmax, ypos)
+ elif event.ControlDown():
+ event.Skip()
+ elif self.SelectedElement:
+ self.SelectedElement.Move(scaling[0], 0)
+ self.SelectedElement.RefreshModel()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(scaling[0], 0)), False)
+ elif keycode == wx.WXK_UP:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xpos, 0)
+ elif event.ControlDown():
+ event.Skip()
+ elif self.SelectedElement:
+ self.SelectedElement.Move(0, -scaling[1])
+ self.SelectedElement.RefreshModel()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(0, -scaling[1])), False)
+ elif keycode == wx.WXK_DOWN:
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll(xpos, ymax)
+ elif event.ControlDown():
+ event.Skip()
+ elif self.SelectedElement:
+ self.SelectedElement.Move(0, scaling[1])
+ self.SelectedElement.RefreshModel()
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(0, scaling[1])), False)
+ else:
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Adding element functions
+#-------------------------------------------------------------------------------
+
+ def AddInitialStep(self, pos):
+ dialog = SFCStepNameDialog(self.ParentWindow, _("Please enter step name"), _("Add a new initial step"), "", wx.OK|wx.CANCEL)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetStepNames([block.GetName() for block in self.Blocks if isinstance(block, SFC_Step)])
+ if dialog.ShowModal() == wx.ID_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.AddBlock(step)
+ self.Controler.AddEditedElementStep(self.TagName, id)
+ self.RefreshStepModel(step)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+ def AddStep(self):
+ if self.SelectedElement in self.Wires or isinstance(self.SelectedElement, SFC_Step):
+ dialog = SFCStepNameDialog(self.ParentWindow, _("Add a new step"), _("Please enter step name"), "", wx.OK|wx.CANCEL)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetStepNames([block.GetName() for block in self.Blocks if isinstance(block, SFC_Step)])
+ if dialog.ShowModal() == wx.ID_OK:
+ name = dialog.GetValue()
+ if self.IsWire(self.SelectedElement):
+ self.SelectedElement.SetSelectedSegment(None)
+ previous = self.SelectedElement.EndConnected
+ next = self.SelectedElement.StartConnected
+ self.SelectedElement.Clean()
+ self.RemoveWire(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.RemoveWire(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.IsWire(self.SelectedElement):
+ self.SelectedElement = wire
+ self.SelectedElement.SetSelectedSegment(0)
+ else:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = step
+ self.SelectedElement.SetSelected(True)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+ def AddStepAction(self):
+ if isinstance(self.SelectedElement, SFC_Step):
+ connectors = self.SelectedElement.GetConnectors()
+ if not connectors["action"]:
+ dialog = ActionBlockDialog(self.ParentWindow)
+ dialog.SetQualifierList(self.Controler.GetQualifierTypes())
+ dialog.SetActionList(self.Controler.GetEditedElementActions(self.TagName, self.Debug))
+ dialog.SetVariableList(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ if dialog.ShowModal() == wx.ID_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([wx.Point(pos.x + SFC_WIRE_MIN_SIZE, pos.y), wx.Point(pos.x, pos.y)])
+ actionblock.SetActions(actions)
+ self.AddBlock(actionblock)
+ self.Controler.AddEditedElementActionBlock(self.TagName, id)
+ self.RefreshActionBlockModel(actionblock)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+ def AddDivergence(self):
+ if self.SelectedElement in self.Wires or isinstance(self.SelectedElement, Graphic_Group) or isinstance(self.SelectedElement, SFC_Step):
+ dialog = SFCDivergenceDialog(self.ParentWindow)
+ dialog.SetPreviewFont(self.GetFont())
+ if dialog.ShowModal() == wx.ID_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.RemoveWire(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.RemoveWire(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([wx.Point(pos.x, pos.y + wire_size), wx.Point(pos.x, pos.y)])
+ self.AddBlock(divergence)
+ self.Controler.AddEditedElementDivergence(self.TagName, 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([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(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.RemoveWire(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.RemoveWire(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([wx.Point(pos.x, pos.y + wire_size), wx.Point(pos.x, pos.y)])
+ self.AddBlock(divergence)
+ self.Controler.AddEditedElementDivergence(self.TagName, 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([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(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.RemoveWire(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([wx.Point(pos.x, pos.y + wire_size), wx.Point(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.AddBlock(divergence)
+ self.Controler.AddEditedElementDivergence(self.TagName, 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([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(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.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+ def AddDivergenceBranch(self, divergence):
+ if isinstance(divergence, SFC_Divergence):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ divergence.AddBranch()
+ self.RefreshDivergenceModel(divergence)
+ else:
+ 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.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+
+ def RemoveDivergenceBranch(self, divergence):
+ if isinstance(divergence, SFC_Divergence):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ divergence.RemoveHandledBranch()
+ self.RefreshDivergenceModel(divergence)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+
+ 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 = wx.SingleChoiceDialog(self.ParentWindow,
+ _("Add a new jump"), _("Please choose a target"),
+ choices, wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_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.AddBlock(jump)
+ self.Controler.AddEditedElementJump(self.TagName, id)
+ jump_connector = jump.GetConnector()
+ wire = self.ConnectConnectors(jump_connector, transition_connectors["output"])
+ transition.RefreshOutputPosition()
+ wire.SetPoints([wx.Point(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wx.Point(pos.x, pos.y)])
+ self.RefreshJumpModel(jump)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+ def EditStepContent(self, step):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.EditStepContent(self, step)
+ else:
+ dialog = SFCStepNameDialog(self.ParentWindow, _("Edit step name"), _("Please enter step name"), step.GetName(), wx.OK|wx.CANCEL)
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetStepNames([block.GetName() for block in self.Blocks if isinstance(block, SFC_Step) and block.GetName() != step.GetName()])
+ if dialog.ShowModal() == wx.ID_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.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.Refresh(False)
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Delete element functions
+#-------------------------------------------------------------------------------
+
+ def DeleteStep(self, step):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteStep(self, step)
+ else:
+ 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.RemoveWire(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([wx.Point(pos.x, pos.y + wire_size), wx.Point(pos.x, pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ previous_block.RefreshOutputModel(True)
+ 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):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteTransition(self, transition)
+ else:
+ 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.RemoveWire(wire)
+ wires = next.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ wire.Clean()
+ self.RemoveWire(wire)
+ transition.Clean()
+ self.RemoveBlock(transition)
+ self.Controler.RemoveEditedElementInstance(self.TagName, 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)
+
+ def DeleteDivergence(self, divergence):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteDivergence(self, divergence)
+ else:
+ 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.RemoveWire(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.RemoveWire(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.RemoveWire(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.RemoveBlock(divergence)
+ self.Controler.RemoveEditedElementInstance(self.TagName, divergence.GetId())
+ if next:
+ wire = self.ConnectConnectors(next, previous)
+ previous_block = previous.GetParentBlock()
+ previous_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([wx.Point(previous_pos.x, previous_pos.y + wire_size),
+ wx.Point(previous_pos.x, previous_pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ previous_block.RefreshOutputModel(True)
+ 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.RemoveWire(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.RemoveWire(wire)
+ divergence.Clean()
+ self.RemoveBlock(divergence)
+ self.Controler.RemoveEditedElementInstance(self.TagName, 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([wx.Point(previous_pos.x, previous_pos.y + wire_size),
+ wx.Point(previous_pos.x, previous_pos.y)])
+ if isinstance(next_block, SFC_Divergence):
+ next_block.RefreshPosition()
+ previous_block.RefreshOutputModel(True)
+
+ def DeleteJump(self, jump):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteJump(self, jump)
+ else:
+ 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.RemoveWire(wire)
+ jump.Clean()
+ self.RemoveBlock(jump)
+ self.Controler.RemoveEditedElementInstance(self.TagName, 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()
+
+ def DeleteActionBlock(self, actionblock):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteActionBlock(self, actionblock)
+ else:
+ connector = actionblock.GetConnector()
+ wires = connector.GetWires()
+ if len(wires) != 1:
+ return
+ wire = wires[0][0]
+ step = wire.EndConnected.GetParentBlock()
+ wire.Clean()
+ self.RemoveWire(wire)
+ actionblock.Clean()
+ self.RemoveBlock(actionblock)
+ self.Controler.RemoveEditedElementInstance(self.TagName, actionblock.GetId())
+ step.RemoveAction()
+ self.RefreshStepModel(step)
+ step.RefreshOutputPosition()
+ step.RefreshOutputModel(True)
+
+ def DeleteWire(self, wire):
+ if self.GetDrawingMode() == FREEDRAWING_MODE:
+ Viewer.DeleteWire(self, wire)
+
+#-------------------------------------------------------------------------------
+# 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.SetEditedElementBlockInfos(self.TagName, blockid, infos)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/TextViewer.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,922 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+from types import *
+
+import wx
+import wx.stc
+
+from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
+from plcopen.structures import ST_BLOCK_START_KEYWORDS, ST_BLOCK_END_KEYWORDS, IEC_BLOCK_START_KEYWORDS, IEC_BLOCK_END_KEYWORDS, LOCATIONDATATYPES
+from EditorPanel import EditorPanel
+
+#-------------------------------------------------------------------------------
+# 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))
+
+[STC_PLC_WORD, STC_PLC_COMMENT, STC_PLC_NUMBER, STC_PLC_STRING,
+ STC_PLC_VARIABLE, STC_PLC_PARAMETER, STC_PLC_FUNCTION, STC_PLC_JUMP,
+ STC_PLC_ERROR, STC_PLC_SEARCH_RESULT] = range(10)
+[SPACE, WORD, NUMBER, STRING, WSTRING, COMMENT, PRAGMA] = range(7)
+
+[ID_TEXTVIEWER, ID_TEXTVIEWERTEXTCTRL,
+] = [wx.NewId() for _init_ctrls in range(2)]
+
+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)
+EXTENSIBLE_PARAMETER = re.compile("IN[1-9][0-9]*$")
+
+HIGHLIGHT_TYPES = {
+ ERROR_HIGHLIGHT: STC_PLC_ERROR,
+ SEARCH_RESULT_HIGHLIGHT: STC_PLC_SEARCH_RESULT,
+}
+
+def GetCursorPos(old, new):
+ old_length = len(old)
+ new_length = len(new)
+ common_length = min(old_length, new_length)
+ i = 0
+ for i in xrange(common_length):
+ if old[i] != new[i]:
+ break
+ if old_length < new_length:
+ if common_length > 0 and old[i] != new[i]:
+ return i + new_length - old_length
+ else:
+ return i + new_length - old_length + 1
+ elif old_length > new_length or i < min(old_length, new_length) - 1:
+ if common_length > 0 and old[i] != new[i]:
+ return i
+ else:
+ return i + 1
+ else:
+ return None
+
+def LineStartswith(line, symbols):
+ return reduce(lambda x, y: x or y, map(lambda x: line.startswith(x), symbols), False)
+
+class TextViewer(EditorPanel):
+
+ ID = ID_TEXTVIEWER
+
+ if wx.VERSION < (2, 6, 0):
+ def Bind(self, event, function, id = None):
+ if id is not None:
+ event(self, id, function)
+ else:
+ event(self, function)
+
+ def _init_Editor(self, prnt):
+ self.Editor = wx.stc.StyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL,
+ parent=prnt, name="TextViewer", size=wx.Size(0, 0), style=0)
+ self.Editor.ParentWindow = self
+
+ self.Editor.CmdKeyAssign(ord('+'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN)
+ self.Editor.CmdKeyAssign(ord('-'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMOUT)
+
+ self.Editor.SetViewWhiteSpace(False)
+
+ self.Editor.SetLexer(wx.stc.STC_LEX_CONTAINER)
+
+ # Global default styles for all languages
+ self.Editor.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+ self.Editor.StyleClearAll() # Reset all to be like the default
+
+ self.Editor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,size:%(size)d" % faces)
+ self.Editor.SetSelBackground(1, "#E0E0E0")
+
+ # Highlighting styles
+ self.Editor.StyleSetSpec(STC_PLC_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_VARIABLE, "fore:#7F0000,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_PARAMETER, "fore:#7F007F,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_FUNCTION, "fore:#7F7F00,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_COMMENT, "fore:#7F7F7F,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_STRING, "fore:#007F00,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_JUMP, "fore:#FF7FFF,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_ERROR, "fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)
+ self.Editor.StyleSetSpec(STC_PLC_SEARCH_RESULT, "fore:#FFFFFF,back:#FFA500,size:%(size)d" % faces)
+
+ # Indicators styles
+ self.Editor.IndicatorSetStyle(0, wx.stc.STC_INDIC_SQUIGGLE)
+ if self.ParentWindow is not None and self.Controler is not None:
+ self.Editor.IndicatorSetForeground(0, wx.RED)
+ else:
+ self.Editor.IndicatorSetForeground(0, wx.WHITE)
+
+ # Line numbers in the margin
+ self.Editor.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
+ self.Editor.SetMarginWidth(1, 50)
+
+ # Folding
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
+ self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "#808080")
+
+ # Indentation size
+ self.Editor.SetTabWidth(2)
+ self.Editor.SetUseTabs(0)
+
+ self.Editor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|
+ wx.stc.STC_MOD_BEFOREDELETE|
+ wx.stc.STC_PERFORMED_USER)
+
+ self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, id=ID_TEXTVIEWERTEXTCTRL)
+ self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
+ self.Editor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+ if self.Controler is not None:
+ self.Editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
+ self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_TEXTVIEWERTEXTCTRL)
+ self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_TEXTVIEWERTEXTCTRL)
+
+ def __init__(self, parent, tagname, window, controler, debug = False, instancepath = ""):
+ if tagname != "" and controler is not None:
+ self.VARIABLE_PANEL_TYPE = controler.GetPouType(tagname.split("::")[1])
+
+ EditorPanel.__init__(self, parent, tagname, window, controler, debug)
+
+ self.Keywords = []
+ self.Variables = {}
+ self.Functions = {}
+ self.TypeNames = []
+ self.Jumps = []
+ self.EnumeratedValues = []
+ self.DisableEvents = True
+ self.TextSyntax = None
+ self.CurrentAction = None
+ self.Highlights = []
+ self.SearchParams = None
+ self.SearchResults = None
+ self.CurrentFindHighlight = None
+ self.InstancePath = instancepath
+ self.ContextStack = []
+ self.CallStack = []
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+
+ def __del__(self):
+ self.RefreshHighlightsTimer.Stop()
+
+ def GetTitle(self):
+ if self.Debug or self.TagName == "":
+ if len(self.InstancePath) > 15:
+ return "..." + self.InstancePath[-12:]
+ return self.InstancePath
+ return EditorPanel.GetTitle(self)
+
+ def GetInstancePath(self):
+ return self.InstancePath
+
+ def IsViewing(self, tagname):
+ if self.Debug or self.TagName == "":
+ return self.InstancePath == tagname
+ else:
+ return self.TagName == tagname
+
+ def GetText(self):
+ return self.Editor.GetText()
+
+ def SetText(self, text):
+ self.Editor.SetText(text)
+
+ def SelectAll(self):
+ self.Editor.SelectAll()
+
+ def Colourise(self, start, end):
+ self.Editor.Colourise(start, end)
+
+ def StartStyling(self, pos, mask):
+ self.Editor.StartStyling(pos, mask)
+
+ def SetStyling(self, length, style):
+ self.Editor.SetStyling(length, style)
+
+ def GetCurrentPos(self):
+ return self.Editor.GetCurrentPos()
+
+ def GetState(self):
+ return {"cursor_pos": self.Editor.GetCurrentPos()}
+
+ def SetState(self, state):
+ if self and state.has_key("cursor_pos"):
+ self.Editor.GotoPos(state.get("cursor_pos", 0))
+
+ def OnModification(self, event):
+ if not self.DisableEvents:
+ mod_type = event.GetModificationType()
+ if mod_type&wx.stc.STC_MOD_BEFOREINSERT:
+ if self.CurrentAction == None:
+ self.StartBuffering()
+ elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
+ self.Controler.EndBuffering()
+ self.StartBuffering()
+ self.CurrentAction = ("Add", event.GetPosition())
+ wx.CallAfter(self.RefreshModel)
+ elif mod_type&wx.stc.STC_MOD_BEFOREDELETE:
+ if self.CurrentAction == None:
+ self.StartBuffering()
+ elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
+ self.Controler.EndBuffering()
+ self.StartBuffering()
+ self.CurrentAction = ("Delete", event.GetPosition())
+ wx.CallAfter(self.RefreshModel)
+ event.Skip()
+
+ def OnDoDrop(self, event):
+ try:
+ values = eval(event.GetDragText())
+ except:
+ values = event.GetDragText()
+ if isinstance(values, tuple):
+ message = None
+ if values[1] in ["program", "debug"]:
+ event.SetDragText("")
+ elif values[1] in ["functionBlock", "function"]:
+ blocktype = values[0]
+ blockname = values[2]
+ if len(values) > 3:
+ blockinputs = values[3]
+ else:
+ blockinputs = None
+ if values[1] != "function":
+ if blockname == "":
+ dialog = wx.TextEntryDialog(self.ParentWindow, "Block name", "Please enter a block name", "", wx.OK|wx.CANCEL|wx.CENTRE)
+ if dialog.ShowModal() == wx.ID_OK:
+ blockname = dialog.GetValue()
+ else:
+ return
+ dialog.Destroy()
+ if blockname.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]:
+ message = _("\"%s\" pou already exists!")%blockname
+ elif blockname.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]:
+ message = _("\"%s\" element for this pou already exists!")%blockname
+ else:
+ self.Controler.AddEditedElementPouVar(self.TagName, values[0], blockname)
+ self.RefreshVariablePanel()
+ self.RefreshVariableTree()
+ blockinfo = self.Controler.GetBlockType(blocktype, blockinputs, self.Debug)
+ hint = ',\n '.join(
+ [ " " + fctdecl[0]+" := (*"+fctdecl[1]+"*)" for fctdecl in blockinfo["inputs"]] +
+ [ " " + fctdecl[0]+" => (*"+fctdecl[1]+"*)" for fctdecl in blockinfo["outputs"]])
+ if values[1] == "function":
+ event.SetDragText(blocktype+"(\n "+hint+")")
+ else:
+ event.SetDragText(blockname+"(\n "+hint+")")
+ elif values[1] == "location":
+ pou_name, pou_type = self.Controler.GetEditedElementType(self.TagName, self.Debug)
+ if len(values) > 2 and pou_type == "program":
+ var_name = values[3]
+ if var_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]:
+ message = _("\"%s\" pou already exists!")%var_name
+ elif var_name.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]:
+ message = _("\"%s\" element for this pou already exists!")%var_name
+ else:
+ location = values[0]
+ if not location.startswith("%"):
+ dialog = wx.SingleChoiceDialog(self.ParentWindow,
+ _("Select a variable class:"), _("Variable class"),
+ ["Input", "Output", "Memory"],
+ wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ selected = dialog.GetSelection()
+ else:
+ selected = None
+ dialog.Destroy()
+ if selected is None:
+ event.SetDragText("")
+ return
+ if selected == 0:
+ location = "%I" + location
+ elif selected == 1:
+ location = "%Q" + location
+ else:
+ location = "%M" + location
+ if values[2] is not None:
+ var_type = values[2]
+ else:
+ var_type = LOCATIONDATATYPES.get(location[2], ["BOOL"])[0]
+ self.Controler.AddEditedElementPouVar(self.TagName, var_type, var_name, location, values[4])
+ self.RefreshVariablePanel()
+ self.RefreshVariableTree()
+ event.SetDragText(var_name)
+ else:
+ event.SetDragText("")
+ elif values[1] == "Global":
+ var_name = values[0]
+ if var_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]:
+ message = _("\"%s\" pou already exists!")%var_name
+ else:
+ if not var_name.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]:
+ self.Controler.AddEditedElementPouExternalVar(self.TagName, values[2], var_name)
+ self.RefreshVariablePanel()
+ self.RefreshVariableTree()
+ event.SetDragText(var_name)
+ elif values[1] == "Constant":
+ event.SetDragText(values[0])
+ elif values[3] == self.TagName:
+ self.ResetBuffer()
+ event.SetDragText(values[0])
+ wx.CallAfter(self.RefreshModel)
+ else:
+ message = _("Variable don't belong to this POU!")
+ if message is not None:
+ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
+ event.SetDragText("")
+ event.Skip()
+
+ def SetTextSyntax(self, syntax):
+ self.TextSyntax = syntax
+ if syntax in ["ST", "ALL"]:
+ self.Editor.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
+ self.Editor.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
+ self.Editor.SetMarginSensitive(2, 1)
+ self.Editor.SetMarginWidth(2, 12)
+ if syntax == "ST":
+ self.BlockStartKeywords = ST_BLOCK_START_KEYWORDS
+ self.BlockEndKeywords = ST_BLOCK_START_KEYWORDS
+ else:
+ self.BlockStartKeywords = IEC_BLOCK_START_KEYWORDS
+ self.BlockEndKeywords = IEC_BLOCK_START_KEYWORDS
+ else:
+ self.BlockStartKeywords = []
+ self.BlockEndKeywords = []
+
+ def SetKeywords(self, keywords):
+ self.Keywords = [keyword.upper() for keyword in keywords]
+ self.Colourise(0, -1)
+
+ def RefreshJumpList(self):
+ if self.TextSyntax != "IL":
+ self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())]
+ self.Colourise(0, -1)
+
+ # Buffer the last model state
+ def RefreshBuffer(self):
+ self.Controler.BufferProject()
+ if self.ParentWindow:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+ def StartBuffering(self):
+ self.Controler.StartBuffering()
+ if self.ParentWindow:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+ def ResetBuffer(self):
+ if self.CurrentAction != None:
+ self.Controler.EndBuffering()
+ self.CurrentAction = None
+
+ def GetBufferState(self):
+ if not self.Debug and self.TextSyntax != "ALL":
+ return self.Controler.GetBufferState()
+ return False, False
+
+ def Undo(self):
+ if not self.Debug and self.TextSyntax != "ALL":
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ if not self.Debug and self.TextSyntax != "ALL":
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def HasNoModel(self):
+ if not self.Debug and self.TextSyntax != "ALL":
+ return self.Controler.GetEditedElement(self.TagName) is None
+ return False
+
+ def RefreshView(self, variablepanel=True):
+ EditorPanel.RefreshView(self, variablepanel)
+
+ if self.Controler is not None:
+ self.ResetBuffer()
+ self.DisableEvents = True
+ old_cursor_pos = self.GetCurrentPos()
+ old_text = self.GetText()
+ new_text = self.Controler.GetEditedElementText(self.TagName, self.Debug)
+ self.SetText(new_text)
+ new_cursor_pos = GetCursorPos(old_text, new_text)
+ if new_cursor_pos != None:
+ self.Editor.GotoPos(new_cursor_pos)
+ else:
+ self.Editor.GotoPos(old_cursor_pos)
+ self.Editor.ScrollToColumn(0)
+ self.RefreshJumpList()
+ self.Editor.EmptyUndoBuffer()
+ self.DisableEvents = False
+
+ self.RefreshVariableTree()
+
+ self.TypeNames = [typename.upper() for typename in self.Controler.GetDataTypes(self.TagName, True, self.Debug)]
+ self.EnumeratedValues = [value.upper() for value in self.Controler.GetEnumeratedDataValues()]
+
+ self.Functions = {}
+ for category in self.Controler.GetBlockTypes(self.TagName, self.Debug):
+ for blocktype in category["list"]:
+ blockname = blocktype["name"].upper()
+ if blocktype["type"] == "function" and blockname not in self.Keywords and blockname not in self.Variables.keys():
+ interface = dict([(name, {}) for name, type, modifier in blocktype["inputs"] + blocktype["outputs"] if name != ''])
+ for param in ["EN", "ENO"]:
+ if not interface.has_key(param):
+ interface[param] = {}
+ if self.Functions.has_key(blockname):
+ self.Functions[blockname]["interface"].update(interface)
+ self.Functions[blockname]["extensible"] |= blocktype["extensible"]
+ else:
+ self.Functions[blockname] = {"interface": interface,
+ "extensible": blocktype["extensible"]}
+
+ self.Colourise(0, -1)
+
+ def RefreshVariableTree(self):
+ words = self.TagName.split("::")
+ self.Variables = self.GenerateVariableTree([(variable["Name"], variable["Type"], variable["Tree"]) for variable in self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)])
+ if self.Controler.GetEditedElementType(self.TagName, self.Debug)[1] == "function" or words[0] == "T" and self.TextSyntax == "IL":
+ return_type = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if return_type is not None:
+ var_tree, var_dimension = self.Controler.GenerateVarTree(return_type, self.Debug)
+ self.Variables[words[-1].upper()] = self.GenerateVariableTree(var_tree)
+ else:
+ self.Variables[words[-1].upper()] = {}
+
+ def GenerateVariableTree(self, list):
+ tree = {}
+ for var_name, var_type, (var_tree, var_dimension) in list:
+ tree[var_name.upper()] = self.GenerateVariableTree(var_tree)
+ return tree
+
+ def IsValidVariable(self, name, context):
+ return context is not None and context.get(name, None) is not None
+
+ def IsCallParameter(self, name, call):
+ if call is not None:
+ return (call["interface"].get(name.upper(), None) is not None or
+ call["extensible"] and EXTENSIBLE_PARAMETER.match(name.upper()) is not None)
+ return False
+
+ def RefreshLineFolding(self, line_number):
+ if self.TextSyntax in ["ST", "ALL"]:
+ level = wx.stc.STC_FOLDLEVELBASE + self.Editor.GetLineIndentation(line_number)
+ line = self.Editor.GetLine(line_number).strip()
+ if line == "":
+ if line_number > 0:
+ if LineStartswith(self.Editor.GetLine(line_number - 1).strip(), self.BlockEndKeywords):
+ level = self.Editor.GetFoldLevel(self.Editor.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK
+ else:
+ level = self.Editor.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK
+ if level != wx.stc.STC_FOLDLEVELBASE:
+ level |= wx.stc.STC_FOLDLEVELWHITEFLAG
+ elif LineStartswith(line, self.BlockStartKeywords):
+ level |= wx.stc.STC_FOLDLEVELHEADERFLAG
+ elif LineStartswith(line, self.BlockEndKeywords):
+ if LineStartswith(self.Editor.GetLine(line_number - 1).strip(), self.BlockEndKeywords):
+ level = self.Editor.GetFoldLevel(self.Editor.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK
+ else:
+ level = self.Editor.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK
+ self.Editor.SetFoldLevel(line_number, level)
+
+ def OnStyleNeeded(self, event):
+ self.TextChanged = True
+ line_number = self.Editor.LineFromPosition(self.Editor.GetEndStyled())
+ if line_number == 0:
+ start_pos = last_styled_pos = 0
+ else:
+ start_pos = last_styled_pos = self.Editor.GetLineEndPosition(line_number - 1) + 1
+ self.RefreshLineFolding(line_number)
+ end_pos = event.GetPosition()
+ self.StartStyling(start_pos, 0xff)
+
+ current_context = self.Variables
+ current_call = None
+
+ current_pos = last_styled_pos
+ state = SPACE
+ line = ""
+ word = ""
+ while current_pos < end_pos:
+ char = chr(self.Editor.GetCharAt(current_pos)).upper()
+ line += char
+ if char == NEWLINE:
+ self.ContextStack = []
+ current_context = self.Variables
+ if state == COMMENT:
+ self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_COMMENT)
+ elif state == NUMBER:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ elif state == WORD:
+ if word in self.Keywords or word in self.TypeNames:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD)
+ elif self.IsValidVariable(word, current_context):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE)
+ elif self.IsCallParameter(word, current_call):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER)
+ elif word in self.Functions:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION)
+ elif self.TextSyntax == "IL" and word in self.Jumps:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP)
+ elif word in self.EnumeratedValues:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ else:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
+ self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
+ self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
+ self.StartStyling(current_pos, 0xff)
+ else:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ last_styled_pos = current_pos
+ state = SPACE
+ line = ""
+ line_number += 1
+ self.RefreshLineFolding(line_number)
+ elif line.endswith("(*") and state != COMMENT:
+ self.SetStyling(current_pos - last_styled_pos - 1, 31)
+ last_styled_pos = current_pos
+ if state == WORD:
+ current_context = self.Variables
+ state = COMMENT
+ elif line.endswith("{") and state != PRAGMA:
+ self.SetStyling(current_pos - last_styled_pos - 1, 31)
+ last_styled_pos = current_pos
+ if state == WORD:
+ current_context = self.Variables
+ state = PRAGMA
+ elif state == COMMENT:
+ if line.endswith("*)"):
+ self.SetStyling(current_pos - last_styled_pos + 2, STC_PLC_COMMENT)
+ last_styled_pos = current_pos + 1
+ state = SPACE
+ elif state == PRAGMA:
+ if line.endswith("}"):
+ self.SetStyling(current_pos - last_styled_pos + 2, 31)
+ last_styled_pos = current_pos + 1
+ state = SPACE
+ elif (line.endswith("'") or line.endswith('"')) and state not in [STRING, WSTRING]:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ last_styled_pos = current_pos
+ if state == WORD:
+ current_context = self.Variables
+ if line.endswith("'"):
+ state = STRING
+ else:
+ state = WSTRING
+ elif state == STRING:
+ if line.endswith("'") and not line.endswith("$'"):
+ self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_STRING)
+ last_styled_pos = current_pos + 1
+ state = SPACE
+ elif state == WSTRING:
+ if line.endswith('"') and not line.endswith('$"'):
+ self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_STRING)
+ last_styled_pos = current_pos + 1
+ state = SPACE
+ elif char in LETTERS:
+ if state == NUMBER:
+ word = "#"
+ state = WORD
+ elif state == SPACE:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ word = char
+ last_styled_pos = current_pos
+ state = WORD
+ else:
+ word += char
+ elif char in NUMBERS or char == '.' and state != WORD:
+ if state == SPACE:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ last_styled_pos = current_pos
+ state = NUMBER
+ elif state == WORD and char != '.':
+ word += char
+ elif char == '(' and state == SPACE:
+ self.CallStack.append(current_call)
+ current_call = None
+ else:
+ if state == WORD:
+ if word in self.Keywords or word in self.TypeNames:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD)
+ elif self.IsValidVariable(word, current_context):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE)
+ elif self.IsCallParameter(word, current_call):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER)
+ elif word in self.Functions:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION)
+ elif self.TextSyntax == "IL" and word in self.Jumps:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP)
+ elif word in self.EnumeratedValues:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ else:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
+ self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
+ self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
+ self.StartStyling(current_pos, 0xff)
+ if char == '.':
+ if word != "]":
+ if current_context is not None:
+ current_context = current_context.get(word, None)
+ else:
+ current_context = None
+ elif char == '(':
+ self.CallStack.append(current_call)
+ current_call = self.Functions.get(word, None)
+ if current_call is None and self.IsValidVariable(word, current_context):
+ current_call = {"interface": current_context.get(word, {}),
+ "extensible": False}
+ current_context = self.Variables
+ else:
+ if char == '[':
+ self.ContextStack.append(current_context.get(word, None))
+ current_context = self.Variables
+
+ word = ""
+ last_styled_pos = current_pos
+ state = SPACE
+ elif state == NUMBER:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ last_styled_pos = current_pos
+ state = SPACE
+ if char == ']':
+ if len(self.ContextStack) > 0:
+ current_context = self.ContextStack.pop()
+ else:
+ current_context = self.Variables
+ word = char
+ state = WORD
+ elif char == ')':
+ current_context = self.Variables
+ if len(self.CallStack) > 0:
+ current_call = self.CallStack.pop()
+ else:
+ current_call = None
+ word = char
+ state = WORD
+ current_pos += 1
+ if state == COMMENT:
+ self.SetStyling(current_pos - last_styled_pos + 2, STC_PLC_COMMENT)
+ elif state == NUMBER:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ elif state == WORD:
+ if word in self.Keywords or word in self.TypeNames:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD)
+ elif self.IsValidVariable(word, current_context):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE)
+ elif self.IsCallParameter(word, current_call):
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER)
+ elif self.TextSyntax == "IL" and word in self.Functions:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION)
+ elif word in self.Jumps:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP)
+ elif word in self.EnumeratedValues:
+ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER)
+ else:
+ self.SetStyling(current_pos - last_styled_pos, 31)
+ else:
+ self.SetStyling(current_pos - start_pos, 31)
+ self.ShowHighlights(start_pos, end_pos)
+ event.Skip()
+
+ def OnMarginClick(self, event):
+ if event.GetMargin() == 2:
+ line = self.Editor.LineFromPosition(event.GetPosition())
+ if self.Editor.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG:
+ self.Editor.ToggleFold(line)
+ event.Skip()
+
+ def Cut(self):
+ self.ResetBuffer()
+ self.DisableEvents = True
+ self.Editor.CmdKeyExecute(wx.stc.STC_CMD_CUT)
+ self.DisableEvents = False
+ self.RefreshModel()
+ self.RefreshBuffer()
+
+ def Copy(self):
+ self.Editor.CmdKeyExecute(wx.stc.STC_CMD_COPY)
+
+ def Paste(self):
+ self.ResetBuffer()
+ self.DisableEvents = True
+ self.Editor.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
+ self.DisableEvents = False
+ self.RefreshModel()
+ self.RefreshBuffer()
+
+ def Find(self, direction, search_params):
+ if self.SearchParams != search_params:
+ self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+
+ self.SearchParams = search_params
+ criteria = {
+ "raw_pattern": search_params["find_pattern"],
+ "pattern": re.compile(search_params["find_pattern"]),
+ "case_sensitive": search_params["case_sensitive"],
+ "regular_expression": search_params["regular_expression"],
+ "filter": "all"}
+
+ self.SearchResults = [
+ (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)
+ for infos, start, end, text in
+ self.Controler.SearchInPou(self.TagName, criteria, self.Debug)]
+
+ if len(self.SearchResults) > 0:
+ if self.CurrentFindHighlight is not None:
+ old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+ if self.SearchParams["wrap"]:
+ idx = (old_idx + direction) % len(self.SearchResults)
+ else:
+ idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+ if idx != old_idx:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = self.SearchResults[idx]
+ self.AddHighlight(*self.CurrentFindHighlight)
+ else:
+ self.CurrentFindHighlight = self.SearchResults[0]
+ self.AddHighlight(*self.CurrentFindHighlight)
+
+ else:
+ if self.CurrentFindHighlight is not None:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = None
+
+ def RefreshModel(self):
+ self.RefreshJumpList()
+ self.Controler.SetEditedElementText(self.TagName, self.GetText())
+
+ def OnKeyDown(self, event):
+ if self.Controler is not None:
+
+ if self.Editor.CallTipActive():
+ self.Editor.CallTipCancel()
+ key = event.GetKeyCode()
+ key_handled = False
+
+ line = self.Editor.GetCurrentLine()
+ if line == 0:
+ start_pos = 0
+ else:
+ start_pos = self.Editor.GetLineEndPosition(line - 1) + 1
+ end_pos = self.GetCurrentPos()
+ lineText = self.Editor.GetTextRange(start_pos, end_pos).replace("\t", " ")
+
+ # Code completion
+ if key == wx.WXK_SPACE and event.ControlDown():
+
+ words = lineText.split(" ")
+ words = [word for i, word in enumerate(words) if word != '' or i == len(words) - 1]
+
+ kw = []
+
+ if self.TextSyntax == "IL":
+ if len(words) == 1:
+ kw = self.Keywords
+ elif len(words) == 2:
+ if words[0].upper() in ["CAL", "CALC", "CALNC"]:
+ kw = self.Functions
+ elif words[0].upper() in ["JMP", "JMPC", "JMPNC"]:
+ kw = self.Jumps
+ else:
+ kw = self.Variables.keys()
+ else:
+ kw = self.Keywords + self.Variables.keys() + self.Functions
+ if len(kw) > 0:
+ if len(words[-1]) > 0:
+ kw = [keyword for keyword in kw if keyword.startswith(words[-1])]
+ kw.sort()
+ self.Editor.AutoCompSetIgnoreCase(True)
+ self.Editor.AutoCompShow(len(words[-1]), " ".join(kw))
+ key_handled = True
+ elif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
+ if self.TextSyntax in ["ST", "ALL"]:
+ indent = self.Editor.GetLineIndentation(line)
+ if LineStartswith(lineText.strip(), self.BlockStartKeywords):
+ indent = (indent / 2 + 1) * 2
+ self.Editor.AddText("\n" + " " * indent)
+ key_handled = True
+ elif key == wx.WXK_BACK:
+ if self.TextSyntax in ["ST", "ALL"]:
+ indent = self.Editor.GetLineIndentation(line)
+ if lineText.strip() == "" and indent > 0:
+ self.Editor.DelLineLeft()
+ self.Editor.AddText(" " * ((max(0, indent - 1) / 2) * 2))
+ key_handled = True
+ if not key_handled:
+ event.Skip()
+
+ def OnKillFocus(self, event):
+ self.Editor.AutoCompCancel()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def ClearHighlights(self, highlight_type=None):
+ EditorPanel.ClearHighlights(self, highlight_type)
+
+ if highlight_type is None:
+ self.Highlights = []
+ else:
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if highlight_type is not None:
+ self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type]
+ self.RefreshView()
+
+ def AddHighlight(self, infos, start, end, highlight_type):
+ EditorPanel.AddHighlight(self, infos, start, end, highlight_type)
+
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if infos[0] == "body" and highlight_type is not None:
+ self.Highlights.append((infos[1], start, end, highlight_type))
+ self.Editor.GotoPos(self.Editor.PositionFromLine(start[0]) + start[1])
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ EditorPanel.RemoveHighlight(self, infos, start, end, highlight_type)
+
+ highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+ if (infos[0] == "body" and highlight_type is not None and
+ (infos[1], start, end, highlight_type) in self.Highlights):
+ self.Highlights.remove((infos[1], start, end, highlight_type))
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ShowHighlights(self, start_pos, end_pos):
+ for indent, start, end, highlight_type in self.Highlights:
+ if start[0] == 0:
+ highlight_start_pos = start[1] - indent
+ else:
+ highlight_start_pos = self.Editor.GetLineEndPosition(start[0] - 1) + start[1] - indent + 1
+ if end[0] == 0:
+ highlight_end_pos = end[1] - indent + 1
+ else:
+ highlight_end_pos = self.Editor.GetLineEndPosition(end[0] - 1) + end[1] - indent + 2
+ if highlight_start_pos < end_pos and highlight_end_pos > start_pos:
+ self.StartStyling(highlight_start_pos, 0xff)
+ self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
+ self.StartStyling(highlight_start_pos, 0x00)
+ self.SetStyling(len(self.Editor.GetText()) - highlight_end_pos, wx.stc.STC_STYLE_DEFAULT)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/Viewer.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,3235 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+import math
+import time
+from types import TupleType
+from threading import Lock
+
+import wx
+
+from plcopen.structures import *
+from PLCControler import ITEM_POU, ITEM_PROGRAM, ITEM_FUNCTIONBLOCK
+
+from dialogs import *
+from graphics import *
+from EditorPanel import EditorPanel
+
+SCROLLBAR_UNIT = 10
+WINDOW_BORDER = 10
+SCROLL_ZONE = 10
+
+CURSORS = None
+
+def ResetCursors():
+ global CURSORS
+ if CURSORS == None:
+ CURSORS = [wx.NullCursor,
+ wx.StockCursor(wx.CURSOR_HAND),
+ wx.StockCursor(wx.CURSOR_SIZENWSE),
+ wx.StockCursor(wx.CURSOR_SIZENESW),
+ wx.StockCursor(wx.CURSOR_SIZEWE),
+ wx.StockCursor(wx.CURSOR_SIZENS)]
+
+def AppendMenu(parent, help, id, kind, text):
+ if wx.VERSION >= (2, 6, 0):
+ parent.Append(help=help, id=id, kind=kind, text=text)
+ else:
+ parent.Append(helpString=help, id=id, kind=kind, item=text)
+
+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,
+ }
+
+ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, 7)]
+
+def GetVariableCreationFunction(variable_type):
+ def variableCreationFunction(viewer, id, specific_values):
+ return FBD_Variable(viewer, variable_type,
+ specific_values["name"],
+ specific_values["value_type"],
+ id,
+ specific_values["executionOrder"])
+ return variableCreationFunction
+
+def GetConnectorCreationFunction(connector_type):
+ def connectorCreationFunction(viewer, id, specific_values):
+ return FBD_Connector(viewer, connector_type,
+ specific_values["name"], id)
+ return connectorCreationFunction
+
+def commentCreationFunction(viewer, id, specific_values):
+ return Comment(viewer, specific_values["content"], id)
+
+def GetPowerRailCreationFunction(powerrail_type):
+ def powerRailCreationFunction(viewer, id, specific_values):
+ return LD_PowerRail(viewer, powerrail_type, id,
+ specific_values["connectors"])
+ return powerRailCreationFunction
+
+CONTACT_TYPES = {(True, "none"): CONTACT_REVERSE,
+ (False, "rising"): CONTACT_RISING,
+ (False, "falling"): CONTACT_FALLING}
+
+def contactCreationFunction(viewer, id, specific_values):
+ contact_type = CONTACT_TYPES.get((specific_values.get("negated", False),
+ specific_values.get("edge", "none")),
+ CONTACT_NORMAL)
+ return LD_Contact(viewer, contact_type, specific_values["name"], id)
+
+COIL_TYPES = {(True, "none", "none"): COIL_REVERSE,
+ (False, "none", "set"): COIL_SET,
+ (False, "none", "reset"): COIL_RESET,
+ (False, "rising", "none"): COIL_RISING,
+ (False, "falling", "none"): COIL_FALLING}
+
+def coilCreationFunction(viewer, id, specific_values):
+ coil_type = COIL_TYPES.get((specific_values.get("negated", False),
+ specific_values.get("edge", "none"),
+ specific_values.get("storage", "none")),
+ COIL_NORMAL)
+ return LD_Coil(viewer, coil_type, specific_values["name"], id)
+
+def stepCreationFunction(viewer, id, specific_values):
+ step = SFC_Step(viewer, specific_values["name"],
+ specific_values.get("initial", False), id)
+ if specific_values.get("action", None):
+ step.AddAction()
+ connector = step.GetActionConnector()
+ connector.SetPosition(wx.Point(*specific_values["action"]["position"]))
+ return step
+
+def transitionCreationFunction(viewer, id, specific_values):
+ transition = SFC_Transition(viewer, specific_values["condition_type"],
+ specific_values.get("condition", None),
+ specific_values["priority"], id)
+ return transition
+
+def GetDivergenceCreationFunction(divergence_type):
+ def divergenceCreationFunction(viewer, id, specific_values):
+ return SFC_Divergence(viewer, divergence_type,
+ specific_values["connectors"], id)
+ return divergenceCreationFunction
+
+def jumpCreationFunction(viewer, id, specific_values):
+ return SFC_Jump(viewer, specific_values["target"], id)
+
+def actionBlockCreationFunction(viewer, id, specific_values):
+ return SFC_ActionBlock(viewer, specific_values["actions"], id)
+
+ElementCreationFunctions = {
+ "input": GetVariableCreationFunction(INPUT),
+ "output": GetVariableCreationFunction(OUTPUT),
+ "inout": GetVariableCreationFunction(INOUT),
+ "connector": GetConnectorCreationFunction(CONNECTOR),
+ "continuation": GetConnectorCreationFunction(CONTINUATION),
+ "comment": commentCreationFunction,
+ "leftPowerRail": GetPowerRailCreationFunction(LEFTRAIL),
+ "rightPowerRail": GetPowerRailCreationFunction(RIGHTRAIL),
+ "contact": contactCreationFunction,
+ "coil": coilCreationFunction,
+ "step": stepCreationFunction,
+ "transition": transitionCreationFunction,
+ "selectionDivergence": GetDivergenceCreationFunction(SELECTION_DIVERGENCE),
+ "selectionConvergence": GetDivergenceCreationFunction(SELECTION_CONVERGENCE),
+ "simultaneousDivergence": GetDivergenceCreationFunction(SIMULTANEOUS_DIVERGENCE),
+ "simultaneousConvergence": GetDivergenceCreationFunction(SIMULTANEOUS_CONVERGENCE),
+ "jump": jumpCreationFunction,
+ "actionBlock": actionBlockCreationFunction,
+}
+
+def sort_blocks(block_infos1, block_infos2):
+ x1, y1 = block_infos1[0].GetPosition()
+ x2, y2 = block_infos2[0].GetPosition()
+ if y1 == y2:
+ return cmp(x1, x2)
+ else:
+ return cmp(y1, y2)
+
+#-------------------------------------------------------------------------------
+# Graphic elements Viewer base class
+#-------------------------------------------------------------------------------
+
+# ID Constants for alignment menu items
+[ID_VIEWERALIGNMENTMENUITEMS0, ID_VIEWERALIGNMENTMENUITEMS1,
+ ID_VIEWERALIGNMENTMENUITEMS2, ID_VIEWERALIGNMENTMENUITEMS4,
+ ID_VIEWERALIGNMENTMENUITEMS5, ID_VIEWERALIGNMENTMENUITEMS6,
+] = [wx.NewId() for _init_coll_AlignmentMenu_Items in range(6)]
+
+# ID Constants for contextual menu items
+[ID_VIEWERCONTEXTUALMENUITEMS0, ID_VIEWERCONTEXTUALMENUITEMS1,
+ ID_VIEWERCONTEXTUALMENUITEMS2, ID_VIEWERCONTEXTUALMENUITEMS3,
+ ID_VIEWERCONTEXTUALMENUITEMS5, ID_VIEWERCONTEXTUALMENUITEMS6,
+ ID_VIEWERCONTEXTUALMENUITEMS8, ID_VIEWERCONTEXTUALMENUITEMS9,
+ ID_VIEWERCONTEXTUALMENUITEMS11, ID_VIEWERCONTEXTUALMENUITEMS12,
+ ID_VIEWERCONTEXTUALMENUITEMS14, ID_VIEWERCONTEXTUALMENUITEMS16,
+ ID_VIEWERCONTEXTUALMENUITEMS17,
+] = [wx.NewId() for _init_coll_ContextualMenu_Items in range(13)]
+
+
+class ViewerDropTarget(wx.TextDropTarget):
+
+ def __init__(self, parent):
+ wx.TextDropTarget.__init__(self)
+ self.ParentWindow = parent
+
+ def OnDropText(self, x, y, data):
+ self.ParentWindow.Select()
+ tagname = self.ParentWindow.GetTagName()
+ pou_name, pou_type = self.ParentWindow.Controler.GetEditedElementType(tagname, self.ParentWindow.Debug)
+ x, y = self.ParentWindow.CalcUnscrolledPosition(x, y)
+ x = int(x / self.ParentWindow.ViewScale[0])
+ y = int(y / self.ParentWindow.ViewScale[1])
+ scaling = self.ParentWindow.Scaling
+ message = None
+ try:
+ values = eval(data)
+ except:
+ message = _("Invalid value \"%s\" for viewer block")%data
+ values = None
+ if not isinstance(values, TupleType):
+ message = _("Invalid value \"%s\" for viewer block")%data
+ values = None
+ if values is not None:
+ if values[1] == "debug":
+ pass
+ elif values[1] == "program":
+ message = _("Programs can't be used by other POUs!")
+ elif values[1] in ["function", "functionBlock"]:
+ words = tagname.split("::")
+ if pou_name == values[0]:
+ message = _("\"%s\" can't use itself!")%pou_name
+ elif pou_type == "function" and values[1] != "function":
+ message = _("Function Blocks can't be used in Functions!")
+ elif words[0] == "T" and values[1] != "function":
+ message = _("Function Blocks can't be used in Transitions!")
+ elif self.ParentWindow.Controler.PouIsUsedBy(pou_name, values[0], self.ParentWindow.Debug):
+ message = _("\"%s\" is already used by \"%s\"!")%(pou_name, values[0])
+ else:
+ blockname = values[2]
+ if len(values) > 3:
+ blockinputs = values[3]
+ else:
+ blockinputs = None
+ if values[1] != "function" and blockname == "":
+ blockname = self.ParentWindow.GenerateNewName(blocktype=values[0])
+ if blockname.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+ message = _("\"%s\" pou already exists!")%blockname
+ elif blockname.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+ message = _("\"%s\" element for this pou already exists!")%blockname
+ else:
+ id = self.ParentWindow.GetNewId()
+ block = FBD_Block(self.ParentWindow, values[0], blockname, id, inputs = blockinputs)
+ width, height = block.GetMinSize()
+ if scaling is not None:
+ x = round(float(x) / float(scaling[0])) * scaling[0]
+ y = round(float(y) / float(scaling[1])) * scaling[1]
+ width = round(float(width) / float(scaling[0]) + 0.5) * scaling[0]
+ height = round(float(height) / float(scaling[1]) + 0.5) * scaling[1]
+ block.SetPosition(x, y)
+ block.SetSize(width, height)
+ self.ParentWindow.AddBlock(block)
+ self.ParentWindow.Controler.AddEditedElementBlock(tagname, id, values[0], blockname)
+ self.ParentWindow.RefreshBlockModel(block)
+ self.ParentWindow.RefreshBuffer()
+ self.ParentWindow.RefreshScrollBars()
+ self.ParentWindow.RefreshVisibleElements()
+ self.ParentWindow.RefreshVariablePanel()
+ self.ParentWindow.Refresh(False)
+ elif values[1] == "location":
+ if pou_type == "program":
+ location = values[0]
+ if not location.startswith("%"):
+ dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow,
+ _("Select a variable class:"), _("Variable class"),
+ ["Input", "Output", "Memory"],
+ wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ selected = dialog.GetSelection()
+ else:
+ selected = None
+ dialog.Destroy()
+ if selected is None:
+ return
+ if selected == 0:
+ location = "%I" + location
+ elif selected == 1:
+ location = "%Q" + location
+ else:
+ location = "%M" + location
+ var_name = values[3]
+ if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+ message = _("\"%s\" pou already exists!")%var_name
+ else:
+ if location[1] == "Q":
+ var_class = OUTPUT
+ else:
+ var_class = INPUT
+ if values[2] is not None:
+ var_type = values[2]
+ else:
+ var_type = LOCATIONDATATYPES.get(location[2], ["BOOL"])[0]
+ if not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+ self.ParentWindow.Controler.AddEditedElementPouVar(tagname, var_type, var_name, location, values[4])
+ self.ParentWindow.RefreshVariablePanel()
+ self.ParentWindow.AddVariableBlock(x, y, scaling, var_class, var_name, var_type)
+ elif values[1] == "Global":
+ var_name = values[0]
+ if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+ message = _("\"%s\" pou already exists!")%var_name
+ else:
+ if not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+ self.ParentWindow.Controler.AddEditedElementPouExternalVar(tagname, values[2], var_name)
+ self.ParentWindow.RefreshVariablePanel()
+ self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, var_name, values[2])
+ elif values[1] == "Constant":
+ self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, values[0], None)
+ elif values[3] == tagname:
+ if values[1] == "Output":
+ var_class = OUTPUT
+ elif values[1] == "InOut":
+ var_class = INPUT
+ else:
+ var_class = INPUT
+ tree = dict([(var["Name"], var["Tree"]) for var in self.ParentWindow.Controler.GetEditedElementInterfaceVars(tagname, self.ParentWindow.Debug)]).get(values[0], None)
+ if tree is not None:
+ if len(tree[0]) > 0:
+ menu = wx.Menu(title='')
+ self.GenerateTreeMenu(x, y, scaling, menu, "", var_class, [(values[0], values[2], tree)])
+ self.ParentWindow.PopupMenuXY(menu)
+ else:
+ self.ParentWindow.AddVariableBlock(x, y, scaling, var_class, values[0], values[2])
+ else:
+ message = _("Unknown variable \"%s\" for this POU!") % values[0]
+ else:
+ message = _("Variable don't belong to this POU!")
+ if message is not None:
+ wx.CallAfter(self.ShowMessage, message)
+
+ def GenerateTreeMenu(self, x, y, scaling, menu, base_path, var_class, tree):
+ for child_name, child_type, (child_tree, child_dimensions) in tree:
+ if base_path:
+ child_path = "%s.%s" % (base_path, child_name)
+ else:
+ child_path = child_name
+ if len(child_dimensions) > 0:
+ child_path += "[%s]" % ",".join([str(dimension[0]) for dimension in child_dimensions])
+ child_name += "[]"
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=child_name)
+ self.ParentWindow.Bind(wx.EVT_MENU, self.GetAddVariableBlockFunction(x, y, scaling, var_class, child_path, child_type), id=new_id)
+ if len(child_tree) > 0:
+ new_id = wx.NewId()
+ child_menu = wx.Menu(title='')
+ self.GenerateTreeMenu(x, y, scaling, child_menu, child_path, var_class, child_tree)
+ menu.AppendMenu(new_id, "%s." % child_name, child_menu)
+
+ def GetAddVariableBlockFunction(self, x, y, scaling, var_class, var_name, var_type):
+ def AddVariableFunction(event):
+ self.ParentWindow.AddVariableBlock(x, y, scaling, var_class, var_name, var_type)
+ return AddVariableFunction
+
+ def ShowMessage(self, message):
+ message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+"""
+Class that implements a Viewer based on a wx.ScrolledWindow for drawing and
+manipulating graphic elements
+"""
+
+class Viewer(EditorPanel, DebugViewer):
+
+ if wx.VERSION < (2, 6, 0):
+ def Bind(self, event, function, id = None):
+ if id is not None:
+ event(self, id, function)
+ else:
+ event(self, function)
+
+ # Add list of menu items to the given menu
+ def AddMenuItems(self, menu, items):
+ for item in items:
+ if item is None:
+ menu.AppendSeparator()
+ else:
+ id, kind, text, help, callback = item
+ AppendMenu(menu, help=help, id=id, kind=kind, text=text)
+ # Link menu event to corresponding called functions
+ self.Bind(wx.EVT_MENU, callback, id=id)
+
+ # Add Block Pin Menu items to the given menu
+ def AddBlockPinMenuItems(self, menu, connector):
+ [ID_NO_MODIFIER, ID_NEGATED, ID_RISING_EDGE,
+ ID_FALLING_EDGE] = [wx.NewId() for i in xrange(4)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_NO_MODIFIER, wx.ITEM_RADIO, _(u'No Modifier'), '', self.OnNoModifierMenu),
+ (ID_NEGATED, wx.ITEM_RADIO, _(u'Negated'), '', self.OnNegatedMenu),
+ (ID_RISING_EDGE, wx.ITEM_RADIO, _(u'Rising Edge'), '', self.OnRisingEdgeMenu),
+ (ID_FALLING_EDGE, wx.ITEM_RADIO, _(u'Falling Edge'), '', self.OnFallingEdgeMenu)])
+
+ type = self.Controler.GetEditedElementType(self.TagName, self.Debug)
+ menu.Enable(ID_RISING_EDGE, type != "function")
+ menu.Enable(ID_FALLING_EDGE, type != "function")
+
+ if connector.IsNegated():
+ menu.Check(ID_NEGATED, True)
+ elif connector.GetEdge() == "rising":
+ menu.Check(ID_RISING_EDGE, True)
+ elif connector.GetEdge() == "falling":
+ menu.Check(ID_FALLING_EDGE, True)
+ else:
+ menu.Check(ID_NO_MODIFIER, True)
+
+ # Add Alignment Menu items to the given menu
+ def AddAlignmentMenuItems(self, menu):
+ [ID_ALIGN_LEFT, ID_ALIGN_CENTER, ID_ALIGN_RIGHT,
+ ID_ALIGN_TOP, ID_ALIGN_MIDDLE, ID_ALIGN_BOTTOM,
+ ] = [wx.NewId() for i in xrange(6)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ALIGN_LEFT, wx.ITEM_NORMAL, _(u'Left'), '', self.OnAlignLeftMenu),
+ (ID_ALIGN_CENTER, wx.ITEM_NORMAL, _(u'Center'), '', self.OnAlignCenterMenu),
+ (ID_ALIGN_RIGHT, wx.ITEM_NORMAL, _(u'Right'), '', self.OnAlignRightMenu),
+ None,
+ (ID_ALIGN_TOP, wx.ITEM_NORMAL, _(u'Top'), '', self.OnAlignTopMenu),
+ (ID_ALIGN_MIDDLE, wx.ITEM_NORMAL, _(u'Middle'), '', self.OnAlignMiddleMenu),
+ (ID_ALIGN_BOTTOM, wx.ITEM_NORMAL, _(u'Bottom'), '', self.OnAlignBottomMenu)])
+
+ # Add Wire Menu items to the given menu
+ def AddWireMenuItems(self, menu, delete=False):
+ [ID_ADD_SEGMENT, ID_DELETE_SEGMENT] = [wx.NewId() for i in xrange(2)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ADD_SEGMENT, wx.ITEM_NORMAL, _(u'Add Wire Segment'), '', self.OnAddSegmentMenu),
+ (ID_DELETE_SEGMENT, wx.ITEM_NORMAL, _(u'Delete Wire Segment'), '', self.OnDeleteSegmentMenu)])
+
+ menu.Enable(ID_DELETE_SEGMENT, delete)
+
+ # Add Divergence Menu items to the given menu
+ def AddDivergenceMenuItems(self, menu, delete=False):
+ [ID_ADD_BRANCH, ID_DELETE_BRANCH] = [wx.NewId() for i in xrange(2)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ADD_BRANCH, wx.ITEM_NORMAL, _(u'Add Divergence Branch'), '', self.OnAddBranchMenu),
+ (ID_DELETE_BRANCH, wx.ITEM_NORMAL, _(u'Delete Divergence Branch'), '', self.OnDeleteBranchMenu)])
+
+ menu.Enable(ID_DELETE_BRANCH, delete)
+
+ # Add Add Menu items to the given menu
+ def AddAddMenuItems(self, menu):
+ [ID_ADD_BLOCK, ID_ADD_VARIABLE, ID_ADD_CONNECTION,
+ ID_ADD_COMMENT] = [wx.NewId() for i in xrange(4)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ADD_BLOCK, wx.ITEM_NORMAL, _(u'Block'), '', self.GetAddMenuCallBack(self.AddNewBlock)),
+ (ID_ADD_VARIABLE, wx.ITEM_NORMAL, _(u'Variable'), '', self.GetAddMenuCallBack(self.AddNewVariable)),
+ (ID_ADD_CONNECTION, wx.ITEM_NORMAL, _(u'Connection'), '', self.GetAddMenuCallBack(self.AddNewConnection)),
+ None])
+
+ if self.CurrentLanguage != "FBD":
+ [ID_ADD_POWER_RAIL, ID_ADD_CONTACT, ID_ADD_COIL,
+ ] = [wx.NewId() for i in xrange(3)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ADD_POWER_RAIL, wx.ITEM_NORMAL, _(u'Power Rail'), '', self.GetAddMenuCallBack(self.AddNewPowerRail)),
+ (ID_ADD_CONTACT, wx.ITEM_NORMAL, _(u'Contact'), '', self.GetAddMenuCallBack(self.AddNewContact))])
+
+ if self.CurrentLanguage != "SFC":
+ self.AddMenuItems(menu, [
+ (ID_ADD_COIL, wx.ITEM_NORMAL, _(u'Coil'), '', self.GetAddMenuCallBack(self.AddNewCoil))])
+
+ menu.AppendSeparator()
+
+ if self.CurrentLanguage == "SFC":
+ [ID_ADD_INITIAL_STEP, ID_ADD_STEP, ID_ADD_TRANSITION,
+ ID_ADD_ACTION_BLOCK, ID_ADD_DIVERGENCE, ID_ADD_JUMP,
+ ] = [wx.NewId() for i in xrange(6)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_ADD_INITIAL_STEP, wx.ITEM_NORMAL, _(u'Initial Step'), '', self.GetAddMenuCallBack(self.AddNewStep, True)),
+ (ID_ADD_STEP, wx.ITEM_NORMAL, _(u'Step'), '', self.GetAddMenuCallBack(self.AddNewStep)),
+ (ID_ADD_TRANSITION, wx.ITEM_NORMAL, _(u'Transition'), '', self.GetAddMenuCallBack(self.AddNewTransition)),
+ (ID_ADD_ACTION_BLOCK, wx.ITEM_NORMAL, _(u'Action Block'), '', self.GetAddMenuCallBack(self.AddNewActionBlock)),
+ (ID_ADD_DIVERGENCE, wx.ITEM_NORMAL, _(u'Divergence'), '', self.GetAddMenuCallBack(self.AddNewDivergence)),
+ (ID_ADD_JUMP, wx.ITEM_NORMAL, _(u'Jump'), '', self.GetAddMenuCallBack(self.AddNewJump)),
+ None])
+
+ self.AddMenuItems(menu, [
+ (ID_ADD_COMMENT, wx.ITEM_NORMAL, _(u'Comment'), '', self.GetAddMenuCallBack(self.AddNewComment))])
+
+ # Add Default Menu items to the given menu
+ def AddDefaultMenuItems(self, menu, edit=False, block=False):
+ if block:
+ [ID_EDIT_BLOCK, ID_DELETE] = [wx.NewId() for i in xrange(2)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_EDIT_BLOCK, wx.ITEM_NORMAL, _(u'Edit Block'), '', self.OnEditBlockMenu),
+ (ID_DELETE, wx.ITEM_NORMAL, _(u'Delete'), '', self.OnDeleteMenu)])
+
+ menu.Enable(ID_EDIT_BLOCK, edit)
+
+ else:
+ [ID_CLEAR_EXEC_ORDER, ID_RESET_EXEC_ORDER] = [wx.NewId() for i in xrange(2)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_CLEAR_EXEC_ORDER, wx.ITEM_NORMAL, _(u'Clear Execution Order'), '', self.OnClearExecutionOrderMenu),
+ (ID_RESET_EXEC_ORDER, wx.ITEM_NORMAL, _(u'Reset Execution Order'), '', self.OnResetExecutionOrderMenu)])
+
+ menu.AppendSeparator()
+
+ add_menu = wx.Menu(title='')
+ self.AddAddMenuItems(add_menu)
+ menu.AppendMenu(-1, _(u'Add'), add_menu)
+
+ menu.AppendSeparator()
+
+ [ID_CUT, ID_COPY, ID_PASTE] = [wx.NewId() for i in xrange(3)]
+
+ # Create menu items
+ self.AddMenuItems(menu, [
+ (ID_CUT, wx.ITEM_NORMAL, _(u'Cut'), '', self.GetClipboardCallBack(self.Cut)),
+ (ID_COPY, wx.ITEM_NORMAL, _(u'Copy'), '', self.GetClipboardCallBack(self.Copy)),
+ (ID_PASTE, wx.ITEM_NORMAL, _(u'Paste'), '', self.GetAddMenuCallBack(self.Paste))])
+
+ menu.Enable(ID_CUT, block)
+ menu.Enable(ID_COPY, block)
+ menu.Enable(ID_PASTE, self.ParentWindow.GetCopyBuffer() is not None)
+
+ def _init_Editor(self, prnt):
+ self.Editor = wx.ScrolledWindow(prnt, name="Viewer",
+ pos=wx.Point(0, 0), size=wx.Size(0, 0),
+ style=wx.HSCROLL | wx.VSCROLL | wx.ALWAYS_SHOW_SB)
+ self.Editor.ParentWindow = self
+
+ # Create a new Viewer
+ def __init__(self, parent, tagname, window, controler, debug = False, instancepath = ""):
+ self.VARIABLE_PANEL_TYPE = controler.GetPouType(tagname.split("::")[1])
+
+ EditorPanel.__init__(self, parent, tagname, window, controler, debug)
+ DebugViewer.__init__(self, controler, debug)
+
+ # Adding a rubberband to Viewer
+ self.rubberBand = RubberBand(viewer=self)
+ self.Editor.SetBackgroundColour(wx.Colour(255,255,255))
+ self.Editor.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ self.ResetView()
+ self.Scaling = None
+ self.DrawGrid = True
+ self.GridBrush = wx.TRANSPARENT_BRUSH
+ self.PageSize = None
+ self.PagePen = wx.TRANSPARENT_PEN
+ self.DrawingWire = False
+ self.current_id = 0
+ self.TagName = tagname
+ self.Highlights = []
+ self.SearchParams = None
+ self.SearchResults = None
+ self.CurrentFindHighlight = None
+ self.InstancePath = instancepath
+ self.StartMousePos = None
+ self.StartScreenPos = None
+ self.Buffering = False
+
+ # Initialize Cursors
+ ResetCursors()
+ self.CurrentCursor = 0
+
+ # Initialize Block, Wire and Comment numbers
+ self.wire_id = 0
+
+ # Initialize Viewer mode to Selection mode
+ self.Mode = MODE_SELECTION
+ self.SavedMode = False
+ self.CurrentLanguage = "FBD"
+
+ if not self.Debug:
+ self.Editor.SetDropTarget(ViewerDropTarget(self))
+
+ self.ElementRefreshList = []
+ self.ElementRefreshList_lock = Lock()
+
+ dc = wx.ClientDC(self.Editor)
+ font = wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"])
+ dc.SetFont(font)
+ width, height = dc.GetTextExtent("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ while width > 260:
+ faces["size"] -= 1
+ font = wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"])
+ dc.SetFont(font)
+ width, height = dc.GetTextExtent("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ self.SetFont(font)
+ self.MiniTextDC = wx.MemoryDC()
+ self.MiniTextDC.SetFont(wx.Font(faces["size"] * 0.75, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["helv"]))
+
+ self.CurrentScale = None
+ self.SetScale(len(ZOOM_FACTORS) / 2, False)
+
+ self.RefreshHighlightsTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+
+ self.ResetView()
+
+ # Link Viewer event to corresponding methods
+ self.Editor.Bind(wx.EVT_PAINT, self.OnPaint)
+ self.Editor.Bind(wx.EVT_LEFT_DOWN, self.OnViewerLeftDown)
+ self.Editor.Bind(wx.EVT_LEFT_UP, self.OnViewerLeftUp)
+ self.Editor.Bind(wx.EVT_LEFT_DCLICK, self.OnViewerLeftDClick)
+ self.Editor.Bind(wx.EVT_RIGHT_DOWN, self.OnViewerRightDown)
+ self.Editor.Bind(wx.EVT_RIGHT_UP, self.OnViewerRightUp)
+ self.Editor.Bind(wx.EVT_MIDDLE_DOWN, self.OnViewerMiddleDown)
+ self.Editor.Bind(wx.EVT_MIDDLE_UP, self.OnViewerMiddleUp)
+ self.Editor.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveViewer)
+ self.Editor.Bind(wx.EVT_MOTION, self.OnViewerMotion)
+ self.Editor.Bind(wx.EVT_CHAR, self.OnChar)
+ self.Editor.Bind(wx.EVT_SCROLLWIN, self.OnScrollWindow)
+ self.Editor.Bind(wx.EVT_SCROLLWIN_THUMBRELEASE, self.OnScrollStop)
+ self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheelWindow)
+ self.Editor.Bind(wx.EVT_SIZE, self.OnMoveWindow)
+ self.Editor.Bind(wx.EVT_MOUSE_EVENTS, self.OnViewerMouseEvent)
+
+ # Destructor
+ def __del__(self):
+ DebugViewer.__del__(self)
+ self.Flush()
+ self.ResetView()
+ self.RefreshHighlightsTimer.Stop()
+
+ def SetCurrentCursor(self, cursor):
+ if self.Mode != MODE_MOTION:
+ global CURSORS
+ if self.CurrentCursor != cursor:
+ self.CurrentCursor = cursor
+ self.Editor.SetCursor(CURSORS[cursor])
+
+ def GetScrolledRect(self, rect):
+ rect.x, rect.y = self.Editor.CalcScrolledPosition(int(rect.x * self.ViewScale[0]),
+ int(rect.y * self.ViewScale[1]))
+ rect.width = int(rect.width * self.ViewScale[0]) + 2
+ rect.height = int(rect.height * self.ViewScale[1]) + 2
+ return rect
+
+ def GetTitle(self):
+ if self.Debug:
+ if len(self.InstancePath) > 15:
+ return "..." + self.InstancePath[-12:]
+ return self.InstancePath
+ return EditorPanel.GetTitle(self)
+
+ def GetScaling(self):
+ return self.Scaling
+
+ def GetInstancePath(self):
+ return self.InstancePath
+
+ def IsViewing(self, tagname):
+ if self.Debug:
+ return self.InstancePath == tagname
+ return EditorPanel.IsViewing(self, tagname)
+
+ # Returns a new id
+ def GetNewId(self):
+ self.current_id += 1
+ return self.current_id
+
+ def SetScale(self, scale_number, refresh=True, mouse_event=None):
+ new_scale = max(0, min(scale_number, len(ZOOM_FACTORS) - 1))
+ if self.CurrentScale != new_scale:
+ if refresh:
+ dc = self.GetLogicalDC()
+ self.CurrentScale = new_scale
+ self.ViewScale = (ZOOM_FACTORS[self.CurrentScale], ZOOM_FACTORS[self.CurrentScale])
+ if refresh:
+ self.Editor.Freeze()
+ if mouse_event is None:
+ client_size = self.Editor.GetClientSize()
+ mouse_pos = wx.Point(client_size[0] / 2, client_size[1] / 2)
+ mouse_event = wx.MouseEvent(wx.EVT_MOUSEWHEEL.typeId)
+ mouse_event.m_x = mouse_pos.x
+ mouse_event.m_y = mouse_pos.y
+ else:
+ mouse_pos = mouse_event.GetPosition()
+ pos = mouse_event.GetLogicalPosition(dc)
+ xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
+ ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
+ scrollx = max(0, round(pos.x * self.ViewScale[0] - mouse_pos.x) / SCROLLBAR_UNIT)
+ scrolly = max(0, round(pos.y * self.ViewScale[1] - mouse_pos.y) / SCROLLBAR_UNIT)
+ if scrollx > xmax or scrolly > ymax:
+ self.RefreshScrollBars(max(0, scrollx - xmax), max(0, scrolly - ymax))
+ self.Scroll(scrollx, scrolly)
+ else:
+ self.Scroll(scrollx, scrolly)
+ self.RefreshScrollBars()
+ self.RefreshScaling(refresh)
+ self.Editor.Thaw()
+
+ def GetScale(self):
+ return self.CurrentScale
+
+ def GetViewScale(self):
+ return self.ViewScale
+
+ def GetState(self):
+ return {"position": self.Editor.GetViewStart(),
+ "zoom": self.CurrentScale}
+
+ def SetState(self, state):
+ if self:
+ if state.has_key("zoom"):
+ self.SetScale(state["zoom"])
+ if state.has_key("position"):
+ self.Scroll(*state["position"])
+ self.RefreshVisibleElements()
+
+ def GetLogicalDC(self, buffered=False):
+ if buffered:
+ bitmap = wx.EmptyBitmap(*self.Editor.GetClientSize())
+ dc = wx.MemoryDC(bitmap)
+ else:
+ dc = wx.ClientDC(self.Editor)
+ dc.SetFont(self.GetFont())
+ if wx.VERSION >= (2, 6, 0):
+ self.Editor.DoPrepareDC(dc)
+ else:
+ self.Editor.PrepareDC(dc)
+ dc.SetUserScale(self.ViewScale[0], self.ViewScale[1])
+ return dc
+
+ def RefreshRect(self, rect, eraseBackground=True):
+ self.Editor.RefreshRect(rect, eraseBackground)
+
+ def Scroll(self, x, y):
+ self.Editor.Scroll(x, y)
+
+ def GetScrollPos(self, orientation):
+ return self.Editor.GetScrollPos(orientation)
+
+ def GetScrollRange(self, orientation):
+ return self.Editor.GetScrollRange(orientation)
+
+ def GetScrollThumb(self, orientation):
+ return self.Editor.GetScrollThumb(orientation)
+
+ def CalcUnscrolledPosition(self, x, y):
+ return self.Editor.CalcUnscrolledPosition(x, y)
+
+ def GetViewStart(self):
+ return self.Editor.GetViewStart()
+
+ def GetTextExtent(self, text):
+ return self.Editor.GetTextExtent(text)
+
+ def GetFont(self):
+ return self.Editor.GetFont()
+
+ def GetMiniTextExtent(self, text):
+ return self.MiniTextDC.GetTextExtent(text)
+
+ def GetMiniFont(self):
+ return self.MiniTextDC.GetFont()
+
+#-------------------------------------------------------------------------------
+# Element management functions
+#-------------------------------------------------------------------------------
+
+ def AddBlock(self, block):
+ self.Blocks[block.GetId()] = block
+
+ def AddWire(self, wire):
+ self.wire_id += 1
+ self.Wires[wire] = self.wire_id
+
+ def AddComment(self, comment):
+ self.Comments[comment.GetId()] = comment
+
+ def IsBlock(self, block):
+ return self.Blocks.get(block.GetId(), False)
+
+ def IsWire(self, wire):
+ return self.Wires.get(wire, False)
+
+ def IsComment(self, comment):
+ return self.Comments.get(comment.GetId(), False)
+
+ def RemoveBlock(self, block):
+ self.Blocks.pop(block.GetId())
+
+ def RemoveWire(self, wire):
+ self.Wires.pop(wire)
+
+ def RemoveComment(self, comment):
+ self.Comments.pop(comment.GetId())
+
+ def GetElements(self, sort_blocks=False, sort_wires=False, sort_comments=False):
+ blocks = self.Blocks.values()
+ wires = self.Wires.keys()
+ comments = self.Comments.values()
+ if sort_blocks:
+ blocks.sort(lambda x, y: cmp(x.GetId(), y.GetId()))
+ if sort_wires:
+ wires.sort(lambda x, y: cmp(self.Wires[x], self.Wires[y]))
+ if sort_comments:
+ comments.sort(lambda x, y: cmp(x.GetId(), y.GetId()))
+ return blocks + wires + comments
+
+ def GetConnectorByName(self, name):
+ for block in self.Blocks.itervalues():
+ if isinstance(block, FBD_Connector) and\
+ block.GetType() == CONNECTOR and\
+ block.GetName() == name:
+ return block
+ return None
+
+ def RefreshVisibleElements(self, xp = None, yp = None):
+ x, y = self.Editor.CalcUnscrolledPosition(0, 0)
+ if xp is not None:
+ x = xp * self.Editor.GetScrollPixelsPerUnit()[0]
+ if yp is not None:
+ y = yp * self.Editor.GetScrollPixelsPerUnit()[1]
+ width, height = self.Editor.GetClientSize()
+ screen = wx.Rect(int(x / self.ViewScale[0]), int(y / self.ViewScale[1]),
+ int(width / self.ViewScale[0]), int(height / self.ViewScale[1]))
+ for comment in self.Comments.itervalues():
+ comment.TestVisible(screen)
+ for wire in self.Wires.iterkeys():
+ wire.TestVisible(screen)
+ for block in self.Blocks.itervalues():
+ block.TestVisible(screen)
+
+ def GetElementIECPath(self, element):
+ iec_path = None
+ if isinstance(element, Wire) and element.EndConnected is not None:
+ block = element.EndConnected.GetParentBlock()
+ if isinstance(block, FBD_Block):
+ blockname = block.GetName()
+ connectorname = element.EndConnected.GetName()
+ if blockname != "":
+ iec_path = "%s.%s.%s"%(self.InstancePath, blockname, connectorname)
+ else:
+ if connectorname == "":
+ iec_path = "%s.%s%d"%(self.InstancePath, block.GetType(), block.GetId())
+ else:
+ iec_path = "%s.%s%d_%s"%(self.InstancePath, block.GetType(), block.GetId(), connectorname)
+ elif isinstance(block, FBD_Variable):
+ iec_path = "%s.%s"%(self.InstancePath, block.GetName())
+ elif isinstance(block, FBD_Connector):
+ connection = self.GetConnectorByName(block.GetName())
+ if connection is not None:
+ connector = connection.GetConnector()
+ if len(connector.Wires) == 1:
+ iec_path = self.GetElementIECPath(connector.Wires[0][0])
+ elif isinstance(element, LD_Contact):
+ iec_path = "%s.%s"%(self.InstancePath, element.GetName())
+ elif isinstance(element, SFC_Step):
+ iec_path = "%s.%s.X"%(self.InstancePath, element.GetName())
+ elif isinstance(element, SFC_Transition):
+ connectors = element.GetConnectors()
+ previous_steps = self.GetPreviousSteps(connectors["inputs"])
+ next_steps = self.GetNextSteps(connectors["outputs"])
+ iec_path = "%s.%s->%s"%(self.InstancePath, ",".join(previous_steps), ",".join(next_steps))
+ return iec_path
+
+#-------------------------------------------------------------------------------
+# Reset functions
+#-------------------------------------------------------------------------------
+
+ # Resets Viewer lists
+ def ResetView(self):
+ self.Blocks = {}
+ self.Wires = {}
+ self.Comments = {}
+ self.Subscribed = {}
+ self.SelectedElement = None
+ self.HighlightedElement = None
+ self.ToolTipElement = None
+
+ def Flush(self):
+ self.DeleteDataConsumers()
+ for block in self.Blocks.itervalues():
+ block.Flush()
+
+ # Remove all elements
+ def CleanView(self):
+ for block in self.Blocks.itervalues():
+ block.Clean()
+ self.ResetView()
+
+ # Changes Viewer mode
+ def SetMode(self, mode):
+ if self.Mode != mode or mode == MODE_SELECTION:
+ if self.Mode == MODE_MOTION:
+ wx.CallAfter(self.Editor.SetCursor, wx.NullCursor)
+ self.Mode = mode
+ self.SavedMode = False
+ else:
+ self.SavedMode = True
+ # Reset selection
+ if self.Mode != MODE_SELECTION and self.SelectedElement:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ if self.Mode == MODE_MOTION:
+ wx.CallAfter(self.Editor.SetCursor, wx.StockCursor(wx.CURSOR_HAND))
+ self.SavedMode = True
+
+ # Return current drawing mode
+ def GetDrawingMode(self):
+ return self.ParentWindow.GetDrawingMode()
+
+ # Buffer the last model state
+ def RefreshBuffer(self):
+ self.Controler.BufferProject()
+ if self.ParentWindow:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+ def StartBuffering(self):
+ if not self.Buffering:
+ self.Buffering = True
+ self.Controler.StartBuffering()
+ if self.ParentWindow:
+ self.ParentWindow.RefreshTitle()
+ self.ParentWindow.RefreshFileMenu()
+ self.ParentWindow.RefreshEditMenu()
+
+ def ResetBuffer(self):
+ if self.Buffering:
+ self.Controler.EndBuffering()
+ self.Buffering = False
+
+ def GetBufferState(self):
+ if not self.Debug:
+ return self.Controler.GetBufferState()
+ return False, False
+
+ def Undo(self):
+ if not self.Debug:
+ self.Controler.LoadPrevious()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def Redo(self):
+ if not self.Debug:
+ self.Controler.LoadNext()
+ self.ParentWindow.CloseTabsWithoutModel()
+
+ def HasNoModel(self):
+ if not self.Debug:
+ return self.Controler.GetEditedElement(self.TagName) is None
+ return False
+
+ # Refresh the current scaling
+ def RefreshScaling(self, refresh=True):
+ properties = self.Controler.GetProjectProperties(self.Debug)
+ scaling = properties["scaling"][self.CurrentLanguage]
+ if scaling[0] != 0 and scaling[1] != 0:
+ self.Scaling = scaling
+ if self.DrawGrid:
+ width = max(2, int(scaling[0] * self.ViewScale[0]))
+ height = max(2, int(scaling[1] * self.ViewScale[1]))
+ bitmap = wx.EmptyBitmap(width, height)
+ dc = wx.MemoryDC(bitmap)
+ dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
+ dc.Clear()
+ dc.SetPen(MiterPen(wx.Colour(180, 180, 180)))
+ dc.DrawPoint(0, 0)
+ self.GridBrush = wx.BrushFromBitmap(bitmap)
+ else:
+ self.GridBrush = wx.TRANSPARENT_BRUSH
+ else:
+ self.Scaling = None
+ self.GridBrush = wx.TRANSPARENT_BRUSH
+ page_size = properties["pageSize"]
+ if page_size != (0, 0):
+ self.PageSize = map(int, page_size)
+ self.PagePen = MiterPen(wx.Colour(180, 180, 180))
+ else:
+ self.PageSize = None
+ self.PagePen = wx.TRANSPARENT_PEN
+ if refresh:
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+
+
+#-------------------------------------------------------------------------------
+# Refresh functions
+#-------------------------------------------------------------------------------
+
+ def ElementNeedRefresh(self, element):
+ self.ElementRefreshList_lock.acquire()
+ self.ElementRefreshList.append(element)
+ self.ElementRefreshList_lock.release()
+
+ def RefreshNewData(self):
+ refresh_rect = None
+ self.ElementRefreshList_lock.acquire()
+ for element in self.ElementRefreshList:
+ if refresh_rect is None:
+ refresh_rect = element.GetRedrawRect()
+ else:
+ refresh_rect.Union(element.GetRedrawRect())
+ self.ElementRefreshList = []
+ self.ElementRefreshList_lock.release()
+
+ if refresh_rect is not None:
+ self.RefreshRect(self.GetScrolledRect(refresh_rect), False)
+ else:
+ DebugViewer.RefreshNewData(self)
+
+ # Refresh Viewer elements
+ def RefreshView(self, variablepanel=True, selection=None):
+ EditorPanel.RefreshView(self, variablepanel)
+
+ if self.ToolTipElement is not None:
+ self.ToolTipElement.ClearToolTip()
+ self.ToolTipElement = None
+
+ self.Inhibit(True)
+ self.current_id = 0
+ # Start by reseting Viewer
+ self.Flush()
+ self.ResetView()
+ self.ResetBuffer()
+ instance = {}
+ # List of ids of already loaded blocks
+ ids = []
+ # Load Blocks until they are all loaded
+ while instance is not None:
+ instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, exclude = ids, debug = self.Debug)
+ if instance is not None:
+ self.loadInstance(instance, ids, selection)
+ self.RefreshScrollBars()
+
+ for wire in self.Wires:
+ if not wire.IsConnectedCompatible():
+ wire.SetValid(False)
+ if self.Debug:
+ iec_path = self.GetElementIECPath(wire)
+ if iec_path is None:
+ block = wire.EndConnected.GetParentBlock()
+ if isinstance(block, LD_PowerRail):
+ wire.SetValue(True)
+ elif self.AddDataConsumer(iec_path.upper(), wire) is None:
+ wire.SetValue("undefined")
+
+ if self.Debug:
+ for block in self.Blocks.itervalues():
+ block.SpreadCurrent()
+ iec_path = self.GetElementIECPath(block)
+ if iec_path is not None:
+ self.AddDataConsumer(iec_path.upper(), block)
+
+ self.Inhibit(False)
+ self.RefreshVisibleElements()
+ self.ShowHighlights()
+ self.Refresh(False)
+
+ def GetPreviousSteps(self, connectors):
+ steps = []
+ for connector in connectors:
+ for wire, handle in connector.GetWires():
+ previous = wire.GetOtherConnected(connector).GetParentBlock()
+ if isinstance(previous, SFC_Step):
+ steps.append(previous.GetName())
+ elif isinstance(previous, SFC_Divergence) and previous.GetType() in [SIMULTANEOUS_CONVERGENCE, SELECTION_DIVERGENCE]:
+ connectors = previous.GetConnectors()
+ steps.extend(self.GetPreviousSteps(connectors["inputs"]))
+ return steps
+
+ def GetNextSteps(self, connectors):
+ steps = []
+ for connector in connectors:
+ for wire, handle in connector.GetWires():
+ next = wire.GetOtherConnected(connector).GetParentBlock()
+ if isinstance(next, SFC_Step):
+ steps.append(next.GetName())
+ elif isinstance(next, SFC_Jump):
+ steps.append(next.GetTarget())
+ elif isinstance(next, SFC_Divergence) and next.GetType() in [SIMULTANEOUS_DIVERGENCE, SELECTION_CONVERGENCE]:
+ connectors = next.GetConnectors()
+ steps.extend(self.GetNextSteps(connectors["outputs"]))
+ return steps
+
+ def GetMaxSize(self):
+ maxx = maxy = 0
+ for element in self.GetElements():
+ bbox = element.GetBoundingBox()
+ maxx = max(maxx, bbox.x + bbox.width)
+ maxy = max(maxy, bbox.y + bbox.height)
+ return maxx, maxy
+
+ def RefreshScrollBars(self, width_incr=0, height_incr=0):
+ xstart, ystart = self.GetViewStart()
+ window_size = self.Editor.GetClientSize()
+ maxx, maxy = self.GetMaxSize()
+ maxx = max(maxx + WINDOW_BORDER, (xstart * SCROLLBAR_UNIT + window_size[0]) / self.ViewScale[0])
+ maxy = max(maxy + WINDOW_BORDER, (ystart * SCROLLBAR_UNIT + window_size[1]) / self.ViewScale[1])
+ if self.rubberBand.IsShown():
+ extent = self.rubberBand.GetCurrentExtent()
+ maxx = max(maxx, extent.x + extent.width)
+ maxy = max(maxy, extent.y + extent.height)
+ maxx = int(maxx * self.ViewScale[0])
+ maxy = int(maxy * self.ViewScale[1])
+ self.Editor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
+ round(maxx / SCROLLBAR_UNIT) + width_incr, round(maxy / SCROLLBAR_UNIT) + height_incr,
+ xstart, ystart, True)
+
+ def EnsureVisible(self, block):
+ xstart, ystart = self.GetViewStart()
+ window_size = self.Editor.GetClientSize()
+ block_bbx = block.GetBoundingBox()
+
+ screen_minx, screen_miny = xstart * SCROLLBAR_UNIT, ystart * SCROLLBAR_UNIT
+ screen_maxx, screen_maxy = screen_minx + window_size[0], screen_miny + window_size[1]
+ block_minx = int(block_bbx.x * self.ViewScale[0])
+ block_miny = int(block_bbx.y * self.ViewScale[1])
+ block_maxx = int(round((block_bbx.x + block_bbx.width) * self.ViewScale[0]))
+ block_maxy = int(round((block_bbx.y + block_bbx.height) * self.ViewScale[1]))
+
+ xpos, ypos = xstart, ystart
+ if block_minx < screen_minx and block_maxx < screen_maxx:
+ xpos -= (screen_minx - block_minx) / SCROLLBAR_UNIT + 1
+ elif block_maxx > screen_maxx and block_minx > screen_minx:
+ xpos += (block_maxx - screen_maxx) / SCROLLBAR_UNIT + 1
+ if block_miny < screen_miny and block_maxy < screen_maxy:
+ ypos -= (screen_miny - block_miny) / SCROLLBAR_UNIT + 1
+ elif block_maxy > screen_maxy and block_miny > screen_miny:
+ ypos += (block_maxy - screen_maxy) / SCROLLBAR_UNIT + 1
+ self.Scroll(xpos, ypos)
+
+ def SelectInGroup(self, element):
+ element.SetSelected(True)
+ if self.SelectedElement is None:
+ self.SelectedElement = element
+ elif isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.SelectElement(element)
+ else:
+ group = Graphic_Group(self)
+ group.SelectElement(self.SelectedElement)
+ group.SelectElement(element)
+ self.SelectedElement = group
+
+ # Load instance from given informations
+ def loadInstance(self, instance, ids, selection):
+ ids.append(instance["id"])
+ self.current_id = max(self.current_id, instance["id"])
+ creation_function = ElementCreationFunctions.get(instance["type"], None)
+ connectors = {"inputs" : [], "outputs" : []}
+ specific_values = instance["specific_values"]
+ if creation_function is not None:
+ element = creation_function(self, instance["id"], specific_values)
+ if isinstance(element, SFC_Step):
+ if len(instance["inputs"]) > 0:
+ element.AddInput()
+ else:
+ element.RemoveInput()
+ if len(instance["outputs"]) > 0:
+ element.AddOutput()
+ if isinstance(element, SFC_Transition) and specific_values["condition_type"] == "connection":
+ connector = element.GetConditionConnector()
+ self.CreateWires(connector, id, specific_values["connection"]["links"], ids, selection)
+ else:
+ executionControl = False
+ for input in instance["inputs"]:
+ if input["negated"]:
+ connectors["inputs"].append((input["name"], None, "negated"))
+ elif input["edge"]:
+ connectors["inputs"].append((input["name"], None, input["edge"]))
+ else:
+ connectors["inputs"].append((input["name"], None, "none"))
+ for output in instance["outputs"]:
+ if output["negated"]:
+ connectors["outputs"].append((output["name"], None, "negated"))
+ elif output["edge"]:
+ connectors["outputs"].append((output["name"], None, output["edge"]))
+ else:
+ connectors["outputs"].append((output["name"], None, "none"))
+ if len(connectors["inputs"]) > 0 and connectors["inputs"][0][0] == "EN":
+ connectors["inputs"].pop(0)
+ executionControl = True
+ if len(connectors["outputs"]) > 0 and connectors["outputs"][0][0] == "ENO":
+ connectors["outputs"].pop(0)
+ executionControl = True
+ if specific_values["name"] is None:
+ specific_values["name"] = ""
+ element = FBD_Block(self, instance["type"], specific_values["name"],
+ instance["id"], len(connectors["inputs"]),
+ connectors=connectors, executionControl=executionControl,
+ executionOrder=specific_values["executionOrder"])
+ if isinstance(element, Comment):
+ self.AddComment(element)
+ else:
+ self.AddBlock(element)
+ connectors = element.GetConnectors()
+ if isinstance(element, SFC_Divergence):
+ element.SetPosition(instance["x"], instance["y"])
+ element.SetSize(instance["width"], instance["height"])
+ for i, input_connector in enumerate(instance["inputs"]):
+ if i < len(connectors["inputs"]):
+ connector = connectors["inputs"][i]
+ connector.SetPosition(wx.Point(*input_connector["position"]))
+ if input_connector.get("negated", False):
+ connector.SetNegated(True)
+ if input_connector.get("edge", "none") != "none":
+ connector.SetEdge(input_connector["edge"])
+ self.CreateWires(connector, instance["id"], input_connector["links"], ids, selection)
+ for i, output_connector in enumerate(instance["outputs"]):
+ if i < len(connectors["outputs"]):
+ connector = connectors["outputs"][i]
+ if output_connector.get("negated", False):
+ connector.SetNegated(True)
+ if output_connector.get("edge", "none") != "none":
+ connector.SetEdge(output_connector["edge"])
+ connector.SetPosition(wx.Point(*output_connector["position"]))
+ if not isinstance(element, SFC_Divergence):
+ element.SetPosition(instance["x"], instance["y"])
+ element.SetSize(instance["width"], instance["height"])
+ if selection is not None and selection[0].get(instance["id"], False):
+ self.SelectInGroup(element)
+
+ def CreateWires(self, start_connector, id, links, ids, selection=None):
+ for link in links:
+ refLocalId = link["refLocalId"]
+ if refLocalId is not None:
+ if refLocalId not in ids:
+ new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
+ if new_instance is not None:
+ self.loadInstance(new_instance, ids, selection)
+ connected = self.FindElementById(refLocalId)
+ if connected is not None:
+ points = link["points"]
+ end_connector = connected.GetConnector(wx.Point(points[-1][0], points[-1][1]), link["formalParameter"])
+ if end_connector is not None:
+ 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.AddWire(wire)
+ if selection is not None and (\
+ selection[1].get((id, refLocalId), False) or \
+ selection[1].get((refLocalId, id), False)):
+ self.SelectInGroup(wire)
+
+ def IsOfType(self, type, reference):
+ return self.Controler.IsOfType(type, reference, self.Debug)
+
+ def IsEndType(self, type):
+ return self.Controler.IsEndType(type)
+
+ def GetBlockType(self, type, inputs = None):
+ return self.Controler.GetBlockType(type, inputs, self.Debug)
+
+#-------------------------------------------------------------------------------
+# Search Element functions
+#-------------------------------------------------------------------------------
+
+ def FindBlock(self, event):
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ for block in self.Blocks.itervalues():
+ if block.HitTest(pos) or block.TestHandle(event) != (0, 0):
+ return block
+ return None
+
+ def FindWire(self, event):
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ for wire in self.Wires:
+ if wire.HitTest(pos) or wire.TestHandle(event) != (0, 0):
+ return wire
+ return None
+
+ def FindElement(self, event, exclude_group = False, connectors = True):
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ if self.SelectedElement and not (exclude_group and isinstance(self.SelectedElement, Graphic_Group)):
+ if self.SelectedElement.HitTest(pos, connectors) or self.SelectedElement.TestHandle(event) != (0, 0):
+ return self.SelectedElement
+ for element in self.GetElements():
+ if element.HitTest(pos, connectors) or element.TestHandle(event) != (0, 0):
+ return element
+ return None
+
+ def FindBlockConnector(self, pos, direction = None, exclude = None):
+ for block in self.Blocks.itervalues():
+ result = block.TestConnector(pos, direction, exclude)
+ if result:
+ return result
+ return None
+
+ def FindElementById(self, id):
+ block = self.Blocks.get(id, None)
+ if block is not None:
+ return block
+ comment = self.Comments.get(id, None)
+ if comment is not None:
+ return comment
+ return None
+
+ def SearchElements(self, bbox):
+ elements = []
+ for element in self.GetElements():
+ if element.IsInSelection(bbox):
+ elements.append(element)
+ return elements
+
+ def SelectAll(self):
+ if self.SelectedElement is not None:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = Graphic_Group(self)
+ for element in self.GetElements():
+ self.SelectedElement.SelectElement(element)
+ self.SelectedElement.SetSelected(True)
+
+#-------------------------------------------------------------------------------
+# Popup menu functions
+#-------------------------------------------------------------------------------
+
+ def GetForceVariableMenuFunction(self, iec_path, element):
+ iec_type = self.GetDataType(iec_path)
+ def ForceVariableFunction(event):
+ if iec_type is not None:
+ dialog = ForceVariableDialog(self.ParentWindow, iec_type, str(element.GetValue()))
+ if dialog.ShowModal() == wx.ID_OK:
+ self.ParentWindow.AddDebugVariable(iec_path)
+ self.ForceDataValue(iec_path, dialog.GetValue())
+ return ForceVariableFunction
+
+ def GetReleaseVariableMenuFunction(self, iec_path):
+ def ReleaseVariableFunction(event):
+ self.ReleaseDataValue(iec_path)
+ return ReleaseVariableFunction
+
+ def PopupForceMenu(self):
+ iec_path = self.GetElementIECPath(self.SelectedElement)
+ if iec_path is not None:
+ menu = wx.Menu(title='')
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Force value"))
+ self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.SelectedElement), id=new_id)
+ new_id = wx.NewId()
+ AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Release value"))
+ self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), id=new_id)
+ if self.SelectedElement.IsForced():
+ menu.Enable(new_id, True)
+ else:
+ menu.Enable(new_id, False)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+ def PopupBlockMenu(self, connector = None):
+ menu = wx.Menu(title='')
+ if connector is not None and connector.IsCompatible("BOOL"):
+ self.AddBlockPinMenuItems(menu, connector)
+ else:
+ edit = self.SelectedElement.GetType() in self.Controler.GetProjectPouNames(self.Debug)
+ self.AddDefaultMenuItems(menu, block=True, edit=edit)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+ def PopupWireMenu(self, delete=True):
+ menu = wx.Menu(title='')
+ self.AddWireMenuItems(menu, delete)
+ menu.AppendSeparator()
+ self.AddDefaultMenuItems(menu, block=True)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+ def PopupDivergenceMenu(self, connector):
+ menu = wx.Menu(title='')
+ self.AddDivergenceMenuItems(menu, connector)
+ menu.AppendSeparator()
+ self.AddDefaultMenuItems(menu, block=True)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+ def PopupGroupMenu(self):
+ menu = wx.Menu(title='')
+ align_menu = wx.Menu(title='')
+ self.AddAlignmentMenuItems(align_menu)
+ menu.AppendMenu(-1, _(u'Alignment'), align_menu)
+ menu.AppendSeparator()
+ self.AddDefaultMenuItems(menu, block=True)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+ def PopupDefaultMenu(self, block=True):
+ menu = wx.Menu(title='')
+ self.AddDefaultMenuItems(menu, block=block)
+ self.Editor.PopupMenu(menu)
+ menu.Destroy()
+
+#-------------------------------------------------------------------------------
+# Menu items functions
+#-------------------------------------------------------------------------------
+
+ def OnAlignLeftMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(ALIGN_LEFT, None)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnAlignCenterMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(ALIGN_CENTER, None)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnAlignRightMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(ALIGN_RIGHT, None)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnAlignTopMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(None, ALIGN_TOP)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnAlignMiddleMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(None, ALIGN_MIDDLE)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnAlignBottomMenu(self, event):
+ if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.AlignElements(None, ALIGN_BOTTOM)
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnNoModifierMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.SelectedElement.SetConnectorNegated(False)
+ self.SelectedElement.Refresh()
+ self.RefreshBuffer()
+
+ def OnNegatedMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.SelectedElement.SetConnectorNegated(True)
+ self.SelectedElement.Refresh()
+ self.RefreshBuffer()
+
+ def OnRisingEdgeMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.SelectedElement.SetConnectorEdge("rising")
+ self.SelectedElement.Refresh()
+ self.RefreshBuffer()
+
+ def OnFallingEdgeMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.SelectedElement.SetConnectorEdge("falling")
+ self.SelectedElement.Refresh()
+ self.RefreshBuffer()
+
+ def OnAddSegmentMenu(self, event):
+ if self.SelectedElement is not None and self.IsWire(self.SelectedElement):
+ self.SelectedElement.AddSegment()
+ self.SelectedElement.Refresh()
+
+ def OnDeleteSegmentMenu(self, event):
+ if self.SelectedElement is not None and self.IsWire(self.SelectedElement):
+ self.SelectedElement.DeleteSegment()
+ self.SelectedElement.Refresh()
+
+ def OnAddBranchMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.AddDivergenceBranch(self.SelectedElement)
+
+ def OnDeleteBranchMenu(self, event):
+ if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
+ self.RemoveDivergenceBranch(self.SelectedElement)
+
+ def OnEditBlockMenu(self, event):
+ if self.SelectedElement is not None:
+ self.ParentWindow.EditProjectElement(ITEM_POU, "P::%s"%self.SelectedElement.GetType())
+
+ def OnDeleteMenu(self, event):
+ if self.SelectedElement is not None:
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ self.RefreshBuffer()
+ self.Refresh(False)
+
+ def OnClearExecutionOrderMenu(self, event):
+ self.Controler.ClearEditedElementExecutionOrder(self.TagName)
+ self.RefreshBuffer()
+ self.RefreshView()
+
+ def OnResetExecutionOrderMenu(self, event):
+ self.Controler.ResetEditedElementExecutionOrder(self.TagName)
+ self.RefreshBuffer()
+ self.RefreshView()
+
+ def GetAddMenuCallBack(self, func, *args):
+ def AddMenuCallBack(event):
+ wx.CallAfter(func, self.rubberBand.GetCurrentExtent(), *args)
+ return AddMenuCallBack
+
+ def GetClipboardCallBack(self, func):
+ def ClipboardCallback(event):
+ wx.CallAfter(func)
+ return ClipboardCallback
+
+#-------------------------------------------------------------------------------
+# Mouse event functions
+#-------------------------------------------------------------------------------
+
+ def OnViewerMouseEvent(self, event):
+ if not event.Entering():
+ self.ResetBuffer()
+ element = None
+ if not event.Leaving() and not event.LeftUp() and not event.LeftDClick():
+ element = self.FindElement(event, True, False)
+ if self.ToolTipElement is not None:
+ self.ToolTipElement.ClearToolTip()
+ self.ToolTipElement = element
+ if self.ToolTipElement is not None:
+ tooltip_pos = self.Editor.ClientToScreen(event.GetPosition())
+ tooltip_pos.x += 10
+ tooltip_pos.y += 10
+ self.ToolTipElement.CreateToolTip(tooltip_pos)
+ event.Skip()
+
+ def OnViewerLeftDown(self, event):
+ self.Editor.CaptureMouse()
+ if self.Mode == MODE_SELECTION:
+ dc = self.GetLogicalDC()
+ pos = event.GetLogicalPosition(dc)
+ if event.ShiftDown() and not event.ControlDown() and self.SelectedElement is not None:
+ element = self.FindElement(event, True)
+ if element is not None:
+ if isinstance(self.SelectedElement, Graphic_Group):
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement.SelectElement(element)
+ elif self.SelectedElement is not None:
+ 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:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, dc, self.Scaling)
+ else:
+ element = self.FindElement(event)
+ if not self.Debug and (element is None or element.TestHandle(event) == (0, 0)):
+ connector = self.FindBlockConnector(pos)
+ else:
+ connector = None
+ if not self.Debug and self.DrawingWire:
+ self.DrawingWire = False
+ if self.SelectedElement is not None:
+ if element is None or element.TestHandle(event) == (0, 0):
+ connector = self.FindBlockConnector(pos, self.SelectedElement.GetConnectionDirection())
+ if connector is not None:
+ event.Dragging = lambda : True
+ self.SelectedElement.OnMotion(event, dc, self.Scaling)
+ if self.SelectedElement.EndConnected is not None:
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.RefreshModel()
+ self.SelectedElement.SetSelected(True)
+ element = self.SelectedElement
+ self.RefreshBuffer()
+ else:
+ rect = self.SelectedElement.GetRedrawRect()
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ element = None
+ self.RefreshRect(self.GetScrolledRect(rect), False)
+ elif not self.Debug and connector is not None and not event.ControlDown():
+ self.DrawingWire = True
+ scaled_pos = GetScaledEventPosition(event, dc, self.Scaling)
+ if (connector.GetDirection() == EAST):
+ wire = Wire(self, [wx.Point(pos.x, pos.y), EAST], [wx.Point(scaled_pos.x, scaled_pos.y), WEST])
+ else:
+ wire = Wire(self, [wx.Point(pos.x, pos.y), WEST], [wx.Point(scaled_pos.x, scaled_pos.y), EAST])
+ wire.oldPos = scaled_pos
+ wire.Handle = (HANDLE_POINT, 0)
+ wire.ProcessDragging(0, 0, event, None)
+ wire.Handle = (HANDLE_POINT, 1)
+ self.AddWire(wire)
+ if self.SelectedElement is not None:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = wire
+ if self.HighlightedElement is not None:
+ self.HighlightedElement.SetHighlighted(False)
+ self.HighlightedElement = wire
+ self.RefreshVisibleElements()
+ self.SelectedElement.SetHighlighted(True)
+ else:
+ if self.SelectedElement is not None and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ if element is not None:
+ self.SelectedElement = element
+ if self.Debug:
+ self.StartMousePos = event.GetPosition()
+ Graphic_Element.OnLeftDown(self.SelectedElement, event, dc, self.Scaling)
+ else:
+ self.SelectedElement.OnLeftDown(event, dc, self.Scaling)
+ self.SelectedElement.Refresh()
+ else:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, dc, self.Scaling)
+ elif self.Mode in [MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT,
+ MODE_CONTACT, MODE_COIL, MODE_POWERRAIL, MODE_INITIALSTEP,
+ MODE_STEP, MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION]:
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
+ elif self.Mode == MODE_MOTION:
+ self.StartMousePos = event.GetPosition()
+ self.StartScreenPos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
+ event.Skip()
+
+ def OnViewerLeftUp(self, event):
+ self.StartMousePos = None
+ if self.rubberBand.IsShown():
+ if self.Mode == MODE_SELECTION:
+ new_elements = self.SearchElements(self.rubberBand.GetCurrentExtent())
+ self.rubberBand.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ if event.ShiftDown() and self.SelectedElement is not None:
+ if isinstance(self.SelectedElement, Graphic_Group):
+ elements = self.SelectedElement.GetElements()
+ else:
+ elements = [self.SelectedElement]
+ for element in elements:
+ if element not in new_elements:
+ new_elements.append(element)
+ if len(new_elements) == 1:
+ self.SelectedElement = new_elements[0]
+ self.SelectedElement.SetSelected(True)
+ elif len(new_elements) > 1:
+ self.SelectedElement = Graphic_Group(self)
+ self.SelectedElement.SetElements(new_elements)
+ self.SelectedElement.SetSelected(True)
+ else:
+ bbox = self.rubberBand.GetCurrentExtent()
+ self.rubberBand.OnLeftUp(event, self.GetLogicalDC(), self.Scaling)
+ if self.Mode == MODE_BLOCK:
+ wx.CallAfter(self.AddNewBlock, bbox)
+ elif self.Mode == MODE_VARIABLE:
+ wx.CallAfter(self.AddNewVariable, bbox)
+ elif self.Mode == MODE_CONNECTION:
+ wx.CallAfter(self.AddNewConnection, bbox)
+ elif self.Mode == MODE_COMMENT:
+ wx.CallAfter(self.AddNewComment, bbox)
+ elif self.Mode == MODE_CONTACT:
+ wx.CallAfter(self.AddNewContact, bbox)
+ elif self.Mode == MODE_COIL:
+ wx.CallAfter(self.AddNewCoil, bbox)
+ elif self.Mode == MODE_POWERRAIL:
+ wx.CallAfter(self.AddNewPowerRail, bbox)
+ elif self.Mode == MODE_INITIALSTEP:
+ wx.CallAfter(self.AddNewStep, bbox, True)
+ elif self.Mode == MODE_STEP:
+ wx.CallAfter(self.AddNewStep, bbox, False)
+ elif self.Mode == MODE_TRANSITION:
+ wx.CallAfter(self.AddNewTransition, bbox)
+ elif self.Mode == MODE_DIVERGENCE:
+ wx.CallAfter(self.AddNewDivergence, bbox)
+ elif self.Mode == MODE_JUMP:
+ wx.CallAfter(self.AddNewJump, bbox)
+ elif self.Mode == MODE_ACTION:
+ wx.CallAfter(self.AddNewActionBlock, bbox)
+ elif self.Mode == MODE_SELECTION and self.SelectedElement is not None:
+ dc = self.GetLogicalDC()
+ if not self.Debug and self.DrawingWire:
+ pos = event.GetLogicalPosition(dc)
+ connector = self.FindBlockConnector(pos, self.SelectedElement.GetConnectionDirection())
+ if self.SelectedElement.EndConnected is not None:
+ self.DrawingWire = False
+ self.SelectedElement.StartConnected.HighlightParentBlock(False)
+ self.SelectedElement.EndConnected.HighlightParentBlock(False)
+ self.SelectedElement.ResetPoints()
+ self.SelectedElement.OnMotion(event, dc, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ self.SelectedElement.RefreshModel()
+ self.SelectedElement.SetSelected(True)
+ self.SelectedElement.HighlightPoint(pos)
+ self.RefreshBuffer()
+ elif connector is None or self.SelectedElement.GetDragging():
+ self.DrawingWire = False
+ rect = self.SelectedElement.GetRedrawRect()
+ wire = self.SelectedElement
+ self.SelectedElement = self.SelectedElement.StartConnected.GetParentBlock()
+ self.SelectedElement.SetSelected(True)
+ rect.Union(self.SelectedElement.GetRedrawRect())
+ wire.Delete()
+ self.RefreshRect(self.GetScrolledRect(rect), False)
+ else:
+ if self.Debug:
+ Graphic_Element.OnLeftUp(self.SelectedElement, event, dc, self.Scaling)
+ else:
+ self.SelectedElement.OnLeftUp(event, dc, self.Scaling)
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ elif self.Mode == MODE_MOTION:
+ self.StartMousePos = None
+ self.StartScreenPos = None
+ if self.Mode != MODE_SELECTION and not self.SavedMode:
+ wx.CallAfter(self.ParentWindow.ResetCurrentMode)
+ if self.Editor.HasCapture():
+ self.Editor.ReleaseMouse()
+ event.Skip()
+
+ def OnViewerMiddleDown(self, event):
+ self.Editor.CaptureMouse()
+ self.StartMousePos = event.GetPosition()
+ self.StartScreenPos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
+ event.Skip()
+
+ def OnViewerMiddleUp(self, event):
+ self.StartMousePos = None
+ self.StartScreenPos = None
+ if self.Editor.HasCapture():
+ self.Editor.ReleaseMouse()
+ event.Skip()
+
+ def OnViewerRightDown(self, event):
+ self.Editor.CaptureMouse()
+ if self.Mode == MODE_SELECTION:
+ element = self.FindElement(event)
+ if self.SelectedElement is not None and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ self.SelectedElement = None
+ if element is not None:
+ self.SelectedElement = element
+ if self.Debug:
+ Graphic_Element.OnRightDown(self.SelectedElement, event, self.GetLogicalDC(), self.Scaling)
+ else:
+ self.SelectedElement.OnRightDown(event, self.GetLogicalDC(), self.Scaling)
+ self.SelectedElement.Refresh()
+ event.Skip()
+
+ def OnViewerRightUp(self, event):
+ dc = self.GetLogicalDC()
+ self.rubberBand.Reset()
+ self.rubberBand.OnLeftDown(event, dc, self.Scaling)
+ self.rubberBand.OnLeftUp(event, dc, self.Scaling)
+ if self.SelectedElement is not None:
+ if self.Debug:
+ Graphic_Element.OnRightUp(self.SelectedElement, event, self.GetLogicalDC(), self.Scaling)
+ else:
+ self.SelectedElement.OnRightUp(event, self.GetLogicalDC(), self.Scaling)
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ elif not self.Debug:
+ self.PopupDefaultMenu(False)
+ if self.Editor.HasCapture():
+ self.Editor.ReleaseMouse()
+ event.Skip()
+
+ def OnViewerLeftDClick(self, event):
+ element = self.FindElement(event, connectors=False)
+ if self.Mode == MODE_SELECTION and element is not None:
+ if self.SelectedElement is not None and self.SelectedElement != element:
+ self.SelectedElement.SetSelected(False)
+ if self.HighlightedElement is not None and self.HighlightedElement != element:
+ self.HighlightedElement.SetHighlighted(False)
+
+ self.SelectedElement = element
+ self.HighlightedElement = element
+ self.SelectedElement.SetHighlighted(True)
+
+ if self.Debug:
+ if self.IsBlock(self.SelectedElement):
+ instance_type = self.SelectedElement.GetType()
+ pou_type = {
+ "program": ITEM_PROGRAM,
+ "functionBlock": ITEM_FUNCTIONBLOCK,
+ }.get(self.Controler.GetPouType(instance_type))
+ if pou_type is not None and instance_type in self.Controler.GetProjectPouNames(self.Debug):
+ self.ParentWindow.OpenDebugViewer(pou_type,
+ "%s.%s"%(self.InstancePath, self.SelectedElement.GetName()),
+ self.Controler.ComputePouName(instance_type))
+ else:
+ iec_path = self.GetElementIECPath(self.SelectedElement)
+ if iec_path is not None:
+ if isinstance(self.SelectedElement, Wire):
+ if self.SelectedElement.EndConnected is not None:
+ var_type = self.SelectedElement.EndConnected.GetType()
+ if self.Controler.IsOfType(var_type, "ANY_NUM", self.Debug) or\
+ self.Controler.IsOfType(var_type, "ANY_BIT", self.Debug):
+ self.ParentWindow.OpenGraphicViewer(iec_path)
+ else:
+ self.ParentWindow.OpenGraphicViewer(iec_path)
+ elif event.ControlDown() and not event.ShiftDown():
+ instance_type = self.SelectedElement.GetType()
+ if self.IsBlock(self.SelectedElement) and instance_type in self.Controler.GetProjectPouNames(self.Debug):
+ self.ParentWindow.EditProjectElement(ITEM_POU,
+ self.Controler.ComputePouName(instance_type))
+ else:
+ self.SelectedElement.OnLeftDClick(event, self.GetLogicalDC(), self.Scaling)
+ elif event.ControlDown() and event.ShiftDown():
+ movex, movey = self.SelectedElement.AdjustToScaling(self.Scaling)
+ self.SelectedElement.RefreshModel()
+ self.RefreshBuffer()
+ if movex != 0 or movey != 0:
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+ else:
+ self.SelectedElement.OnLeftDClick(event, self.GetLogicalDC(), self.Scaling)
+ event.Skip()
+
+ def OnViewerMotion(self, event):
+ if self.Editor.HasCapture() and not event.Dragging():
+ return
+ refresh = False
+ dc = self.GetLogicalDC()
+ pos = GetScaledEventPosition(event, dc, self.Scaling)
+ if event.MiddleIsDown() or self.Mode == MODE_MOTION:
+ if self.StartMousePos is not None and self.StartScreenPos is not None:
+ new_pos = event.GetPosition()
+ xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
+ ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
+ scrollx = max(0, self.StartScreenPos[0] - (new_pos[0] - self.StartMousePos[0]) / SCROLLBAR_UNIT)
+ scrolly = max(0, self.StartScreenPos[1] - (new_pos[1] - self.StartMousePos[1]) / SCROLLBAR_UNIT)
+ if scrollx > xmax or scrolly > ymax:
+ self.RefreshScrollBars(max(0, scrollx - xmax), max(0, scrolly - ymax))
+ self.Scroll(scrollx, scrolly)
+ else:
+ self.Scroll(scrollx, scrolly)
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ else:
+ if not event.Dragging():
+ highlighted = self.FindElement(event, connectors=False)
+ if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
+ self.HighlightedElement.SetHighlighted(False)
+ self.HighlightedElement = None
+ if highlighted is not None:
+ if isinstance(highlighted, (Wire, Graphic_Group)):
+ highlighted.HighlightPoint(pos)
+ if self.HighlightedElement != highlighted:
+ highlighted.SetHighlighted(True)
+ self.HighlightedElement = highlighted
+ if self.rubberBand.IsShown():
+ self.rubberBand.OnMotion(event, dc, self.Scaling)
+ elif not self.Debug and self.Mode == MODE_SELECTION and self.SelectedElement is not None:
+ if self.DrawingWire:
+ connector = self.FindBlockConnector(pos, self.SelectedElement.GetConnectionDirection(), self.SelectedElement.EndConnected)
+ if not connector or self.SelectedElement.EndConnected == None:
+ self.SelectedElement.ResetPoints()
+ movex, movey = self.SelectedElement.OnMotion(event, dc, self.Scaling)
+ self.SelectedElement.GeneratePoints()
+ if movex != 0 or movey != 0:
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+ else:
+ self.SelectedElement.HighlightPoint(pos)
+ else:
+ movex, movey = self.SelectedElement.OnMotion(event, dc, self.Scaling)
+ if movex != 0 or movey != 0:
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+ elif self.Debug and self.StartMousePos is not None and event.Dragging():
+ pos = event.GetPosition()
+ if abs(self.StartMousePos.x - pos.x) > 5 or abs(self.StartMousePos.y - pos.y) > 5:
+ iec_path = self.GetElementIECPath(self.SelectedElement)
+ if iec_path is not None:
+ self.StartMousePos = None
+ if self.HighlightedElement is not None:
+ self.HighlightedElement.SetHighlighted(False)
+ self.HighlightedElement = None
+ data = wx.TextDataObject(str((iec_path, "debug")))
+ dragSource = wx.DropSource(self.Editor)
+ dragSource.SetData(data)
+ dragSource.DoDragDrop()
+ self.UpdateScrollPos(event)
+ event.Skip()
+
+ def OnLeaveViewer(self, event):
+ if self.StartScreenPos is None:
+ self.StartMousePos = None
+ if self.SelectedElement is not None and self.SelectedElement.GetDragging():
+ event.Skip()
+ elif self.HighlightedElement is not None:
+ self.HighlightedElement.SetHighlighted(False)
+ self.HighlightedElement = None
+ event.Skip()
+
+ def UpdateScrollPos(self, event):
+ if (event.Dragging() and self.SelectedElement is not None) or self.rubberBand.IsShown():
+ position = event.GetPosition()
+ move_window = wx.Point()
+ window_size = self.Editor.GetClientSize()
+ xstart, ystart = self.GetViewStart()
+ if position.x < SCROLL_ZONE and xstart > 0:
+ move_window.x = -1
+ elif position.x > window_size[0] - SCROLL_ZONE:
+ move_window.x = 1
+ if position.y < SCROLL_ZONE and ystart > 0:
+ move_window.y = -1
+ elif position.y > window_size[1] - SCROLL_ZONE:
+ move_window.y = 1
+ if move_window.x != 0 or move_window.y != 0:
+ self.RefreshVisibleElements(xp = xstart + move_window.x, yp = ystart + move_window.y)
+ self.Scroll(xstart + move_window.x, ystart + move_window.y)
+ self.RefreshScrollBars(move_window.x, move_window.y)
+
+#-------------------------------------------------------------------------------
+# Keyboard event functions
+#-------------------------------------------------------------------------------
+
+ ARROW_KEY_MOVE = {
+ wx.WXK_LEFT: (-1, 0),
+ wx.WXK_RIGHT: (1, 0),
+ wx.WXK_UP: (0, -1),
+ wx.WXK_DOWN: (0, 1),
+ }
+
+ def OnChar(self, event):
+ xpos, ypos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
+ xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
+ ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
+ keycode = event.GetKeyCode()
+ if self.Scaling is not None:
+ scaling = self.Scaling
+ else:
+ scaling = (8, 8)
+ if not self.Debug and keycode == wx.WXK_DELETE and self.SelectedElement is not None:
+ rect = self.SelectedElement.GetRedrawRect(1, 1)
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ wx.CallAfter(self.SetCurrentCursor, 0)
+ self.RefreshRect(self.GetScrolledRect(rect), False)
+ elif not self.Debug and keycode == wx.WXK_RETURN and self.SelectedElement is not None:
+ self.SelectedElement.OnLeftDClick(event, self.GetLogicalDC(), self.Scaling)
+ elif self.ARROW_KEY_MOVE.has_key(keycode):
+ move = self.ARROW_KEY_MOVE[keycode]
+ if event.ControlDown() and event.ShiftDown():
+ self.Scroll({-1: 0, 0: xpos, 1: xmax}[move[0]],
+ {-1: 0, 0: ypos, 1: ymax}[move[1]])
+ self.RefreshVisibleElements()
+ elif event.ControlDown():
+ self.Scroll(xpos + move[0], ypos + move[1])
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ elif not self.Debug and self.SelectedElement is not None:
+ movex, movey = move
+ if not event.AltDown() or event.ShiftDown():
+ movex *= scaling[0]
+ movey *= scaling[1]
+ if event.ShiftDown() and not event.AltDown():
+ movex *= 10
+ movey *= 10
+ self.SelectedElement.Move(movex, movey)
+ self.StartBuffering()
+ self.SelectedElement.RefreshModel()
+ self.RefreshScrollBars()
+ self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+ elif not self.Debug and keycode == wx.WXK_SPACE and self.SelectedElement is not None and self.SelectedElement.Dragging:
+ if self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement):
+ block = self.CopyBlock(self.SelectedElement, wx.Point(*self.SelectedElement.GetPosition()))
+ event = wx.MouseEvent()
+ event.m_x, event.m_y = self.Editor.ScreenToClient(wx.GetMousePosition())
+ dc = self.GetLogicalDC()
+ self.SelectedElement.OnLeftUp(event, dc, self.Scaling)
+ self.SelectedElement.SetSelected(False)
+ block.OnLeftDown(event, dc, self.Scaling)
+ self.SelectedElement = block
+ self.SelectedElement.SetSelected(True)
+ self.RefreshVariablePanel()
+ self.RefreshVisibleElements()
+ else:
+ event.Skip()
+ elif keycode == ord("+"):
+ self.SetScale(self.CurrentScale + 1)
+ self.ParentWindow.RefreshDisplayMenu()
+ elif keycode == ord("-"):
+ self.SetScale(self.CurrentScale - 1)
+ self.ParentWindow.RefreshDisplayMenu()
+ else:
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Model adding functions from Drop Target
+#-------------------------------------------------------------------------------
+
+ def AddVariableBlock(self, x, y, scaling, var_class, var_name, var_type):
+ id = self.GetNewId()
+ variable = FBD_Variable(self, var_class, var_name, var_type, id)
+ width, height = variable.GetMinSize()
+ if scaling is not None:
+ x = round(float(x) / float(scaling[0])) * scaling[0]
+ y = round(float(y) / float(scaling[1])) * scaling[1]
+ width = round(float(width) / float(scaling[0]) + 0.5) * scaling[0]
+ height = round(float(height) / float(scaling[1]) + 0.5) * scaling[1]
+ variable.SetPosition(x, y)
+ variable.SetSize(width, height)
+ self.AddBlock(variable)
+ self.Controler.AddEditedElementVariable(self.GetTagName(), id, var_class)
+ self.RefreshVariableModel(variable)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.Refresh(False)
+
+#-------------------------------------------------------------------------------
+# Model adding functions
+#-------------------------------------------------------------------------------
+
+ def GetScaledSize(self, width, height):
+ if self.Scaling is not None:
+ width = round(float(width) / float(self.Scaling[0]) + 0.4) * self.Scaling[0]
+ height = round(float(height) / float(self.Scaling[1]) + 0.4) * self.Scaling[1]
+ return width, height
+
+ def AddNewBlock(self, bbox):
+ dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
+ dialog.SetMinBlockSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ values.setdefault("name", "")
+ block = FBD_Block(self, values["type"], values["name"], id,
+ values["extension"], values["inputs"],
+ executionControl = values["executionControl"],
+ executionOrder = values["executionOrder"])
+ block.SetPosition(bbox.x, bbox.y)
+ block.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(block)
+ self.Controler.AddEditedElementBlock(self.TagName, id, values["type"], values.get("name", None))
+ self.RefreshBlockModel(block)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ self.RefreshVariablePanel()
+ self.ParentWindow.RefreshPouInstanceVariablesPanel()
+ block.Refresh()
+ dialog.Destroy()
+
+ def AddNewVariable(self, bbox):
+ words = self.TagName.split("::")
+ if words[0] == "T":
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
+ else:
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinVariableSize((bbox.width, bbox.height))
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Edit"]:
+ varlist.append((var["Name"], var["Class"], var["Type"]))
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype:
+ varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
+ dialog.SetVariables(varlist)
+ if dialog.ShowModal() == wx.ID_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(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(variable)
+ self.Controler.AddEditedElementVariable(self.TagName, id, values["type"])
+ self.RefreshVariableModel(variable)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ variable.Refresh()
+ dialog.Destroy()
+
+ def AddNewConnection(self, bbox):
+ dialog = ConnectionDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
+ dialog.SetMinConnectionSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ connection = FBD_Connector(self, values["type"], values["name"], id)
+ connection.SetPosition(bbox.x, bbox.y)
+ connection.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(connection)
+ self.Controler.AddEditedElementConnection(self.TagName, id, values["type"])
+ self.RefreshConnectionModel(connection)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ connection.Refresh()
+ dialog.Destroy()
+
+ def AddNewComment(self, bbox):
+ if wx.VERSION >= (2, 5, 0):
+ dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL|wx.TE_MULTILINE)
+ else:
+ dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL)
+ dialog.SetClientSize(wx.Size(400, 200))
+ if dialog.ShowModal() == wx.ID_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(*self.GetScaledSize(max(min_width,bbox.width),max(min_height,bbox.height)))
+ self.AddComment(comment)
+ self.Controler.AddEditedElementComment(self.TagName, id)
+ self.RefreshCommentModel(comment)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ comment.Refresh()
+ dialog.Destroy()
+
+ def AddNewContact(self, bbox):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":CONTACT_NORMAL})
+ dialog.SetElementSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ contact = LD_Contact(self, values["type"], values["name"], id)
+ contact.SetPosition(bbox.x, bbox.y)
+ contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(contact)
+ self.Controler.AddEditedElementContact(self.TagName, id)
+ self.RefreshContactModel(contact)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ contact.Refresh()
+ dialog.Destroy()
+
+ def AddNewCoil(self, bbox):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Class"] != "Input" and var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetEditedElementName(self.TagName))
+ dialog.SetVariables(varlist)
+ dialog.SetValues({"name":"","type":COIL_NORMAL})
+ dialog.SetElementSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ coil = LD_Coil(self, values["type"], values["name"], id)
+ coil.SetPosition(bbox.x, bbox.y)
+ coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(coil)
+ self.Controler.AddEditedElementCoil(self.TagName, id)
+ self.RefreshCoilModel(coil)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ coil.Refresh()
+ dialog.Destroy()
+
+ def AddNewPowerRail(self, bbox):
+ dialog = LDPowerRailDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ powerrail = LD_PowerRail(self, values["type"], id, values["number"])
+ powerrail.SetPosition(bbox.x, bbox.y)
+ powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ self.AddBlock(powerrail)
+ self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
+ self.RefreshPowerRailModel(powerrail)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ powerrail.Refresh()
+ dialog.Destroy()
+
+ def AddNewStep(self, bbox, initial = False):
+ dialog = SFCStepDialog(self.ParentWindow, self.Controler, initial)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step)])
+ dialog.SetMinStepSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ step = SFC_Step(self, values["name"], initial, id)
+ if values["input"]:
+ step.AddInput()
+ else:
+ step.RemoveInput()
+ if values["output"]:
+ step.AddOutput()
+ else:
+ step.RemoveOutput()
+ if values["action"]:
+ step.AddAction()
+ else:
+ step.RemoveAction()
+ step.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = step.GetMinSize()
+ step.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
+ self.AddBlock(step)
+ self.Controler.AddEditedElementStep(self.TagName, id)
+ self.RefreshStepModel(step)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ step.Refresh()
+ dialog.Destroy()
+
+ def AddNewTransition(self, bbox):
+ dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ transition = SFC_Transition(self, values["type"], values["value"], values["priority"], id)
+ transition.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = transition.GetMinSize()
+ transition.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
+ self.AddBlock(transition)
+ self.Controler.AddEditedElementTransition(self.TagName, id)
+ self.RefreshTransitionModel(transition)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ transition.Refresh()
+ dialog.Destroy()
+
+ def AddNewDivergence(self, bbox):
+ dialog = SFCDivergenceDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinSize((bbox.width, bbox.height))
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ values = dialog.GetValues()
+ divergence = SFC_Divergence(self, values["type"], values["number"], id)
+ divergence.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = divergence.GetMinSize(True)
+ divergence.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
+ self.AddBlock(divergence)
+ self.Controler.AddEditedElementDivergence(self.TagName, id, values["type"])
+ self.RefreshDivergenceModel(divergence)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ divergence.Refresh()
+ dialog.Destroy()
+
+ def AddNewJump(self, bbox):
+ choices = []
+ for block in self.Blocks.itervalues():
+ if isinstance(block, SFC_Step):
+ choices.append(block.GetName())
+ dialog = wx.SingleChoiceDialog(self.ParentWindow,
+ _("Add a new jump"), _("Please choose a target"),
+ choices, wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ if dialog.ShowModal() == wx.ID_OK:
+ id = self.GetNewId()
+ value = dialog.GetStringSelection()
+ jump = SFC_Jump(self, value, id)
+ jump.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = jump.GetMinSize()
+ jump.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
+ self.AddBlock(jump)
+ self.Controler.AddEditedElementJump(self.TagName, id)
+ self.RefreshJumpModel(jump)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ jump.Refresh()
+ dialog.Destroy()
+
+ def AddNewActionBlock(self, bbox):
+ dialog = ActionBlockDialog(self.ParentWindow)
+ dialog.SetQualifierList(self.Controler.GetQualifierTypes())
+ dialog.SetActionList(self.Controler.GetEditedElementActions(self.TagName, self.Debug))
+ dialog.SetVariableList(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ if dialog.ShowModal() == wx.ID_OK:
+ actions = dialog.GetValues()
+ id = self.GetNewId()
+ actionblock = SFC_ActionBlock(self, actions, id)
+ actionblock.SetPosition(bbox.x, bbox.y)
+ min_width, min_height = actionblock.GetMinSize()
+ actionblock.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
+ self.AddBlock(actionblock)
+ self.Controler.AddEditedElementActionBlock(self.TagName, id)
+ self.RefreshActionBlockModel(actionblock)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ actionblock.Refresh()
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Edit element content functions
+#-------------------------------------------------------------------------------
+
+ def EditBlockContent(self, block):
+ dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ variable_names = self.Controler.GetEditedElementVariables(self.TagName, self.Debug)
+ if block.GetName() != "":
+ variable_names.remove(block.GetName())
+ dialog.SetPouElementNames(variable_names)
+ dialog.SetMinBlockSize(block.GetSize())
+ old_values = {"name" : block.GetName(),
+ "type" : block.GetType(),
+ "extension" : block.GetExtension(),
+ "inputs" : block.GetInputTypes(),
+ "executionControl" : block.GetExecutionControl(),
+ "executionOrder" : block.GetExecutionOrder()}
+ dialog.SetValues(old_values)
+ if dialog.ShowModal() == wx.ID_OK:
+ new_values = dialog.GetValues()
+ rect = block.GetRedrawRect(1, 1)
+ if "name" in new_values:
+ block.SetName(new_values["name"])
+ else:
+ block.SetName("")
+ block.SetSize(*self.GetScaledSize(new_values["width"], new_values["height"]))
+ block.SetType(new_values["type"], new_values["extension"], executionControl = new_values["executionControl"])
+ block.SetExecutionOrder(new_values["executionOrder"])
+ rect = rect.Union(block.GetRedrawRect())
+ self.RefreshBlockModel(block)
+ self.RefreshBuffer()
+ if old_values["executionOrder"] != new_values["executionOrder"]:
+ self.RefreshView(selection=({block.GetId(): True}, {}))
+ else:
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ block.Refresh(rect)
+ self.RefreshVariablePanel()
+ self.ParentWindow.RefreshPouInstanceVariablesPanel()
+ dialog.Destroy()
+
+ def EditVariableContent(self, variable):
+ words = self.TagName.split("::")
+ if words[0] == "T":
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
+ else:
+ dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinVariableSize(variable.GetSize())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Edit"]:
+ varlist.append((var["Name"], var["Class"], var["Type"]))
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype:
+ varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
+ dialog.SetVariables(varlist)
+ old_values = {"name" : variable.GetName(), "type" : variable.GetType(),
+ "executionOrder" : variable.GetExecutionOrder()}
+ dialog.SetValues(old_values)
+ if dialog.ShowModal() == wx.ID_OK:
+ new_values = dialog.GetValues()
+ rect = variable.GetRedrawRect(1, 1)
+ variable.SetName(new_values["name"])
+ variable.SetType(new_values["type"], new_values["value_type"])
+ variable.SetSize(*self.GetScaledSize(new_values["width"], new_values["height"]))
+ variable.SetExecutionOrder(new_values["executionOrder"])
+ rect = rect.Union(variable.GetRedrawRect())
+ if old_values["type"] != new_values["type"]:
+ id = variable.GetId()
+ self.Controler.RemoveEditedElementInstance(self.TagName, id)
+ self.Controler.AddEditedElementVariable(self.TagName, id, new_values["type"])
+ self.RefreshVariableModel(variable)
+ self.RefreshBuffer()
+ if old_values["executionOrder"] != new_values["executionOrder"]:
+ self.RefreshView(selection=({variable.GetId(): True}, {}))
+ else:
+ self.RefreshVisibleElements()
+ self.RefreshScrollBars()
+ variable.Refresh(rect)
+ dialog.Destroy()
+
+ def EditConnectionContent(self, connection):
+ dialog = ConnectionDialog(self.ParentWindow, self.Controler)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
+ dialog.SetMinConnectionSize(connection.GetSize())
+ values = {"name" : connection.GetName(), "type" : connection.GetType()}
+ dialog.SetValues(values)
+ if dialog.ShowModal() == wx.ID_OK:
+ old_type = connection.GetType()
+ old_name = connection.GetName()
+ values = dialog.GetValues()
+ rect = connection.GetRedrawRect(1, 1)
+ connection.SetName(values["name"])
+ connection.SetType(values["type"])
+ connection.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ rect = rect.Union(connection.GetRedrawRect())
+ if old_type != values["type"]:
+ id = connection.GetId()
+ self.Controler.RemoveEditedElementInstance(self.TagName, id)
+ self.Controler.AddEditedElementConnection(self.TagName, id, values["type"])
+ self.RefreshConnectionModel(connection)
+ self.RefreshBuffer()
+ if old_name != values["name"]:
+ self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_name, values["name"])
+ self.RefreshView(selection=({connection.GetId(): True}, {}))
+ else:
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ connection.Refresh(rect)
+ dialog.Destroy()
+
+ def EditContactContent(self, contact):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ dialog.SetVariables(varlist)
+ values = {"name" : contact.GetName(), "type" : contact.GetType()}
+ dialog.SetValues(values)
+ dialog.SetElementSize(contact.GetSize())
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ rect = contact.GetRedrawRect(1, 1)
+ contact.SetName(values["name"])
+ contact.SetType(values["type"])
+ contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ rect = rect.Union(contact.GetRedrawRect())
+ self.RefreshContactModel(contact)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ contact.Refresh(rect)
+ dialog.Destroy()
+
+ def EditCoilContent(self, coil):
+ dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+ dialog.SetPreviewFont(self.GetFont())
+ varlist = []
+ vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
+ if vars:
+ for var in vars:
+ if var["Class"] != "Input" and var["Type"] == "BOOL":
+ varlist.append(var["Name"])
+ returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
+ if returntype == "BOOL":
+ varlist.append(self.Controler.GetEditedElementName(self.TagName))
+ dialog.SetVariables(varlist)
+ values = {"name" : coil.GetName(), "type" : coil.GetType()}
+ dialog.SetValues(values)
+ dialog.SetElementSize(coil.GetSize())
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ rect = coil.GetRedrawRect(1, 1)
+ coil.SetName(values["name"])
+ coil.SetType(values["type"])
+ coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ rect = rect.Union(coil.GetRedrawRect())
+ self.RefreshCoilModel(coil)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ coil.Refresh(rect)
+ dialog.Destroy()
+
+ def EditPowerRailContent(self, powerrail):
+ connectors = powerrail.GetConnectors()
+ type = powerrail.GetType()
+ if type == LEFTRAIL:
+ pin_number = len(connectors["outputs"])
+ else:
+ pin_number = len(connectors["inputs"])
+ dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, type, pin_number)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetMinSize(powerrail.GetSize())
+ if dialog.ShowModal() == wx.ID_OK:
+ old_type = powerrail.GetType()
+ values = dialog.GetValues()
+ rect = powerrail.GetRedrawRect(1, 1)
+ powerrail.SetType(values["type"], values["number"])
+ powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
+ rect = rect.Union(powerrail.GetRedrawRect())
+ if old_type != values["type"]:
+ id = powerrail.GetId()
+ self.Controler.RemoveEditedElementInstance(self.TagName, id)
+ self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
+ self.RefreshPowerRailModel(powerrail)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ powerrail.Refresh(rect)
+ dialog.Destroy()
+
+ def EditStepContent(self, step):
+ dialog = SFCStepDialog(self.ParentWindow, self.Controler, step.GetInitial())
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
+ dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step) and block.GetName() != step.GetName()])
+ dialog.SetMinStepSize(step.GetSize())
+ values = {"name" : step.GetName()}
+ connectors = step.GetConnectors()
+ values["input"] = len(connectors["inputs"]) > 0
+ values["output"] = len(connectors["outputs"]) > 0
+ values["action"] = step.GetActionConnector() != None
+ dialog.SetValues(values)
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ rect = step.GetRedrawRect(1, 1)
+ step.SetName(values["name"])
+ if values["input"]:
+ step.AddInput()
+ else:
+ step.RemoveInput()
+ if values["output"]:
+ step.AddOutput()
+ else:
+ step.RemoveOutput()
+ if values["action"]:
+ step.AddAction()
+ else:
+ step.RemoveAction()
+ step.UpdateSize(*self.GetScaledSize(values["width"], values["height"]))
+ rect = rect.Union(step.GetRedrawRect())
+ self.RefreshStepModel(step)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ step.Refresh(rect)
+
+ def EditTransitionContent(self, transition):
+ dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
+ dialog.SetPreviewFont(self.GetFont())
+ dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
+ dialog.SetValues({"type":transition.GetType(),"value":transition.GetCondition(), "priority":transition.GetPriority()})
+ dialog.SetElementSize(transition.GetSize())
+ if dialog.ShowModal() == wx.ID_OK:
+ values = dialog.GetValues()
+ rect = transition.GetRedrawRect(1, 1)
+ transition.SetType(values["type"],values["value"])
+ transition.SetPriority(values["priority"])
+ rect = rect.Union(transition.GetRedrawRect())
+ self.RefreshTransitionModel(transition)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ transition.Refresh(rect)
+ dialog.Destroy()
+
+ def EditJumpContent(self, jump):
+ choices = []
+ for block in self.Blocks.itervalues():
+ if isinstance(block, SFC_Step):
+ choices.append(block.GetName())
+ dialog = wx.SingleChoiceDialog(self.ParentWindow,
+ _("Edit jump target"), _("Please choose a target"),
+ choices, wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+ dialog.SetSelection(choices.index(jump.GetTarget()))
+ if dialog.ShowModal() == wx.ID_OK:
+ value = dialog.GetStringSelection()
+ rect = jump.GetRedrawRect(1, 1)
+ jump.SetTarget(value)
+ rect = rect.Union(jump.GetRedrawRect())
+ self.RefreshJumpModel(jump)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ jump.Refresh(rect)
+ dialog.Destroy()
+
+ def EditActionBlockContent(self, actionblock):
+ dialog = ActionBlockDialog(self.ParentWindow)
+ dialog.SetQualifierList(self.Controler.GetQualifierTypes())
+ dialog.SetActionList(self.Controler.GetEditedElementActions(self.TagName, self.Debug))
+ dialog.SetVariableList(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
+ dialog.SetValues(actionblock.GetActions())
+ if dialog.ShowModal() == wx.ID_OK:
+ actions = dialog.GetValues()
+ rect = actionblock.GetRedrawRect(1, 1)
+ actionblock.SetActions(actions)
+ actionblock.SetSize(*self.GetScaledSize(*actionblock.GetSize()))
+ rect = rect.Union(actionblock.GetRedrawRect())
+ self.RefreshActionBlockModel(actionblock)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ actionblock.Refresh(rect)
+ dialog.Destroy()
+
+ def EditCommentContent(self, comment):
+ if wx.VERSION >= (2, 5, 0):
+ dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL|wx.TE_MULTILINE)
+ else:
+ dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL)
+ dialog.SetClientSize(wx.Size(400, 200))
+ if dialog.ShowModal() == wx.ID_OK:
+ value = dialog.GetValue()
+ rect = comment.GetRedrawRect(1, 1)
+ comment.SetContent(value)
+ comment.SetSize(*self.GetScaledSize(*comment.GetSize()))
+ rect = rect.Union(comment.GetRedrawRect())
+ self.RefreshCommentModel(comment)
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ comment.Refresh(rect)
+ dialog.Destroy()
+
+#-------------------------------------------------------------------------------
+# Model update functions
+#-------------------------------------------------------------------------------
+
+ def RefreshBlockModel(self, block):
+ blockid = block.GetId()
+ infos = {}
+ infos["type"] = block.GetType()
+ infos["name"] = block.GetName()
+ if self.CurrentLanguage == "FBD":
+ infos["executionOrder"] = block.GetExecutionOrder()
+ infos["x"], infos["y"] = block.GetPosition()
+ infos["width"], infos["height"] = block.GetSize()
+ infos["connectors"] = block.GetConnectors()
+ self.Controler.SetEditedElementBlockInfos(self.TagName, blockid, infos)
+
+ def RefreshVariableModel(self, variable):
+ variableid = variable.GetId()
+ infos = {}
+ infos["name"] = variable.GetName()
+ if self.CurrentLanguage == "FBD":
+ infos["executionOrder"] = variable.GetExecutionOrder()
+ infos["x"], infos["y"] = variable.GetPosition()
+ infos["width"], infos["height"] = variable.GetSize()
+ infos["connectors"] = variable.GetConnectors()
+ self.Controler.SetEditedElementVariableInfos(self.TagName, 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.SetEditedElementConnectionInfos(self.TagName, 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.SetEditedElementCommentInfos(self.TagName, 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.SetEditedElementPowerRailInfos(self.TagName, 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.SetEditedElementContactInfos(self.TagName, 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.SetEditedElementCoilInfos(self.TagName, 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()
+ infos["action"] = step.GetActionConnector()
+ self.Controler.SetEditedElementStepInfos(self.TagName, stepid, infos)
+
+ def RefreshTransitionModel(self, transition):
+ transitionid = transition.GetId()
+ infos = {}
+ infos["type"] = transition.GetType()
+ infos["priority"] = transition.GetPriority()
+ infos["condition"] = transition.GetCondition()
+ infos["x"], infos["y"] = transition.GetPosition()
+ infos["width"], infos["height"] = transition.GetSize()
+ infos["connectors"] = transition.GetConnectors()
+ infos["connection"] = transition.GetConditionConnector()
+ self.Controler.SetEditedElementTransitionInfos(self.TagName, 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.SetEditedElementDivergenceInfos(self.TagName, 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.SetEditedElementJumpInfos(self.TagName, 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.SetEditedElementActionBlockInfos(self.TagName, actionblockid, infos)
+
+
+#-------------------------------------------------------------------------------
+# Model delete functions
+#-------------------------------------------------------------------------------
+
+
+ def DeleteBlock(self, block):
+ elements = []
+ for output in block.GetConnectors()["outputs"]:
+ for element in output.GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ block.Clean()
+ self.RemoveBlock(block)
+ self.Controler.RemoveEditedElementInstance(self.TagName, block.GetId())
+ for element in elements:
+ element.RefreshModel()
+ wx.CallAfter(self.RefreshVariablePanel)
+ wx.CallAfter(self.ParentWindow.RefreshPouInstanceVariablesPanel)
+
+ def DeleteVariable(self, variable):
+ connectors = variable.GetConnectors()
+ if len(connectors["outputs"]) > 0:
+ elements = connectors["outputs"][0].GetConnectedBlocks()
+ else:
+ elements = []
+ variable.Clean()
+ self.RemoveBlock(variable)
+ self.Controler.RemoveEditedElementInstance(self.TagName, variable.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteConnection(self, connection):
+ if connection.GetType() == CONTINUATION:
+ elements = connection.GetConnector().GetConnectedBlocks()
+ else:
+ elements = []
+ connection.Clean()
+ self.RemoveBlock(connection)
+ self.Controler.RemoveEditedElementInstance(self.TagName, connection.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteComment(self, comment):
+ self.RemoveComment(comment)
+ self.Controler.RemoveEditedElementInstance(self.TagName, comment.GetId())
+
+ def DeleteWire(self, wire):
+ if wire in self.Wires:
+ connected = wire.GetConnected()
+ wire.Clean()
+ self.RemoveWire(wire)
+ for connector in connected:
+ connector.RefreshParentBlock()
+
+ def DeleteContact(self, contact):
+ connectors = contact.GetConnectors()
+ elements = connectors["outputs"][0].GetConnectedBlocks()
+ contact.Clean()
+ self.RemoveBlock(contact)
+ self.Controler.RemoveEditedElementInstance(self.TagName, contact.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteCoil(self, coil):
+ connectors = coil.GetConnectors()
+ elements = connectors["outputs"][0].GetConnectedBlocks()
+ coil.Clean()
+ self.RemoveBlock(coil)
+ self.Controler.RemoveEditedElementInstance(self.TagName, coil.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeletePowerRail(self, powerrail):
+ elements = []
+ if powerrail.GetType() == LEFTRAIL:
+ connectors = powerrail.GetConnectors()
+ for connector in connectors["outputs"]:
+ for element in connector.GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ powerrail.Clean()
+ self.RemoveBlock(powerrail)
+ self.Controler.RemoveEditedElementInstance(self.TagName, powerrail.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteStep(self, step):
+ elements = []
+ connectors = step.GetConnectors()
+ action_connector = step.GetActionConnector()
+ if len(connectors["outputs"]) > 0:
+ for element in connectors["outputs"][0].GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ if action_connector is not None:
+ for element in action_connector.GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ step.Clean()
+ self.RemoveBlock(step)
+ self.Controler.RemoveEditedElementInstance(self.TagName, step.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteTransition(self, transition):
+ elements = []
+ connectors = transition.GetConnectors()
+ for element in connectors["outputs"][0].GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ transition.Clean()
+ self.RemoveBlock(transition)
+ self.Controler.RemoveEditedElementInstance(self.TagName, transition.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteDivergence(self, divergence):
+ elements = []
+ connectors = divergence.GetConnectors()
+ for output in connectors["outputs"]:
+ for element in output.GetConnectedBlocks():
+ if element not in elements:
+ elements.append(element)
+ divergence.Clean()
+ self.RemoveBlock(divergence)
+ self.Controler.RemoveEditedElementInstance(self.TagName, divergence.GetId())
+ for element in elements:
+ element.RefreshModel()
+
+ def DeleteJump(self, jump):
+ jump.Clean()
+ self.RemoveBlock(jump)
+ self.Controler.RemoveEditedElementInstance(self.TagName, jump.GetId())
+
+ def DeleteActionBlock(self, actionblock):
+ actionblock.Clean()
+ self.RemoveBlock(actionblock)
+ self.Controler.RemoveEditedElementInstance(self.TagName, actionblock.GetId())
+
+
+#-------------------------------------------------------------------------------
+# Editing functions
+#-------------------------------------------------------------------------------
+
+ def Cut(self):
+ if not self.Debug and (self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement) or isinstance(self.SelectedElement, Graphic_Group)):
+ blocks, wires = self.SelectedElement.GetDefinition()
+ text = self.Controler.GetEditedElementInstancesCopy(self.TagName, blocks, wires, self.Debug)
+ self.ParentWindow.SetCopyBuffer(text)
+ rect = self.SelectedElement.GetRedrawRect(1, 1)
+ self.SelectedElement.Delete()
+ self.SelectedElement = None
+ self.RefreshBuffer()
+ self.RefreshScrollBars()
+ self.RefreshVariablePanel()
+ self.ParentWindow.RefreshPouInstanceVariablesPanel()
+ self.RefreshRect(self.GetScrolledRect(rect), False)
+
+ def Copy(self):
+ if not self.Debug and (self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement) or isinstance(self.SelectedElement, Graphic_Group)):
+ blocks, wires = self.SelectedElement.GetDefinition()
+ text = self.Controler.GetEditedElementInstancesCopy(self.TagName, blocks, wires, self.Debug)
+ self.ParentWindow.SetCopyBuffer(text)
+
+ def Paste(self, bbx=None):
+ if not self.Debug:
+ element = self.ParentWindow.GetCopyBuffer()
+ if bbx is None:
+ mouse_pos = self.Editor.ScreenToClient(wx.GetMousePosition())
+ middle = wx.Rect(0, 0, *self.Editor.GetClientSize()).InsideXY(mouse_pos.x, mouse_pos.y)
+ if middle:
+ x, y = self.CalcUnscrolledPosition(mouse_pos.x, mouse_pos.y)
+ else:
+ x, y = self.CalcUnscrolledPosition(0, 0)
+ new_pos = [int(x / self.ViewScale[0]), int(y / self.ViewScale[1])]
+ else:
+ middle = True
+ new_pos = [bbx.x, bbx.y]
+ result = self.Controler.PasteEditedElementInstances(self.TagName, element, new_pos, middle, self.Debug)
+ if not isinstance(result, (StringType, UnicodeType)):
+ self.RefreshBuffer()
+ self.RefreshView(selection=result)
+ self.RefreshVariablePanel()
+ self.ParentWindow.RefreshPouInstanceVariablesPanel()
+ else:
+ message = wx.MessageDialog(self.Editor, result, "Error", wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+ def CanAddElement(self, block):
+ if isinstance(block, Graphic_Group):
+ return block.CanAddBlocks(self)
+ elif self.CurrentLanguage == "SFC":
+ return True
+ elif self.CurrentLanguage == "LD" and not isinstance(block, (SFC_Step, SFC_Transition, SFC_Divergence, SFC_Jump, SFC_ActionBlock)):
+ return True
+ elif self.CurrentLanguage == "FBD" and isinstance(block, (FBD_Block, FBD_Variable, FBD_Connector, Comment)):
+ return True
+ return False
+
+ def GenerateNewName(self, element=None, blocktype=None, exclude={}):
+ if element is not None and isinstance(element, SFC_Step):
+ format = "Step%d"
+ else:
+ if element is not None:
+ blocktype = element.GetType()
+ if blocktype is None:
+ blocktype = "Block"
+ format = "%s%%d" % blocktype
+ return self.Controler.GenerateNewName(self.TagName, None, format, exclude, self.Debug)
+
+ def IsNamedElement(self, element):
+ return isinstance(element, FBD_Block) and element.GetName() != "" or isinstance(element, SFC_Step)
+
+ def CopyBlock(self, element, pos):
+ id = self.GetNewId()
+ if isinstance(element, Graphic_Group):
+ block = element.Clone(self, pos=pos)
+ else:
+ if self.IsNamedElement(element):
+ name = self.GenerateNewName(element)
+ block = element.Clone(self, id, name, pos)
+ else:
+ name = None
+ block = element.Clone(self, id, pos=pos)
+ self.AddBlockInModel(block)
+ return block
+
+ def AddBlockInModel(self, block):
+ if isinstance(block, Comment):
+ self.AddComment(block)
+ self.Controler.AddEditedElementComment(self.TagName, block.GetId())
+ self.RefreshCommentModel(block)
+ else:
+ self.AddBlock(block)
+ if isinstance(block, FBD_Block):
+ self.Controler.AddEditedElementBlock(self.TagName, block.GetId(), block.GetType(), block.GetName())
+ self.RefreshBlockModel(block)
+ elif isinstance(block, FBD_Variable):
+ self.Controler.AddEditedElementVariable(self.TagName, block.GetId(), block.GetType())
+ self.RefreshVariableModel(block)
+ elif isinstance(block, FBD_Connector):
+ self.Controler.AddEditedElementConnection(self.TagName, block.GetId(), block.GetType())
+ self.RefreshConnectionModel(block)
+ elif isinstance(block, LD_Contact):
+ self.Controler.AddEditedElementContact(self.TagName, block.GetId())
+ self.RefreshContactModel(block)
+ elif isinstance(block, LD_Coil):
+ self.Controler.AddEditedElementCoil(self.TagName, block.GetId())
+ self.RefreshCoilModel(block)
+ elif isinstance(block, LD_PowerRail):
+ self.Controler.AddEditedElementPowerRail(self.TagName, block.GetId(), block.GetType())
+ self.RefreshPowerRailModel(block)
+ elif isinstance(block, SFC_Step):
+ self.Controler.AddEditedElementStep(self.TagName, block.GetId())
+ self.RefreshStepModel(block)
+ elif isinstance(block, SFC_Transition):
+ self.Controler.AddEditedElementTransition(self.TagName, block.GetId())
+ self.RefreshTransitionModel(block)
+ elif isinstance(block, SFC_Divergence):
+ self.Controler.AddEditedElementDivergence(self.TagName, block.GetId(), block.GetType())
+ self.RefreshDivergenceModel(block)
+ elif isinstance(block, SFC_Jump):
+ self.Controler.AddEditedElementJump(self.TagName, block.GetId())
+ self.RefreshJumpModel(block)
+ elif isinstance(block, SFC_ActionBlock):
+ self.Controler.AddEditedElementActionBlock(self.TagName, block.GetId())
+ self.RefreshActionBlockModel(block)
+
+#-------------------------------------------------------------------------------
+# Find and Replace functions
+#-------------------------------------------------------------------------------
+
+ def Find(self, direction, search_params):
+ if self.SearchParams != search_params:
+ self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+
+ self.SearchParams = search_params
+ criteria = {
+ "raw_pattern": search_params["find_pattern"],
+ "pattern": re.compile(search_params["find_pattern"]),
+ "case_sensitive": search_params["case_sensitive"],
+ "regular_expression": search_params["regular_expression"],
+ "filter": "all"}
+
+ self.SearchResults = []
+ blocks = []
+ for infos, start, end, text in self.Controler.SearchInPou(self.TagName, criteria, self.Debug):
+ if infos[1] in ["var_local", "var_input", "var_output", "var_inout"]:
+ self.SearchResults.append((infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT))
+ else:
+ block = self.Blocks.get(infos[2])
+ if block is not None:
+ blocks.append((block, (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)))
+ blocks.sort(sort_blocks)
+ self.SearchResults.extend([infos for block, infos in blocks])
+
+ if len(self.SearchResults) > 0:
+ if self.CurrentFindHighlight is not None:
+ old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+ if self.SearchParams["wrap"]:
+ idx = (old_idx + direction) % len(self.SearchResults)
+ else:
+ idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+ if idx != old_idx:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = self.SearchResults[idx]
+ self.AddHighlight(*self.CurrentFindHighlight)
+ else:
+ self.CurrentFindHighlight = self.SearchResults[0]
+ self.AddHighlight(*self.CurrentFindHighlight)
+
+ else:
+ if self.CurrentFindHighlight is not None:
+ self.RemoveHighlight(*self.CurrentFindHighlight)
+ self.CurrentFindHighlight = None
+
+#-------------------------------------------------------------------------------
+# Highlights showing functions
+#-------------------------------------------------------------------------------
+
+ def OnRefreshHighlightsTimer(self, event):
+ self.RefreshView()
+ event.Skip()
+
+ def ClearHighlights(self, highlight_type=None):
+ EditorPanel.ClearHighlights(self, highlight_type)
+
+ if highlight_type is None:
+ self.Highlights = []
+ else:
+ self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type]
+ self.RefreshView()
+
+ def AddHighlight(self, infos, start, end, highlight_type):
+ EditorPanel.AddHighlight(self, infos, start, end, highlight_type)
+
+ self.Highlights.append((infos, start, end, highlight_type))
+ if infos[0] not in ["var_local", "var_input", "var_output", "var_inout"]:
+ block = self.Blocks.get(infos[1])
+ if block is not None:
+ self.EnsureVisible(block)
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ EditorPanel.RemoveHighlight(self, infos, start, end, highlight_type)
+
+ if (infos, start, end, highlight_type) in self.Highlights:
+ self.Highlights.remove((infos, start, end, highlight_type))
+ self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+ def ShowHighlights(self):
+ for infos, start, end, highlight_type in self.Highlights:
+ if infos[0] in ["comment", "io_variable", "block", "connector", "coil", "contact", "step", "transition", "jump", "action_block"]:
+ block = self.FindElementById(infos[1])
+ if block is not None:
+ block.AddHighlight(infos[2:], start, end, highlight_type)
+
+#-------------------------------------------------------------------------------
+# Drawing functions
+#-------------------------------------------------------------------------------
+
+ def OnScrollWindow(self, event):
+ if self.Editor.HasCapture() and self.StartMousePos:
+ return
+ if wx.Platform == '__WXMSW__':
+ wx.CallAfter(self.RefreshVisibleElements)
+ elif event.GetOrientation() == wx.HORIZONTAL:
+ self.RefreshVisibleElements(xp = event.GetPosition())
+ else:
+ self.RefreshVisibleElements(yp = event.GetPosition())
+ event.Skip()
+
+ def OnScrollStop(self, event):
+ self.RefreshScrollBars()
+ event.Skip()
+
+ def OnMouseWheelWindow(self, event):
+ if self.StartMousePos is None or self.StartScreenPos is None:
+ rotation = event.GetWheelRotation() / event.GetWheelDelta()
+ if event.ShiftDown():
+ x, y = self.GetViewStart()
+ xp = max(0, min(x - rotation * 3, self.Editor.GetVirtualSize()[0] / self.Editor.GetScrollPixelsPerUnit()[0]))
+ self.RefreshVisibleElements(xp = xp)
+ self.Scroll(xp, y)
+ elif event.ControlDown():
+ dc = self.GetLogicalDC()
+ self.SetScale(self.CurrentScale + rotation, mouse_event = event)
+ self.ParentWindow.RefreshDisplayMenu()
+ else:
+ x, y = self.GetViewStart()
+ yp = max(0, min(y - rotation * 3, self.Editor.GetVirtualSize()[1] / self.Editor.GetScrollPixelsPerUnit()[1]))
+ self.RefreshVisibleElements(yp = yp)
+ self.Scroll(x, yp)
+
+ def OnMoveWindow(self, event):
+ self.RefreshScrollBars()
+ self.RefreshVisibleElements()
+ event.Skip()
+
+ def DoDrawing(self, dc, printing = False):
+ if printing:
+ if getattr(dc, "printing", False):
+ font = wx.Font(self.GetFont().GetPointSize(), wx.MODERN, wx.NORMAL, wx.NORMAL)
+ dc.SetFont(font)
+ else:
+ dc.SetFont(self.GetFont())
+ else:
+ dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
+ dc.Clear()
+ dc.BeginDrawing()
+ if self.Scaling is not None and self.DrawGrid and not printing:
+ dc.SetPen(wx.TRANSPARENT_PEN)
+ dc.SetBrush(self.GridBrush)
+ xstart, ystart = self.GetViewStart()
+ window_size = self.Editor.GetClientSize()
+ width, height = self.Editor.GetVirtualSize()
+ width = int(max(width, xstart * SCROLLBAR_UNIT + window_size[0]) / self.ViewScale[0])
+ height = int(max(height, ystart * SCROLLBAR_UNIT + window_size[1]) / self.ViewScale[1])
+ dc.DrawRectangle(1, 1, width, height)
+ if self.PageSize is not None and not printing:
+ dc.SetPen(self.PagePen)
+ xstart, ystart = self.GetViewStart()
+ window_size = self.Editor.GetClientSize()
+ for x in xrange(self.PageSize[0] - (xstart * SCROLLBAR_UNIT) % self.PageSize[0], int(window_size[0] / self.ViewScale[0]), self.PageSize[0]):
+ dc.DrawLine(xstart * SCROLLBAR_UNIT + x + 1, int(ystart * SCROLLBAR_UNIT / self.ViewScale[0]),
+ xstart * SCROLLBAR_UNIT + x + 1, int((ystart * SCROLLBAR_UNIT + window_size[1]) / self.ViewScale[0]))
+ for y in xrange(self.PageSize[1] - (ystart * SCROLLBAR_UNIT) % self.PageSize[1], int(window_size[1] / self.ViewScale[1]), self.PageSize[1]):
+ dc.DrawLine(int(xstart * SCROLLBAR_UNIT / self.ViewScale[0]), ystart * SCROLLBAR_UNIT + y + 1,
+ int((xstart * SCROLLBAR_UNIT + window_size[0]) / self.ViewScale[1]), ystart * SCROLLBAR_UNIT + y + 1)
+
+ # Draw all elements
+ for comment in self.Comments.itervalues():
+ if comment != self.SelectedElement and (comment.IsVisible() or printing):
+ comment.Draw(dc)
+ for wire in self.Wires.iterkeys():
+ if wire != self.SelectedElement and (wire.IsVisible() or printing):
+ if not self.Debug or wire.GetValue() != True:
+ wire.Draw(dc)
+ if self.Debug:
+ for wire in self.Wires.iterkeys():
+ if wire != self.SelectedElement and (wire.IsVisible() or printing) and wire.GetValue() == True:
+ wire.Draw(dc)
+ for block in self.Blocks.itervalues():
+ if block != self.SelectedElement and (block.IsVisible() or printing):
+ block.Draw(dc)
+
+ if self.SelectedElement is not None and (self.SelectedElement.IsVisible() or printing):
+ self.SelectedElement.Draw(dc)
+
+ if not printing:
+ if self.Debug:
+ xstart, ystart = self.GetViewStart()
+ dc.DrawText(_("Debug: %s") % self.InstancePath, 2, 2)
+ if self.rubberBand.IsShown():
+ self.rubberBand.Draw(dc)
+ dc.EndDrawing()
+
+ def OnPaint(self, event):
+ dc = self.GetLogicalDC(True)
+ self.DoDrawing(dc)
+ wx.BufferedPaintDC(self.Editor, dc.GetAsBitmap())
+ if self.Debug:
+ DebugViewer.RefreshNewData(self)
+ event.Skip()
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/generate_IEC_std.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,539 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007-2011: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+ THIS CODE GENARATES C++ CODE FOR IEC2C COMPILER
+"""
+
+file_list = [
+ ('absyntax_utils', 'function_type_decl','h' ),
+ ('absyntax_utils', 'get_function_type_decl','c' ),
+ ('absyntax_utils', 'search_type_code','c' ),
+ ('stage4/generate_c', 'st_code_gen','c' ),
+ ('stage4/generate_c', 'il_code_gen','c' ),
+ ('stage1_2', 'standard_function_names','c' ),
+ ('lib', 'iec_std_lib_generated','h' )
+ ]
+
+# Get definitions
+from plcopen.structures import *
+
+if len(sys.argv) != 2 :
+ print "Usage: " + sys.argv[0] + " path_name\n -> create files in path_name"
+ sys.exit(0)
+
+#import pprint
+#pp = pprint.PrettyPrinter(indent=4)
+
+matiec_header = """/*
+ * Copyright (C) 2007-2011: Edouard TISSERANT and Laurent BESSARD
+ *
+ * See COPYING and COPYING.LESSER files for copyright details.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/****
+ * IEC 61131-3 standard function library
+ * generated code, do not edit by hand
+ */
+
+ """
+
+matiec_lesser_header = """/*
+ * Copyright (C) 2007-2011: Edouard TISSERANT and Laurent BESSARD
+ *
+ * See COPYING and COPYING.LESSER files for copyright 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/****
+ * IEC 61131-3 standard function library
+ * generated code, do not edit by hand
+ */
+
+ """
+
+def ANY_to_compiler_test_type_GEN(typename, paramname):
+ """
+ Convert ANY_XXX IEC type declaration into IEC2C's generated type test.
+ This tests are defined in search_expression_type.cc
+ """
+ return {"ANY" : "",
+ "ANY_BIT" : "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_binary_type(%(paramname)s_type_symbol))",
+ "ANY_NBIT" : "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_nbinary_type(%(paramname)s_type_symbol))",
+ "ANY_NUM" : "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_num_type(%(paramname)s_type_symbol))",
+ "ANY_REAL" : "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_real_type(%(paramname)s_type_symbol))",
+ "ANY_INT" : "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_integer_type(%(paramname)s_type_symbol))"
+ }.get(typename,
+ #"if (typeid(*last_type_symbol) == typeid(%(typename)s_type_name_c))")%{
+ "if(%(paramname)s_type_symbol == NULL || search_expression_type->is_same_type(&search_constant_type_c::%(typename)s_type_name, last_type_symbol))")%{
+ "paramname" : paramname, "typename": typename.lower()}
+
+def recurse_and_indent(fdecls, indent, do_type_search_only = False, do_il = False):
+ """
+ This function generate visit(function_invocation) code for
+ - ST code generator
+ - IL code generator
+ - search_expression_type class for ST
+ - search_expression_type class for IL
+
+ Input data is a
+ "{fname : {IN[0]paramname : {IN[0]paramtype : {IN[1]paraname : {IN[1]paramtype : {... : {IN[N]paraname : {IN[N]paramtype : (fdecl,)}}}}}}"
+ nested dictionary structure.
+ """
+ if type(fdecls) != type(tuple()):
+ res = ""
+ for Paramname, ParamTypes in fdecls.iteritems():
+ if do_il:
+ res += """
+{"""
+ if not do_type_search_only:
+ res += """
+ symbol_c *%(input_name)s_param_name = (symbol_c *)(new identifier_c("%(input_name)s"));
+ /* Get the value from a foo(<param_name> = <param_value>) style call */
+ symbol_c *%(input_name)s_param_value = &this->default_variable_name;
+"""%{"input_name":Paramname}
+ res += """
+ symbol_c *%(input_name)s_type_symbol = param_data_type;
+ last_type_symbol = %(input_name)s_type_symbol;
+"""%{"input_name":Paramname}
+ else:
+ res += """
+{
+ symbol_c *%(input_name)s_param_name = (symbol_c *)(new identifier_c("%(input_name)s"));
+ /* Get the value from a foo(<param_name> = <param_value>) style call */
+ symbol_c *%(input_name)s_param_value = function_call_param_iterator.search_f(%(input_name)s_param_name);
+ symbol_c *%(input_name)s_type_symbol = NULL;
+
+ /* Get the value from a foo(<param_value>) style call */
+ if (%(input_name)s_param_value == NULL)
+ %(input_name)s_param_value = function_call_param_iterator.next_nf();
+ if (%(input_name)s_param_value != NULL) {
+ %(input_name)s_type_symbol = search_expression_type->get_type(%(input_name)s_param_value);
+ last_type_symbol = last_type_symbol && %(input_name)s_type_symbol && search_expression_type->is_same_type(%(input_name)s_type_symbol, last_type_symbol) ? search_expression_type->common_type(%(input_name)s_type_symbol, last_type_symbol) : %(input_name)s_type_symbol ;
+ }
+"""%{"input_name":Paramname}
+
+ for ParamType,NextParamDecl in ParamTypes.iteritems():
+
+ res += """
+ %(type_test)s
+ {
+%(if_good_type_code)s
+ }
+"""%{
+ "type_test":ANY_to_compiler_test_type_GEN(ParamType,Paramname),
+ "if_good_type_code":recurse_and_indent(NextParamDecl,indent,do_type_search_only).replace('\n','\n ')}
+
+ res += """
+
+ ERROR;
+}
+"""
+
+ return res.replace('\n','\n'+indent)
+ else:
+ res = "\n"
+ fdecl=fdecls[0]
+
+ if not do_type_search_only:
+ code_gen = eval(fdecl["python_eval_c_code_format"])
+
+ if code_gen[1] is not None:
+ res += "function_name = (symbol_c*)(new pragma_c(\"%s\"));\n"%code_gen[1]
+ if fdecl["extensible"]:
+ res += """
+if (nb_param < %(min_nb_param)d)
+ nb_param = %(min_nb_param)d;
+char* nb_param_str = new char[10];
+sprintf(nb_param_str, "%%d", nb_param);
+symbol_c * nb_param_name = (symbol_c *)(new identifier_c("nb_param"));
+ADD_PARAM_LIST(nb_param_name, (symbol_c*)(new integer_c((const char *)nb_param_str)), (symbol_c*)(new int_type_name_c()), function_param_iterator_c::direction_in)
+"""%{"min_nb_param" : len(fdecl["inputs"])}
+ for paramname,paramtype,unused in fdecl["inputs"]:
+ res += """
+if (%(input_name)s_type_symbol == NULL)
+ %(input_name)s_type_symbol = last_type_symbol;
+ADD_PARAM_LIST(%(input_name)s_param_name, %(input_name)s_param_value, %(input_name)s_type_symbol, function_param_iterator_c::direction_in)
+"""%{"input_name" : paramname}
+ if fdecl["extensible"]:
+ res += """
+int base_num = %d;
+symbol_c *param_value = NULL;
+symbol_c *param_name = NULL;
+do{
+ char my_name[10];
+ sprintf(my_name, "IN%%d", base_num++);
+ param_name = (symbol_c*)(new identifier_c(my_name));
+
+ /* Get the value from a foo(<param_name> = <param_value>) style call */
+ param_value = function_call_param_iterator.search_f(param_name);
+
+ /* Get the value from a foo(<param_value>) style call */
+ if (param_value == NULL)
+ param_value = function_call_param_iterator.next_nf();
+ if (param_value != NULL){
+ symbol_c *current_type_symbol = search_expression_type->get_type(param_value);
+ last_type_symbol = last_type_symbol && search_expression_type->is_same_type(current_type_symbol, last_type_symbol) ? search_expression_type->common_type(current_type_symbol, last_type_symbol) : current_type_symbol ;
+
+ /*Function specific CODE */
+ ADD_PARAM_LIST(param_name, param_value, current_type_symbol, function_param_iterator_c::direction_in)
+ }
+
+}while(param_value != NULL);
+"""%(fdecl["baseinputnumber"] + 2)
+
+ result_type_rule = fdecl["return_type_rule"]
+ res += {
+ "copy_input" : "symbol_c * return_type_symbol = last_type_symbol;\n",
+ "defined" : "symbol_c * return_type_symbol = &search_constant_type_c::%s_type_name;\n"%fdecl["outputs"][0][1].lower(),
+ }.get(result_type_rule, "symbol_c * return_type_symbol = %s;\n"%result_type_rule)
+
+ if not do_type_search_only:
+ if code_gen[0] is not None:
+ res += "function_type_prefix = %s;\n"%{"return_type" : "return_type_symbol"}.get(code_gen[0], "(symbol_c*)(new pragma_c(\"%s\"))"%code_gen[0])
+ if code_gen[2] is not None:
+ res += "function_type_suffix = %s_symbol;\n"%{"common_type" : "last_type"}.get(code_gen[2], code_gen[2])
+
+ any_common = reduce(lambda x, y: {"ANY": lambda x: x,
+ "ANY_BIT": lambda x: {"ANY": y, "ANY_NUM": y}.get(y, x),
+ "ANY_NUM": lambda x: {"ANY": y}.get(y, x),
+ "ANY_REAL": lambda x: {"ANY": y, "ANY_NUM": y}.get(y, x)}.get(x, y),
+ [paramtype for paramname,paramtype,unused in fdecl["inputs"]], "BOOL")
+
+ first = True
+ for list, test_type, default_type in [(["ANY", "ANY_NUM"], "integer", "lint"),
+ (["ANY_BIT"], "integer", "lword"),
+ (["ANY", "ANY_REAL"], "real", "lreal")]:
+
+ if any_common in list:
+ if not first:
+ res += "else "
+ first = False
+ res += """if (search_expression_type->is_literal_%s_type(function_type_suffix))
+ function_type_suffix = &search_constant_type_c::%s_type_name;
+"""%(test_type, default_type)
+
+ res += "break;\n"
+ else:
+ res += "return return_type_symbol;\n"
+
+ return res.replace('\n','\n'+indent)
+
+def get_default_input_type(fdecls):
+ if type(fdecls) != type(tuple()) and len(fdecls) == 1:
+ ParamTypes = fdecls.values()[0]
+ if len(ParamTypes) == 1:
+ ParamType_name, ParamType_value = ParamTypes.items()[0]
+ if not ParamType_name.startswith("ANY") and type(ParamType_value) == type(tuple()):
+ return "&search_constant_type_c::%s_type_name" % ParamType_name.lower()
+ return "NULL"
+
+###################################################################
+### ###
+### MAIN ###
+### ###
+###################################################################
+
+"""
+Reorganize std_decl from structure.py
+into a nested dictionnary structure (i.e. a tree):
+"{fname : {IN[0]paramname : {IN[0]paramtype : {IN[1]paraname : {IN[1]paramtype : {... : {IN[N]paraname : {IN[N]paramtype : (fdecl,)}}}}}}"
+Keep ptrack of original declaration order in a
+separated list called official_order
+"""
+std_fdecls = {}
+official_order = []
+for section in std_decl:
+ for fdecl in section["list"]:
+ if len(official_order)==0 or fdecl["name"] not in official_order:
+ official_order.append(fdecl["name"])
+ # store all func by name in a dict
+ std_fdecls_fdecl_name = std_fdecls.get(fdecl["name"], {})
+ current = std_fdecls_fdecl_name
+ for i in fdecl["inputs"]:
+ current[i[0]] = current.get(i[0], {})
+ current = current[i[0]]
+ last = current
+ current[i[1]] = current.get(i[1], {})
+ current = current[i[1]]
+ last[i[1]]=(fdecl,)
+ std_fdecls[fdecl["name"]] = std_fdecls_fdecl_name
+
+###################################################################
+
+"""
+Generate the long enumeration of std function types
+"""
+function_type_decl = matiec_header + """
+typedef enum {
+"""
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ function_type_decl += " function_"+fname.lower()+",\n"
+
+function_type_decl += """ function_none
+} function_type_t;
+"""
+###################################################################
+"""
+Generate the funct that return enumerated according function name
+"""
+get_function_type_decl = matiec_header + """
+function_type_t get_function_type(identifier_c *function_name) {
+"""
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ get_function_type_decl += """
+if (!strcasecmp(function_name->value, "%s"))
+ return function_%s;
+"""%(fname,fname.lower())
+
+get_function_type_decl += """
+ else return function_none;
+}
+
+"""
+###################################################################
+"""
+Generate the part of generate_c_st_c::visit(function_invocation)
+that is responsible to generate C code for std lib calls.
+"""
+st_code_gen = matiec_header + """
+switch(current_function_type){
+"""
+
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ st_code_gen += """
+/****
+ *%s
+ */
+ case function_%s :
+ {
+ symbol_c *last_type_symbol = %s;
+""" %(fname, fname.lower(), get_default_input_type(fdecls))
+ indent = " "
+
+ st_code_gen += recurse_and_indent(fdecls, indent).replace('\n','\n ')
+
+ st_code_gen += """
+ }/*function_%s*/
+ break;
+""" %(fname.lower())
+st_code_gen += """
+ case function_none :
+ ERROR;
+}
+"""
+
+###################################################################
+"""
+Generate the part of generate_c_il_c::visit(il_function_call)
+that is responsible to generate C code for std lib calls.
+"""
+il_code_gen = matiec_header + """
+switch(current_function_type){
+"""
+
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ il_code_gen += """
+/****
+ *%s
+ */
+ case function_%s :
+ {
+ symbol_c *last_type_symbol = %s;
+""" %(fname, fname.lower(), get_default_input_type(fdecls))
+ indent = " "
+
+ il_code_gen += recurse_and_indent(fdecls, indent, do_il=True).replace('\n','\n ')
+
+ il_code_gen += """
+ }/*function_%s*/
+ break;
+""" %(fname.lower())
+il_code_gen += """
+ case function_none :
+ ERROR;
+}
+"""
+
+###################################################################
+"""
+Generate the part of search_expression_type_c::visit(function_invocation)
+that is responsible of returning type symbol for function invocation.
+"""
+search_type_code = matiec_header + """
+
+void *search_expression_type_c::compute_standard_function_default(function_invocation_c *st_symbol = NULL, il_formal_funct_call_c *il_symbol = NULL) {
+ function_type_t current_function_type;
+ function_call_param_iterator_c *tmp_function_call_param_iterator;
+ if (st_symbol != NULL && il_symbol == NULL) {
+ current_function_type = get_function_type((identifier_c *)st_symbol->function_name);
+ tmp_function_call_param_iterator = new function_call_param_iterator_c(st_symbol);
+ }
+ else if (st_symbol == NULL && il_symbol != NULL) {
+ current_function_type = get_function_type((identifier_c *)il_symbol->function_name);
+ tmp_function_call_param_iterator = new function_call_param_iterator_c(il_symbol);
+ }
+ else
+ ERROR;
+ function_call_param_iterator_c function_call_param_iterator(*tmp_function_call_param_iterator);
+ search_expression_type_c* search_expression_type = this;
+
+ switch(current_function_type){
+"""
+
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ search_type_code += """
+/****
+ *%s
+ */
+ case function_%s :
+ {
+ symbol_c *last_type_symbol = %s;
+""" %(fname, fname.lower(), get_default_input_type(fdecls))
+ indent = " "
+
+ search_type_code += recurse_and_indent(fdecls, indent, True).replace('\n','\n ')
+
+ search_type_code += """
+ }/*function_%s*/
+ break;
+""" %(fname.lower())
+search_type_code += """
+ case function_none :
+ ERROR;
+ }
+ return NULL;
+}
+
+void *search_expression_type_c::compute_standard_function_il(il_function_call_c *symbol, symbol_c *param_data_type) {
+
+ function_type_t current_function_type = get_function_type((identifier_c *)symbol->function_name);
+ function_call_param_iterator_c function_call_param_iterator(symbol);
+ search_expression_type_c* search_expression_type = this;
+
+ switch(current_function_type){
+"""
+
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ search_type_code += """
+/****
+ *%s
+ */
+ case function_%s :
+ {
+ symbol_c *last_type_symbol = %s;
+""" %(fname, fname.lower(), get_default_input_type(fdecls))
+ indent = " "
+
+ search_type_code += recurse_and_indent(fdecls, indent, True, True).replace('\n','\n ')
+
+ search_type_code += """
+ }/*function_%s*/
+ break;
+""" %(fname.lower())
+search_type_code += """
+ case function_none :
+ ERROR;
+ }
+ return NULL;
+}
+"""
+
+###################################################################
+###################################################################
+###################################################################
+"""
+Generate the standard_function_names[] for inclusion in bizon generated code
+"""
+standard_function_names = matiec_header + """
+const char *standard_function_names[] = {
+"""
+for fname, fdecls in [ (fname,std_fdecls[fname]) for fname in official_order ]:
+ standard_function_names += "\""+fname+"\",\n"
+standard_function_names += """
+/* end of array marker! Do not remove! */
+NULL
+};
+
+"""
+
+###################################################################
+###################################################################
+###################################################################
+"""
+Generate the C implementation of the IEC standard function library.
+"""
+iec_std_lib_generated = matiec_lesser_header + """
+
+/* Macro that expand to subtypes */
+"""
+for typename, parenttypename in TypeHierarchy_list:
+ if (typename.startswith("ANY")):
+ iec_std_lib_generated += "#define " + typename + "(DO)"
+ for typename2, parenttypename2 in TypeHierarchy_list:
+ if(parenttypename2 == typename):
+ if(typename2.startswith("ANY")):
+ iec_std_lib_generated += " " + typename2 + "(DO)"
+ else:
+ iec_std_lib_generated += " DO(" + typename2 + ")"
+ iec_std_lib_generated += "\n"
+ else:
+ break
+
+# Now, print that out, or write to files from sys.argv
+for path, name, ext in file_list :
+ fd = open(os.path.join(sys.argv[1], path, name+'.'+ext),'w')
+ fd.write(eval(name))
+ fd.close()
+
+#print "/* Code to eventually paste in iec_std_lib.h if type hierarchy changed */"
+#print "/* you also have to change iec_std_lib.h according to new types */\n\n"
+#print iec_std_lib_generated
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/.cvsignore Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1 @@
+*.pyc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/FBD_Objects.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1028 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from GraphicCommons import *
+from plcopen.structures import *
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Block
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a function block
+"""
+
+def TestConnectorName(name, block_type):
+ return name in ["OUT", "MN", "MX"] or name.startswith("IN") and (block_type, name) != ("EXPT", "IN2")
+
+class FBD_Block(Graphic_Element):
+
+ # Create a new block
+ def __init__(self, parent, type, name, id = None, extension = 0, inputs = None, connectors = {}, executionControl = False, executionOrder = 0):
+ Graphic_Element.__init__(self, parent)
+ self.Type = None
+ self.Description = None
+ self.Extension = None
+ self.ExecutionControl = False
+ self.Id = id
+ self.SetName(name)
+ self.SetExecutionOrder(executionOrder)
+ self.Inputs = []
+ self.Outputs = []
+ self.Colour = wx.BLACK
+ self.Pen = MiterPen(wx.BLACK)
+ self.SetType(type, extension, inputs, connectors, executionControl)
+ self.Highlights = {}
+
+ # Make a clone of this FBD_Block
+ def Clone(self, parent, id = None, name = "", pos = None):
+ if self.Name != "" and name == "":
+ name = self.Name
+ block = FBD_Block(parent, self.Type, name, id, self.Extension)
+ block.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ block.SetPosition(pos.x, pos.y)
+ else:
+ block.SetPosition(self.Pos.x, self.Pos.y)
+ block.Inputs = [input.Clone(block) for input in self.Inputs]
+ block.Outputs = [output.Clone(block) for output in self.Outputs]
+ return block
+
+ def GetConnectorTranslation(self, element):
+ return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
+
+ def Flush(self):
+ for input in self.Inputs:
+ input.Flush()
+ self.Inputs = []
+ for output in self.Outputs:
+ output.Flush()
+ self.Outputs = []
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ for input in self.Inputs:
+ if input.IsConnected():
+ rect = rect.Union(input.GetConnectedRedrawRect(movex, movey))
+ for output in self.Outputs:
+ if output.IsConnected():
+ rect = rect.Union(output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Delete this block by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteBlock(self)
+
+ # Unconnect all inputs and outputs
+ def Clean(self):
+ for input in self.Inputs:
+ input.UnConnect(delete = True)
+ for output in self.Outputs:
+ output.UnConnect(delete = True)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the size of text for execution order
+ def RefreshExecutionOrderSize(self):
+ self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt, connectors=True):
+ if self.Name != "":
+ test_text = self.GetTextBoundingBox().InsideXY(pt.x, pt.y)
+ else:
+ test_text = False
+ test_block = self.GetBlockBoundingBox(connectors).InsideXY(pt.x, pt.y)
+ return test_text or test_block
+
+ # Returns the bounding box of the name outside the block
+ def GetTextBoundingBox(self):
+ # Calculate the size of the name outside the block
+ text_width, text_height = self.NameSize
+ return wx.Rect(self.Pos.x + (self.Size[0] - text_width) / 2,
+ self.Pos.y - (text_height + 2),
+ text_width,
+ text_height)
+
+ # Returns the bounding box of function block without name outside
+ def GetBlockBoundingBox(self, connectors=True):
+ bbx_x, bbx_y = self.Pos.x, self.Pos.y
+ bbx_width, bbx_height = self.Size
+ if connectors:
+ bbx_x -= min(1, len(self.Inputs)) * CONNECTOR_SIZE
+ bbx_width += (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE
+ if self.ExecutionOrder != 0:
+ bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
+ bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
+ bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
+ return wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
+
+ # Refresh the block bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = self.GetBlockBoundingBox()
+ if self.Name != "":
+ self.BoundingBox.Union(self.GetTextBoundingBox())
+
+ # Refresh the positions of the block connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ # Calculate the size for the connector lines
+ lines = max(len(self.Inputs), len(self.Outputs))
+ if lines > 0:
+ linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE)
+ # Update inputs and outputs positions
+ position = BLOCK_LINE_SIZE + linesize / 2
+ for i in xrange(lines):
+ if scaling is not None:
+ ypos = round_scaling(self.Pos.y + position, scaling[1]) - self.Pos.y
+ else:
+ ypos = position
+ if i < len(self.Inputs):
+ self.Inputs[i].SetPosition(wx.Point(0, ypos))
+ if i < len(self.Outputs):
+ self.Outputs[i].SetPosition(wx.Point(self.Size[0], ypos))
+ position += linesize
+ self.RefreshConnected()
+
+ # Refresh the positions of wires connected to inputs and outputs
+ def RefreshConnected(self, exclude = []):
+ for input in self.Inputs:
+ input.MoveConnected(exclude)
+ for output in self.Outputs:
+ output.MoveConnected(exclude)
+
+ # Returns the block connector that starts with the point given if it exists
+ def GetConnector(self, position, name = None):
+ # if a name is given
+ if name is not None:
+ # Test each input and output connector
+ #for input in self.Inputs:
+ # if name == input.GetName():
+ # return input
+ for output in self.Outputs:
+ if name == output.GetName():
+ return output
+ return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+
+ def GetInputTypes(self):
+ return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
+
+ def SetOutputValues(self, values):
+ for output in self.Outputs:
+ output.SetValue(values.get(ouput.getName(), None))
+
+ def GetConnectionResultType(self, connector, connectortype):
+ if not TestConnectorName(connector.GetName(), self.Type):
+ return connectortype
+ resulttype = connectortype
+ for input in self.Inputs:
+ if input != connector and input.GetType(True) == "ANY" and TestConnectorName(input.GetName(), self.Type):
+ inputtype = input.GetConnectedType()
+ if resulttype is None or inputtype is not None and self.IsOfType(inputtype, resulttype):
+ resulttype = inputtype
+ for output in self.Outputs:
+ if output != connector and output.GetType(True) == "ANY" and TestConnectorName(output.GetName(), self.Type):
+ outputtype = output.GetConnectedType()
+ if resulttype is None or outputtype is not None and self.IsOfType(outputtype, resulttype):
+ resulttype = outputtype
+ return resulttype
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ return {"inputs" : self.Inputs, "outputs" : self.Outputs}
+
+ # Test if point given is on one of the block connectors
+ def TestConnector(self, pt, direction = None, exclude = True):
+ # Test each input connector
+ for input in self.Inputs:
+ if input.TestPoint(pt, direction, exclude):
+ return input
+ # Test each output connector
+ for output in self.Outputs:
+ if output.TestPoint(pt, direction, exclude):
+ return output
+ return None
+
+ # Changes the block type
+ def SetType(self, type, extension, inputs = None, connectors = {}, executionControl = False):
+ if type != self.Type or self.Extension != extension or executionControl != self.ExecutionControl:
+ if type != self.Type:
+ self.Type = type
+ self.TypeSize = self.Parent.GetTextExtent(self.Type)
+ self.Extension = extension
+ self.ExecutionControl = executionControl
+ # Find the block definition from type given and create the corresponding
+ # inputs and outputs
+ blocktype = self.Parent.GetBlockType(type, inputs)
+ if blocktype:
+ self.Colour = wx.BLACK
+ inputs = [input for input in blocktype["inputs"]]
+ outputs = [output for output in blocktype["outputs"]]
+ if blocktype["extensible"]:
+ start = int(inputs[-1][0].replace("IN", ""))
+ for i in xrange(self.Extension - len(blocktype["inputs"])):
+ start += 1
+ inputs.append(("IN%d"%start, inputs[-1][1], inputs[-1][2]))
+ comment = blocktype["comment"]
+ self.Description = _(comment) + blocktype.get("usage", "")
+ else:
+ self.Colour = wx.RED
+ inputs = connectors.get("inputs", [])
+ outputs = connectors.get("outputs", [])
+ self.Description = None
+ if self.ExecutionControl:
+ inputs.insert(0, ("EN","BOOL","none"))
+ outputs.insert(0, ("ENO","BOOL","none"))
+ self.Pen = MiterPen(self.Colour)
+
+ # Extract the inputs properties and create or modify the corresponding connector
+ idx = 0
+ for idx, (input_name, input_type, input_modifier) in enumerate(inputs):
+ if idx < len(self.Inputs):
+ connector = self.Inputs[idx]
+ connector.SetName(input_name)
+ connector.SetType(input_type)
+ else:
+ connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
+ self.Inputs.append(connector)
+ if input_modifier == "negated":
+ connector.SetNegated(True)
+ elif input_modifier != "none":
+ connector.SetEdge(input_modifier)
+ for i in xrange(idx + 1, len(self.Inputs)):
+ self.Inputs[i].UnConnect(delete = True)
+ self.Inputs = self.Inputs[:idx + 1]
+
+ # Extract the outputs properties and create or modify the corresponding connector
+ idx = 0
+ for idx, (output_name, output_type, output_modifier) in enumerate(outputs):
+ if idx < len(self.Outputs):
+ connector = self.Outputs[idx]
+ connector.SetName(output_name)
+ connector.SetType(output_type)
+ else:
+ connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
+ self.Outputs.append(connector)
+ if output_modifier == "negated":
+ connector.SetNegated(True)
+ elif output_modifier != "none":
+ connector.SetEdge(output_modifier)
+ for i in xrange(idx + 1, len(self.Outputs)):
+ self.Outputs[i].UnConnect(delete = True)
+ self.Outputs = self.Outputs[:idx + 1]
+
+ self.RefreshMinSize()
+ self.RefreshConnectors()
+ self.RefreshBoundingBox()
+
+ # Returns the block type
+ def GetType(self):
+ return self.Type
+
+ # Changes the block name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returs the block name
+ def GetName(self):
+ return self.Name
+
+ # Changes the extension name
+ def SetExtension(self, extension):
+ self.Extension = extension
+
+ # Returs the extension name
+ def GetExtension(self):
+ return self.Extension
+
+ # Changes the execution order
+ def SetExecutionOrder(self, executionOrder):
+ self.ExecutionOrder = executionOrder
+ self.RefreshExecutionOrderSize()
+
+ # Returs the execution order
+ def GetExecutionOrder(self):
+ return self.ExecutionOrder
+
+ # Returs the execution order
+ def GetExecutionControl(self):
+ return self.ExecutionControl
+
+ # Refresh the block minimum size
+ def RefreshMinSize(self):
+ # Calculate the inputs maximum width
+ max_input = 0
+ for input in self.Inputs:
+ w, h = input.GetNameSize()
+ max_input = max(max_input, w)
+ # Calculate the outputs maximum width
+ max_output = 0
+ for output in self.Outputs:
+ w, h = output.GetNameSize()
+ max_output = max(max_output, w)
+ width = max(self.TypeSize[0] + 10, max_input + max_output + 15)
+ height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE
+ self.MinSize = width, height
+
+ # Returns the block minimum size
+ def GetMinSize(self):
+ return self.MinSize
+
+ # Changes the negated property of the connector handled
+ def SetConnectorNegated(self, negated):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetNegated(negated)
+ self.RefreshModel(False)
+
+ # Changes the edge property of the connector handled
+ def SetConnectorEdge(self, edge):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetEdge(edge)
+ self.RefreshModel(False)
+
+## # Method called when a Motion event have been generated
+## def OnMotion(self, event, dc, scaling):
+## if not event.Dragging():
+## pos = event.GetLogicalPosition(dc)
+## for input in self.Inputs:
+## rect = input.GetRedrawRect()
+## if rect.InsideXY(pos.x, pos.y):
+## print "Find input"
+## tip = wx.TipWindow(self.Parent, "Test")
+## tip.SetBoundingRect(rect)
+## return Graphic_Element.OnMotion(self, event, dc, scaling)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the block properties
+ self.Parent.EditBlockContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Popup the menu with special items for a block and a connector if one is handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ self.Parent.PopupBlockMenu(connector)
+ else:
+ self.Parent.PopupBlockMenu()
+
+ # Refreshes the block model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshBlockModel(self)
+ # If block has moved, refresh the model of wires connected to outputs
+ if move:
+ for output in self.Outputs:
+ output.RefreshWires()
+
+ def GetToolTipValue(self):
+ return self.Description
+
+ # Adds an highlight to the block
+ def AddHighlight(self, infos, start, end ,highlight_type):
+ if infos[0] in ["type", "name"] and start[0] == 0 and end[0] == 0:
+ highlights = self.Highlights.setdefault(infos[0], [])
+ AddHighlight(highlights, (start, end, highlight_type))
+ elif infos[0] == "input" and infos[1] < len(self.Inputs):
+ self.Inputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
+ elif infos[0] == "output" and infos[1] < len(self.Outputs):
+ self.Outputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
+
+ # Removes an highlight from the block
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] in ["type", "name"]:
+ highlights = self.Highlights.get(infos[0], [])
+ if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
+ self.Highlights.pop(infos[0])
+ elif infos[0] == "input" and infos[1] < len(self.Inputs):
+ self.Inputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
+ elif infos[0] == "output" and infos[1] < len(self.Outputs):
+ self.Outputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
+
+ # Removes all the highlights of one particular type from the block
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for name, highlights in highlight_items:
+ highlights = ClearHighlights(highlights, highlight_type)
+ if len(highlights) == 0:
+ self.Highlights.pop(name)
+ for input in self.Inputs:
+ input.ClearHighlights(highlight_type)
+ for output in self.Outputs:
+ output.ClearHighlights(highlight_type)
+
+ # Draws block
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(self.Pen)
+ dc.SetBrush(wx.WHITE_BRUSH)
+ dc.SetTextForeground(self.Colour)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ type_size = dc.GetTextExtent(self.Type)
+ executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
+ else:
+ name_size = self.NameSize
+ type_size = self.TypeSize
+ executionorder_size = self.ExecutionOrderSize
+
+ # Draw a rectangle with the block size
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw block name and block type
+ name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y - (name_size[1] + 2))
+ type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2,
+ self.Pos.y + 5)
+ dc.DrawText(self.Name, name_pos[0], name_pos[1])
+ dc.DrawText(self.Type, type_pos[0], type_pos[1])
+ # Draw inputs and outputs connectors
+ for input in self.Inputs:
+ input.Draw(dc)
+ for output in self.Outputs:
+ output.Draw(dc)
+ if self.ExecutionOrder != 0:
+ # Draw block execution order
+ dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
+ self.Pos.y + self.Size[1] + 2)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights.get("name", []), name_pos[0], name_pos[1])
+ DrawHighlightedText(dc, self.Type, self.Highlights.get("type", []), type_pos[0], type_pos[1])
+
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Variable
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a variable
+"""
+
+class FBD_Variable(Graphic_Element):
+
+ # Create a new variable
+ def __init__(self, parent, type, name, value_type, id = None, executionOrder = 0):
+ Graphic_Element.__init__(self, parent)
+ self.Type = None
+ self.ValueType = None
+ self.Id = id
+ self.SetName(name)
+ self.SetExecutionOrder(executionOrder)
+ self.Input = None
+ self.Output = None
+ self.SetType(type, value_type)
+ self.Highlights = []
+
+ # Make a clone of this FBD_Variable
+ def Clone(self, parent, id = None, pos = None):
+ variable = FBD_Variable(parent, self.Type, self.Name, self.ValueType, id)
+ variable.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ variable.SetPosition(pos.x, pos.y)
+ else:
+ variable.SetPosition(self.Pos.x, self.Pos.y)
+ if self.Input:
+ variable.Input = self.Input.Clone(variable)
+ if self.Output:
+ variable.Output = self.Output.Clone(variable)
+ return variable
+
+ def GetConnectorTranslation(self, element):
+ connectors = {}
+ if self.Input is not None:
+ connectors[self.Input] = element.Input
+ if self.Output is not None:
+ connectors[self.Output] = element.Output
+ return connectors
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ if self.Input and self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output and self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Unconnect connector
+ def Clean(self):
+ if self.Input:
+ self.Input.UnConnect(delete = True)
+ if self.Output:
+ self.Output.UnConnect(delete = True)
+
+ # Delete this variable by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteVariable(self)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the size of text for execution order
+ def RefreshExecutionOrderSize(self):
+ self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
+
+ # Refresh the variable bounding box
+ def RefreshBoundingBox(self):
+ if self.Type in (OUTPUT, INOUT):
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ else:
+ bbx_x = self.Pos.x
+ if self.Type == INOUT:
+ bbx_width = self.Size[0] + 2 * CONNECTOR_SIZE
+ else:
+ bbx_width = self.Size[0] + CONNECTOR_SIZE
+ bbx_x = min(bbx_x, self.Pos.x + (self.Size[0] - self.NameSize[0]) / 2)
+ bbx_width = max(bbx_width, self.NameSize[0])
+ bbx_height = self.Size[1]
+ if self.ExecutionOrder != 0:
+ bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
+ bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
+ bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
+ self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width + 1, bbx_height + 1)
+
+ # Refresh the position of the variable connector
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ if scaling is not None:
+ position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
+ else:
+ position = self.Size[1] / 2
+ if self.Input:
+ self.Input.SetPosition(wx.Point(0, position))
+ if self.Output:
+ self.Output.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected to connector
+ def RefreshConnected(self, exclude = []):
+ if self.Input:
+ self.Input.MoveConnected(exclude)
+ if self.Output:
+ self.Output.MoveConnected(exclude)
+
+ # Test if point given is on the variable connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ if self.Input and self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ if self.Output and self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ return None
+
+ # Returns the block connector that starts with the point given if it exists
+ def GetConnector(self, position, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input and output connector if they exists
+ #if self.Input and name == self.Input.GetName():
+ # return self.Input
+ if self.Output and name == self.Output.GetName():
+ return self.Output
+ connectors = []
+ # Test input connector if it exists
+ if self.Input:
+ connectors.append(self.Input)
+ # Test output connector if it exists
+ if self.Output:
+ connectors.append(self.Output)
+ return self.FindNearestConnector(position, connectors)
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ connectors = {"inputs": [], "outputs": []}
+ if self.Input:
+ connectors["inputs"].append(self.Input)
+ if self.Output:
+ connectors["outputs"].append(self.Output)
+ return connectors
+
+ # Changes the negated property of the variable connector if handled
+ def SetConnectorNegated(self, negated):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.SetNegated(negated)
+ self.RefreshModel(False)
+
+ # Changes the variable type
+ def SetType(self, type, value_type):
+ if type != self.Type:
+ self.Type = type
+ self.Clean()
+ self.Input = None
+ self.Output = None
+ # Create an input or output connector according to variable type
+ if self.Type != INPUT:
+ self.Input = Connector(self, "", value_type, wx.Point(0, 0), WEST, onlyone = True)
+ if self.Type != OUTPUT:
+ self.Output = Connector(self, "", value_type, wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+ elif value_type != self.ValueType:
+ if self.Input:
+ self.Input.SetType(value_type)
+ if self.Output:
+ self.Output.SetType(value_type)
+ self.RefreshConnectors()
+
+ # Returns the variable type
+ def GetType(self):
+ return self.Type
+
+ # Changes the variable name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the variable name
+ def GetName(self):
+ return self.Name
+
+ # Changes the execution order
+ def SetExecutionOrder(self, executionOrder):
+ self.ExecutionOrder = executionOrder
+ self.RefreshExecutionOrderSize()
+
+ # Returs the execution order
+ def GetExecutionOrder(self):
+ return self.ExecutionOrder
+
+ # Changes the element size
+ def SetSize(self, width, height):
+ scaling = self.Parent.GetScaling()
+ min_width, min_height = self.GetMinSize()
+ if width < min_width:
+ if self.Type == INPUT:
+ posx = max(0, self.Pos.x + width - min_width)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ elif self.Type == OUTPUT:
+ posx = max(0, self.Pos.x + (width - min_width) / 2)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ width = min_width
+ if scaling is not None:
+ width = round_scaling(width, scaling[0], 1)
+ if height < min_height:
+ posy = max(0, self.Pos.y + (height - min_height) / 2)
+ if scaling is not None:
+ posy = round_scaling(posy, scaling[1])
+ self.Pos.y = posy
+ height = min_height
+ if scaling is not None:
+ height = round_scaling(height, scaling[1], 1)
+ Graphic_Element.SetSize(self, width, height)
+
+ # Returns the variable minimum size
+ def GetMinSize(self):
+ return self.NameSize[0] + 10, self.NameSize[1] + 10
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the variable properties
+ self.Parent.EditVariableContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the variable model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshVariableModel(self)
+ # If variable has moved and variable is not of type OUTPUT, refresh the model
+ # of wires connected to output connector
+ if move and self.Type != OUTPUT:
+ if self.Output:
+ self.Output.RefreshWires()
+
+ # Adds an highlight to the variable
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "expression" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the variable
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "expression":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the variable
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws variable
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
+ else:
+ name_size = self.NameSize
+ executionorder_size = self.ExecutionOrderSize
+
+ text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y + (self.Size[1] - name_size[1]) / 2)
+ # Draw a rectangle with the variable size
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ # Draw variable name
+ dc.DrawText(self.Name, text_pos[0], text_pos[1])
+ # Draw connectors
+ if self.Input:
+ self.Input.Draw(dc)
+ if self.Output:
+ self.Output.Draw(dc)
+ if self.ExecutionOrder != 0:
+ # Draw variable execution order
+ dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
+ self.Pos.y + self.Size[1] + 2)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
+
+#-------------------------------------------------------------------------------
+# Function Block Diagram Connector
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a connection
+"""
+
+class FBD_Connector(Graphic_Element):
+
+ # Create a new connection
+ def __init__(self, parent, type, name, id = None):
+ Graphic_Element.__init__(self, parent)
+ self.Type = type
+ self.Id = id
+ self.SetName(name)
+ self.Pos = wx.Point(0, 0)
+ self.Size = wx.Size(0, 0)
+ self.Highlights = []
+ # Create an input or output connector according to connection type
+ if self.Type == CONNECTOR:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
+ else:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+ self.RefreshNameSize()
+
+ def Flush(self):
+ if self.Connector:
+ self.Connector.Flush()
+ self.Connector = None
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ if self.Connector and self.Connector.IsConnected():
+ rect = rect.Union(self.Connector.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Make a clone of this FBD_Connector
+ def Clone(self, parent, id = None, pos = None):
+ connection = FBD_Connector(parent, self.Type, self.Name, id)
+ connection.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ connection.SetPosition(pos.x, pos.y)
+ else:
+ connection.SetPosition(self.Pos.x, self.Pos.y)
+ connection.Connector = self.Connector.Clone(connection)
+ return connection
+
+ def GetConnectorTranslation(self, element):
+ return {self.Connector : element.Connector}
+
+ # Unconnect connector
+ def Clean(self):
+ if self.Connector:
+ self.Connector.UnConnect(delete = True)
+
+ # Delete this connection by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteConnection(self)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Refresh the connection bounding box
+ def RefreshBoundingBox(self):
+ if self.Type == CONNECTOR:
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ else:
+ bbx_x = self.Pos.x
+ bbx_width = self.Size[0] + CONNECTOR_SIZE
+ self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width, self.Size[1])
+
+ # Refresh the position of the connection connector
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ if scaling is not None:
+ position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
+ else:
+ position = self.Size[1] / 2
+ if self.Type == CONNECTOR:
+ self.Connector.SetPosition(wx.Point(0, position))
+ else:
+ self.Connector.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected to connector
+ def RefreshConnected(self, exclude = []):
+ if self.Connector:
+ self.Connector.MoveConnected(exclude)
+
+ # Test if point given is on the connection connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ if self.Connector and self.Connector.TestPoint(pt, direction, exclude):
+ return self.Connector
+ return None
+
+ # Returns the connection connector
+ def GetConnector(self, position = None, name = None):
+ return self.Connector
+
+ # Returns all the block connectors
+ def GetConnectors(self):
+ connectors = {"inputs": [], "outputs": []}
+ if self.Type == CONNECTOR:
+ connectors["inputs"].append(self.Connector)
+ else:
+ connectors["outputs"].append(self.Connector)
+ return connectors
+
+ # Changes the variable type
+ def SetType(self, type):
+ if type != self.Type:
+ self.Type = type
+ self.Clean()
+ # Create an input or output connector according to connection type
+ if self.Type == CONNECTOR:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
+ else:
+ self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
+ self.RefreshConnectors()
+
+ # Returns the connection type
+ def GetType(self):
+ return self.Type
+
+ def GetConnectionResultType(self, connector, connectortype):
+ if self.Type == CONTINUATION:
+ connector = self.Parent.GetConnectorByName(self.Name)
+ if connector is not None:
+ return connector.Connector.GetConnectedType()
+ return connectortype
+
+ # Changes the connection name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the connection name
+ def GetName(self):
+ return self.Name
+
+ # Changes the element size
+ def SetSize(self, width, height):
+ scaling = self.Parent.GetScaling()
+ min_width, min_height = self.GetMinSize()
+ if width < min_width:
+ if self.Type == CONTINUATION:
+ posx = max(0, self.Pos.x + width - min_width)
+ if scaling is not None:
+ posx = round_scaling(posx, scaling[0])
+ self.Pos.x = posx
+ width = min_width
+ if scaling is not None:
+ width = round_scaling(width, scaling[0], 1)
+ if height < min_height:
+ posy = max(0, self.Pos.y + (height - min_height) / 2)
+ if scaling is not None:
+ posy = round_scaling(posy, scaling[1])
+ self.Pos.y = posy
+ height = min_height
+ if scaling is not None:
+ height = round_scaling(height, scaling[1], 1)
+ Graphic_Element.SetSize(self, width, height)
+
+ # Returns the connection minimum size
+ def GetMinSize(self):
+ text_width, text_height = self.NameSize
+ if text_height % 2 == 1:
+ text_height += 1
+ return text_width + text_height + 20, text_height + 10
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the connection properties
+ self.Parent.EditConnectionContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the connection model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshConnectionModel(self)
+ # If connection has moved and connection is of type CONTINUATION, refresh
+ # the model of wires connected to connector
+ if move and self.Type == CONTINUATION:
+ if self.Connector:
+ self.Connector.RefreshWires()
+
+ # Adds an highlight to the connection
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the connection
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the connection
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws connection
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ else:
+ name_size = self.NameSize
+
+ # Draw a rectangle with the connection size with arrows inside
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+ arrowsize = min(self.Size[1] / 2, (self.Size[0] - name_size[0] - 10) / 2)
+ dc.DrawLine(self.Pos.x, self.Pos.y, self.Pos.x + arrowsize,
+ self.Pos.y + self.Size[1] / 2)
+ dc.DrawLine(self.Pos.x + arrowsize, self.Pos.y + self.Size[1] / 2,
+ self.Pos.x, self.Pos.y + self.Size[1])
+ dc.DrawLine(self.Pos.x + self.Size[0] - arrowsize, self.Pos.y,
+ self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2)
+ dc.DrawLine(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2,
+ self.Pos.x + self.Size[0] - arrowsize, self.Pos.y + self.Size[1])
+ # Draw connection name
+ text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y + (self.Size[1] - name_size[1]) / 2)
+ dc.DrawText(self.Name, text_pos[0], text_pos[1])
+ # Draw connector
+ if self.Connector:
+ self.Connector.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/GraphicCommons.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,3269 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+from time import time as gettime
+from math import *
+from types import *
+import datetime
+from threading import Semaphore
+
+#-------------------------------------------------------------------------------
+# 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_POWERRAIL_WIDTH = 3 # Width of a Powerrail
+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 = 40 # 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, COIL_RISING, COIL_FALLING] = range(6)
+[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_COIL, MODE_CONTACT, MODE_POWERRAIL, MODE_INITIALSTEP, MODE_STEP,
+ MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = range(15)
+
+# Contants for defining alignment types for graphic group
+[ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = range(6)
+
+# Contants for defining which drawing mode is selected for app
+[FREEDRAWING_MODE, DRIVENDRAWING_MODE] = [1, 2]
+
+# Color for Highlighting
+HIGHLIGHTCOLOR = wx.CYAN
+
+# Define highlight types
+ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0), wx.RED)
+SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0), wx.WHITE)
+
+# Define highlight refresh inhibition period in second
+REFRESH_HIGHLIGHT_PERIOD = 0.1
+
+# Define tooltip wait for displaying period in second
+TOOLTIP_WAIT_PERIOD = 0.5
+
+HANDLE_CURSORS = {
+ (1, 1) : 2,
+ (3, 3) : 2,
+ (1, 3) : 3,
+ (3, 1) : 3,
+ (1, 2) : 4,
+ (3, 2) : 4,
+ (2, 1) : 5,
+ (2, 3) : 5
+}
+
+def round_scaling(x, n, constraint=0):
+ fraction = float(x) / float(n)
+ if constraint == - 1:
+ xround = int(fraction)
+ else:
+ xround = round(fraction)
+ if constraint == 1 and int(fraction) == xround:
+ xround += 1
+ return xround * n
+
+"""
+Basic vector operations for calculate wire points
+"""
+
+# 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
+
+# Calculate the scalar product of two vectors
+def is_null_vector(v):
+ return v == (0, 0)
+
+# Calculate the scalar product of two vectors
+def add_vectors(v1, v2):
+ return (v1[0] + v2[0], v1[1] + v2[1])
+
+# Calculate the scalar product of two vectors
+def product(v1, v2):
+ return v1[0] * v2[0] + v1[1] * v2[1]
+
+
+"""
+Function that calculates the nearest point of the grid defined by scaling for the given point
+"""
+
+def GetScaledEventPosition(event, dc, scaling):
+ pos = event.GetLogicalPosition(dc)
+ 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
+
+SECOND = 1000000
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+DAY = 24 * HOUR
+
+def generate_time(value):
+ microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
+ negative = microseconds < 0
+ microseconds = abs(microseconds)
+ data = "T#"
+ not_null = False
+ if negative:
+ data += "-"
+ for val, format in [(int(microseconds) / DAY, "%dd"),
+ ((int(microseconds) % DAY) / HOUR, "%dh"),
+ ((int(microseconds) % HOUR) / MINUTE, "%dm"),
+ ((int(microseconds) % MINUTE) / SECOND, "%ds")]:
+ if val > 0 or not_null:
+ data += format % val
+ not_null = True
+ data += "%gms" % (microseconds % SECOND / 1000.)
+ return data
+
+def generate_date(value):
+ base_date = datetime.datetime(1970, 1, 1)
+ date = base_date + value
+ return date.strftime("DATE#%Y-%m-%d")
+
+def generate_datetime(value):
+ base_date = datetime.datetime(1970, 1, 1)
+ date_time = base_date + value
+ return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
+
+def generate_timeofday(value):
+ microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
+ negative = microseconds < 0
+ microseconds = abs(microseconds)
+ data = "TOD#"
+ for val, format in [(int(microseconds) / HOUR, "%2.2d:"),
+ ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),
+ ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),
+ (microseconds % SECOND, "%6.6d")]:
+ data += format % val
+ return data
+
+TYPE_TRANSLATOR = {"TIME": generate_time,
+ "DATE": generate_date,
+ "DT": generate_datetime,
+ "TOD": generate_timeofday}
+
+def MiterPen(colour, width=1, style=wx.SOLID):
+ pen = wx.Pen(colour, width, style)
+ pen.SetJoin(wx.JOIN_MITER)
+ pen.SetCap(wx.CAP_PROJECTING)
+ return pen
+
+#-------------------------------------------------------------------------------
+# Debug Data Consumer Class
+#-------------------------------------------------------------------------------
+
+class DebugDataConsumer:
+
+ def __init__(self):
+ self.LastValue = None
+ self.Value = None
+ self.DataType = None
+ self.LastForced = False
+ self.Forced = False
+ self.Inhibited = False
+
+ def Inhibit(self, inhibit):
+ self.Inhibited = inhibit
+ if not inhibit and self.LastValue is not None:
+ self.SetForced(self.LastForced)
+ self.SetValue(self.LastValue)
+ self.LastValue = None
+
+ def SetDataType(self, data_type):
+ self.DataType = data_type
+
+ def NewValue(self, tick, value, forced=False):
+ value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value)
+ if self.Inhibited:
+ self.LastValue = value
+ self.LastForced = forced
+ else:
+ self.SetForced(forced)
+ self.SetValue(value)
+
+ def SetValue(self, value):
+ self.Value = value
+
+ def GetValue(self):
+ return self.Value
+
+ def SetForced(self, forced):
+ self.Forced = forced
+
+ def IsForced(self):
+ return self.Forced
+
+#-------------------------------------------------------------------------------
+# Debug Viewer Class
+#-------------------------------------------------------------------------------
+
+REFRESH_PERIOD = 0.1
+
+class DebugViewer:
+
+ def __init__(self, producer, debug, register_tick=True):
+ self.DataProducer = None
+ self.Debug = debug
+ self.RegisterTick = register_tick
+ self.Inhibited = False
+
+ self.DataConsumers = {}
+
+ self.LastRefreshTime = gettime()
+ self.RefreshLock = Semaphore()
+
+ self.RefreshTimer = wx.Timer(self, -1)
+ self.Bind(wx.EVT_TIMER, self.OnRefreshTimer, self.RefreshTimer)
+
+ self.SetDataProducer(producer)
+
+ def __del__(self):
+ self.DataProducer = None
+ self.DeleteDataConsumers()
+ self.RefreshTimer.Stop()
+
+ def SetDataProducer(self, producer):
+ if self.RegisterTick and self.Debug:
+ if producer is not None:
+ producer.SubscribeDebugIECVariable("__tick__", self)
+ if self.DataProducer is not None:
+ self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
+ self.DataProducer = producer
+
+ def IsDebugging(self):
+ return self.Debug
+
+ def Inhibit(self, inhibit):
+ for consumer, iec_path in self.DataConsumers.iteritems():
+ consumer.Inhibit(inhibit)
+ self.Inhibited = inhibit
+
+ def AddDataConsumer(self, iec_path, consumer):
+ if self.DataProducer is None:
+ return None
+ result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer)
+ if result is not None and consumer != self:
+ self.DataConsumers[consumer] = iec_path
+ consumer.SetDataType(self.GetDataType(iec_path))
+ return result
+
+ def RemoveDataConsumer(self, consumer):
+ iec_path = self.DataConsumers.pop(consumer, None)
+ if iec_path is not None:
+ self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
+
+ def GetDataType(self, iec_path):
+ if self.DataProducer is not None:
+ return self.DataProducer.GetDebugIECVariableType(iec_path)
+ return None
+
+ def ForceDataValue(self, iec_path, value):
+ if self.DataProducer is not None:
+ self.DataProducer.ForceDebugIECVariable(iec_path, value)
+
+ def ReleaseDataValue(self, iec_path):
+ if self.DataProducer is not None:
+ self.DataProducer.ReleaseDebugIECVariable(iec_path)
+
+ def DeleteDataConsumers(self):
+ if self.DataProducer is not None:
+ for consumer, iec_path in self.DataConsumers.iteritems():
+ self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
+ self.DataConsumers = {}
+
+ def OnRefreshTimer(self, event):
+ self.RefreshNewData()
+ event.Skip()
+
+ def NewDataAvailable(self, *args, **kwargs):
+ self.RefreshTimer.Stop()
+ if not self.Inhibited:
+ current_time = gettime()
+ if current_time - self.LastRefreshTime > REFRESH_PERIOD and self.RefreshLock.acquire(False):
+ self.LastRefreshTime = gettime()
+ self.Inhibit(True)
+ wx.CallAfter(self.RefreshViewOnNewData, *args, **kwargs)
+
+ def RefreshViewOnNewData(self, *args, **kwargs):
+ if self:
+ self.RefreshNewData(*args, **kwargs)
+ self.RefreshTimer.Start(int(REFRESH_PERIOD * 1000), oneShot=True)
+
+ def RefreshNewData(self, *args, **kwargs):
+ self.Inhibit(False)
+ self.RefreshLock.release()
+
+#-------------------------------------------------------------------------------
+# Viewer Rubberband
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a rubberband
+"""
+
+class RubberBand:
+
+ # Create a rubberband by indicated on which window it must be drawn
+ def __init__(self, viewer):
+ self.Viewer = viewer
+ self.drawingSurface = viewer.Editor
+ 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):
+ if self.currentBox is None:
+ return self.lastBox
+ return self.currentBox
+
+ # Method called when a new box starts to be edited
+ def OnLeftDown(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Save the point for calculate the box position and size
+ self.startPoint = pos
+ self.currentBox = wx.Rect(pos.x, pos.y, 0, 0)
+ self.drawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
+ self.Redraw()
+
+ # Method called when dragging with a box edited
+ def OnMotion(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Save the last position and size of the box for erasing it
+ self.lastBox = wx.Rect(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, dc, scaling):
+ self.drawingSurface.SetCursor(wx.NullCursor)
+ self.lastBox = self.currentBox
+ self.currentBox = None
+ self.Redraw()
+
+ # Method that erase the last box and draw the new box
+ def Redraw(self, dc = None):
+ if dc is None:
+ dc = self.Viewer.GetLogicalDC()
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.XOR)
+ if self.lastBox:
+ # Erase last box
+ dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
+ self.lastBox.width * scalex, self.lastBox.height * scaley)
+ if self.currentBox:
+ # Draw current box
+ dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
+ self.currentBox.width * scalex, self.currentBox.height * scaley)
+ dc.SetUserScale(scalex, scaley)
+
+ # Erase last box
+ def Erase(self, dc = None):
+ if dc is None:
+ dc = self.Viewer.GetLogicalDC()
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.XOR)
+ if self.lastBox:
+ dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
+ self.lastBox.width * scalex, self.lastBox.height * scalex)
+ dc.SetUserScale(scalex, scaley)
+
+ # Draw current box
+ def Draw(self, dc = None):
+ if dc is None:
+ dc = self.Viewer.GetLogicalDC()
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.XOR)
+ if self.currentBox:
+ # Draw current box
+ dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
+ self.currentBox.width * scalex, self.currentBox.height * scaley)
+ dc.SetUserScale(scalex, scaley)
+
+#-------------------------------------------------------------------------------
+# Viewer ToolTip
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom tool tip
+"""
+
+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,
+ }
+
+TOOLTIP_MAX_CHARACTERS = 30
+TOOLTIP_MAX_LINE = 5
+
+class ToolTip(wx.PopupWindow):
+
+ def __init__(self, parent, tip):
+ wx.PopupWindow.__init__(self, parent)
+
+ self.CurrentPosition = wx.Point(0, 0)
+
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ self.SetTip(tip)
+
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+
+ def SetTip(self, tip):
+ lines = []
+ for line in tip.splitlines():
+ if line != "":
+ words = line.split()
+ new_line = words[0]
+ for word in words[1:]:
+ if len(new_line + " " + word) <= TOOLTIP_MAX_CHARACTERS:
+ new_line += " " + word
+ else:
+ lines.append(new_line)
+ new_line = word
+ lines.append(new_line)
+ else:
+ lines.append(line)
+ if len(lines) > TOOLTIP_MAX_LINE:
+ self.Tip = lines[:TOOLTIP_MAX_LINE]
+ if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
+ self.Tip[-1] += "..."
+ else:
+ self.Tip[-1] = self.Tip[-1][:TOOLTIP_MAX_CHARACTERS - 3] + "..."
+ else:
+ self.Tip = lines
+ wx.CallAfter(self.RefreshTip)
+
+ def MoveToolTip(self, pos):
+ self.CurrentPosition = pos
+ self.SetPosition(pos)
+
+ def GetTipExtent(self):
+ max_width = 0
+ max_height = 0
+ for line in self.Tip:
+ w, h = self.GetTextExtent(line)
+ max_width = max(max_width, w)
+ max_height += h
+ return max_width, max_height
+
+ def RefreshTip(self):
+ if self:
+ w, h = self.GetTipExtent()
+ self.SetSize(wx.Size(w + 4, h + 4))
+ self.SetPosition(self.CurrentPosition)
+ self.Refresh()
+
+ def OnPaint(self, event):
+ dc = wx.AutoBufferedPaintDC(self)
+ dc.Clear()
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
+ dc.SetFont(wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"]))
+ dc.BeginDrawing()
+ w, h = self.GetTipExtent()
+ dc.DrawRectangle(0, 0, w + 4, h + 4)
+ offset = 0
+ for line in self.Tip:
+ dc.DrawText(line, 2, offset + 2)
+ w, h = dc.GetTextExtent(line)
+ offset += h
+ dc.EndDrawing()
+ event.Skip()
+
+#-------------------------------------------------------------------------------
+# Helpers for highlighting text
+#-------------------------------------------------------------------------------
+
+def AddHighlight(highlights, infos):
+ RemoveHighlight(highlights, infos)
+ highlights.append(infos)
+
+def RemoveHighlight(highlights, infos):
+ if infos in highlights:
+ highlights.remove(infos)
+ return True
+ return False
+
+def ClearHighlight(highlights, highlight_type=None):
+ if highlight_type is not None:
+ return [highlight for highlight in highlights if highlight[2] != highlight_type]
+ return []
+
+def DrawHighlightedText(dc, text, highlights, x, y):
+ current_pen = dc.GetPen()
+ dc.SetPen(wx.TRANSPARENT_PEN)
+ for start, end, highlight_type in highlights:
+ dc.SetBrush(wx.Brush(highlight_type[0]))
+ offset_width, offset_height = dc.GetTextExtent(text[:start[1]])
+ part = text[start[1]:end[1] + 1]
+ part_width, part_height = dc.GetTextExtent(part)
+ dc.DrawRectangle(x + offset_width, y, part_width, part_height)
+ dc.SetTextForeground(highlight_type[1])
+ dc.DrawText(part, x + offset_width, y)
+ dc.SetPen(current_pen)
+ dc.SetTextForeground(wx.BLACK)
+
+#-------------------------------------------------------------------------------
+# 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.StartPos = None
+ self.CurrentDrag = None
+ self.Handle = (None,None)
+ self.Dragging = False
+ self.Selected = False
+ self.Highlighted = False
+ self.Pos = wx.Point(0, 0)
+ self.Size = wx.Size(0, 0)
+ self.BoundingBox = wx.Rect(0, 0, 0, 0)
+ self.Visible = False
+ self.ToolTip = None
+ self.ToolTipPos = None
+ self.ToolTipTimer = wx.Timer(self.Parent, -1)
+ self.Parent.Bind(wx.EVT_TIMER, self.OnToolTipTimer, self.ToolTipTimer)
+
+ def __del__(self):
+ self.ToolTipTimer.Stop()
+
+ def GetDefinition(self):
+ return [self.Id], []
+
+ def TestVisible(self, screen):
+ self.Visible = self.GetRedrawRect().Intersects(screen)
+
+ def IsVisible(self):
+ return self.Visible
+
+ def SpreadCurrent(self):
+ pass
+
+ def GetConnectorTranslation(self, element):
+ return {}
+
+ def FindNearestConnector(self, position, connectors):
+ distances = []
+ for connector in connectors:
+ connector_pos = connector.GetRelPosition()
+ distances.append((sqrt((self.Pos.x + connector_pos.x - position.x) ** 2 +
+ (self.Pos.y + connector_pos.y - position.y) ** 2),
+ connector))
+ distances.sort()
+ if len(distances) > 0:
+ return distances[0][1]
+ return None
+
+ def IsOfType(self, type, reference):
+ return self.Parent.IsOfType(type, reference)
+
+ def IsEndType(self, type):
+ return self.Parent.IsEndType(type)
+
+ def GetDragging(self):
+ return self.Dragging
+
+ # Make a clone of this element
+ def Clone(self, parent):
+ return Graphic_Element(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()
+
+ # Returns the minimum element size
+ def GetMinSize(self):
+ return 0, 0
+
+ # Refresh the element Bounding Box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wx.Rect(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, connectors=True):
+ if connectors:
+ rect = self.BoundingBox
+ else:
+ rect = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
+ return rect.InsideXY(pt.x, pt.y)
+
+ # Returns if the point given is in the bounding box
+ def IsInSelection(self, rect):
+ return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
+
+ # Override this method for refreshing the bounding box
+ def RefreshBoundingBox(self):
+ pass
+
+ # Returns the bounding box
+ def GetBoundingBox(self):
+ return self.BoundingBox
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ scalex, scaley = self.Parent.GetViewScale()
+ rect = wx.Rect()
+ rect.x = self.BoundingBox.x - int(HANDLE_SIZE / scalex) - 3 - abs(movex)
+ rect.y = self.BoundingBox.y - int(HANDLE_SIZE / scaley) - 3 - abs(movey)
+ rect.width = self.BoundingBox.width + 2 * (int(HANDLE_SIZE / scalex) + abs(movex) + 1) + 4
+ rect.height = self.BoundingBox.height + 2 * (int(HANDLE_SIZE / scaley) + abs(movey) + 1) + 4
+ return rect
+
+ def Refresh(self, rect = None):
+ if self.Visible:
+ if rect is not None:
+ self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
+ else:
+ self.Parent.RefreshRect(self.Parent.GetScrolledRect(self.GetRedrawRect()), False)
+
+ # Change the variable that indicates if this element is selected
+ def SetSelected(self, selected):
+ self.Selected = selected
+ self.Refresh()
+
+ # Change the variable that indicates if this element is highlighted
+ def SetHighlighted(self, highlighted):
+ self.Highlighted = highlighted
+ self.Refresh()
+
+ # Test if the point is on a handle of this element
+ def TestHandle(self, event):
+ dc = self.Parent.GetLogicalDC()
+ scalex, scaley = dc.GetUserScale()
+ pos = event.GetPosition()
+ pt = wx.Point(*self.Parent.CalcUnscrolledPosition(pos.x, pos.y))
+
+ left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
+ center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
+ right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
+
+ top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
+ middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
+ bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
+
+ extern_rect = wx.Rect(left, top, right + HANDLE_SIZE - left, bottom + HANDLE_SIZE - top)
+ intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
+
+ # Verify that this element is selected
+ if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
+ # Find if point is on a handle horizontally
+ if left <= pt.x < left + HANDLE_SIZE:
+ handle_x = 1
+ elif center <= pt.x < center + HANDLE_SIZE:
+ handle_x = 2
+ elif right <= pt.x < right + HANDLE_SIZE:
+ handle_x = 3
+ else:
+ handle_x = 0
+ # Find if point is on a handle vertically
+ if top <= pt.y < top + HANDLE_SIZE:
+ handle_y = 1
+ elif middle <= pt.y < middle + HANDLE_SIZE:
+ handle_y = 2
+ elif bottom <= pt.y < bottom + HANDLE_SIZE:
+ 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, dc, scaling):
+ pos = event.GetLogicalPosition(dc)
+ # Test if an handle have been clicked
+ handle = self.TestHandle(event)
+ # Find which type of handle have been clicked,
+ # Save a resize event and change the cursor
+ cursor = HANDLE_CURSORS.get(handle, 1)
+ wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
+ if cursor > 1:
+ self.Handle = (HANDLE_RESIZE, handle)
+ else:
+ self.Handle = (HANDLE_MOVE, None)
+ self.SetSelected(False)
+ # Initializes the last position
+ self.oldPos = GetScaledEventPosition(event, dc, scaling)
+ self.StartPos = wx.Point(self.Pos.x, self.Pos.y)
+ self.CurrentDrag = wx.Point(0, 0)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, dc, scaling):
+ # If a dragging have been initiated
+ if self.Dragging and self.oldPos:
+ self.RefreshModel()
+ self.Parent.RefreshBuffer()
+ wx.CallAfter(self.Parent.SetCurrentCursor, 0)
+ self.SetSelected(True)
+ self.oldPos = None
+
+ # Method called when a RightDown event have been generated
+ def OnRightDown(self, event, dc, scaling):
+ pass
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ if self.Dragging and self.oldPos:
+ self.RefreshModel()
+ self.Parent.RefreshBuffer()
+ wx.CallAfter(self.Parent.SetCurrentCursor, 0)
+ self.SetSelected(True)
+ self.oldPos = None
+ if self.Parent.Debug:
+ self.Parent.PopupForceMenu()
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ pass
+
+ # Method called when a Motion event have been generated
+ def OnMotion(self, event, dc, scaling):
+ # If the cursor is dragging and the element have been clicked
+ if event.Dragging() and self.oldPos:
+ # Calculate the movement of cursor
+ pos = event.GetLogicalPosition(dc)
+ 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:
+ dragx, dragy = self.ProcessDragging(movex, movey, event, scaling)
+ if event.ControlDown() and self.Handle[0] == HANDLE_MOVE:
+ self.oldPos.x = self.StartPos.x + self.CurrentDrag.x
+ self.oldPos.y = self.StartPos.y + self.CurrentDrag.y
+ else:
+ self.oldPos.x += dragx
+ self.oldPos.y += dragy
+ return dragx, dragy
+ return movex, movey
+ # If cursor just pass over the element, changes the cursor if it is on a handle
+ else:
+ pos = event.GetLogicalPosition(dc)
+ handle = self.TestHandle(event)
+ # Find which type of handle have been clicked,
+ # Save a resize event and change the cursor
+ cursor = HANDLE_CURSORS.get(handle, 0)
+ wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
+ return 0, 0
+
+ # Moves the element
+ def Move(self, dx, dy, exclude = []):
+ self.Pos.x += max(-self.BoundingBox.x, dx)
+ self.Pos.y += max(-self.BoundingBox.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)
+
+ # Moves and Resizes the element for fitting scaling
+ def AdjustToScaling(self, scaling):
+ if scaling is not None:
+ movex = round_scaling(self.Pos.x, scaling[0]) - self.Pos.x
+ movey = round_scaling(self.Pos.y, scaling[1]) - self.Pos.y
+ min_width, min_height = self.GetMinSize()
+ width = max(round_scaling(min_width, scaling[0], 1),
+ round_scaling(self.Size.width, scaling[0]))
+ height = max(round_scaling(min_height, scaling[1], 1),
+ round_scaling(self.Size.height, scaling[1]))
+ self.Resize(movex, movey, width, height)
+ return movex, movey
+ return 0, 0
+
+ # Refreshes the element state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling, width_fac = 1, height_fac = 1):
+ handle_type, handle = self.Handle
+ # If it is a resize handle, calculate the values from resizing
+ if handle_type == HANDLE_RESIZE:
+ if scaling is not None:
+ scaling = (scaling[0] * width_fac, scaling[1] * height_fac)
+ x = y = start_x = start_y = 0
+ width, height = start_width, start_height = self.GetSize()
+ if handle[0] == 1:
+ movex = max(-self.BoundingBox.x, movex)
+ if scaling is not None:
+ movex = -(round_scaling(width - movex, scaling[0]) - width)
+ x = movex
+ if event.ShiftDown():
+ width -= 2 * movex
+ else:
+ width -= movex
+ elif handle[0] == 3:
+ if scaling is not None:
+ movex = round_scaling(width + movex, scaling[0]) - width
+ if event.ShiftDown():
+ movex = min(self.BoundingBox.x, movex)
+ x = -movex
+ width += 2 * movex
+ else:
+ width += movex
+ if handle[1] == 1:
+ movey = max(-self.BoundingBox.y, movey)
+ if scaling is not None:
+ movey = -(round_scaling(height - movey, scaling[1]) - height)
+ y = movey
+ if event.ShiftDown():
+ height -= 2 * movey
+ else:
+ height -= movey
+ elif handle[1] == 3:
+ if scaling is not None:
+ movey = round_scaling(height + movey, scaling[1]) - height
+ if event.ShiftDown():
+ movey = min(self.BoundingBox.y, movey)
+ y = -movey
+ height += 2 * movey
+ else:
+ height += movey
+ # Verify that new size is not lesser than minimum
+ min_width, min_height = self.GetMinSize()
+ if handle[0] != 2 and (width >= min_width or width > self.Size[0]):
+ start_x = x
+ start_width = width
+ else:
+ movex = 0
+ if handle[1] != 2 and (height >= min_height or height > self.Size[1]):
+ start_y = y
+ start_height = height
+ else:
+ movey = 0
+ if movex != 0 or movey != 0:
+ self.Resize(start_x, start_y, start_width, start_height)
+ return movex, movey
+ # If it is a move handle, Move this element
+ elif handle_type == HANDLE_MOVE:
+ movex = max(-self.BoundingBox.x, movex)
+ movey = max(-self.BoundingBox.y, movey)
+ if scaling is not None:
+ movex = round_scaling(self.Pos.x + movex, scaling[0]) - self.Pos.x
+ movey = round_scaling(self.Pos.y + movey, scaling[1]) - self.Pos.y
+ if event.ControlDown():
+ self.CurrentDrag.x = self.CurrentDrag.x + movex
+ self.CurrentDrag.y = self.CurrentDrag.y + movey
+ if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
+ movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
+ movey = self.StartPos.y - self.Pos.y
+ else:
+ movex = self.StartPos.x - self.Pos.x
+ movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
+ self.Move(movex, movey)
+ return movex, movey
+ return 0, 0
+
+ def OnToolTipTimer(self, event):
+ value = self.GetToolTipValue()
+ if value is not None and self.ToolTipPos is not None:
+ self.ToolTip = ToolTip(self.Parent, value)
+ self.ToolTip.MoveToolTip(self.ToolTipPos)
+ self.ToolTip.Show()
+
+ def GetToolTipValue(self):
+ return None
+
+ def CreateToolTip(self, pos):
+ value = self.GetToolTipValue()
+ if value is not None:
+ self.ToolTipPos = pos
+ self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+
+ def MoveToolTip(self, pos):
+ if self.ToolTip is not None:
+ self.ToolTip.MoveToolTip(pos)
+ elif self.ToolTipPos is not None:
+ self.ToolTipPos = pos
+ self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+
+ def ClearToolTip(self):
+ self.ToolTipTimer.Stop()
+ self.ToolTipPos = None
+ if self.ToolTip is not None:
+ self.ToolTip.Destroy()
+ self.ToolTip = None
+
+ # Override this method for defining the method to call for adding an highlight to this element
+ def AddHighlight(self, infos, start, end, highlight_type):
+ pass
+
+ # Override this method for defining the method to call for removing an highlight from this element
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ pass
+
+ # Override this method for defining the method to call for removing all the highlights of one particular type from this element
+ def ClearHighlight(self, highlight_type=None):
+ pass
+
+ # Override this method for defining the method to call for refreshing the model of this element
+ def RefreshModel(self, move=True):
+ pass
+
+ # Draws the highlightment of this element if it is highlighted (can be overwritten)
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ dc.DrawRectangle(int(round((self.Pos.x - 1) * scalex)) - 2,
+ int(round((self.Pos.y - 1) * scaley)) - 2,
+ int(round((self.Size.width + 3) * scalex)) + 5,
+ int(round((self.Size.height + 3) * scaley)) + 5)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Draws the handles of this element if it is selected
+ def Draw(self, dc):
+ if not getattr(dc, "printing", False):
+ if self.Highlighted:
+ self.DrawHighlightment(dc)
+ if self.Selected:
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
+ center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
+ right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
+
+ top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
+ middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
+ bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
+
+ for x, y in [(left, top), (center, top), (right, top),
+ (left, middle), (right, middle),
+ (left, bottom), (center, bottom), (right, bottom)]:
+ dc.DrawRectangle(x, y, HANDLE_SIZE, HANDLE_SIZE)
+
+ dc.SetUserScale(scalex, scaley)
+
+
+#-------------------------------------------------------------------------------
+# 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.RefreshWireExclusion()
+ self.RefreshBoundingBox()
+
+ # Destructor
+ def __del__(self):
+ self.Elements = []
+
+ def GetDefinition(self):
+ blocks = []
+ wires = []
+ for element in self.Elements:
+ block, wire = element.GetDefinition()
+ blocks.extend(block)
+ wires.extend(wire)
+ return blocks, wires
+
+ # Make a clone of this element
+ def Clone(self, parent, pos = None):
+ group = Graphic_Group(parent)
+ connectors = {}
+ exclude_names = {}
+ wires = []
+ if pos is not None:
+ dx, dy = pos.x - self.BoundingBox.x, pos.y - self.BoundingBox.y
+ for element in self.Elements:
+ if isinstance(element, Wire):
+ wires.append(element)
+ else:
+ if pos is not None:
+ x, y = element.GetPosition()
+ new_pos = wx.Point(x + dx, y + dy)
+ newid = parent.GetNewId()
+ if parent.IsNamedElement(element):
+ name = parent.GenerateNewName(element, exclude_names)
+ exclude_names[name.upper()] = True
+ new_element = element.Clone(parent, newid, name, pos = new_pos)
+ else:
+ new_element = element.Clone(parent, newid, pos = new_pos)
+ new_element.AdjustToScaling(parent.Scaling)
+ else:
+ new_element = element.Clone(parent)
+ connectors.update(element.GetConnectorTranslation(new_element))
+ group.SelectElement(new_element)
+ for element in wires:
+ if pos is not None:
+ new_wire = element.Clone(parent, connectors, dx, dy)
+ else:
+ new_wire = element.Clone(parent, connectors)
+ if new_wire is not None:
+ if pos is not None:
+ parent.AddWire(new_wire)
+ group.SelectElement(new_wire)
+ if pos is not None:
+ for element in group.Elements:
+ if not isinstance(element, Wire):
+ parent.AddBlockInModel(element)
+ return group
+
+ def CanAddBlocks(self, parent):
+ valid = True
+ for element in self.Elements:
+ if not isinstance(element, Wire):
+ valid &= parent.CanAddElement(element)
+ return valid
+
+ def IsVisible(self):
+ for element in self.Elements:
+ if element.IsVisible():
+ return True
+ return False
+
+ # Refresh the list of wire excluded
+ def RefreshWireExclusion(self):
+ self.WireExcluded = []
+ for element in self.Elements:
+ if isinstance(element, Wire):
+ startblock = element.StartConnected.GetParentBlock()
+ endblock = element.EndConnected.GetParentBlock()
+ if startblock in self.Elements and endblock in self.Elements:
+ self.WireExcluded.append(element)
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = None
+ for element in self.Elements:
+ if rect is None:
+ rect = element.GetRedrawRect(movex, movey)
+ else:
+ rect = rect.Union(element.GetRedrawRect(movex, movey))
+ return rect
+
+ # 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()
+ self.WireExcluded = []
+
+ # Returns if the point given is in the bounding box of one of the elements of this group
+ def HitTest(self, pt, connectors=True):
+ result = False
+ for element in self.Elements:
+ result |= element.HitTest(pt, connectors)
+ 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.RefreshWireExclusion()
+ self.RefreshBoundingBox()
+
+ # Returns the elements of the group
+ def GetElements(self):
+ return self.Elements
+
+ # Align the group elements
+ def AlignElements(self, horizontally, vertically):
+ minx = self.BoundingBox.x + self.BoundingBox.width
+ miny = self.BoundingBox.y + self.BoundingBox.height
+ maxx = self.BoundingBox.x
+ maxy = self.BoundingBox.y
+ for element in self.Elements:
+ if not isinstance(element, Wire):
+ posx, posy = element.GetPosition()
+ width, height = element.GetSize()
+ minx = min(minx, posx)
+ miny = min(miny, posy)
+ maxx = max(maxx, posx + width)
+ maxy = max(maxy, posy + height)
+ for element in self.Elements:
+ if not isinstance(element, Wire):
+ posx, posy = element.GetPosition()
+ width, height = element.GetSize()
+ movex = movey = 0
+ if horizontally == ALIGN_LEFT:
+ movex = minx - posx
+ elif horizontally == ALIGN_CENTER:
+ movex = (maxx + minx - width) / 2 - posx
+ elif horizontally == ALIGN_RIGHT:
+ movex = maxx - width - posx
+ if vertically == ALIGN_TOP:
+ movey = miny - posy
+ elif vertically == ALIGN_MIDDLE:
+ movey = (maxy + miny - height) / 2 - posy
+ elif vertically == ALIGN_BOTTOM:
+ movey = maxy - height - posy
+ if movex != 0 or movey != 0:
+ element.Move(movex, movey)
+ element.RefreshModel()
+ self.RefreshWireExclusion()
+ self.RefreshBoundingBox()
+
+ # 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.RefreshWireExclusion()
+ self.RefreshBoundingBox()
+
+ # Move this group of elements
+ def Move(self, movex, movey):
+ movex = max(-self.BoundingBox.x, movex)
+ movey = max(-self.BoundingBox.y, movey)
+ # Move all the elements of the group
+ for element in self.Elements:
+ if not isinstance(element, Wire):
+ element.Move(movex, movey, self.WireExcluded)
+ elif element in self.WireExcluded:
+ element.Move(movex, movey, True)
+ 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 = wx.Rect(minx, miny, maxx - minx, maxy - miny)
+ else:
+ self.BoundingBox = wx.Rect(0, 0, 0, 0)
+ self.Pos = wx.Point(self.BoundingBox.x, self.BoundingBox.y)
+ self.Size = wx.Size(self.BoundingBox.width, self.BoundingBox.height)
+
+ # 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
+
+ # Moves and Resizes the group elements for fitting scaling
+ def AdjustToScaling(self, scaling):
+ movex_max = movey_max = 0
+ for element in self.Elements:
+ movex, movey = element.AdjustToScaling(scaling)
+ movex_max = max(movex_max, abs(movex))
+ movey_max = max(movey_max, abs(movey))
+ return movex_max, movey_max
+
+ # Refreshes the group elements to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ handle_type, handle = self.Handle
+ # If it is a move handle, Move this group elements
+ if handle_type == HANDLE_MOVE:
+ movex = max(-self.BoundingBox.x, movex)
+ movey = max(-self.BoundingBox.y, movey)
+ if scaling is not None:
+ movex = round_scaling(movex, scaling[0])
+ movey = round_scaling(movey, scaling[1])
+ if event.ControlDown():
+ self.CurrentDrag.x = self.CurrentDrag.x + movex
+ self.CurrentDrag.y = self.CurrentDrag.y + movey
+ if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
+ movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
+ movey = self.StartPos.y - self.Pos.y
+ else:
+ movex = self.StartPos.x - self.Pos.x
+ movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
+ self.Move(movex, movey)
+ return movex, movey
+ return 0, 0
+
+ # Change the variable that indicates if this element is highlighted
+ def SetHighlighted(self, highlighted):
+ for element in self.Elements:
+ element.SetHighlighted(highlighted)
+
+ def HighlightPoint(self, pos):
+ for element in self.Elements:
+ if isinstance(element, Wire):
+ element.HighlightPoint(pos)
+
+ # Method called when a LeftDown event have been generated
+ def OnLeftDown(self, event, dc, scaling):
+ Graphic_Element.OnLeftDown(self, event, dc, scaling)
+ for element in self.Elements:
+ element.Handle = self.Handle
+
+ # Change the variable that indicates if the elemente is selected
+ def SetSelected(self, selected):
+ for element in self.Elements:
+ element.SetSelected(selected)
+
+ # Method called when a RightUp event has been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the menu with special items for a group
+ self.Parent.PopupGroupMenu()
+
+ # 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", onlyone = False):
+ self.ParentBlock = parent
+ self.Name = name
+ self.Type = type
+ self.Pos = position
+ self.Direction = direction
+ self.Wires = []
+ if self.ParentBlock.IsOfType("BOOL", type):
+ self.Negated = negated
+ self.Edge = edge
+ else:
+ self.Negated = False
+ self.Edge = "none"
+ self.OneConnected = onlyone
+ self.Valid = True
+ self.Value = None
+ self.Forced = False
+ self.Selected = False
+ self.Highlights = []
+ self.RefreshNameSize()
+
+ def Flush(self):
+ self.ParentBlock = None
+ for wire, handle in self.Wires:
+ wire.Flush()
+ self.Wires = []
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ parent_pos = self.ParentBlock.GetPosition()
+ x = min(parent_pos[0] + self.Pos.x, parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE)
+ y = min(parent_pos[1] + self.Pos.y, parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE)
+ if self.Direction[0] == 0:
+ width = 5
+ else:
+ width = CONNECTOR_SIZE
+ if self.Direction[1] == 0:
+ height = 5
+ else:
+ height = CONNECTOR_SIZE
+ return wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
+
+ # Change the connector selection
+ def SetSelected(self, selected):
+ self.Selected = selected
+
+ # Make a clone of the connector
+ def Clone(self, parent = None):
+ if parent is None:
+ parent = self.ParentBlock
+ return Connector(parent, self.Name, self.Type, wx.Point(self.Pos[0], self.Pos[1]),
+ self.Direction, self.Negated)
+
+ # Returns the connector parent block
+ def GetParentBlock(self):
+ return self.ParentBlock
+
+ # Returns the connector type
+ def GetType(self, raw = False):
+ if self.ParentBlock.IsEndType(self.Type) or raw:
+ return self.Type
+ elif (self.Negated or self.Edge != "none") and self.ParentBlock.IsOfType("BOOL", self.Type):
+ return "BOOL"
+ else:
+ return self.ParentBlock.GetConnectionResultType(self, self.Type)
+
+ # Returns the connector type
+ def GetConnectedType(self):
+ if self.ParentBlock.IsEndType(self.Type):
+ return self.Type
+ elif len(self.Wires) == 1:
+ return self.Wires[0][0].GetOtherConnectedType(self.Wires[0][1])
+ return self.Type
+
+ # Returns the connector type
+ def GetConnectedRedrawRect(self, movex, movey):
+ rect = None
+ for wire, handle in self.Wires:
+ if rect is None:
+ rect = wire.GetRedrawRect()
+ else:
+ rect = rect.Union(wire.GetRedrawRect())
+ return rect
+
+ # Returns if connector type is compatible with type given
+ def IsCompatible(self, type):
+ reference = self.GetType()
+ return self.ParentBlock.IsOfType(type, reference) or self.ParentBlock.IsOfType(reference, type)
+
+ # Changes the connector name
+ def SetType(self, type):
+ self.Type = type
+ for wire, handle in self.Wires:
+ wire.SetValid(wire.IsConnectedCompatible())
+
+ # Returns the connector name
+ def GetName(self):
+ return self.Name
+
+ # Changes the connector name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ def RefreshForced(self):
+ self.Forced = False
+ for wire, handle in self.Wires:
+ self.Forced |= wire.IsForced()
+
+ def RefreshValue(self):
+ self.Value = self.ReceivingCurrent()
+
+ def RefreshValid(self):
+ self.Valid = True
+ for wire, handle in self.Wires:
+ self.Valid &= wire.GetValid()
+
+ def ReceivingCurrent(self):
+ current = False
+ for wire, handle in self.Wires:
+ value = wire.GetValue()
+ if current != "undefined" and isinstance(value, BooleanType):
+ current |= wire.GetValue()
+ elif value == "undefined":
+ current = "undefined"
+ return current
+
+ def SpreadCurrent(self, spreading):
+ for wire, handle in self.Wires:
+ wire.SetValue(spreading)
+
+ # Changes the connector name size
+ def RefreshNameSize(self):
+ if self.Name != "":
+ self.NameSize = self.ParentBlock.Parent.GetTextExtent(self.Name)
+ else:
+ self.NameSize = 0, 0
+
+ # Returns the connector name size
+ def GetNameSize(self):
+ return self.NameSize
+
+ # 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 wx.Point(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, delete = False):
+ 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(delete)
+ else:
+ self.Wires[i][0].UnConnectEndPoint(delete)
+ # 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.RefreshValid()
+ 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(wx.Point(x, y))
+ else:
+ wire.MoveEndPoint(wx.Point(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)
+
+ # Highlight the parent block
+ def HighlightParentBlock(self, highlight):
+ self.ParentBlock.SetHighlighted(highlight)
+ self.ParentBlock.Refresh()
+
+ # Returns all the blocks connected to this connector
+ def GetConnectedBlocks(self):
+ blocks = []
+ for wire, handle in self.Wires:
+ # Get other connector connected to each wire
+ if handle == 0:
+ connector = wire.GetEndConnected()
+ else:
+ connector = wire.GetStartConnected()
+ # Get parent block for this connector
+ if connector:
+ block = connector.GetParentBlock()
+ if block not in blocks:
+ blocks.append(block)
+ return blocks
+
+ # Returns the connector negated property
+ def IsNegated(self):
+ return self.Negated
+
+ # Changes the connector negated property
+ def SetNegated(self, negated):
+ if self.ParentBlock.IsOfType("BOOL", self.Type):
+ 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):
+ if self.ParentBlock.IsOfType("BOOL", self.Type):
+ self.Edge = edge
+ self.Negated = False
+
+ # Tests if the point given is near from the end point of this connector
+ def TestPoint(self, pt, direction = None, exclude = True):
+ parent_pos = self.ParentBlock.GetPosition()
+ if (not (len(self.Wires) > 0 and self.OneConnected and exclude) or self.Type == "BOOL")\
+ and direction is None or self.Direction == direction:
+ # 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 = wx.Rect(x, y, width, height)
+ return rect.InsideXY(pt.x, pt.y)
+ return False
+
+ # Draws the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ pen = MiterPen(HIGHLIGHTCOLOR, 2 * scalex + 5)
+ pen.SetCap(wx.CAP_BUTT)
+ dc.SetPen(pen)
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ parent_pos = self.ParentBlock.GetPosition()
+ posx = parent_pos[0] + self.Pos.x
+ posy = parent_pos[1] + self.Pos.y
+ xstart = parent_pos[0] + self.Pos.x
+ ystart = parent_pos[1] + self.Pos.y
+ if self.Direction[0] < 0:
+ xstart += 1
+ if self.Direction[1] < 0:
+ ystart += 1
+ xend = xstart + CONNECTOR_SIZE * self.Direction[0]
+ yend = ystart + CONNECTOR_SIZE * self.Direction[1]
+ dc.DrawLine(round((xstart + self.Direction[0]) * scalex), round((ystart + self.Direction[1]) * scaley),
+ round(xend * scalex), round(yend * scaley))
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Adds an highlight to the connector
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if highlight_type == ERROR_HIGHLIGHT:
+ for wire, handle in self.Wires:
+ wire.SetValid(False)
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the connector
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ error = False
+ highlights = []
+ for highlight in self.Highlights:
+ if highlight != (start, end, highlight_type):
+ highlights.append(highlight)
+ error |= highlight == ERROR_HIGHLIGHT
+ self.Highlights = highlights
+ if not error:
+ for wire, handle in self.Wires:
+ wire.SetValid(wire.IsConnectedCompatible())
+
+ # Removes all the highlights of one particular type from the connector
+ def ClearHighlight(self, highlight_type=None):
+ error = False
+ if highlight_type is None:
+ self.Highlights = []
+ else:
+ highlights = []
+ for highlight in self.Highlights:
+ if highlight[2] != highlight_type:
+ highlights.append(highlight)
+ error |= highlight == ERROR_HIGHLIGHT
+ self.Highlights = highlights
+ if not error:
+ for wire, handle in self.Wires:
+ wire.SetValid(wire.IsConnectedCompatible())
+
+ # Draws the connector
+ def Draw(self, dc):
+ if self.Selected:
+ dc.SetPen(MiterPen(wx.BLUE, 3))
+ dc.SetBrush(wx.WHITE_BRUSH)
+ #elif len(self.Highlights) > 0:
+ # dc.SetPen(MiterPen(self.Highlights[-1][1]))
+ # dc.SetBrush(wx.Brush(self.Highlights[-1][0]))
+ else:
+ if not self.Valid:
+ dc.SetPen(MiterPen(wx.RED))
+ elif isinstance(self.Value, BooleanType) and self.Value:
+ if self.Forced:
+ dc.SetPen(MiterPen(wx.CYAN))
+ else:
+ dc.SetPen(MiterPen(wx.GREEN))
+ elif self.Value == "undefined":
+ dc.SetPen(MiterPen(wx.NamedColour("orange")))
+ elif self.Forced:
+ dc.SetPen(MiterPen(wx.BLUE))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+ parent_pos = self.ParentBlock.GetPosition()
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ else:
+ name_size = self.NameSize
+
+ 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)
+ if self.Direction[0] < 0:
+ xstart += 1
+ if self.Direction[1] < 0:
+ ystart += 1
+ if self.Selected:
+ xend = xstart + (CONNECTOR_SIZE - 2) * self.Direction[0]
+ yend = ystart + (CONNECTOR_SIZE - 2) * self.Direction[1]
+ dc.DrawLine(xstart + 2 * self.Direction[0], ystart + 2 * self.Direction[1], xend, yend)
+ else:
+ 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)
+ if self.Direction[0] != 0:
+ ytext = parent_pos[1] + self.Pos.y - name_size[1] / 2
+ if self.Direction[0] < 0:
+ xtext = parent_pos[0] + self.Pos.x + 5
+ else:
+ xtext = parent_pos[0] + self.Pos.x - (name_size[0] + 5)
+ if self.Direction[1] != 0:
+ xtext = parent_pos[0] + self.Pos.x - name_size[0] / 2
+ if self.Direction[1] < 0:
+ ytext = parent_pos[1] + self.Pos.y + 5
+ else:
+ ytext = parent_pos[1] + self.Pos.y - (name_size[1] + 5)
+ # Draw the text
+ dc.DrawText(self.Name, xtext, ytext)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
+
+#-------------------------------------------------------------------------------
+# Common Wire Element
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a wire for connecting two blocks
+"""
+
+class Wire(Graphic_Element, DebugDataConsumer):
+
+ # Create a new wire
+ def __init__(self, parent, start = None, end = None):
+ Graphic_Element.__init__(self, parent)
+ DebugDataConsumer.__init__(self)
+ 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.Valid = True
+ self.ValueSize = None
+ self.ComputedValue = None
+ self.OverStart = False
+ self.OverEnd = False
+ self.ComputingType = False
+ self.Font = parent.GetMiniFont()
+
+ def GetDefinition(self):
+ if self.StartConnected is not None and self.EndConnected is not None:
+ startblock = self.StartConnected.GetParentBlock()
+ endblock = self.EndConnected.GetParentBlock()
+ return [], [(startblock.GetId(), endblock.GetId())]
+ return [], []
+
+ def Flush(self):
+ self.StartConnected = None
+ self.EndConnected = None
+
+ def GetToolTipValue(self):
+ if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
+ if isinstance(self.Value, StringType) and self.Value.find("#") == -1:
+ return "\"%s\""%self.Value
+ else:
+ return str(self.Value)
+ return None
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if self.StartConnected:
+ rect = rect.Union(self.StartConnected.GetRedrawRect(movex, movey))
+ if self.EndConnected:
+ rect = rect.Union(self.EndConnected.GetRedrawRect(movex, movey))
+ if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+ self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
+ if self.ValueSize is not None:
+ width, height = self.ValueSize
+ if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
+ x = self.Points[0].x + width * self.StartPoint[1][0] / 2
+ y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
+ rect = rect.Union(wx.Rect(x, y, width, height))
+ x = self.Points[-1].x + width * self.EndPoint[1][0] / 2
+ y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
+ rect = rect.Union(wx.Rect(x, y, width, height))
+ else:
+ middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
+ x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
+ if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
+ y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
+ else:
+ y = self.Points[middle].y - height
+ rect = rect.Union(wx.Rect(x, y, width, height))
+ return rect
+
+ def Clone(self, parent, connectors = {}, dx = 0, dy = 0):
+ start_connector = connectors.get(self.StartConnected, None)
+ end_connector = connectors.get(self.EndConnected, None)
+ if start_connector is not None and end_connector is not None:
+ wire = Wire(parent)
+ wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
+ start_connector.Connect((wire, 0), False)
+ end_connector.Connect((wire, -1), False)
+ wire.ConnectStartPoint(start_connector.GetPosition(), start_connector)
+ wire.ConnectEndPoint(end_connector.GetPosition(), end_connector)
+ return wire
+ return None
+
+ # Forbids to change the wire position
+ def SetPosition(x, y):
+ pass
+
+ # Forbids to change the wire size
+ def SetSize(width, height):
+ pass
+
+ # Moves and Resizes the element for fitting scaling
+ def AdjustToScaling(self, scaling):
+ if scaling is not None:
+ movex_max = movey_max = 0
+ for idx, point in enumerate(self.Points):
+ if 0 < idx < len(self.Points) - 1:
+ movex = round_scaling(point.x, scaling[0]) - point.x
+ movey = round_scaling(point.y, scaling[1]) - point.y
+ if idx == 1:
+ if self.Segments[0][0] == 0:
+ movex = 0
+ elif (point.x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
+ movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
+ if self.Segments[0][1] == 0:
+ movey = 0
+ elif (point.y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
+ movey = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - point.y
+ elif idx == len(self.Points) - 2:
+ if self.Segments[-1][0] == 0:
+ movex = 0
+ elif (self.Points[-1].x - (point.x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
+ movex = round_scaling(self.Points[-1].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
+ if self.Segments[-1][1] == 0:
+ movey = 0
+ elif (self.Points[-1].y - (point.y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
+ movey = round_scaling(self.Points[-1].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - point.y
+ movex_max = max(movex_max, movex)
+ movey_max = max(movey_max, movey)
+ point.x += movex
+ point.y += movey
+ return movex_max, movey_max
+ return 0, 0
+
+ # Returns connector to which start point is connected
+ def GetStartConnected(self):
+ return self.StartConnected
+
+ # Returns connector to which start point is connected
+ def GetStartConnectedType(self):
+ if self.StartConnected and not self.ComputingType:
+ self.ComputingType = True
+ computed_type = self.StartConnected.GetType()
+ self.ComputingType = False
+ return computed_type
+ return None
+
+ # Returns connector to which end point is connected
+ def GetEndConnected(self):
+ return self.EndConnected
+
+ # Returns connector to which end point is connected
+ def GetEndConnectedType(self):
+ if self.EndConnected and not self.ComputingType:
+ self.ComputingType = True
+ computed_type = self.EndConnected.GetType()
+ self.ComputingType = False
+ return computed_type
+ return None
+
+ def GetConnectionDirection(self):
+ if self.StartConnected is None and self.EndConnected is None:
+ return None
+ elif self.StartConnected is not None and self.EndConnected is None:
+ return (-self.StartPoint[1][0], -self.StartPoint[1][1])
+ elif self.StartConnected is None and self.EndConnected is not None:
+ return self.EndPoint
+ elif self.Handle is not None:
+ handle_type, handle = self.Handle
+ # A point has been handled
+ if handle_type == HANDLE_POINT:
+ if handle == 0:
+ return self.EndPoint
+ else:
+ return (-self.StartPoint[1][0], -self.StartPoint[1][1])
+ return None
+
+ def GetOtherConnected(self, connector):
+ if self.StartConnected == connector:
+ return self.EndConnected
+ else:
+ return self.StartConnected
+
+ def GetOtherConnectedType(self, handle):
+ if handle == 0:
+ return self.GetEndConnectedType()
+ else:
+ return self.GetStartConnectedType()
+
+ def IsConnectedCompatible(self):
+ if self.StartConnected:
+ return self.StartConnected.IsCompatible(self.GetEndConnectedType())
+ elif self.EndConnected:
+ return True
+ return False
+
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ if self.StartConnected:
+ self.StartConnected.RefreshForced()
+ if self.EndConnected:
+ self.EndConnected.RefreshForced()
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ def SetValue(self, value):
+ if self.Value != value:
+ self.Value = value
+ if value is not None and not isinstance(value, BooleanType):
+ if isinstance(value, StringType) and value.find('#') == -1:
+ self.ComputedValue = "\"%s\""%value
+ else:
+ self.ComputedValue = str(value)
+ if self.ToolTip is not None:
+ self.ToolTip.SetTip(self.ComputedValue)
+ if len(self.ComputedValue) > 4:
+ self.ComputedValue = self.ComputedValue[:4] + "..."
+ self.ValueSize = None
+ if self.StartConnected:
+ self.StartConnected.RefreshValue()
+ if self.EndConnected:
+ self.EndConnected.RefreshValue()
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ if isinstance(value, BooleanType) and self.StartConnected is not None:
+ block = self.StartConnected.GetParentBlock()
+ block.SpreadCurrent()
+
+ # 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.SetSelected(False)
+ if self.EndConnected:
+ self.EndConnected.SetSelected(False)
+ # The segment selected is the first
+ elif segment == 0:
+ if self.StartConnected:
+ self.StartConnected.SetSelected(True)
+ if self.EndConnected:
+ # There is only one segment
+ if len(self.Segments) == 1:
+ self.EndConnected.SetSelected(True)
+ else:
+ self.EndConnected.SetSelected(False)
+ # The segment selected is the last
+ elif segment == len(self.Segments) - 1:
+ if self.StartConnected:
+ self.StartConnected.SetSelected(False)
+ if self.EndConnected:
+ self.EndConnected.SetSelected(True)
+ self.SelectedSegment = segment
+ self.Refresh()
+
+ def SetValid(self, valid):
+ self.Valid = valid
+ if self.StartConnected:
+ self.StartConnected.RefreshValid()
+ if self.EndConnected:
+ self.EndConnected.RefreshValid()
+
+ def GetValid(self):
+ return self.Valid
+
+ # 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.x, self.Pos.y = minx, miny
+ self.Size = wx.Size(maxx - minx, maxy - miny)
+ self.BoundingBox = wx.Rect(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, connectors=True):
+ test = False
+ for i in xrange(len(self.Points) - 1):
+ rect = wx.Rect(0, 0, 0, 0)
+ if i == 0 and self.StartConnected is not None:
+ x1 = self.Points[i].x - self.Segments[0][0] * CONNECTOR_SIZE
+ y1 = self.Points[i].y - self.Segments[0][1] * CONNECTOR_SIZE
+ else:
+ x1, y1 = self.Points[i].x, self.Points[i].y
+ if i == len(self.Points) - 2 and self.EndConnected is not None:
+ x2 = self.Points[i + 1].x + self.Segments[-1][0] * CONNECTOR_SIZE
+ y2 = self.Points[i + 1].y + self.Segments[-1][1] * CONNECTOR_SIZE
+ else:
+ x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
+ # Calculate a rectangle around the segment
+ rect = wx.Rect(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 = wx.Rect(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 = wx.Rect(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 all or 0 < i < len(self.Segments) - 1:
+ 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 = wx.Rect(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, verify=True):
+ if len(points) > 1:
+ self.Points = [wx.Point(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] = wx.Point(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0],
+ self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
+ self.EndPoint[0] = wx.Point(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 = []
+ i = 0
+ while i < len(self.Points) - 1:
+ if verify and 0 < i < len(self.Points) - 2 and \
+ self.Points[i] == self.Points[i + 1] and \
+ self.Segments[-1] == vector(self.Points[i + 1], self.Points[i + 2]):
+ for j in xrange(2):
+ self.Points.pop(i)
+ else:
+ segment = vector(self.Points[i], self.Points[i + 1])
+ if is_null_vector(segment) and i > 0:
+ segment = (self.Segments[-1][1], self.Segments[-1][0])
+ if i < len(self.Points) - 2:
+ next = vector(self.Points[i + 1], self.Points[i + 2])
+ if next == segment or is_null_vector(add_vectors(segment, next)):
+ self.Points.insert(i + 1, wx.Point(self.Points[i + 1].x, self.Points[i + 1].y))
+ self.Segments.append(segment)
+ 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] = wx.Point(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0],
+ points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
+ points[-1] = wx.Point(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 GetConnectedInfos(self, index):
+ if index == 0 and self.StartConnected:
+ return self.StartConnected.GetBlockId(), self.StartConnected.GetName()
+ elif index == -1 and self.EndConnected:
+ return self.EndConnected.GetBlockId(), self.EndConnected.GetName()
+ 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 = wx.Point(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
+ self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
+ start = wx.Point(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, wx.Point(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, wx.Point(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, wx.Point(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, wx.Point(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 = wx.Point(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
+ vector_test = vector(self.Points[0], test_point, False)
+ test = norm(vector_test) > MIN_SEGMENT_SIZE and product(self.Segments[0], vector_test) > 0
+ # 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, wx.Point(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, wx.Point(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, wx.Point(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 / float(max(lastwidth, 1)))),
+ width - dir[0] * MIN_SEGMENT_SIZE))
+ pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * height / float(max(lastheight, 1)))),
+ height - dir[1] * MIN_SEGMENT_SIZE))
+ self.Points[i] = wx.Point(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
+ # during a 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 / float(max(lastwidth, 1))
+ point[1] = point[1] * height / float(max(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))
+ realpointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1])),
+ height - dir[1] * MIN_SEGMENT_SIZE))
+ self.Points[i] = wx.Point(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
+ self.RefreshBoundingBox()
+
+ # Unconnects wire start point
+ def UnConnectStartPoint(self, delete = False):
+ if delete:
+ self.StartConnected = None
+ self.Delete()
+ elif self.StartConnected:
+ self.StartConnected.UnConnect(self, unconnect = False)
+ self.StartConnected = None
+ self.RefreshBoundingBox()
+
+ # 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
+ self.RefreshBoundingBox()
+
+ # Unconnects wire end point
+ def UnConnectEndPoint(self, delete = False):
+ if delete:
+ self.EndConnected = None
+ self.Delete()
+ elif self.EndConnected:
+ self.EndConnected.UnConnect(self, unconnect = False)
+ self.EndConnected = None
+ self.RefreshBoundingBox()
+
+ # Moves the wire segment given by its index
+ def MoveSegment(self, idx, movex, movey, scaling):
+ if 0 < idx < len(self.Segments) - 1:
+ if self.Segments[idx] in (NORTH, SOUTH):
+ start_x = self.Points[idx].x
+ if scaling is not None:
+ movex = round_scaling(self.Points[idx].x + movex, scaling[0]) - self.Points[idx].x
+ if idx == 1 and (self.Points[1].x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
+ movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - self.Points[idx].x
+ elif idx == len(self.Segments) - 2 and (self.Points[-1].x - (self.Points[-2].x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
+ movex = round_scaling(self.Points[-1].x - MIN_SEGMENT_SIZE * self.Segments[-1][0], scaling[0], -self.Segments[-1][0]) - self.Points[idx].x
+ self.Points[idx].x += movex
+ self.Points[idx + 1].x += movex
+ self.GeneratePoints()
+ if start_x != self.Points[idx].x:
+ return self.Points[idx].x - start_x, 0
+ elif self.Segments[idx] in (EAST, WEST):
+ start_y = self.Points[idx].y
+ if scaling is not None:
+ movey = round_scaling(self.Points[idx].y + movey, scaling[1]) - self.Points[idx].y
+ if idx == 1 and (self.Points[1].y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
+ movex = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - self.Points[idx].y
+ elif idx == len(self.Segments) - 2 and (self.Points[-1].y - (self.Points[-2].y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
+ movey = round_scaling(self.Points[idx].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - self.Points[idx].y
+ self.Points[idx].y += movey
+ self.Points[idx + 1].y += movey
+ self.GeneratePoints()
+ if start_y != self.Points[idx].y:
+ return 0, self.Points[idx].y - start_y
+ return 0, 0
+
+ # 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
+ if len(self.Segments) > 1:
+ 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, wx.Point(pointx, pointy))
+ self.Segments.insert(segment + 1, (dir[1], dir[0]))
+ self.Points.insert(segment + 2, wx.Point(pointx, pointy))
+ self.Segments.insert(segment + 2, dir)
+ else:
+ p1x = p2x = self.Points[segment].x
+ p1y = p2y = self.Points[segment].y
+ if dir[0] != 0:
+ p1x = (2 * self.Points[segment].x + self.Points[segment + 1].x) / 3
+ p2x = (self.Points[segment].x + 2 * self.Points[segment + 1].x) / 3
+ if dir[1] != 0:
+ p1y = (2 * self.Points[segment].y + self.Points[segment + 1].y) / 3
+ p2y = (self.Points[segment].y + 2 * self.Points[segment + 1].y) / 3
+ self.Points.insert(segment + 1, wx.Point(p1x, p1y))
+ self.Segments.insert(segment + 1, (dir[1], dir[0]))
+ self.Points.insert(segment + 2, wx.Point(p1x, p1y))
+ self.Segments.insert(segment + 2, dir)
+ self.Points.insert(segment + 3, wx.Point(p2x, p2y))
+ self.Segments.insert(segment + 3, (dir[1], dir[0]))
+ self.Points.insert(segment + 4, wx.Point(p2x, p2y))
+ self.Segments.insert(segment + 4, 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, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Test if a point have been handled
+ #result = self.TestPoint(pos)
+ #if result != None:
+ # self.Handle = (HANDLE_POINT, result)
+ # wx.CallAfter(self.Parent.SetCurrentCursor, 1)
+ #else:
+ # Test if a segment have been handled
+ result = self.TestSegment(pos)
+ if result != None:
+ if result[1] in (NORTH, SOUTH):
+ wx.CallAfter(self.Parent.SetCurrentCursor, 4)
+ elif result[1] in (EAST, WEST):
+ wx.CallAfter(self.Parent.SetCurrentCursor, 5)
+ self.Handle = (HANDLE_SEGMENT, result)
+ # Execute the default method for a graphic element
+ else:
+ Graphic_Element.OnLeftDown(self, event, dc, scaling)
+ self.oldPos = pos
+
+ # Method called when a RightUp event has been generated
+ def OnRightUp(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Test if a segment has been handled
+ result = self.TestSegment(pos, True)
+ if result != None:
+ self.Handle = (HANDLE_SEGMENT, result)
+ # Popup the menu with special items for a wire
+ self.Parent.PopupWireMenu(0 < result[0] < len(self.Segments) - 1)
+ else:
+ # Execute the default method for a graphic element
+ Graphic_Element.OnRightUp(self, event, dc, scaling)
+
+ # Method called when a LeftDClick event has been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ rect = self.GetRedrawRect()
+ if event.ControlDown():
+ direction = (self.StartPoint[1], self.EndPoint[1])
+ if direction in [(EAST, WEST), (WEST, EAST)]:
+ avgy = (self.StartPoint[0].y + self.EndPoint[0].y) / 2
+ if scaling is not None:
+ avgy = round(float(avgy) / scaling[1]) * scaling[1]
+ if self.StartConnected is not None:
+ movey = avgy - self.StartPoint[0].y
+ startblock = self.StartConnected.GetParentBlock()
+ startblock.Move(0, movey)
+ startblock.RefreshModel()
+ rect.Union(startblock.GetRedrawRect(0, movey))
+ else:
+ self.MoveStartPoint(wx.Point(self.StartPoint[0].x, avgy))
+ if self.EndConnected is not None:
+ movey = avgy - self.EndPoint[0].y
+ endblock = self.EndConnected.GetParentBlock()
+ endblock.Move(0, movey)
+ endblock.RefreshModel()
+ rect.Union(endblock.GetRedrawRect(0, movey))
+ else:
+ self.MoveEndPoint(wx.Point(self.EndPoint[0].x, avgy))
+ self.Parent.RefreshBuffer()
+ elif direction in [(NORTH, SOUTH), (SOUTH, NORTH)]:
+ avgx = (self.StartPoint[0].x + self.EndPoint[0].x) / 2
+ if scaling is not None:
+ avgx = round(float(avgx) / scaling[0]) * scaling[0]
+ if self.StartConnected is not None:
+ movex = avgx - self.StartPoint[0].x
+ startblock = self.StartConnected.GetParentBlock()
+ startblock.Move(movex, 0)
+ startblock.RefreshModel()
+ rect.Union(startblock.GetRedrawRect(movex, 0))
+ else:
+ self.MoveStartPoint(wx.Point(avgx, self.StartPoint[0].y))
+ if self.EndConnected is not None:
+ movex = avgx - self.EndPoint[0].x
+ endblock = self.EndConnected.GetParentBlock()
+ endblock.Move(movex, 0)
+ endblock.RefreshModel()
+ rect.Union(endblock.GetRedrawRect(movex, 0))
+ else:
+ self.MoveEndPoint(wx.Point(avgx, self.EndPoint[0].y))
+ self.Parent.RefreshBuffer()
+ else:
+ self.ResetPoints()
+ self.GeneratePoints()
+ self.RefreshModel()
+ self.Parent.RefreshBuffer()
+ rect.Union(self.GetRedrawRect())
+ self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
+
+ # Method called when a Motion event has been generated
+ def OnMotion(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ if not event.Dragging():
+ # Test if a segment has been handled
+ result = self.TestSegment(pos)
+ if result:
+ if result[1] in (NORTH, SOUTH):
+ wx.CallAfter(self.Parent.SetCurrentCursor, 4)
+ elif result[1] in (EAST, WEST):
+ wx.CallAfter(self.Parent.SetCurrentCursor, 5)
+ return 0, 0
+ else:
+ # Execute the default method for a graphic element
+ return Graphic_Element.OnMotion(self, event, dc, scaling)
+ else:
+ # Execute the default method for a graphic element
+ return Graphic_Element.OnMotion(self, event, dc, scaling)
+
+ # Refreshes the wire state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ handle_type, handle = self.Handle
+ # A point has been handled
+ if handle_type == HANDLE_POINT:
+ movex = max(-self.Points[handle].x + POINT_RADIUS, movex)
+ movey = max(-self.Points[handle].y + POINT_RADIUS, movey)
+ if scaling is not None:
+ movex = round_scaling(self.Points[handle].x + movex, scaling[0]) - self.Points[handle].x
+ movey = round_scaling(self.Points[handle].y + movey, scaling[1]) - self.Points[handle].y
+ # Try to connect point to a connector
+ new_pos = wx.Point(self.Points[handle].x + movex, self.Points[handle].y + movey)
+ connector = self.Parent.FindBlockConnector(new_pos, self.GetConnectionDirection())
+ if connector:
+ if handle == 0 and self.EndConnected != connector:
+ connector.HighlightParentBlock(True)
+ connector.Connect((self, handle))
+ self.SetStartPointDirection(connector.GetDirection())
+ self.ConnectStartPoint(connector.GetPosition(), connector)
+ pos = connector.GetPosition()
+ movex = pos.x - self.oldPos.x
+ movey = pos.y - self.oldPos.y
+ if not connector.IsCompatible(self.GetEndConnectedType()):
+ self.SetValid(False)
+ self.Dragging = False
+ elif handle != 0 and self.StartConnected != connector:
+ connector.HighlightParentBlock(True)
+ connector.Connect((self, handle))
+ self.SetEndPointDirection(connector.GetDirection())
+ self.ConnectEndPoint(connector.GetPosition(), connector)
+ pos = connector.GetPosition()
+ movex = pos.x - self.oldPos.x
+ movey = pos.y - self.oldPos.y
+ if not connector.IsCompatible(self.GetStartConnectedType()):
+ self.SetValid(False)
+ 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:
+ self.SetValid(True)
+ if self.StartConnected:
+ self.StartConnected.HighlightParentBlock(False)
+ self.UnConnectStartPoint()
+ self.MoveStartPoint(new_pos)
+ else:
+ self.SetValid(True)
+ if self.EndConnected:
+ self.EndConnected.HighlightParentBlock(False)
+ self.UnConnectEndPoint()
+ self.MoveEndPoint(new_pos)
+ return movex, movey
+ # A segment has been handled, move a segment
+ elif handle_type == HANDLE_SEGMENT:
+ return self.MoveSegment(handle[0], movex, movey, scaling)
+ # Execute the default method for a graphic element
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+
+ # 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()
+
+ # Change the variable that indicates if this element is highlighted
+ def SetHighlighted(self, highlighted):
+ self.Highlighted = highlighted
+ if not highlighted:
+ self.OverStart = False
+ self.OverEnd = False
+ self.Refresh()
+
+ def HighlightPoint(self, pos):
+ refresh = False
+ start, end = self.OverStart, self.OverEnd
+ self.OverStart = False
+ self.OverEnd = False
+ # Test if a point has been handled
+ result = self.TestPoint(pos)
+ if result != None:
+ if result == 0 and self.StartConnected is not None:
+ self.OverStart = True
+ elif result != 0 and self.EndConnected is not None:
+ self.OverEnd = True
+ if start != self.OverStart or end != self.OverEnd:
+ self.Refresh()
+
+ # Draws the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR, (2 * scalex + 5)))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ # 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(round(self.Points[0].x * scalex),
+ round(self.Points[0].y * scaley),
+ (POINT_RADIUS + 1) * scalex + 2)
+ if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
+ dc.DrawCircle(self.Points[-1].x * scalex, self.Points[-1].y * scaley, (POINT_RADIUS + 1) * scalex + 2)
+ # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
+ if len(self.Points) > 1:
+ points = [wx.Point(round((self.Points[0].x - self.Segments[0][0]) * scalex),
+ round((self.Points[0].y - self.Segments[0][1]) * scaley))]
+ points.extend([wx.Point(round(point.x * scalex), round(point.y * scaley)) for point in self.Points[1:-1]])
+ points.append(wx.Point(round((self.Points[-1].x + self.Segments[-1][0]) * scalex),
+ round((self.Points[-1].y + self.Segments[-1][1]) * scaley)))
+ else:
+ points = []
+ dc.DrawLines(points)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ if self.StartConnected is not None:
+ self.StartConnected.DrawHighlightment(dc)
+ self.StartConnected.Draw(dc)
+ if self.EndConnected is not None:
+ self.EndConnected.DrawHighlightment(dc)
+ self.EndConnected.Draw(dc)
+
+ # Draws the wire lines and points
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if not self.Valid:
+ dc.SetPen(MiterPen(wx.RED))
+ dc.SetBrush(wx.RED_BRUSH)
+ elif isinstance(self.Value, BooleanType) and self.Value:
+ if self.Forced:
+ dc.SetPen(MiterPen(wx.CYAN))
+ dc.SetBrush(wx.CYAN_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.GREEN))
+ dc.SetBrush(wx.GREEN_BRUSH)
+ elif self.Value == "undefined":
+ dc.SetPen(MiterPen(wx.NamedColour("orange")))
+ dc.SetBrush(wx.Brush(wx.NamedColour("orange")))
+ elif self.Forced:
+ dc.SetPen(MiterPen(wx.BLUE))
+ dc.SetBrush(wx.BLUE_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_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)
+ if len(self.Points) > 1:
+ points = [wx.Point(self.Points[0].x - self.Segments[0][0], self.Points[0].y - self.Segments[0][1])]
+ points.extend([point for point in self.Points[1:-1]])
+ points.append(wx.Point(self.Points[-1].x + self.Segments[-1][0], self.Points[-1].y + self.Segments[-1][1]))
+ else:
+ points = []
+ dc.DrawLines(points)
+ # Draw the segment selected in red
+ if not getattr(dc, "printing", False) and self.SelectedSegment is not None:
+ dc.SetPen(MiterPen(wx.BLUE, 3))
+ if self.SelectedSegment == len(self.Segments) - 1:
+ end = 0
+ else:
+ end = 1
+ dc.DrawLine(self.Points[self.SelectedSegment].x - 1, self.Points[self.SelectedSegment].y,
+ self.Points[self.SelectedSegment + 1].x + end, self.Points[self.SelectedSegment + 1].y)
+ if self.Value is not None and not isinstance(self.Value, BooleanType) and self.Value != "undefined":
+ dc.SetFont(self.Parent.GetMiniFont())
+ dc.SetTextForeground(wx.NamedColour("purple"))
+ if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+ self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
+ if self.ValueSize is not None:
+ width, height = self.ValueSize
+ if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
+ x = self.Points[0].x + width * (self.StartPoint[1][0] - 1) / 2
+ y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
+ dc.DrawText(self.ComputedValue, x, y)
+ x = self.Points[-1].x + width * (self.EndPoint[1][0] - 1) / 2
+ y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
+ dc.DrawText(self.ComputedValue, x, y)
+ else:
+ middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
+ x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
+ if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
+ y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
+ else:
+ y = self.Points[middle].y - height
+ dc.DrawText(self.ComputedValue, x, y)
+ dc.SetFont(self.Parent.GetFont())
+ dc.SetTextForeground(wx.BLACK)
+
+
+#-------------------------------------------------------------------------------
+# Graphic comment element
+#-------------------------------------------------------------------------------
+
+def FilterHighlightsByRow(highlights, row, length):
+ _highlights = []
+ for start, end, highlight_type in highlights:
+ if start[0] <= row and end[0] >= row:
+ if start[0] < row:
+ start = (row, 0)
+ if end[0] > row:
+ end = (row, length)
+ _highlights.append((start, end, highlight_type))
+ return _highlights
+
+def FilterHighlightsByColumn(highlights, start_col, end_col):
+ _highlights = []
+ for start, end, highlight_type in highlights:
+ if end[1] > start_col and start[1] < end_col:
+ start = (start[0], max(start[1], start_col) - start_col)
+ end = (end[0], min(end[1], end_col) - start_col)
+ _highlights.append((start, end, highlight_type))
+ return _highlights
+
+"""
+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 = wx.Point(0, 0)
+ self.Size = wx.Size(0, 0)
+ self.Highlights = []
+
+ # Make a clone of this comment
+ def Clone(self, parent, id = None, pos = None):
+ comment = Comment(parent, self.Content, id)
+ if pos is not None:
+ comment.SetPosition(pos.x, pos.y)
+ comment.SetSize(self.Size[0], self.Size[1])
+ return comment
+
+ # 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 = wx.Rect(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 = wx.ClientDC(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, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the wire state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE and self.Parent.CurrentLanguage == "LD":
+ movex = movey = 0
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+
+ # 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, dc, scaling):
+ # Edit the comment content
+ self.Parent.EditCommentContent(self)
+
+ # Adds an highlight to the comment
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "content":
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the comment
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the comment
+ def ClearHighlight(self, highlight_type=None):
+ self.Highlights = ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+
+ left = (self.Pos.x - 1) * scalex - 2
+ right = (self.Pos.x + self.Size[0] + 1) * scalex + 2
+ top = (self.Pos.y - 1) * scaley - 2
+ bottom = (self.Pos.y + self.Size[1] + 1) * scaley + 2
+ angle_top = (self.Pos.x + self.Size[0] - 9) * scalex + 2
+ angle_right = (self.Pos.y + 9) * scaley - 2
+
+ polygon = [wx.Point(left, top), wx.Point(angle_top, top),
+ wx.Point(right, angle_right), wx.Point(right, bottom),
+ wx.Point(left, bottom)]
+ dc.DrawPolygon(polygon)
+
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Draws the comment and its content
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+ # Draws the comment shape
+ polygon = [wx.Point(self.Pos.x, self.Pos.y),
+ wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
+ wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10),
+ wx.Point(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1]),
+ wx.Point(self.Pos.x, self.Pos.y + self.Size[1])]
+ dc.DrawPolygon(polygon)
+ lines = [wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
+ wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10),
+ wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10)]
+ dc.DrawLines(lines)
+ # Draws the comment content
+ y = self.Pos.y + 10
+ for idx, line in enumerate(self.Content.splitlines()):
+ first = True
+ linetext = ""
+ words = line.split(" ")
+ if not getattr(dc, "printing", False):
+ highlights = FilterHighlightsByRow(self.Highlights, idx, len(line))
+ highlights_offset = 0
+ for i, word in enumerate(words):
+ if first:
+ text = word
+ else:
+ text = linetext + " " + word
+ wordwidth, wordheight = dc.GetTextExtent(text)
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+ if wordwidth < self.Size[0] - 20:
+ if i < len(words) - 1:
+ linetext = text
+ first = False
+ else:
+ dc.DrawText(text, self.Pos.x + 10, y)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, text, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(text)), self.Pos.x + 10, y)
+ highlights_offset += len(text) + 1
+ y += wordheight + 5
+ else:
+ if not first:
+ dc.DrawText(linetext, self.Pos.x + 10, y)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, linetext, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(linetext)), self.Pos.x + 10, y)
+ highlights_offset += len(linetext) + 1
+ if first or i == len(words) - 1:
+ if not first:
+ y += wordheight + 5
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+ dc.DrawText(word, self.Pos.x + 10, y)
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, word, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(word)), self.Pos.x + 10, y)
+ highlights_offset += len(word) + 1
+ else:
+ linetext = word
+ y += wordheight + 5
+ if y + wordheight > self.Pos.y + self.Size[1] - 10:
+ break
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/LD_Objects.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1013 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from GraphicCommons import *
+from plcopen.structures import *
+
+#-------------------------------------------------------------------------------
+# 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=1):
+ Graphic_Element.__init__(self, parent)
+ self.Type = None
+ self.Connectors = []
+ self.RealConnectors = None
+ self.Id = id
+ self.Extensions = [LD_LINE_SIZE / 2, LD_LINE_SIZE / 2]
+ self.SetType(type, connectors)
+
+ def Flush(self):
+ for connector in self.Connectors:
+ connector.Flush()
+ self.Connectors = []
+
+ # Make a clone of this LD_PowerRail
+ def Clone(self, parent, id = None, pos = None):
+ powerrail = LD_PowerRail(parent, self.Type, id)
+ powerrail.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ powerrail.SetPosition(pos.x, pos.y)
+ else:
+ powerrail.SetPosition(self.Pos.x, self.Pos.y)
+ powerrail.Connectors = []
+ for connector in self.Connectors:
+ powerrail.Connectors.append(connector.Clone(powerrail))
+ return powerrail
+
+ def GetConnectorTranslation(self, element):
+ return dict(zip([connector for connector in self.Connectors],
+ [connector for connector in element.Connectors]))
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ for connector in self.Connectors:
+ rect = rect.Union(connector.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ for connector in self.Connectors:
+ if connector.IsConnected():
+ rect = rect.Union(connector.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Forbids to change the power rail size
+ def SetSize(self, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.SetSize(self, width, height)
+ else:
+ Graphic_Element.SetSize(self, LD_POWERRAIL_WIDTH, height)
+ self.RefreshConnectors()
+
+ # Forbids to select a power rail
+ def HitTest(self, pt, connectors=True):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ return Graphic_Element.HitTest(self, pt, connectors) or self.TestConnector(pt, exclude=False) != None
+ return False
+
+ # Forbids to select a power rail
+ def IsInSelection(self, rect):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ return Graphic_Element.IsInSelection(self, rect)
+ 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:
+ connector.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Refresh the power rail bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+
+ # Refresh the power rail size
+ def RefreshSize(self):
+ self.Size = wx.Size(LD_POWERRAIL_WIDTH, max(LD_LINE_SIZE * len(self.Connectors), self.Size[1]))
+ self.RefreshBoundingBox()
+
+ # Returns the block minimum size
+ def GetMinSize(self):
+ return LD_POWERRAIL_WIDTH, self.Extensions[0] + self.Extensions[1]
+
+ # Add a connector or a blank to this power rail at the last place
+ def AddConnector(self):
+ self.InsertConnector(len(self.Connectors))
+
+ # Add a connector or a blank to this power rail at the place given
+ def InsertConnector(self, idx):
+ if self.Type == LEFTRAIL:
+ connector = Connector(self, "", "BOOL", wx.Point(self.Size[0], 0), EAST)
+ elif self.Type == RIGHTRAIL:
+ connector = Connector(self, "", "BOOL", wx.Point(0, 0), WEST)
+ self.Connectors.insert(idx, connector)
+ self.RefreshSize()
+ self.RefreshConnectors()
+
+ # Moves the divergence connector given
+ def MoveConnector(self, connector, movey):
+ position = connector.GetRelPosition()
+ connector.SetPosition(wx.Point(position.x, position.y + movey))
+ miny = self.Size[1]
+ maxy = 0
+ for connect in self.Connectors:
+ connect_pos = connect.GetRelPosition()
+ miny = min(miny, connect_pos.y - self.Extensions[0])
+ maxy = max(maxy, connect_pos.y - self.Extensions[0])
+ min_pos = self.Pos.y + miny
+ self.Pos.y = min(min_pos, self.Pos.y)
+ if min_pos == self.Pos.y:
+ for connect in self.Connectors:
+ connect_pos = connect.GetRelPosition()
+ connect.SetPosition(wx.Point(connect_pos.x, connect_pos.y - miny))
+ self.Connectors.sort(lambda x, y: cmp(x.Pos.y, y.Pos.y))
+ maxy = 0
+ for connect in self.Connectors:
+ connect_pos = connect.GetRelPosition()
+ maxy = max(maxy, connect_pos.y)
+ self.Size[1] = max(maxy + self.Extensions[1], self.Size[1])
+ connector.MoveConnected()
+ self.RefreshBoundingBox()
+
+ # 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
+
+ # 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):
+ scaling = self.Parent.GetScaling()
+ height = self.Size[1] - self.Extensions[0] - self.Extensions[1]
+ interval = float(height) / float(max(len(self.Connectors) - 1, 1))
+ for i, connector in enumerate(self.Connectors):
+ if self.RealConnectors:
+ position = self.Extensions[0] + int(round(self.RealConnectors[i] * height))
+ else:
+ position = self.Extensions[0] + int(round(i * interval))
+ if scaling is not None:
+ position = round(float(self.Pos.y + position) / float(scaling[1])) * scaling[1] - self.Pos.y
+ if self.Type == LEFTRAIL:
+ connector.SetPosition(wx.Point(self.Size[0], position))
+ elif self.Type == RIGHTRAIL:
+ connector.SetPosition(wx.Point(0, position))
+ self.RefreshConnected()
+
+ # Refresh the position of wires connected 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, name = None):
+ # if a name is given
+ if name is not None:
+ # Test each connector if it exists
+ for connector in self.Connectors:
+ if name == connector.GetName():
+ return connector
+ return self.FindNearestConnector(position, [connector for connector in self.Connectors if connector is not None])
+
+ # Returns all the power rail connectors
+ def GetConnectors(self):
+ connectors = [connector for connector in self.Connectors if connector]
+ if self.Type == LEFTRAIL:
+ return {"inputs": [], "outputs": connectors}
+ else:
+ return {"inputs": connectors, "outputs": []}
+
+ # Test if point given is on one of the power rail connectors
+ def TestConnector(self, pt, direction = None, exclude = True):
+ for connector in self.Connectors:
+ if connector.TestPoint(pt, direction, exclude):
+ return connector
+ return None
+
+ # Returns the power rail type
+ def SetType(self, type, connectors):
+ if type != self.Type or len(self.Connectors) != connectors:
+ # Create a connector or a blank according to 'connectors' and add it in
+ # the connectors list
+ self.Type = type
+ self.Clean()
+ self.Connectors = []
+ for connector in xrange(connectors):
+ self.AddConnector()
+ self.RefreshSize()
+
+ # Returns the power rail type
+ def GetType(self):
+ return self.Type
+
+ # Method called when a LeftDown event have been generated
+ def OnLeftDown(self, event, dc, scaling):
+ self.RealConnectors = []
+ height = self.Size[1] - self.Extensions[0] - self.Extensions[1]
+ if height > 0:
+ for connector in self.Connectors:
+ position = connector.GetRelPosition()
+ self.RealConnectors.append(max(0., min(float(position.y - self.Extensions[0]) / float(height), 1.)))
+ elif len(self.Connectors) > 1:
+ self.RealConnectors = map(lambda x : x * 1 / (len(self.Connectors) - 1), xrange(len(self.Connectors)))
+ else:
+ self.RealConnectors = [0.5]
+ Graphic_Element.OnLeftDown(self, event, dc, scaling)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, dc, scaling):
+ Graphic_Element.OnLeftUp(self, event, dc, scaling)
+ self.RealConnectors = None
+
+ # Method called when a LeftDown event have been generated
+ def OnRightDown(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Test if a connector have been handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ wx.CallAfter(self.Parent.SetCurrentCursor, 1)
+ self.Selected = False
+ # Initializes the last position
+ self.oldPos = GetScaledEventPosition(event, dc, scaling)
+ else:
+ Graphic_Element.OnRightDown(self, event, dc, scaling)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the powerrail properties
+ self.Parent.EditPowerRailContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ wires = handle.GetWires()
+ if len(wires) == 1:
+ if handle == wires[0][0].StartConnected:
+ block = wires[0][0].EndConnected.GetParentBlock()
+ else:
+ block = wires[0][0].StartConnected.GetParentBlock()
+ block.RefreshModel(False)
+ Graphic_Element.OnRightUp(self, event, dc, scaling)
+ else:
+ self.Parent.PopupDefaultMenu()
+
+ def Resize(self, x, y, width, height):
+ self.Move(x, y)
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ self.SetSize(width, height)
+ else:
+ self.SetSize(LD_POWERRAIL_WIDTH, height)
+
+ # Refreshes the powerrail state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ handle_type, handle = self.Handle
+ # A connector has been handled
+ if handle_type == HANDLE_CONNECTOR:
+ movey = max(-self.BoundingBox.y, movey)
+ if scaling is not None:
+ position = handle.GetRelPosition()
+ movey = round(float(self.Pos.y + position.y + movey) / float(scaling[1])) * scaling[1] - self.Pos.y - position.y
+ self.MoveConnector(handle, movey)
+ return 0, movey
+ elif self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+ return 0, 0
+
+ # 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:
+ connector.RefreshWires()
+
+ # Draws power rail
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_BRUSH)
+ # Draw a rectangle with the power rail size
+ if self.Type == LEFTRAIL:
+ dc.DrawRectangle(self.Pos.x + self.Size[0] - LD_POWERRAIL_WIDTH, self.Pos.y, LD_POWERRAIL_WIDTH + 1, self.Size[1] + 1)
+ else:
+ dc.DrawRectangle(self.Pos.x, self.Pos.y, LD_POWERRAIL_WIDTH + 1, self.Size[1] + 1)
+ # Draw connectors
+ for connector in self.Connectors:
+ connector.Draw(dc)
+
+
+#-------------------------------------------------------------------------------
+# Ladder Diagram Contact
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a contact
+"""
+
+class LD_Contact(Graphic_Element, DebugDataConsumer):
+
+ # Create a new contact
+ def __init__(self, parent, type, name, id = None):
+ Graphic_Element.__init__(self, parent)
+ DebugDataConsumer.__init__(self)
+ self.Type = type
+ self.Name = name
+ self.Id = id
+ self.Size = wx.Size(LD_ELEMENT_SIZE[0], LD_ELEMENT_SIZE[1])
+ self.Highlights = {}
+ # Create an input and output connector
+ self.Input = Connector(self, "", "BOOL", wx.Point(0, self.Size[1] / 2 + 1), WEST)
+ self.Output = Connector(self, "", "BOOL", wx.Point(self.Size[0], self.Size[1] / 2 + 1), EAST)
+ self.PreviousValue = False
+ self.PreviousSpreading = False
+ self.RefreshNameSize()
+ self.RefreshTypeSize()
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ def SetValue(self, value):
+ if self.Type == CONTACT_RISING:
+ refresh = self.Value and not self.PreviousValue
+ elif self.Type == CONTACT_FALLING:
+ refresh = not self.Value and self.PreviousValue
+ else:
+ refresh = False
+ self.PreviousValue = self.Value
+ self.Value = value
+ if self.Value != self.PreviousValue or refresh:
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ self.SpreadCurrent()
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ if self.Value is None:
+ self.Value = False
+ spreading = self.Input.ReceivingCurrent()
+ if self.Type == CONTACT_NORMAL:
+ spreading &= self.Value
+ elif self.Type == CONTACT_REVERSE:
+ spreading &= not self.Value
+ elif self.Type == CONTACT_RISING:
+ spreading &= self.Value and not self.PreviousValue
+ elif self.Type == CONTACT_FALLING:
+ spreading &= not self.Value and self.PreviousValue
+ else:
+ spreading = False
+ if spreading and not self.PreviousSpreading:
+ self.Output.SpreadCurrent(True)
+ elif not spreading and self.PreviousSpreading:
+ self.Output.SpreadCurrent(False)
+ self.PreviousSpreading = spreading
+
+ # Make a clone of this LD_Contact
+ def Clone(self, parent, id = None, pos = None):
+ contact = LD_Contact(parent, self.Type, self.Name, id)
+ contact.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ contact.SetPosition(pos.x, pos.y)
+ else:
+ contact.SetPosition(self.Pos.x, self.Pos.y)
+ contact.Input = self.Input.Clone(contact)
+ contact.Output = self.Output.Clone(contact)
+ return contact
+
+ def GetConnectorTranslation(self, element):
+ return {self.Input : element.Input, self.Output : element.Output}
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ def ProcessDragging(self, movex, movey, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ movex = movey = 0
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, height_fac = 2)
+
+ # Forbids to change the contact size
+ def SetSize(self, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.SetSize(self, width, height)
+ self.RefreshConnectors()
+
+ # 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(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Output.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ if self.Name != "":
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+ else:
+ self.NameSize = 0, 0
+
+ # Refresh the size of text for type
+ def RefreshTypeSize(self):
+ typetext = ""
+ if self.Type == CONTACT_REVERSE:
+ typetext = "/"
+ elif self.Type == CONTACT_RISING:
+ typetext = "P"
+ elif self.Type == CONTACT_FALLING:
+ typetext = "N"
+ if typetext != "":
+ self.TypeSize = self.Parent.GetTextExtent(typetext)
+ else:
+ self.TypeSize = 0, 0
+
+ # Refresh the contact bounding box
+ def RefreshBoundingBox(self):
+ # Calculate the size of the name outside the contact
+ text_width, text_height = self.Parent.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 = wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
+
+ # Returns the block minimum size
+ def GetMinSize(self):
+ return LD_ELEMENT_SIZE
+
+ # 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, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input and output connector
+ #if name == self.Input.GetName():
+ # return self.Input
+ if name == self.Output.GetName():
+ return self.Output
+ return self.FindNearestConnector(position, [self.Input, self.Output])
+
+ # Returns input and output contact connectors
+ def GetConnectors(self):
+ return {"inputs": [self.Input], "outputs": [self.Output]}
+
+ # Test if point given is on contact input or output connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ return None
+
+ # Refresh the positions of the block connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ position = self.Size[1] / 2 + 1
+ if scaling is not None:
+ position = round(float(self.Pos.y + position) / float(scaling[1])) * scaling[1] - self.Pos.y
+ self.Input.SetPosition(wx.Point(0, position))
+ self.Output.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Changes the contact name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the contact name
+ def GetName(self):
+ return self.Name
+
+ # Changes the contact type
+ def SetType(self, type):
+ self.Type = type
+ self.RefreshTypeSize()
+
+ # Returns the contact type
+ def GetType(self):
+ return self.Type
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the contact properties
+ self.Parent.EditContactContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the 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 the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ # Draw two rectangles for representing the contact
+ left_left = (self.Pos.x - 1) * scalex - 2
+ right_left = (self.Pos.x + self.Size[0] - 2) * scalex - 2
+ top = (self.Pos.y - 1) * scaley - 2
+ width = 4 * scalex + 5
+ height = (self.Size[1] + 3) * scaley + 5
+
+ dc.DrawRectangle(left_left, top, width, height)
+ dc.DrawRectangle(right_left, top, width, height)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Adds an highlight to the connection
+ def AddHighlight(self, infos, start, end, highlight_type):
+ highlights = self.Highlights.setdefault(infos[0], [])
+ if infos[0] == "reference":
+ if start[0] == 0 and end[0] == 0:
+ AddHighlight(highlights, (start, end, highlight_type))
+ else:
+ AddHighlight(highlights, ((0, 0), (0, 1), highlight_type))
+
+ # Removes an highlight from the connection
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ highlights = self.Highlights.get(infos[0], [])
+ if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
+ self.Highlights.pop(infos[0])
+
+ # Removes all the highlights of one particular type from the connection
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for name, highlights in highlight_items:
+ highlights = ClearHighlights(highlight, highlight_type)
+ if len(highlights) == 0:
+ self.Highlights.pop(name)
+
+ # Draws contact
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value is not None:
+ if self.Type == CONTACT_NORMAL and self.Value or \
+ self.Type == CONTACT_REVERSE and not self.Value or \
+ self.Type == CONTACT_RISING and self.Value and not self.PreviousValue or \
+ self.Type == CONTACT_RISING and not self.Value and self.PreviousValue:
+ if self.Forced:
+ dc.SetPen(MiterPen(wx.CYAN))
+ else:
+ dc.SetPen(MiterPen(wx.GREEN))
+ elif self.Forced:
+ dc.SetPen(MiterPen(wx.BLUE))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ # Compiling contact type modifier symbol
+ typetext = ""
+ if self.Type == CONTACT_REVERSE:
+ typetext = "/"
+ elif self.Type == CONTACT_RISING:
+ typetext = "P"
+ elif self.Type == CONTACT_FALLING:
+ typetext = "N"
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ if typetext != "":
+ type_size = dc.GetTextExtent(typetext)
+ else:
+ name_size = self.NameSize
+ if typetext != "":
+ type_size = self.TypeSize
+
+ # 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
+ name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y - (name_size[1] + 2))
+ dc.DrawText(self.Name, name_pos[0], name_pos[1])
+ # Draw the modifier symbol in the middle of contact
+ if typetext != "":
+ type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2 + 1,
+ self.Pos.y + (self.Size[1] - type_size[1]) / 2)
+ dc.DrawText(typetext, type_pos[0], type_pos[1])
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ for name, highlights in self.Highlights.iteritems():
+ if name == "reference":
+ DrawHighlightedText(dc, self.Name, highlights, name_pos[0], name_pos[1])
+ elif typetext != "":
+ DrawHighlightedText(dc, typetext, highlights, type_pos[0], type_pos[1])
+
+#-------------------------------------------------------------------------------
+# 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 = wx.Size(LD_ELEMENT_SIZE[0], LD_ELEMENT_SIZE[1])
+ self.Highlights = {}
+ # Create an input and output connector
+ self.Input = Connector(self, "", "BOOL", wx.Point(0, self.Size[1] / 2 + 1), WEST)
+ self.Output = Connector(self, "", "BOOL", wx.Point(self.Size[0], self.Size[1] / 2 + 1), EAST)
+ self.Value = None
+ self.PreviousValue = False
+ self.RefreshNameSize()
+ self.RefreshTypeSize()
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ self.PreviousValue = self.Value
+ self.Value = self.Input.ReceivingCurrent()
+ if self.Value and not self.PreviousValue:
+ self.Output.SpreadCurrent(True)
+ elif not self.Value and self.PreviousValue:
+ self.Output.SpreadCurrent(False)
+ if self.Value != self.PreviousValue and self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ # Make a clone of this LD_Coil
+ def Clone(self, parent, id = None, pos = None):
+ coil = LD_Coil(parent, self.Type, self.Name, id)
+ coil.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ coil.SetPosition(pos.x, pos.y)
+ else:
+ coil.SetPosition(self.Pos.x, self.Pos.y)
+ coil.Input = self.Input.Clone(coil)
+ coil.Output = self.Output.Clone(coil)
+ return coil
+
+ def GetConnectorTranslation(self, element):
+ return {self.Input : element.Input, self.Output : element.Output}
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ def ProcessDragging(self, movex, movey, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ movex = movey = 0
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, height_fac = 2)
+
+ # Forbids to change the Coil size
+ def SetSize(self, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.SetSize(self, width, height)
+ self.RefreshConnectors()
+
+ # 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 size of text for name
+ def RefreshNameSize(self):
+ if self.Name != "":
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+ else:
+ self.NameSize = 0, 0
+
+ # Refresh the size of text for type
+ def RefreshTypeSize(self):
+ typetext = ""
+ if self.Type == COIL_REVERSE:
+ typetext = "/"
+ elif self.Type == COIL_SET:
+ typetext = "S"
+ elif self.Type == COIL_RESET:
+ typetext = "R"
+ elif self.Type == COIL_RISING:
+ typetext = "P"
+ elif self.Type == COIL_FALLING:
+ typetext = "N"
+ if typetext != "":
+ self.TypeSize = self.Parent.GetTextExtent(typetext)
+ else:
+ self.TypeSize = 0, 0
+
+ # Refresh the coil bounding box
+ def RefreshBoundingBox(self):
+ # Calculate the size of the name outside the coil
+ text_width, text_height = self.Parent.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 = wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
+
+ # Returns the block minimum size
+ def GetMinSize(self):
+ return LD_ELEMENT_SIZE
+
+ # 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, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input and output connector
+ #if self.Input and name == self.Input.GetName():
+ # return self.Input
+ if self.Output and name == self.Output.GetName():
+ return self.Output
+ return self.FindNearestConnector(position, [self.Input, self.Output])
+
+ # Returns input and output coil connectors
+ def GetConnectors(self):
+ return {"inputs": [self.Input], "outputs": [self.Output]}
+
+ # Test if point given is on coil input or output connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ return None
+
+ # Refresh the positions of the block connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ position = self.Size[1] / 2 + 1
+ if scaling is not None:
+ position = round(float(self.Pos.y + position) / float(scaling[1])) * scaling[1] - self.Pos.y
+ self.Input.SetPosition(wx.Point(0, position))
+ self.Output.SetPosition(wx.Point(self.Size[0], position))
+ self.RefreshConnected()
+
+ # Changes the coil name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # Returns the coil name
+ def GetName(self):
+ return self.Name
+
+ # Changes the coil type
+ def SetType(self, type):
+ self.Type = type
+ self.RefreshTypeSize()
+
+ # Returns the coil type
+ def GetType(self):
+ return self.Type
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the coil properties
+ self.Parent.EditCoilContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the 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 the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR, (3 * scalex + 5), wx.SOLID))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.AND)
+ # Draw a two circle arcs for representing the coil
+ dc.DrawEllipticArc(round(self.Pos.x * scalex),
+ round((self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1) * scaley),
+ round(self.Size[0] * scalex),
+ round((int(self.Size[1] * sqrt(2)) - 1) * scaley),
+ 135, 225)
+ dc.DrawEllipticArc(round(self.Pos.x * scalex),
+ round((self.Pos.y - int(self.Size[1] * (sqrt(2) - 1.) / 2.) + 1) * scaley),
+ round(self.Size[0] * scalex),
+ round((int(self.Size[1] * sqrt(2)) - 1) * scaley),
+ -45, 45)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Adds an highlight to the connection
+ def AddHighlight(self, infos, start, end, highlight_type):
+ highlights = self.Highlights.setdefault(infos[0], [])
+ if infos[0] == "reference":
+ if start[0] == 0 and end[0] == 0:
+ AddHighlight(highlights, (start, end, highlight_type))
+ else:
+ AddHighlight(highlights, ((0, 0), (0, 1), highlight_type))
+
+ # Removes an highlight from the connection
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ highlights = self.Highlights.get(infos[0], [])
+ if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
+ self.Highlights.pop(infos[0])
+
+ # Removes all the highlights of one particular type from the connection
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for name, highlights in highlight_items:
+ highlights = ClearHighlights(highlight, highlight_type)
+ if len(highlights) == 0:
+ self.Highlights.pop(name)
+
+ # Draws coil
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value is not None and self.Value:
+ dc.SetPen(MiterPen(wx.GREEN, 2, wx.SOLID))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK, 2, wx.SOLID))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ # Compiling coil type modifier symbol
+ typetext = ""
+ if self.Type == COIL_REVERSE:
+ typetext = "/"
+ elif self.Type == COIL_SET:
+ typetext = "S"
+ elif self.Type == COIL_RESET:
+ typetext = "R"
+ elif self.Type == COIL_RISING:
+ typetext = "P"
+ elif self.Type == COIL_FALLING:
+ typetext = "N"
+
+ if getattr(dc, "printing", False) and not isinstance(dc, wx.PostScriptDC):
+ # Draw an clipped ellipse for representing the coil
+ clipping_box = dc.GetClippingBox()
+ dc.SetClippingRegion(self.Pos.x - 1, self.Pos.y, self.Size[0] + 2, self.Size[1] + 1)
+ dc.DrawEllipse(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)
+ dc.DestroyClippingRegion()
+ if clipping_box != (0, 0, 0, 0):
+ dc.SetClippingRegion(*clipping_box)
+ name_size = dc.GetTextExtent(self.Name)
+ if typetext != "":
+ type_size = dc.GetTextExtent(typetext)
+ else:
+ # Draw a two ellipse 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)
+ # Draw a point to avoid hole in left arc
+ if not getattr(dc, "printing", False):
+ if self.Value is not None and self.Value:
+ dc.SetPen(MiterPen(wx.GREEN))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.DrawPoint(self.Pos.x + 1, self.Pos.y + self.Size[1] / 2 + 1)
+ name_size = self.NameSize
+ if typetext != "":
+ type_size = self.TypeSize
+
+ # Draw coil name
+ name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y - (name_size[1] + 2))
+ dc.DrawText(self.Name, name_pos[0], name_pos[1])
+ # Draw the modifier symbol in the middle of coil
+ if typetext != "":
+ type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2 + 1,
+ self.Pos.y + (self.Size[1] - type_size[1]) / 2)
+ dc.DrawText(typetext, type_pos[0], type_pos[1])
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ for name, highlights in self.Highlights.iteritems():
+ if name == "reference":
+ DrawHighlightedText(dc, self.Name, highlights, name_pos[0], name_pos[1])
+ elif typetext != "":
+ DrawHighlightedText(dc, typetext, highlights, type_pos[0], type_pos[1])
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/SFC_Objects.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,2056 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import wx
+
+from GraphicCommons import *
+from plcopen.structures import *
+
+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, DebugDataConsumer):
+
+ # Create a new step
+ def __init__(self, parent, name, initial = False, id = None):
+ Graphic_Element.__init__(self, parent)
+ DebugDataConsumer.__init__(self)
+ self.SetName(name)
+ self.Initial = initial
+ self.Id = id
+ self.Highlights = []
+ self.Size = wx.Size(SFC_STEP_DEFAULT_SIZE[0], SFC_STEP_DEFAULT_SIZE[1])
+ # Create an input and output connector
+ if not self.Initial:
+ self.Input = Connector(self, "", None, wx.Point(self.Size[0] / 2, 0), NORTH)
+ else:
+ self.Input = None
+ self.Output = None
+ self.Action = None
+ self.PreviousValue = None
+ self.PreviousSpreading = False
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+ if self.Output is not None:
+ self.Action.Flush()
+ self.Action = None
+
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ def SetValue(self, value):
+ self.PreviousValue = self.Value
+ self.Value = value
+ if self.Value != self.PreviousValue:
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ self.SpreadCurrent()
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ spreading = self.Value
+ if spreading and not self.PreviousSpreading:
+ if self.Output is not None:
+ self.Output.SpreadCurrent(True)
+ if self.Action is not None:
+ self.Action.SpreadCurrent(True)
+ elif not spreading and self.PreviousSpreading:
+ if self.Output is not None:
+ self.Output.SpreadCurrent(False)
+ if self.Action is not None:
+ self.Action.SpreadCurrent(False)
+ self.PreviousSpreading = spreading
+
+ # Make a clone of this SFC_Step
+ def Clone(self, parent, id = None, name = "Step", pos = None):
+ step = SFC_Step(parent, name, self.Initial, id)
+ step.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ step.SetPosition(pos.x, pos.y)
+ else:
+ step.SetPosition(self.Pos.x, self.Pos.y)
+ if self.Input:
+ step.Input = self.Input.Clone(step)
+ if self.Output:
+ step.Output = self.Output.Clone(step)
+ if self.Action:
+ step.Action = self.Action.Clone(step)
+ return step
+
+ def GetConnectorTranslation(self, element):
+ connectors = {}
+ if self.Input is not None:
+ connectors[self.Input] = element.Input
+ if self.Output is not None:
+ connectors[self.Output] = element.Output
+ if self.Action is not None:
+ connectors[self.Action] = element.Action
+ return connectors
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if self.Input:
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ if self.Output:
+ rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
+ if self.Action:
+ rect = rect.Union(self.Action.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input and self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output and self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ if self.Action and self.Action.IsConnected():
+ rect = rect.Union(self.Action.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # 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(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ if self.Output:
+ self.Output.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ if self.Action:
+ self.Action.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Refresh the size of text for name
+ def RefreshNameSize(self):
+ self.NameSize = self.Parent.GetTextExtent(self.Name)
+
+ # Add output connector to step
+ def AddInput(self):
+ if not self.Input:
+ self.Input = Connector(self, "", None, wx.Point(self.Size[0] / 2, 0), NORTH)
+ self.RefreshBoundingBox()
+
+ # Remove output connector from step
+ def RemoveInput(self):
+ if self.Input:
+ self.Input.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Input = None
+ self.RefreshBoundingBox()
+
+ # Add output connector to step
+ def AddOutput(self):
+ if not self.Output:
+ self.Output = Connector(self, "", None, wx.Point(self.Size[0] / 2, self.Size[1]), SOUTH, onlyone = True)
+ self.RefreshBoundingBox()
+
+ # Remove output connector from step
+ def RemoveOutput(self):
+ if self.Output:
+ self.Output.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Output = None
+ self.RefreshBoundingBox()
+
+ # Add action connector to step
+ def AddAction(self):
+ if not self.Action:
+ self.Action = Connector(self, "", None, wx.Point(self.Size[0], self.Size[1] / 2), EAST, onlyone = True)
+ self.RefreshBoundingBox()
+
+ # Remove action connector from step
+ def RemoveAction(self):
+ if self.Action:
+ self.Action.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Action = None
+ self.RefreshBoundingBox()
+
+ # Refresh the step bounding box
+ def RefreshBoundingBox(self):
+ # 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 = wx.Rect(self.Pos.x, bbx_y, bbx_width + 1, bbx_height + 1)
+ self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
+
+ # Refresh the positions of the step connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ horizontal_pos = self.Size[0] / 2
+ vertical_pos = self.Size[1] / 2
+ if scaling is not None:
+ horizontal_pos = round(float(self.Pos.x + horizontal_pos) / float(scaling[0])) * scaling[0] - self.Pos.x
+ vertical_pos = round(float(self.Pos.y + vertical_pos) / float(scaling[1])) * scaling[1] - self.Pos.y
+ # Update input position if it exists
+ if self.Input:
+ self.Input.SetPosition(wx.Point(horizontal_pos, 0))
+ # Update output position
+ if self.Output:
+ self.Output.SetPosition(wx.Point(horizontal_pos, self.Size[1]))
+ # Update action position if it exists
+ if self.Action:
+ self.Action.SetPosition(wx.Point(self.Size[0], vertical_pos))
+ 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, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input, output and action connector if they exists
+ #if self.Input and name == self.Input.GetName():
+ # return self.Input
+ if self.Output and name == self.Output.GetName():
+ return self.Output
+ if self.Action and name == self.Action.GetName():
+ return self.Action
+ connectors = []
+ # Test input connector if it exists
+ if self.Input:
+ connectors.append(self.Input)
+ # Test output connector if it exists
+ if self.Output:
+ connectors.append(self.Output)
+ # Test action connector if it exists
+ if self.Action:
+ connectors.append(self.Action)
+ return self.FindNearestConnector(position, connectors)
+
+ # Returns action step connector
+ def GetActionConnector(self):
+ return self.Action
+
+ # Returns input and output step connectors
+ def GetConnectors(self):
+ connectors = {"inputs": [], "outputs": []}
+ if self.Input:
+ connectors["inputs"].append(self.Input)
+ if self.Output:
+ connectors["outputs"].append(self.Output)
+ return connectors
+
+ # Test if point given is on step input or output connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ # Test input connector if it exists
+ if self.Input and self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output and self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ # Test action connector
+ if self.Action and self.Action.TestPoint(pt, direction, exclude):
+ return self.Action
+ return None
+
+ # Changes the step name
+ def SetName(self, name):
+ self.Name = name
+ self.RefreshNameSize()
+
+ # 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].GetOtherConnected(self.Input)
+ 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].GetOtherConnected(self.Output)
+ return None
+
+ # Returns the connector connected to action
+ def GetActionConnected(self):
+ if self.Action:
+ wires = self.Action.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].GetOtherConnected(self.Action)
+ 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].GetOtherConnected(self.Action).GetParentBlock()
+ return max(0, action_block.GetLineNumber() - 1)
+ return 0
+
+ # Returns the step minimum size
+ def GetMinSize(self):
+ text_width, text_height = self.Parent.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)
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ self.RefreshConnected()
+ else:
+ 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].GetOtherConnected(self.Output)
+ 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([wx.Point(current_pos.x, current_pos.y + wire_size),
+ wx.Point(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].GetOtherConnected(self.Action).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):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.UpdateSize(width, height)
+ else:
+ Graphic_Element.Resize(self, x, y, width, height)
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the step properties
+ self.Parent.EditStepContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, 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, event, scaling):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_MOVE:
+ movex = max(-self.BoundingBox.x, movex)
+ movey = max(-self.BoundingBox.y, movey)
+ if scaling is not None:
+ movex = round(float(self.Pos.x + movex) / float(scaling[0])) * scaling[0] - self.Pos.x
+ movey = round(float(self.Pos.y + movey) / float(scaling[1])) * scaling[1] - self.Pos.y
+ action_block = None
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ self.Move(movex, movey)
+ self.RefreshConnected()
+ return movex, movey
+ elif self.Initial:
+ self.MoveActionBlock((movex, movey))
+ self.Move(movex, movey, self.Parent.Wires)
+ self.RefreshOutputPosition((movex, movey))
+ return movex, movey
+ else:
+ self.MoveActionBlock((movex, 0))
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+ self.RefreshOutputPosition()
+ return movex, 0
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+
+ # 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.GetActionConnected()
+ if action:
+ action_block = action.GetParentBlock()
+ action_block.RefreshModel(False)
+ # If step has moved, refresh the model of wires connected to output
+ if move:
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.RefreshInputModel()
+ self.RefreshOutputModel(self.Initial)
+ elif self.Output:
+ self.Output.RefreshWires()
+
+ # Adds an highlight to the connection
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the connection
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "name":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the connection
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws step
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value:
+ if self.Forced:
+ dc.SetPen(MiterPen(wx.CYAN))
+ else:
+ dc.SetPen(MiterPen(wx.GREEN))
+ elif self.Forced:
+ dc.SetPen(MiterPen(wx.BLUE))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_BRUSH)
+
+ if getattr(dc, "printing", False):
+ name_size = dc.GetTextExtent(self.Name)
+ else:
+ name_size = self.NameSize
+
+ # Draw 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
+ name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
+ self.Pos.y + (self.Size[1] - name_size[1]) / 2)
+ dc.DrawText(self.Name, name_pos[0], name_pos[1])
+ # 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)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Name, self.Highlights, name_pos[0], name_pos[1])
+
+
+#-------------------------------------------------------------------------------
+# Sequencial Function Chart Transition
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements the graphic representation of a transition
+"""
+
+class SFC_Transition(Graphic_Element, DebugDataConsumer):
+
+ # Create a new transition
+ def __init__(self, parent, type = "reference", condition = None, priority = 0, id = None):
+ Graphic_Element.__init__(self, parent)
+ DebugDataConsumer.__init__(self)
+ self.Type = None
+ self.Id = id
+ self.Priority = 0
+ self.Size = wx.Size(SFC_TRANSITION_SIZE[0], SFC_TRANSITION_SIZE[1])
+ # Create an input and output connector
+ self.Input = Connector(self, "", None, wx.Point(self.Size[0] / 2, 0), NORTH, onlyone = True)
+ self.Output = Connector(self, "", None, wx.Point(self.Size[0] / 2, self.Size[1]), SOUTH, onlyone = True)
+ self.SetType(type, condition)
+ self.SetPriority(priority)
+ self.Highlights = {}
+ self.PreviousValue = None
+ self.PreviousSpreading = False
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+ if self.Output is not None:
+ self.Output.Flush()
+ self.Output = None
+ if self.Type == "connection" and self.Condition is not None:
+ self.Condition.Flush()
+ self.Condition = None
+
+ def SetForced(self, forced):
+ if self.Forced != forced:
+ self.Forced = forced
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ def SetValue(self, value):
+ self.PreviousValue = self.Value
+ self.Value = value
+ if self.Value != self.PreviousValue:
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ self.SpreadCurrent()
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ if self.Value is None:
+ self.Value = False
+ spreading = self.Input.ReceivingCurrent() & self.Value
+ if spreading and not self.PreviousSpreading:
+ self.Output.SpreadCurrent(True)
+ elif not spreading and self.PreviousSpreading:
+ self.Output.SpreadCurrent(False)
+ self.PreviousSpreading = spreading
+
+ # Make a clone of this SFC_Transition
+ def Clone(self, parent, id = None, pos = None):
+ transition = SFC_Transition(parent, self.Type, self.Condition, self.Priority, id)
+ transition.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ transition.SetPosition(pos.x, pos.y)
+ else:
+ transition.SetPosition(self.Pos.x, self.Pos.y)
+ transition.Input = self.Input.Clone(transition)
+ transition.Output = self.Output.Clone(transition)
+ if self.Type == "connection":
+ transition.Condition = self.Condition.Clone(transition)
+ return transition
+
+ def GetConnectorTranslation(self, element):
+ connectors = {self.Input : element.Input, self.Output : element.Output}
+ if self.Type == "connection" and self.Condition is not None:
+ connectors[self.Condition] = element.Condition
+ return connectors
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ if self.Output.IsConnected():
+ rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
+ if self.Type == "connection" and self.Condition.IsConnected():
+ rect = rect.Union(self.Condition.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Forbids to change the transition size
+ def SetSize(self, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.SetSize(self, width, height)
+
+ # Forbids to resize the transition
+ def Resize(self, x, y, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.Resize(self, x, y, width, height)
+
+ # Refresh the size of text for name
+ def RefreshConditionSize(self):
+ if self.Type != "connection":
+ if self.Condition != "":
+ self.ConditionSize = self.Parent.GetTextExtent(self.Condition)
+ else:
+ self.ConditionSize = self.Parent.GetTextExtent("Transition")
+
+ # Refresh the size of text for name
+ def RefreshPrioritySize(self):
+ if self.Priority != "":
+ self.PrioritySize = self.Parent.GetTextExtent(str(self.Priority))
+ else:
+ self.PrioritySize = None
+
+ # 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(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Output.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ if self.Type == "connection":
+ self.Condition.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt, connectors=True):
+ if self.Type != "connection":
+ # Calculate the bounding box of the condition outside the transition
+ text_width, text_height = self.ConditionSize
+ text_bbx = wx.Rect(self.Pos.x + self.Size[0] + 5,
+ self.Pos.y + (self.Size[1] - text_height) / 2,
+ text_width,
+ text_height)
+ test_text = text_bbx.InsideXY(pt.x, pt.y)
+ else:
+ test_text = False
+ return test_text or Graphic_Element.HitTest(self, pt, connectors)
+
+ # Refresh the transition bounding box
+ def RefreshBoundingBox(self):
+ bbx_x, bbx_y, bbx_width, bbx_height = self.Pos.x, self.Pos.y, self.Size[0], self.Size[1]
+ if self.Priority != 0:
+ bbx_y = self.Pos.y - self.PrioritySize[1] - 2
+ bbx_width = max(self.Size[0], self.PrioritySize[0])
+ bbx_height = self.Size[1] + self.PrioritySize[1] + 2
+ if self.Type == "connection":
+ bbx_x = self.Pos.x - CONNECTOR_SIZE
+ bbx_width = bbx_width + CONNECTOR_SIZE
+ else:
+ text_width, text_height = self.ConditionSize
+ # Calculate the bounding box size
+ bbx_width = max(bbx_width, self.Size[0] + 5 + text_width)
+ bbx_y = min(bbx_y, self.Pos.y - max(0, (text_height - self.Size[1]) / 2))
+ bbx_height = max(bbx_height, self.Pos.y - bbx_y + (self.Size[1] + text_height) / 2)
+ self.BoundingBox = wx.Rect(bbx_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].GetOtherConnected(self.Input)
+ return None
+
+ # Returns the connector connected to output
+ def GetNextConnector(self):
+ wires = self.Output.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].GetOtherConnected(self.Output)
+ return None
+
+ # Refresh the positions of the transition connectors
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ horizontal_pos = self.Size[0] / 2
+ vertical_pos = self.Size[1] / 2
+ if scaling is not None:
+ horizontal_pos = round(float(self.Pos.x + horizontal_pos) / float(scaling[0])) * scaling[0] - self.Pos.x
+ vertical_pos = round(float(self.Pos.y + vertical_pos) / float(scaling[1])) * scaling[1] - self.Pos.y
+ # Update input position
+ self.Input.SetPosition(wx.Point(horizontal_pos, 0))
+ # Update output position
+ self.Output.SetPosition(wx.Point(horizontal_pos, self.Size[1]))
+ if self.Type == "connection":
+ self.Condition.SetPosition(wx.Point(0, vertical_pos))
+ self.RefreshConnected()
+
+ # Refresh the position of the wires connected to transition
+ def RefreshConnected(self, exclude = []):
+ self.Input.MoveConnected(exclude)
+ self.Output.MoveConnected(exclude)
+ if self.Type == "connection":
+ self.Condition.MoveConnected(exclude)
+
+ # Returns the transition connector that starts with the point given if it exists
+ def GetConnector(self, position, name = None):
+ # if a name is given
+ if name is not None:
+ # Test input and output connector
+ #if name == self.Input.GetName():
+ # return self.Input
+ if name == self.Output.GetName():
+ return self.Output
+ if self.Type == "connection" and name == self.Condition.GetName():
+ return self.Condition
+ connectors = [self.Input, self.Output]
+ if self.Type == "connection":
+ connectors.append(self.Condition)
+ return self.FindNearestConnector(position, connectors)
+
+ # Returns the transition condition connector
+ def GetConditionConnector(self):
+ if self.Type == "connection":
+ return self.Condition
+ return None
+
+ # Returns input and output transition connectors
+ def GetConnectors(self):
+ return {"inputs": [self.Input], "outputs": [self.Output]}
+
+ # Test if point given is on transition input or output connector
+ def TestConnector(self, pt, direction = None, exclude=True):
+ # Test input connector
+ if self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ # Test output connector
+ if self.Output.TestPoint(pt, direction, exclude):
+ return self.Output
+ # Test condition connector
+ if self.Type == "connection" and self.Condition.TestPoint(pt, direction, exclude):
+ return self.Condition
+ return None
+
+ # Changes the transition type
+ def SetType(self, type, condition = None):
+ if self.Type != type:
+ if self.Type == "connection":
+ self.Condition.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ self.Type = type
+ if type == "connection":
+ self.Condition = Connector(self, "", "BOOL", wx.Point(0, self.Size[1] / 2), WEST)
+ else:
+ if condition == None:
+ condition = ""
+ self.Condition = condition
+ self.RefreshConditionSize()
+ elif self.Type != "connection":
+ if condition == None:
+ condition = ""
+ self.Condition = condition
+ self.RefreshConditionSize()
+ self.RefreshBoundingBox()
+
+ # Returns the transition type
+ def GetType(self):
+ return self.Type
+
+ # Changes the transition priority
+ def SetPriority(self, priority):
+ self.Priority = priority
+ self.RefreshPrioritySize()
+ self.RefreshBoundingBox()
+
+ # Returns the transition type
+ def GetPriority(self):
+ return self.Priority
+
+ # Returns the transition condition
+ def GetCondition(self):
+ if self.Type != "connection":
+ return self.Condition
+ return None
+
+ # 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].GetOtherConnected(self.Output)
+ 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, dc, scaling):
+ # Edit the transition properties
+ self.Parent.EditTransitionContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, 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, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ movex = max(-self.BoundingBox.x, movex)
+ if scaling is not None:
+ movex = round(float(self.Pos.x + movex) / float(scaling[0])) * scaling[0] - self.Pos.x
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+ self.RefreshOutputPosition()
+ return movex, 0
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, width_fac = 2, height_fac = 2)
+
+ # Refresh input element model
+ def RefreshInputModel(self):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ 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:
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.RefreshInputModel()
+ self.RefreshOutputModel()
+ else:
+ self.Output.RefreshWires()
+
+ # Adds an highlight to the block
+ def AddHighlight(self, infos, start, end ,highlight_type):
+ if infos[0] in ["reference", "inline", "priority"] and start[0] == 0 and end[0] == 0:
+ highlights = self.Highlights.setdefault(infos[0], [])
+ AddHighlight(highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the block
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] in ["reference", "inline", "priority"]:
+ highlights = self.Highlights.get(infos[0], [])
+ if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
+ self.Highlights.pop(infos[0])
+
+ # Removes all the highlights of one particular type from the block
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for name, highlights in highlight_items:
+ highlights = ClearHighlights(highlight, highlight_type)
+ if len(highlights) == 0:
+ self.Highlights.pop(name)
+
+ # Draws transition
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value:
+ if self.Forced:
+ dc.SetPen(MiterPen(wx.CYAN))
+ dc.SetBrush(wx.CYAN_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.GREEN))
+ dc.SetBrush(wx.GREEN_BRUSH)
+ elif self.Forced:
+ dc.SetPen(MiterPen(wx.BLUE))
+ dc.SetBrush(wx.BLUE_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ if getattr(dc, "printing", False):
+ if self.Type != "connection":
+ condition_size = dc.GetTextExtent(self.Condition)
+ if self.Priority != 0:
+ priority_size = dc.GetTextExtent(str(self.Priority))
+ else:
+ if self.Type != "connection":
+ condition_size = self.ConditionSize
+ if self.Priority != 0:
+ priority_size = self.PrioritySize
+
+ # Draw plain rectangle for representing the transition
+ dc.DrawRectangle(self.Pos.x,
+ self.Pos.y + (self.Size[1] - SFC_TRANSITION_SIZE[1])/2,
+ self.Size[0] + 1,
+ SFC_TRANSITION_SIZE[1] + 1)
+ vertical_line_x = self.Input.GetPosition()[0]
+ dc.DrawLine(vertical_line_x, self.Pos.y, vertical_line_x, self.Pos.y + self.Size[1] + 1)
+ # Draw transition condition
+ if self.Type != "connection":
+ if self.Condition != "":
+ condition = self.Condition
+ else:
+ condition = "Transition"
+ condition_pos = (self.Pos.x + self.Size[0] + 5,
+ self.Pos.y + (self.Size[1] - condition_size[1]) / 2)
+ dc.DrawText(condition, condition_pos[0], condition_pos[1])
+ # Draw priority number
+ if self.Priority != 0:
+ priority_pos = (self.Pos.x, self.Pos.y - priority_size[1] - 2)
+ dc.DrawText(str(self.Priority), priority_pos[0], priority_pos[1])
+ # Draw input and output connectors
+ self.Input.Draw(dc)
+ self.Output.Draw(dc)
+ if self.Type == "connection":
+ self.Condition.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ for name, highlights in self.Highlights.iteritems():
+ if name == "priority":
+ DrawHighlightedText(dc, str(self.Priority), highlights, priority_pos[0], priority_pos[1])
+ else:
+ DrawHighlightedText(dc, condition, highlights, condition_pos[0], condition_pos[1])
+
+#-------------------------------------------------------------------------------
+# 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)
+ self.Size = wx.Size((number - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL, self.GetMinSize()[1])
+ # Create an input and output connector
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ self.Inputs = [Connector(self, "", None, wx.Point(self.Size[0] / 2, 0), NORTH, onlyone = True)]
+ self.Outputs = []
+ for i in xrange(number):
+ self.Outputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH, onlyone = True))
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.Inputs = []
+ for i in xrange(number):
+ self.Inputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH, onlyone = True))
+ self.Outputs = [Connector(self, "", None, wx.Point(self.Size[0] / 2, self.Size[1]), SOUTH, onlyone = True)]
+ self.Value = None
+ self.PreviousValue = None
+
+ def Flush(self):
+ for input in self.Inputs:
+ input.Flush()
+ self.Inputs = []
+ for output in self.Outputs:
+ output.Flush()
+ self.Outputs = []
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ self.PreviousValue = self.Value
+ if self.Type == SELECTION_CONVERGENCE:
+ self.Value = False
+ for input in self.Inputs:
+ self.Value |= input.ReceivingCurrent()
+ elif self.Type == SIMULTANEOUS_CONVERGENCE:
+ self.Value = True
+ for input in self.Inputs:
+ self.Value &= input.ReceivingCurrent()
+ elif self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ self.Value = self.Inputs[0].ReceivingCurrent()
+ else:
+ self.Value = False
+ if self.Value and not self.PreviousValue:
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ for output in self.Outputs:
+ output.SpreadCurrent(True)
+ elif not self.Value and self.PreviousValue:
+ if self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+ for output in self.Outputs:
+ output.SpreadCurrent(False)
+
+ # Make a clone of this SFC_Divergence
+ def Clone(self, parent, id = None, pos = None):
+ divergence = SFC_Divergence(parent, self.Type, max(len(self.Inputs), len(self.Outputs)), id)
+ divergence.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ divergence.SetPosition(pos.x, pos.y)
+ else:
+ divergence.SetPosition(self.Pos.x, self.Pos.y)
+ divergence.Inputs = [input.Clone(divergence) for input in self.Inputs]
+ divergence.Outputs = [output.Clone(divergence) for output in self.Outputs]
+ return divergence
+
+ def GetConnectorTranslation(self, element):
+ return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ if movex != 0 or movey != 0:
+ for input in self.Inputs:
+ if input.IsConnected():
+ rect = rect.Union(input.GetConnectedRedrawRect(movex, movey))
+ for output in self.Outputs:
+ if output.IsConnected():
+ rect = rect.Union(output.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Forbids to resize the divergence
+ def Resize(self, x, y, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.Resize(self, x, 0, width, self.GetMinSize()[1])
+
+ # 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(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+ for output in self.Outputs:
+ output.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # 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, "", None, wx.Point(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH, onlyone = True)
+ 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, "", None, wx.Point(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH, onlyone = True)
+ 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 and len(self.Outputs) > 2:
+ self.Outputs.remove(connector)
+ self.MoveConnector(self.Outputs[0], 0)
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ if connector in self.Inputs and len(self.Inputs) > 2:
+ self.Inputs.remove(connector)
+ self.MoveConnector(self.Inputs[0], 0)
+
+ # Remove the handled branch from the divergence
+ def RemoveHandledBranch(self):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR:
+ handle.UnConnect(delete=True)
+ self.RemoveBranch(handle)
+
+ # 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, connectors=True):
+ return self.BoundingBox.InsideXY(pt.x, pt.y) or self.TestConnector(pt, exclude=False) != None
+
+ # Refresh the divergence bounding box
+ def RefreshBoundingBox(self):
+ if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y,
+ self.Size[0] + 1, self.Size[1] + 1)
+ elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ self.BoundingBox = wx.Rect(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y,
+ self.Size[0] + 2 * SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Size[1] + 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(wx.Point(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(wx.Point(input_pos.x - minx, input_pos.y))
+ for output in self.Outputs:
+ output_pos = output.GetRelPosition()
+ output.SetPosition(wx.Point(output_pos.x - minx, output_pos.y))
+ self.Inputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
+ self.Outputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
+ 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, name = None):
+ # if a name is given
+ if name is not None:
+ # Test each input and output connector
+ #for input in self.Inputs:
+ # if name == input.GetName():
+ # return input
+ for output in self.Outputs:
+ if name == output.GetName():
+ return output
+ return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+
+ # 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, direction = None, exclude=True):
+ # Test input connector
+ for input in self.Inputs:
+ if input.TestPoint(pt, direction, exclude):
+ return input
+ # Test output connector
+ for output in self.Outputs:
+ if output.TestPoint(pt, direction, exclude):
+ return output
+ return None
+
+ # Changes the divergence size
+ def SetSize(self, width, height):
+ height = self.GetMinSize()[1]
+ for i, input in enumerate(self.Inputs):
+ position = input.GetRelPosition()
+ if self.RealConnectors:
+ input.SetPosition(wx.Point(int(round(self.RealConnectors["Inputs"][i] * width)), 0))
+ else:
+ input.SetPosition(wx.Point(int(round(float(position.x)*float(width)/float(self.Size[0]))), 0))
+ input.MoveConnected()
+ for i, output in enumerate(self.Outputs):
+ position = output.GetRelPosition()
+ if self.RealConnectors:
+ output.SetPosition(wx.Point(int(round(self.RealConnectors["Outputs"][i] * width)), height))
+ else:
+ output.SetPosition(wx.Point(int(round(float(position.x)*float(width)/float(self.Size[0]))), height))
+ output.MoveConnected()
+ self.Size = wx.Size(width, height)
+ self.RefreshBoundingBox()
+
+ # Returns the divergence minimum size
+ def GetMinSize(self, default=False):
+ width = 0
+ if default:
+ if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
+ width = (len(self.Outputs) - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL
+ elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ width = (len(self.Inputs) - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL
+ if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
+ return width, 1
+ elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ return width, 3
+ return 0, 0
+
+ # 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)
+ next = wires[0][0].GetOtherConnected(connector)
+ 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].GetOtherConnected(input)
+ 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].GetOtherConnected(self.Output)
+ 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, dc, scaling):
+ connector = None
+ if event.ControlDown():
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Test if a connector have been handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ wx.CallAfter(self.Parent.SetCurrentCursor, 1)
+ self.Selected = False
+ # Initializes the last position
+ self.oldPos = GetScaledEventPosition(event, dc, 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, dc, scaling)
+
+ # Method called when a LeftUp event have been generated
+ def OnLeftUp(self, event, dc, scaling):
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
+ wires = handle.GetWires()
+ if len(wires) == 1:
+ block = wires[0][0].GetOtherConnected(handle).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, dc, scaling)
+ self.RealConnectors = None
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ pos = GetScaledEventPosition(event, dc, scaling)
+ # Popup the menu with special items for a block and a connector if one is handled
+ connector = self.TestConnector(pos, exclude=False)
+ if connector:
+ self.Handle = (HANDLE_CONNECTOR, connector)
+ self.Parent.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, event, scaling):
+ handle_type, handle = self.Handle
+ # A connector has been handled
+ if handle_type == HANDLE_CONNECTOR:
+ movex = max(-self.BoundingBox.x, movex)
+ if scaling is not None:
+ movex = round(float(self.Pos.x + movex) / float(scaling[0])) * scaling[0] - self.Pos.x
+ self.MoveConnector(handle, movex)
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.RefreshConnectedPosition(handle)
+ return movex, 0
+ elif self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+ return 0, 0
+
+ # Refresh output element model
+ def RefreshOutputModel(self, move=False):
+ if move and self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ for output in self.Outputs:
+ wires = output.GetWires()
+ if len(wires) != 1:
+ return
+ output_block = wires[0][0].GetOtherConnected(output).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:
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.RefreshOutputModel()
+ else:
+ for output in self.Outputs:
+ output.RefreshWires()
+
+ # Draws the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ # Draw two rectangles for representing the contact
+ posx = self.Pos.x
+ width = self.Size[0]
+ if self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
+ posx -= SFC_SIMULTANEOUS_SEQUENCE_EXTRA
+ width += SFC_SIMULTANEOUS_SEQUENCE_EXTRA * 2
+ dc.DrawRectangle(int(round((posx - 1) * scalex)) - 2,
+ int(round((self.Pos.y - 1) * scaley)) - 2,
+ int(round((width + 3) * scalex)) + 5,
+ int(round((self.Size.height + 3) * scaley)) + 5)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Draws divergence
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value:
+ dc.SetPen(MiterPen(wx.GREEN))
+ dc.SetBrush(wx.GREEN_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_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 + self.Size[1],
+ self.Pos.x + self.Size[0] + SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Pos.y + self.Size[1])
+ # Draw inputs and outputs connectors
+ for input in self.Inputs:
+ input.Draw(dc)
+ for output in self.Outputs:
+ output.Draw(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.SetTarget(target)
+ self.Id = id
+ self.Size = wx.Size(SFC_JUMP_SIZE[0], SFC_JUMP_SIZE[1])
+ self.Highlights = []
+ # Create an input and output connector
+ self.Input = Connector(self, "", None, wx.Point(self.Size[0] / 2, 0), NORTH, onlyone = True)
+ self.Value = None
+ self.PreviousValue = None
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ self.PreviousValue = self.Value
+ self.Value = self.Input.ReceivingCurrent()
+ if self.Value != self.PreviousValue and self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ # Make a clone of this SFC_Jump
+ def Clone(self, parent, id = None, pos = None):
+ jump = SFC_Jump(parent, self.Target, id)
+ jump.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ jump.SetPosition(pos.x, pos.y)
+ else:
+ jump.SetPosition(self.Pos.x, self.Pos.y)
+ jump.Input = self.Input.Clone(jump)
+ return jump
+
+ def GetConnectorTranslation(self, element):
+ return {self.Input : element.Input}
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Forbids to change the jump size
+ def SetSize(self, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.SetSize(self, width, height)
+
+ # Forbids to resize jump
+ def Resize(self, x, y, width, height):
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ Graphic_Element.Resize(self, x, y, width, height)
+
+ # Delete this jump by calling the appropriate method
+ def Delete(self):
+ self.Parent.DeleteJump(self)
+
+ # Unconnect input
+ def Clean(self):
+ self.Input.UnConnect(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Refresh the size of text for target
+ def RefreshTargetSize(self):
+ self.TargetSize = self.Parent.GetTextExtent(self.Target)
+
+ # Returns if the point given is in the bounding box
+ def HitTest(self, pt, connectors=True):
+ # Calculate the bounding box of the condition outside the transition
+ text_width, text_height = self.TargetSize
+ text_bbx = wx.Rect(self.Pos.x + self.Size[0] + 2,
+ self.Pos.y + (self.Size[1] - text_height) / 2,
+ text_width,
+ text_height)
+ return text_bbx.InsideXY(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)
+
+ # Refresh the jump bounding box
+ def RefreshBoundingBox(self):
+ text_width, text_height = self.Parent.GetTextExtent(self.Target)
+ # Calculate the bounding box size
+ bbx_width = self.Size[0] + 2 + text_width
+ self.BoundingBox = wx.Rect(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):
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ return wires[0][0].GetOtherConnected(self.Input)
+ return None
+
+ # Refresh the element connectors position
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ horizontal_pos = self.Size[0] / 2
+ if scaling is not None:
+ horizontal_pos = round(float(self.Pos.x + horizontal_pos) / float(scaling[0])) * scaling[0] - self.Pos.x
+ self.Input.SetPosition(wx.Point(horizontal_pos, 0))
+ self.RefreshConnected()
+
+ # 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, name = None):
+ return self.Input
+
+ # Returns all the jump connectors
+ def GetConnectors(self):
+ return {"inputs": [self.Input], "outputs": []}
+
+ # Test if point given is on jump input connector
+ def TestConnector(self, pt, direction = None, exclude = True):
+ # Test input connector
+ if self.Input and self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ return None
+
+ # Changes the jump target
+ def SetTarget(self, target):
+ self.Target = target
+ self.RefreshTargetSize()
+ 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, dc, scaling):
+ # Edit the jump properties
+ self.Parent.EditJumpContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the jump state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ movex = max(-self.BoundingBox.x, movex)
+ if scaling is not None:
+ movex = round(float(self.Pos.x + movex) / float(scaling[0])) * scaling[0] - self.Pos.x
+ self.Move(movex, 0)
+ self.RefreshInputPosition()
+ return movex, 0
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, width_fac = 2)
+
+ # Refresh input element model
+ def RefreshInputModel(self):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ 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:
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ self.RefreshInputModel()
+
+ # Adds an highlight to the variable
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "target" and start[0] == 0 and end[0] == 0:
+ AddHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the variable
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "target":
+ RemoveHighlight(self.Highlights, (start, end, highlight_type))
+
+ # Removes all the highlights of one particular type from the variable
+ def ClearHighlight(self, highlight_type=None):
+ ClearHighlights(self.Highlights, highlight_type)
+
+ # Draws the highlightment of this element if it is highlighted
+ def DrawHighlightment(self, dc):
+ scalex, scaley = dc.GetUserScale()
+ dc.SetUserScale(1, 1)
+ dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
+ dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
+ dc.SetLogicalFunction(wx.AND)
+ points = [wx.Point(int(round((self.Pos.x - 2) * scalex)) - 3,
+ int(round((self.Pos.y - 2) * scaley)) - 2),
+ wx.Point(int(round((self.Pos.x + self.Size[0] + 2) * scalex)) + 4,
+ int(round((self.Pos.y - 2) * scaley)) - 2),
+ wx.Point(int(round((self.Pos.x + self.Size[0] / 2) * scalex)),
+ int(round((self.Pos.y + self.Size[1] + 3) * scaley)) + 4)]
+ dc.DrawPolygon(points)
+ dc.SetLogicalFunction(wx.COPY)
+ dc.SetUserScale(scalex, scaley)
+
+ # Draws divergence
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value:
+ dc.SetPen(MiterPen(wx.GREEN))
+ dc.SetBrush(wx.GREEN_BRUSH)
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ if getattr(dc, "printing", False):
+ target_size = dc.GetTextExtent(self.Target)
+ else:
+ target_size = self.TargetSize
+
+ # 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 = [wx.Point(self.Pos.x, self.Pos.y),
+ wx.Point(self.Pos.x + self.Size[0] / 2, self.Pos.y + self.Size[1] / 3),
+ wx.Point(self.Pos.x + self.Size[0], self.Pos.y),
+ wx.Point(self.Pos.x + self.Size[0] / 2, self.Pos.y + self.Size[1])]
+ dc.DrawPolygon(points)
+ target_pos = (self.Pos.x + self.Size[0] + 2,
+ self.Pos.y + (self.Size[1] - target_size[1]) / 2)
+ dc.DrawText(self.Target, target_pos[0], target_pos[1])
+ # Draw input connector
+ if self.Input:
+ self.Input.Draw(dc)
+
+ if not getattr(dc, "printing", False):
+ DrawHighlightedText(dc, self.Target, self.Highlights, target_pos[0], target_pos[1])
+
+
+#-------------------------------------------------------------------------------
+# 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 = wx.Size(SFC_ACTION_MIN_SIZE[0], SFC_ACTION_MIN_SIZE[1])
+ self.MinSize = wx.Size(SFC_ACTION_MIN_SIZE[0], SFC_ACTION_MIN_SIZE[1])
+ self.Highlights = {}
+ # Create an input and output connector
+ self.Input = Connector(self, "", None, wx.Point(0, SFC_ACTION_MIN_SIZE[1] / 2), WEST, onlyone = True)
+ self.SetActions(actions)
+ self.Value = None
+ self.PreviousValue = None
+
+ def Flush(self):
+ if self.Input is not None:
+ self.Input.Flush()
+ self.Input = None
+
+ def SpreadCurrent(self):
+ if self.Parent.Debug:
+ self.PreviousValue = self.Value
+ self.Value = self.Input.ReceivingCurrent()
+ if self.Value != self.PreviousValue and self.Visible:
+ self.Parent.ElementNeedRefresh(self)
+
+ # Make a clone of this SFC_ActionBlock
+ def Clone(self, parent, id = None, pos = None):
+ actions = [action.copy() for action in self.Actions]
+ action_block = SFC_ActionBlock(parent, actions, id)
+ action_block.SetSize(self.Size[0], self.Size[1])
+ if pos is not None:
+ action_block.SetPosition(pos.x, pos.y)
+ else:
+ action_block.SetPosition(self.Pos.x, self.Pos.y)
+ action_block.Input = self.Input.Clone(action_block)
+ return action_block
+
+ def GetConnectorTranslation(self, element):
+ return {self.Input : element.Input}
+
+ # Returns the RedrawRect
+ def GetRedrawRect(self, movex = 0, movey = 0):
+ rect = Graphic_Element.GetRedrawRect(self, movex, movey)
+ rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
+ if movex != 0 or movey != 0:
+ if self.Input.IsConnected():
+ rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
+ return rect
+
+ # Returns the number of action lines
+ def GetLineNumber(self):
+ return len(self.Actions)
+
+ def GetLineSize(self):
+ if len(self.Actions) > 0:
+ return self.Size[1] / len(self.Actions)
+ else:
+ return SFC_ACTION_MIN_SIZE[1]
+
+ # Forbids to resize the action block
+ def Resize(self, x, y, width, height):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ if x == 0:
+ self.SetSize(width, self.Size[1])
+ else:
+ Graphic_Element.Resize(self, x, y, width, height)
+
+ # 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(delete = self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
+
+ # Refresh the action block bounding box
+ def RefreshBoundingBox(self):
+ self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 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, name = None):
+ return self.Input
+
+ # Returns all the action block connectors
+ def GetConnectors(self):
+ return {"inputs": [self.Input], "outputs": []}
+
+ # Test if point given is on action block input connector
+ def TestConnector(self, pt, direction = None, exclude = True):
+ # Test input connector
+ if self.Input.TestPoint(pt, direction, exclude):
+ return self.Input
+ return None
+
+ # Refresh the element connectors position
+ def RefreshConnectors(self):
+ scaling = self.Parent.GetScaling()
+ vertical_pos = SFC_ACTION_MIN_SIZE[1] / 2
+ if scaling is not None:
+ vertical_pos = round(float(self.Pos.y + vertical_pos) / float(scaling[1])) * scaling[1] - self.Pos.y
+ self.Input.SetPosition(wx.Point(0, vertical_pos))
+ self.RefreshConnected()
+
+ # Changes the action block actions
+ def SetActions(self, actions):
+ self.Actions = actions
+ self.ColSize = [0, 0, 0]
+ min_height = 0
+ for action in self.Actions:
+ width, height = self.Parent.GetTextExtent(action["qualifier"])
+ self.ColSize[0] = max(self.ColSize[0], width + 10)
+ row_height = height
+ if action.has_key("duration"):
+ width, height = self.Parent.GetTextExtent(action["duration"])
+ row_height = max(row_height, height)
+ self.ColSize[0] = max(self.ColSize[0], width + 10)
+ width, height = self.Parent.GetTextExtent(action["value"])
+ row_height = max(row_height, height)
+ self.ColSize[1] = max(self.ColSize[1], width + 10)
+ if action.get("indicator", "") != "":
+ width, height = self.Parent.GetTextExtent(action["indicator"])
+ row_height = max(row_height, height)
+ self.ColSize[2] = max(self.ColSize[2], width + 10)
+ min_height += row_height + 5
+ if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
+ self.Size = wx.Size(self.ColSize[0] + self.ColSize[1] + self.ColSize[2], max(min_height, SFC_ACTION_MIN_SIZE[1], self.Size[1]))
+ self.MinSize = max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
+ SFC_ACTION_MIN_SIZE[0]), max(SFC_ACTION_MIN_SIZE[1], min_height)
+ self.RefreshBoundingBox()
+ else:
+ self.Size = wx.Size(max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
+ SFC_ACTION_MIN_SIZE[0]), len(self.Actions) * SFC_ACTION_MIN_SIZE[1])
+ self.MinSize = max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
+ SFC_ACTION_MIN_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].GetOtherConnected(self.Input).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 self.MinSize
+
+ # Method called when a LeftDClick event have been generated
+ def OnLeftDClick(self, event, dc, scaling):
+ # Edit the action block properties
+ self.Parent.EditActionBlockContent(self)
+
+ # Method called when a RightUp event have been generated
+ def OnRightUp(self, event, dc, scaling):
+ # Popup the default menu
+ self.Parent.PopupDefaultMenu()
+
+ # Refreshes the action block state according to move defined and handle selected
+ def ProcessDragging(self, movex, movey, event, scaling):
+ if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
+ handle_type, handle = self.Handle
+ if handle_type == HANDLE_MOVE:
+ movex = max(-self.BoundingBox.x, movex)
+ if scaling is not None:
+ movex = round(float(self.Pos.x + movex) / float(scaling[0])) * scaling[0] - self.Pos.x
+ wires = self.Input.GetWires()
+ if len(wires) == 1:
+ input_pos = wires[0][0].GetOtherConnected(self.Input).GetPosition(False)
+ if self.Pos.x - input_pos.x + movex >= SFC_WIRE_MIN_SIZE:
+ self.Move(movex, 0)
+ return movex, 0
+ return 0, 0
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+ else:
+ return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
+
+
+ # Refreshes the action block model
+ def RefreshModel(self, move=True):
+ self.Parent.RefreshActionBlockModel(self)
+
+ # Adds an highlight to the variable
+ def AddHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "action" and infos[1] < len(self.Actions):
+ action_highlights = self.Highlights.setdefault(infos[1], {})
+ attribute_highlights = action_highlights.setdefault(infos[2], [])
+ AddHighlight(attribute_highlights, (start, end, highlight_type))
+
+ # Removes an highlight from the block
+ def RemoveHighlight(self, infos, start, end, highlight_type):
+ if infos[0] == "action" and infos[1] < len(self.Actions):
+ action_highlights = self.Highlights.get(infos[1], {})
+ attribute_highlights = action_highlights.setdefault(infos[2], [])
+ if RemoveHighlight(attribute_highlights, (start, end, highlight_type)) and len(attribute_highlights) == 0:
+ action_highlights.pop(infos[2])
+ if len(action_highlights) == 0:
+ self.Highlights.pop(infos[1])
+
+ # Removes all the highlights of one particular type from the block
+ def ClearHighlight(self, highlight_type=None):
+ if highlight_type is None:
+ self.Highlights = {}
+ else:
+ highlight_items = self.Highlights.items()
+ for number, action_highlights in highlight_items:
+ action_highlight_items = action_highlights.items()
+ for name, attribute_highlights in action_highlights:
+ attribute_highlights = ClearHighlights(attribute_highlights, highlight_type)
+ if len(attribute_highlights) == 0:
+ action_highlights.pop(name)
+ if len(action_highlights) == 0:
+ self.Highlights.pop(number)
+
+ # Draws divergence
+ def Draw(self, dc):
+ Graphic_Element.Draw(self, dc)
+ if self.Value:
+ dc.SetPen(MiterPen(wx.GREEN))
+ else:
+ dc.SetPen(MiterPen(wx.BLACK))
+ dc.SetBrush(wx.WHITE_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])
+ line_size = self.GetLineSize()
+ for i, action in enumerate(self.Actions):
+ if i != 0:
+ dc.DrawLine(self.Pos.x, self.Pos.y + i * line_size,
+ self.Pos.x + self.Size[0], self.Pos.y + i * line_size)
+ qualifier_size = dc.GetTextExtent(action["qualifier"])
+ if action.has_key("duration"):
+ qualifier_pos = (self.Pos.x + (colsize[0] - qualifier_size[0]) / 2,
+ self.Pos.y + i * line_size + line_size / 2 - qualifier_size[1])
+ duration_size = dc.GetTextExtent(action["duration"])
+ duration_pos = (self.Pos.x + (colsize[0] - duration_size[0]) / 2,
+ self.Pos.y + i * line_size + line_size / 2)
+ dc.DrawText(action["duration"], duration_pos[0], duration_pos[1])
+ else:
+ qualifier_pos = (self.Pos.x + (colsize[0] - qualifier_size[0]) / 2,
+ self.Pos.y + i * line_size + (line_size - qualifier_size[1]) / 2)
+ dc.DrawText(action["qualifier"], qualifier_pos[0], qualifier_pos[1])
+ content_size = dc.GetTextExtent(action["value"])
+ content_pos = (self.Pos.x + colsize[0] + (colsize[1] - content_size[0]) / 2,
+ self.Pos.y + i * line_size + (line_size - content_size[1]) / 2)
+ dc.DrawText(action["value"], content_pos[0], content_pos[1])
+ if action.has_key("indicator"):
+ indicator_size = dc.GetTextExtent(action["indicator"])
+ indicator_pos = (self.Pos.x + colsize[0] + colsize[1] + (colsize[2] - indicator_size[0]) / 2,
+ self.Pos.y + i * line_size + (line_size - indicator_size[1]) / 2)
+ dc.DrawText(action["indicator"], indicator_pos[0], indicator_pos[1])
+
+ if not getattr(dc, "printing", False):
+ action_highlights = self.Highlights.get(i, {})
+ for name, attribute_highlights in action_highlights.iteritems():
+ if name == "qualifier":
+ DrawHighlightedText(dc, action["qualifier"], attribute_highlights, qualifier_pos[0], qualifier_pos[1])
+ elif name == "duration":
+ DrawHighlightedText(dc, action["duration"], attribute_highlights, duration_pos[0], duration_pos[1])
+ elif name in ["reference", "inline"]:
+ DrawHighlightedText(dc, action["value"], attribute_highlights, content_pos[0], content_pos[1])
+ elif name == "indicator":
+ DrawHighlightedText(dc, action["indicator"], attribute_highlights, indicator_pos[0], indicator_pos[1])
+
+ # Draw input connector
+ self.Input.Draw(dc)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialisation
+
+from GraphicCommons import *
+from FBD_Objects import *
+from LD_Objects import *
+from SFC_Objects import *
\ No newline at end of file
--- a/i18n/Beremiz_fr_FR.po Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/Beremiz_fr_FR.po Fri Sep 07 16:45:55 2012 +0200
@@ -7,8 +7,8 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-08-24 18:28+0200\n"
-"PO-Revision-Date: 2012-08-24 18:46+0100\n"
+"POT-Creation-Date: 2012-09-07 01:17+0200\n"
+"PO-Revision-Date: 2012-09-07 01:31+0100\n"
"Last-Translator: Laurent BESSARD <laurent.bessard@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -16,7 +16,29 @@
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ../Beremiz.py:1069
+#: ../PLCOpenEditor.py:520
+msgid ""
+"\n"
+"An error has occurred.\n"
+"\n"
+"Click OK to save an error report.\n"
+"\n"
+"Please be kind enough to send this file to:\n"
+"edouard.tisserant@gmail.com\n"
+"\n"
+"Error:\n"
+msgstr ""
+"\n"
+"Une erreur est apparue.\n"
+"\n"
+"Appuyer sur 'Valider' pour enregistrer un rapport d'erreur.\n"
+"\n"
+"Envoyez ce fichier à l'adresse :\n"
+"edouard.tisserant@gmail.com\n"
+"\n"
+"Erreur:\n"
+
+#: ../Beremiz.py:1071
#, python-format
msgid ""
"\n"
@@ -41,84 +63,602 @@
"\n"
"Trace d'exécution:\n"
-#: ../ProjectController.py:891
+#: ../controls/VariablePanel.py:77
+msgid " External"
+msgstr " Externe"
+
+#: ../controls/VariablePanel.py:76
+msgid " InOut"
+msgstr " Entrée-Sortie"
+
+#: ../controls/VariablePanel.py:76
+msgid " Input"
+msgstr " Entrée"
+
+#: ../controls/VariablePanel.py:77
+msgid " Local"
+msgstr " Locale"
+
+#: ../controls/VariablePanel.py:76
+msgid " Output"
+msgstr " Sortie"
+
+#: ../controls/VariablePanel.py:78
+msgid " Temp"
+msgstr " Temporaire"
+
+#: ../PLCOpenEditor.py:530
+msgid " : "
+msgstr " : "
+
+#: ../dialogs/PouTransitionDialog.py:94
+#: ../dialogs/PouActionDialog.py:91
+#: ../dialogs/PouDialog.py:111
+#: ../dialogs/SFCTransitionDialog.py:144
+#, python-format
+msgid " and %s"
+msgstr " et %s"
+
+#: ../ProjectController.py:890
msgid " generation failed !\n"
msgstr "la construction a échouée !\n"
-#: ../Beremiz.py:892
+#: ../plcopen/plcopen.py:1051
+#, python-format
+msgid "\"%s\" Data Type doesn't exist !!!"
+msgstr "Le type de donnée \"%s\" n'existe pas !!!"
+
+#: ../plcopen/plcopen.py:1069
+#, python-format
+msgid "\"%s\" POU already exists !!!"
+msgstr "Le POU \"%s\" existe déjà !!!"
+
+#: ../plcopen/plcopen.py:1090
+#, python-format
+msgid "\"%s\" POU doesn't exist !!!"
+msgstr "Le POU \"%s\" n'existe pas !!!"
+
+#: ../editors/Viewer.py:234
+#, python-format
+msgid "\"%s\" can't use itself!"
+msgstr "\"%s\" ne peut pas s'utiliser lui-même !"
+
+#: ../IDEFrame.py:1706
+#: ../IDEFrame.py:1725
+#, python-format
+msgid "\"%s\" config already exists!"
+msgstr "La configuration \"%s\" existe déjà !"
+
+#: ../plcopen/plcopen.py:315
+#, python-format
+msgid "\"%s\" configuration already exists !!!"
+msgstr "La configuration \"%s\" existe déjà !!!"
+
+#: ../IDEFrame.py:1660
+#, python-format
+msgid "\"%s\" data type already exists!"
+msgstr "Le type de données \"%s\" existe déjà !"
+
+#: ../PLCControler.py:2040
+#: ../PLCControler.py:2044
+#, python-format
+msgid "\"%s\" element can't be pasted here!!!"
+msgstr "L'élément \"%s\" ne peut être collé ici !!!"
+
+#: ../editors/TextViewer.py:305
+#: ../editors/TextViewer.py:325
+#: ../editors/Viewer.py:252
+#: ../dialogs/PouTransitionDialog.py:105
+#: ../dialogs/ConnectionDialog.py:150
+#: ../dialogs/PouActionDialog.py:102
+#: ../dialogs/FBDBlockDialog.py:162
+#, python-format
+msgid "\"%s\" element for this pou already exists!"
+msgstr "Un élément \"%s\" existe déjà dans ce POU !"
+
+#: ../Beremiz.py:894
#, python-format
msgid "\"%s\" folder is not a valid Beremiz project\n"
msgstr "Le dossier \"%s\" ne contient pas de projet Beremiz valide\n"
+#: ../plcopen/structures.py:106
+#, python-format
+msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
+msgstr "L'appel à la fonction \"%s\" dans le POU \"%s\" a été abandonné : aucune entrée connectée"
+
+#: ../controls/VariablePanel.py:656
+#: ../IDEFrame.py:1651
+#: ../editors/DataTypeEditor.py:548
+#: ../editors/DataTypeEditor.py:577
+#: ../dialogs/PouNameDialog.py:49
+#: ../dialogs/PouTransitionDialog.py:101
+#: ../dialogs/SFCStepNameDialog.py:51
+#: ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/FBDVariableDialog.py:199
+#: ../dialogs/PouActionDialog.py:98
+#: ../dialogs/PouDialog.py:118
+#: ../dialogs/SFCStepDialog.py:122
+#: ../dialogs/FBDBlockDialog.py:158
+#, python-format
+msgid "\"%s\" is a keyword. It can't be used!"
+msgstr "\"%s\" est un mot réservé. Il ne peut être utilisé !"
+
+#: ../editors/Viewer.py:240
+#, python-format
+msgid "\"%s\" is already used by \"%s\"!"
+msgstr "\"%s\" est déjà utilisé par \"%s\" !"
+
+#: ../plcopen/plcopen.py:2786
+#, python-format
+msgid "\"%s\" is an invalid value!"
+msgstr "\"%s\" n'est pas une valeur valide !"
+
+#: ../PLCOpenEditor.py:362
+#: ../PLCOpenEditor.py:399
+#, python-format
+msgid "\"%s\" is not a valid folder!"
+msgstr "\"%s\" n'est pas un répertoire valide !"
+
+#: ../controls/VariablePanel.py:654
+#: ../IDEFrame.py:1649
+#: ../editors/DataTypeEditor.py:572
+#: ../dialogs/PouNameDialog.py:47
+#: ../dialogs/PouTransitionDialog.py:99
+#: ../dialogs/SFCStepNameDialog.py:49
+#: ../dialogs/ConnectionDialog.py:144
+#: ../dialogs/PouActionDialog.py:96
+#: ../dialogs/PouDialog.py:116
+#: ../dialogs/SFCStepDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:156
+#, python-format
+msgid "\"%s\" is not a valid identifier!"
+msgstr "\"%s\" n'est pas un identifiant valide !"
+
+#: ../IDEFrame.py:214
+#: ../IDEFrame.py:2445
+#: ../IDEFrame.py:2464
+#, python-format
+msgid "\"%s\" is used by one or more POUs. It can't be removed!"
+msgstr "Le POU \"%s\" est utilisé par un ou plusieurs POUs. Il ne peut être supprimé !"
+
+#: ../controls/VariablePanel.py:311
+#: ../IDEFrame.py:1669
+#: ../editors/TextViewer.py:303
+#: ../editors/TextViewer.py:323
+#: ../editors/TextViewer.py:360
+#: ../editors/Viewer.py:250
+#: ../editors/Viewer.py:295
+#: ../editors/Viewer.py:312
+#: ../dialogs/ConnectionDialog.py:148
+#: ../dialogs/PouDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:160
+#, python-format
+msgid "\"%s\" pou already exists!"
+msgstr "Le POU \"%s\" existe déjà !"
+
+#: ../plcopen/plcopen.py:346
+#, python-format
+msgid "\"%s\" resource already exists in \"%s\" configuration !!!"
+msgstr "La ressource \"%s\" existe déjà dans la configuration \"%s\" !!!"
+
+#: ../plcopen/plcopen.py:362
+#, python-format
+msgid "\"%s\" resource doesn't exist in \"%s\" configuration !!!"
+msgstr "La ressource \"%s\" n'existe pas dans la configuration \"%s\" !!!"
+
+#: ../dialogs/SFCStepNameDialog.py:57
+#: ../dialogs/SFCStepDialog.py:128
+#, python-format
+msgid "\"%s\" step already exists!"
+msgstr "L'étape \"%s\" existe déjà !"
+
+#: ../editors/DataTypeEditor.py:543
+#, python-format
+msgid "\"%s\" value already defined!"
+msgstr "La valeur \"%s\" est déjà définie !"
+
+#: ../editors/DataTypeEditor.py:719
+#: ../dialogs/ArrayTypeDialog.py:97
+#, python-format
+msgid "\"%s\" value isn't a valid array dimension!"
+msgstr "\"%s\" n'est pas une dimension de tableau valide !"
+
+#: ../editors/DataTypeEditor.py:726
+#: ../dialogs/ArrayTypeDialog.py:103
+#, python-format
+msgid ""
+"\"%s\" value isn't a valid array dimension!\n"
+"Right value must be greater than left value."
+msgstr ""
+"\"%s\" n'est pas une dimension de tableau valide !\n"
+"La valeur de droite doit être supérieur à celle de gauche."
+
+#: ../PLCControler.py:793
+#, python-format
+msgid "%s \"%s\" can't be pasted as a %s."
+msgstr "Le %s \"%s\" ne peut être collé en tant que %s."
+
+#: ../PLCControler.py:1422
+#, python-format
+msgid "%s Data Types"
+msgstr "Types de données de %s"
+
+#: ../editors/GraphicViewer.py:278
+#, python-format
+msgid "%s Graphics"
+msgstr "Graphique %s"
+
+#: ../PLCControler.py:1417
+#, python-format
+msgid "%s POUs"
+msgstr "POUs de %s"
+
#: ../canfestival/SlaveEditor.py:42
#: ../canfestival/NetworkEditor.py:72
#, python-format
msgid "%s Profile"
msgstr "Profil %s"
-#: ../Beremiz.py:308
+#: ../plcopen/plcopen.py:1780
+#: ../plcopen/plcopen.py:1790
+#: ../plcopen/plcopen.py:1800
+#: ../plcopen/plcopen.py:1810
+#: ../plcopen/plcopen.py:1819
+#, python-format
+msgid "%s body don't have instances!"
+msgstr "Le code d'un %s n'a pas d'instances !"
+
+#: ../plcopen/plcopen.py:1842
+#: ../plcopen/plcopen.py:1849
+#, python-format
+msgid "%s body don't have text!"
+msgstr "Le code d'un %s n'a pas de texte !"
+
+#: ../IDEFrame.py:364
+msgid "&Add Element"
+msgstr "&Ajouter un élément"
+
+#: ../IDEFrame.py:334
+msgid "&Configuration"
+msgstr "&Configuration"
+
+#: ../IDEFrame.py:325
+msgid "&Data Type"
+msgstr "&Type de donnée"
+
+#: ../IDEFrame.py:368
+msgid "&Delete"
+msgstr "&Supprimer"
+
+#: ../IDEFrame.py:317
+msgid "&Display"
+msgstr "&Affichage"
+
+#: ../IDEFrame.py:316
+msgid "&Edit"
+msgstr "&Editer"
+
+#: ../IDEFrame.py:315
+msgid "&File"
+msgstr "&Fichier"
+
+#: ../IDEFrame.py:327
+msgid "&Function"
+msgstr "&Fonction"
+
+#: ../IDEFrame.py:318
+msgid "&Help"
+msgstr "&Aide"
+
+#: ../IDEFrame.py:331
+msgid "&Program"
+msgstr "&Programme"
+
+#: ../PLCOpenEditor.py:148
+msgid "&Properties"
+msgstr "&Propriétés"
+
+#: ../Beremiz.py:310
msgid "&Recent Projects"
msgstr "Projets &récent"
-#: ../Beremiz.py:350
+#: ../Beremiz.py:352
msgid "&Resource"
msgstr "&Ressource"
+#: ../controls/SearchResultPanel.py:237
+#, python-format
+msgid "'%s' - %d match in project"
+msgstr "'%s' - %d correspondance dans le projet"
+
+#: ../controls/SearchResultPanel.py:239
+#, python-format
+msgid "'%s' - %d matches in project"
+msgstr "'%s' - %d correspondances dans le projet"
+
#: ../connectors/PYRO/__init__.py:51
#, python-format
msgid "'%s' is located at %s\n"
msgstr "'%s' is disponible à l'adresse %s\n"
-#: ../ProjectController.py:1269
+#: ../controls/SearchResultPanel.py:289
+#, python-format
+msgid "(%d matches)"
+msgstr "(%d correspondances)"
+
+#: ../PLCOpenEditor.py:508
+#: ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:511
+msgid ", "
+msgstr ", "
+
+#: ../dialogs/PouTransitionDialog.py:96
+#: ../dialogs/PouActionDialog.py:93
+#: ../dialogs/PouDialog.py:113
+#: ../dialogs/SFCTransitionDialog.py:146
+#, python-format
+msgid ", %s"
+msgstr ", %s"
+
+#: ../PLCOpenEditor.py:506
+msgid ". "
+msgstr ". "
+
+#: ../ProjectController.py:1268
msgid "... debugger recovered\n"
msgstr "... déboggueur operationel\n"
+#: ../IDEFrame.py:1672
+#: ../IDEFrame.py:1714
+#: ../IDEFrame.py:1733
+#: ../dialogs/PouDialog.py:122
+#, python-format
+msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr "Un POU a un élément nommé \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
+
+#: ../controls/VariablePanel.py:658
+#: ../IDEFrame.py:1684
+#: ../IDEFrame.py:1695
+#: ../dialogs/PouNameDialog.py:51
+#: ../dialogs/PouTransitionDialog.py:103
+#: ../dialogs/SFCStepNameDialog.py:53
+#: ../dialogs/PouActionDialog.py:100
+#: ../dialogs/SFCStepDialog.py:124
+#, python-format
+msgid "A POU named \"%s\" already exists!"
+msgstr "Un POU nommé \"%s\" existe déjà !"
+
#: ../ConfigTreeNode.py:371
#, python-format
msgid "A child named \"%s\" already exist -> \"%s\"\n"
msgstr "Un noeud enfant nommé \"%s\" existe déjà -> \"%s\"\n"
-#: ../Beremiz.py:360
+#: ../dialogs/BrowseLocationsDialog.py:175
+msgid "A location must be selected!"
+msgstr "Une adresse doit être sélectionné !"
+
+#: ../controls/VariablePanel.py:660
+#: ../IDEFrame.py:1686
+#: ../IDEFrame.py:1697
+#: ../dialogs/SFCStepNameDialog.py:55
+#: ../dialogs/SFCStepDialog.py:126
+#, python-format
+msgid "A variable with \"%s\" as name already exists in this pou!"
+msgstr "Une variable nommée \"%s\" existe déjà dans ce POU !"
+
+#: ../Beremiz.py:362
+#: ../PLCOpenEditor.py:181
msgid "About"
msgstr "A propos"
-#: ../Beremiz.py:929
+#: ../Beremiz.py:931
msgid "About Beremiz"
msgstr "A propos de Beremiz"
+#: ../PLCOpenEditor.py:376
+msgid "About PLCOpenEditor"
+msgstr "A propos de PLCOpenEditor"
+
+#: ../plcopen/iec_std.csv:22
+msgid "Absolute number"
+msgstr "Nombre absolu"
+
+#: ../dialogs/ActionBlockDialog.py:41
+#: ../dialogs/SFCStepDialog.py:69
+msgid "Action"
+msgstr "Action"
+
+#: ../editors/Viewer.py:495
+msgid "Action Block"
+msgstr "Ajouter un bloc fonctionnel"
+
+#: ../dialogs/PouActionDialog.py:81
+msgid "Action Name"
+msgstr "Nom de l'action"
+
+#: ../dialogs/PouActionDialog.py:49
+msgid "Action Name:"
+msgstr "Nom de l'action :"
+
+#: ../plcopen/plcopen.py:1480
+#, python-format
+msgid "Action with name %s doesn't exist!"
+msgstr "L'action nommée %s n'existe pas !"
+
+#: ../PLCControler.py:95
+msgid "Actions"
+msgstr "Actions"
+
+#: ../dialogs/ActionBlockDialog.py:134
+msgid "Actions:"
+msgstr "Actions :"
+
#: ../canfestival/SlaveEditor.py:54
#: ../canfestival/NetworkEditor.py:84
+#: ../editors/Viewer.py:527
msgid "Add"
msgstr "Ajouter"
+#: ../IDEFrame.py:1925
+#: ../IDEFrame.py:1956
+msgid "Add Action"
+msgstr "Ajouter une action"
+
#: ../features.py:7
msgid "Add C code accessing located variables synchronously"
msgstr "Ajoute un code C ayant accès à des variables localisées de façon synchrone"
-#: ../util/discovery.py:115
+#: ../IDEFrame.py:1908
+msgid "Add Configuration"
+msgstr "Ajouter une configuration"
+
+#: ../IDEFrame.py:1888
+msgid "Add DataType"
+msgstr "Ajouter un type de donnée"
+
+#: ../editors/Viewer.py:453
+msgid "Add Divergence Branch"
+msgstr "Ajouter une branche à la divergence"
+
+#: ../dialogs/DiscoveryDialog.py:115
msgid "Add IP"
msgstr "Ajouter IP"
+#: ../IDEFrame.py:1896
+msgid "Add POU"
+msgstr "Ajouter un POU"
+
#: ../features.py:8
msgid "Add Python code executed asynchronously"
msgstr "Ajoute un code Python executé de façon asynchone"
+#: ../IDEFrame.py:1936
+#: ../IDEFrame.py:1982
+msgid "Add Resource"
+msgstr "Ajouter une resource"
+
+#: ../IDEFrame.py:1914
+#: ../IDEFrame.py:1953
+msgid "Add Transition"
+msgstr "Ajouter une transition"
+
+#: ../editors/Viewer.py:442
+msgid "Add Wire Segment"
+msgstr "Ajouter un segment au fil"
+
+#: ../editors/SFCViewer.py:359
+msgid "Add a new initial step"
+msgstr "Ajouter une nouvelle étape initiale"
+
+#: ../editors/Viewer.py:2289
+#: ../editors/SFCViewer.py:696
+msgid "Add a new jump"
+msgstr "Ajouter un nouveau renvoi"
+
+#: ../editors/SFCViewer.py:381
+msgid "Add a new step"
+msgstr "Ajouter une nouvelle étape"
+
#: ../features.py:9
msgid "Add a simple WxGlade based GUI."
msgstr "Ajoute une interface simple utilisant WxGlade"
+#: ../dialogs/ActionBlockDialog.py:138
+msgid "Add action"
+msgstr "Ajouter une action"
+
+#: ../editors/DataTypeEditor.py:345
+msgid "Add element"
+msgstr "Ajouter un élément"
+
+#: ../editors/ResourceEditor.py:251
+msgid "Add instance"
+msgstr "Ajouter une instance"
+
#: ../canfestival/NetworkEditor.py:86
msgid "Add slave"
msgstr "Ajouter un esclave"
-#: ../util/FileManagementPanel.py:35
+#: ../editors/ResourceEditor.py:222
+msgid "Add task"
+msgstr "Ajouter une tâche"
+
+#: ../controls/VariablePanel.py:378
+msgid "Add variable"
+msgstr "Ajouter une variable"
+
+#: ../plcopen/iec_std.csv:33
+msgid "Addition"
+msgstr "Addition"
+
+#: ../plcopen/structures.py:250
+msgid "Additional function blocks"
+msgstr "Blocs fonctionnels additionnels"
+
+#: ../editors/Viewer.py:1395
+msgid "Alignment"
+msgstr "Alignement"
+
+#: ../controls/VariablePanel.py:75
+#: ../dialogs/BrowseLocationsDialog.py:35
+#: ../dialogs/BrowseLocationsDialog.py:116
+msgid "All"
+msgstr "Toutes"
+
+#: ../editors/FileManagementPanel.py:35
msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
msgstr "Tous les fichiers|*.*|Fichiers CSV (*.csv)|*.csv"
-#: ../ProjectController.py:1336
+#: ../ProjectController.py:1335
msgid "Already connected. Please disconnect\n"
msgstr "Déjà connecté. Veuillez déconnecter\n"
+#: ../editors/DataTypeEditor.py:587
+#, python-format
+msgid "An element named \"%s\" already exists in this structure!"
+msgstr "Un élément nommé \"%s\" existe déjà dans la structure !"
+
+#: ../plcopen/iec_std.csv:31
+msgid "Arc cosine"
+msgstr "Arc cosinus"
+
+#: ../plcopen/iec_std.csv:30
+msgid "Arc sine"
+msgstr "Arc sinus"
+
+#: ../plcopen/iec_std.csv:32
+msgid "Arc tangent"
+msgstr "Arc tangente"
+
+#: ../plcopen/iec_std.csv:33
+msgid "Arithmetic"
+msgstr "Arithmétique"
+
+#: ../controls/VariablePanel.py:729
+#: ../editors/DataTypeEditor.py:52
+msgid "Array"
+msgstr "Tableau"
+
+#: ../plcopen/iec_std.csv:39
+msgid "Assignment"
+msgstr "Assignation"
+
+#: ../dialogs/FBDVariableDialog.py:197
+msgid "At least a variable or an expression must be selected!"
+msgstr "Au moins une variable ou une expression doit être sélectionné !"
+
+#: ../controls/ProjectPropertiesPanel.py:99
+msgid "Author"
+msgstr "Auteur"
+
+#: ../controls/ProjectPropertiesPanel.py:96
+msgid "Author Name (optional):"
+msgstr "Nom de l'auteur (optionel) :"
+
+#: ../dialogs/FindInPouDialog.py:72
+msgid "Backward"
+msgstr "Vers le haut"
+
#: ../util/Zeroconf.py:599
msgid "Bad domain name (circular) at "
msgstr "Mauvais nom de domaine (circulaire) à l'adresse"
@@ -133,36 +673,92 @@
msgid "Bad location size : %s"
msgstr "Mauvaise taille d'adresse : %s"
-#: ../Beremiz.py:484
+#: ../editors/DataTypeEditor.py:168
+#: ../editors/DataTypeEditor.py:198
+#: ../editors/DataTypeEditor.py:290
+#: ../dialogs/ArrayTypeDialog.py:55
+msgid "Base Type:"
+msgstr "Type de base :"
+
+#: ../controls/VariablePanel.py:699
+#: ../editors/DataTypeEditor.py:617
+msgid "Base Types"
+msgstr "Types de base"
+
+#: ../Beremiz.py:486
msgid "Beremiz"
msgstr "Beremiz"
-#: ../util/BrowseValuesLibraryDialog.py:37
+#: ../plcopen/iec_std.csv:70
+msgid "Binary selection (1 of 2)"
+msgstr "Selection binaire (sélectionne 1 sur 2)"
+
+#: ../plcopen/iec_std.csv:62
+msgid "Bit-shift"
+msgstr "Décalage de bit"
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise"
+msgstr "Bit à bit"
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise AND"
+msgstr "ET bit à bit"
+
+#: ../plcopen/iec_std.csv:67
+msgid "Bitwise OR"
+msgstr "OU bit à bit"
+
+#: ../plcopen/iec_std.csv:68
+msgid "Bitwise XOR"
+msgstr "OU exclusif bit à bit"
+
+#: ../plcopen/iec_std.csv:69
+msgid "Bitwise inverting"
+msgstr "Inversion bit à bit"
+
+#: ../editors/Viewer.py:465
+msgid "Block"
+msgstr "Block"
+
+#: ../dialogs/FBDBlockDialog.py:38
+msgid "Block Properties"
+msgstr "Propriétés du bloc"
+
+#: ../editors/Viewer.py:434
+msgid "Bottom"
+msgstr "Bas"
+
+#: ../dialogs/BrowseValuesLibraryDialog.py:37
#, python-format
msgid "Browse %s values library"
msgstr "Explorer la liste des valeurs du paramètre '%s'"
-#: ../ProjectController.py:1485
+#: ../dialogs/BrowseLocationsDialog.py:55
+msgid "Browse Locations"
+msgstr "Naviger dans les adresses"
+
+#: ../ProjectController.py:1484
msgid "Build"
msgstr "Compiler"
-#: ../ProjectController.py:1052
+#: ../ProjectController.py:1051
msgid "Build directory already clean\n"
msgstr "Le répertoire de compilation est déjà nettoyé\n"
-#: ../ProjectController.py:1486
+#: ../ProjectController.py:1485
msgid "Build project into build folder"
msgstr "Compiler le projet dans le répertoire ce compilation"
-#: ../ProjectController.py:911
+#: ../ProjectController.py:910
msgid "C Build crashed !\n"
msgstr "La compilation du C a mal fonctionné !\n"
-#: ../ProjectController.py:908
+#: ../ProjectController.py:907
msgid "C Build failed.\n"
msgstr "La compilation du C a échouée !\n"
-#: ../ProjectController.py:896
+#: ../ProjectController.py:895
msgid "C code generated successfully.\n"
msgstr "Code C généré avec succès.\n"
@@ -179,6 +775,35 @@
msgid "CANopen support"
msgstr "Support CANopen"
+#: ../plcopen/plcopen.py:1722
+#: ../plcopen/plcopen.py:1736
+#: ../plcopen/plcopen.py:1757
+#: ../plcopen/plcopen.py:1773
+msgid "Can only generate execution order on FBD networks!"
+msgstr "L'ordre d'exécution ne peut être généré que dans les FBD !"
+
+#: ../controls/VariablePanel.py:256
+msgid "Can only give a location to local or global variables"
+msgstr "Une adresse ne peut être affecté qu'à des variables locales ou globales"
+
+#: ../PLCOpenEditor.py:357
+#, python-format
+msgid "Can't generate program to file %s!"
+msgstr "Le programme n'a pu être généré dans le fichier \"%s\" !"
+
+#: ../controls/VariablePanel.py:254
+msgid "Can't give a location to a function block instance"
+msgstr "Une adresse ne peut être affectée à une instance de Function Block"
+
+#: ../PLCOpenEditor.py:397
+#, python-format
+msgid "Can't save project to file %s!"
+msgstr "Le projet n'a pu être sauvé dans le fichier \"%s\" !"
+
+#: ../controls/VariablePanel.py:298
+msgid "Can't set an initial value to a function block instance"
+msgstr "Une valeur initiale ne peut être affectée une instance de Function Block"
+
#: ../ConfigTreeNode.py:470
#, python-format
msgid "Cannot create child %s of type %s "
@@ -193,7 +818,7 @@
msgid "Cannot get PLC status - connection failed.\n"
msgstr "Impossible d'obtenir le statut de l'automate - la connexion a échoué.\n"
-#: ../ProjectController.py:716
+#: ../ProjectController.py:715
msgid "Cannot open/parse VARIABLES.csv!\n"
msgstr "Impossible d'ouvrir ou d'analyser le fichier VARIABLES.csv !\n"
@@ -202,6 +827,15 @@
msgid "Cannot set bit offset for non bool '%s' variable (ID:%d,Idx:%x,sIdx:%x))"
msgstr "Impossible de définir un numéro de bit sur la variable '%s' non booléenne (ID:%d,Idx:%x,sIdx:%x)"
+#: ../dialogs/FindInPouDialog.py:81
+#: ../dialogs/SearchInProjectDialog.py:67
+msgid "Case sensitive"
+msgstr "Respecter la casse"
+
+#: ../editors/Viewer.py:429
+msgid "Center"
+msgstr "Centre"
+
#: ../Beremiz_service.py:322
msgid "Change IP of interface to bind"
msgstr "Changer l'adresse IP de l'interface à lier"
@@ -210,6 +844,10 @@
msgid "Change Name"
msgstr "Changer le nom"
+#: ../IDEFrame.py:1974
+msgid "Change POU Type To"
+msgstr "Changer le type du POU pour"
+
#: ../Beremiz_service.py:325
msgid "Change Port Number"
msgstr "Changer le numéro de port"
@@ -218,24 +856,31 @@
msgid "Change working directory"
msgstr "Changer le dossier de travail"
+#: ../plcopen/iec_std.csv:81
+msgid "Character string"
+msgstr "Chaîne de caractères"
+
#: ../svgui/svgui.py:92
msgid "Choose a SVG file"
msgstr "Choisissez un fichier SVG"
-#: ../ProjectController.py:354
+#: ../ProjectController.py:353
msgid "Choose a directory to save project"
msgstr "Choisissez un dossier où enregistrer le projet"
#: ../canfestival/canfestival.py:118
+#: ../PLCOpenEditor.py:313
+#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:391
msgid "Choose a file"
msgstr "Choisissez un fichier"
-#: ../Beremiz.py:829
-#: ../Beremiz.py:864
+#: ../Beremiz.py:831
+#: ../Beremiz.py:866
msgid "Choose a project"
msgstr "Choisissez un projet"
-#: ../util/BrowseValuesLibraryDialog.py:42
+#: ../dialogs/BrowseValuesLibraryDialog.py:42
#, python-format
msgid "Choose a value for %s:"
msgstr "Choisissez une valeur pour le paramètre %s :"
@@ -244,51 +889,110 @@
msgid "Choose a working directory "
msgstr "Choisissez un dossier de travail"
-#: ../ProjectController.py:282
+#: ../ProjectController.py:281
msgid "Chosen folder doesn't contain a program. It's not a valid project!"
msgstr "Le répertoire ne contient pas de programme. Ce n'est pas un projet valide !"
-#: ../ProjectController.py:248
+#: ../ProjectController.py:247
msgid "Chosen folder isn't empty. You can't use it for a new project!"
msgstr "Le répertoire n'est pas vide. Vous ne pouvez pas l'utiliser pour créer un nouveau projet !"
-#: ../ProjectController.py:1489
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Class"
+msgstr "Classe"
+
+#: ../controls/VariablePanel.py:369
+msgid "Class Filter:"
+msgstr "Filtre de classe :"
+
+#: ../dialogs/FBDVariableDialog.py:62
+msgid "Class:"
+msgstr "Classe :"
+
+#: ../ProjectController.py:1488
msgid "Clean"
msgstr "Nettoyer"
-#: ../ProjectController.py:1491
+#: ../ProjectController.py:1490
msgid "Clean project build folder"
msgstr "Nettoyer le répertoire de compilation"
-#: ../ProjectController.py:1049
+#: ../ProjectController.py:1048
msgid "Cleaning the build directory\n"
msgstr "Répertoire de compilation en cours de nettoyage\n"
-#: ../Beremiz.py:596
+#: ../IDEFrame.py:411
+msgid "Clear Errors"
+msgstr "Effacer les erreurs"
+
+#: ../editors/Viewer.py:520
+msgid "Clear Execution Order"
+msgstr "Effacer l'ordre d'exécution"
+
+#: ../editors/GraphicViewer.py:125
+msgid "Clear the graph values"
+msgstr "Vider les valeurs du graphique"
+
+#: ../Beremiz.py:598
+#: ../PLCOpenEditor.py:221
msgid "Close Application"
msgstr "Fermer l'application"
-#: ../Beremiz.py:550
+#: ../IDEFrame.py:1089
+#: ../Beremiz.py:319
+#: ../Beremiz.py:552
+#: ../PLCOpenEditor.py:131
msgid "Close Project"
msgstr "Fermer le projet"
#: ../Beremiz.py:317
-msgid "Close Project\tCTRL+SHIFT+W"
-msgstr "Fermer le project\tCTRL+SHIFT+W"
-
-#: ../Beremiz.py:315
-msgid "Close Tab\tCTRL+W"
-msgstr "Fermer l'onglet\tCTRL+W"
-
-#: ../ProjectController.py:539
+#: ../PLCOpenEditor.py:129
+msgid "Close Tab"
+msgstr "Fermer l'onglet"
+
+#: ../editors/Viewer.py:481
+msgid "Coil"
+msgstr "Relai"
+
+#: ../editors/Viewer.py:501
+#: ../editors/LDViewer.py:503
+msgid "Comment"
+msgstr "Commentaire"
+
+#: ../controls/ProjectPropertiesPanel.py:94
+msgid "Company Name (required):"
+msgstr "Nom de l'entreprise (obligatoire) :"
+
+#: ../controls/ProjectPropertiesPanel.py:95
+msgid "Company URL (optional):"
+msgstr "URL de l'entreprise (optionel) :"
+
+#: ../plcopen/iec_std.csv:75
+msgid "Comparison"
+msgstr "Comparaison"
+
+#: ../ProjectController.py:538
msgid "Compiling IEC Program into C code...\n"
msgstr "Compilation du program en IEC vers du code C en cours...\n"
-#: ../ProjectController.py:1504
+#: ../plcopen/iec_std.csv:85
+msgid "Concatenation"
+msgstr "Concaténation"
+
+#: ../dialogs/SearchInProjectDialog.py:47
+msgid "Configuration"
+msgstr "Configuration"
+
+#: ../PLCControler.py:96
+msgid "Configurations"
+msgstr "Configurations"
+
+#: ../ProjectController.py:1503
msgid "Connect"
msgstr "Connecter"
-#: ../ProjectController.py:1505
+#: ../ProjectController.py:1504
msgid "Connect to the target PLC"
msgstr "Connecter à l'automate cible"
@@ -297,11 +1001,20 @@
msgid "Connecting to URI : %s\n"
msgstr "Connection à l'URI %s en cours...\n"
-#: ../ProjectController.py:1360
+#: ../editors/Viewer.py:467
+#: ../dialogs/SFCTransitionDialog.py:76
+msgid "Connection"
+msgstr "Connexion"
+
+#: ../dialogs/ConnectionDialog.py:37
+msgid "Connection Properties"
+msgstr "Propriétés de la connexion"
+
+#: ../ProjectController.py:1359
msgid "Connection canceled!\n"
msgstr "La connection a été abandonnée !\n"
-#: ../ProjectController.py:1385
+#: ../ProjectController.py:1384
#, python-format
msgid "Connection failed to %s!\n"
msgstr "La connection à \"%s\" a échouée !\n"
@@ -311,14 +1024,68 @@
msgid "Connection to '%s' failed.\n"
msgstr "La connexion à l'adresse '%s' a échouée.\n"
-#: ../util/FileManagementPanel.py:283
+#: ../dialogs/ConnectionDialog.py:56
+msgid "Connector"
+msgstr "Connecteur"
+
+#: ../dialogs/SFCStepDialog.py:58
+msgid "Connectors:"
+msgstr "Connecteurs :"
+
+#: ../controls/VariablePanel.py:65
+msgid "Constant"
+msgstr "Constante"
+
+#: ../editors/Viewer.py:477
+msgid "Contact"
+msgstr "Contact"
+
+#: ../controls/ProjectPropertiesPanel.py:197
+msgid "Content Description (optional):"
+msgstr "Description du contenu (optionel) :"
+
+#: ../dialogs/ConnectionDialog.py:61
+msgid "Continuation"
+msgstr "Prolongement"
+
+#: ../plcopen/iec_std.csv:18
+msgid "Conversion from BCD"
+msgstr "Conversion d'un BCD"
+
+#: ../plcopen/iec_std.csv:19
+msgid "Conversion to BCD"
+msgstr "Conversion en BCD"
+
+#: ../plcopen/iec_std.csv:21
+msgid "Conversion to date"
+msgstr "Conversion en date"
+
+#: ../plcopen/iec_std.csv:20
+msgid "Conversion to time-of-day"
+msgstr "Conversion en heure de la journée"
+
+#: ../IDEFrame.py:348
+#: ../IDEFrame.py:401
+#: ../editors/Viewer.py:536
+msgid "Copy"
+msgstr "Copier"
+
+#: ../IDEFrame.py:1961
+msgid "Copy POU"
+msgstr "Copier ce POU"
+
+#: ../editors/FileManagementPanel.py:283
msgid "Copy file from left folder to right"
msgstr "Copier un fichier du dossier de gauche vers celui de droite"
-#: ../util/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:282
msgid "Copy file from right folder to left"
msgstr "Copier un fichier du dossier de droite vers celui de gauche"
+#: ../plcopen/iec_std.csv:28
+msgid "Cosine"
+msgstr "Cosinus"
+
#: ../ConfigTreeNode.py:582
#, python-format
msgid ""
@@ -346,15 +1113,20 @@
"Impossible de charger les paramètres du plugin %s :\n"
" %s"
-#: ../ProjectController.py:1318
+#: ../PLCControler.py:765
+#: ../PLCControler.py:802
+msgid "Couldn't paste non-POU object."
+msgstr "Impossible de coller autre chose qu'un POU."
+
+#: ../ProjectController.py:1317
msgid "Couldn't start PLC !\n"
msgstr "Impossible de démarrer l'automate !\n"
-#: ../ProjectController.py:1326
+#: ../ProjectController.py:1325
msgid "Couldn't stop PLC !\n"
msgstr "Impossible d'arrêter l'automate !\n"
-#: ../ProjectController.py:1296
+#: ../ProjectController.py:1295
msgid "Couldn't stop debugger.\n"
msgstr "Impossible d'arrêter le débogage de l'automate !\n"
@@ -362,6 +1134,111 @@
msgid "Create HMI"
msgstr "Créer une IHM"
+#: ../dialogs/PouDialog.py:43
+msgid "Create a new POU"
+msgstr "Créer un nouveau POU"
+
+#: ../dialogs/PouActionDialog.py:38
+msgid "Create a new action"
+msgstr "Créer une nouvelle action"
+
+#: ../IDEFrame.py:135
+msgid "Create a new action block"
+msgstr "Créer un nouveau bloc d'actions"
+
+#: ../IDEFrame.py:84
+#: ../IDEFrame.py:114
+#: ../IDEFrame.py:147
+msgid "Create a new block"
+msgstr "Créer un nouveau bloc"
+
+#: ../IDEFrame.py:108
+msgid "Create a new branch"
+msgstr "Créer une nouvelle branche"
+
+#: ../IDEFrame.py:102
+msgid "Create a new coil"
+msgstr "Créer un nouveau relai"
+
+#: ../IDEFrame.py:78
+#: ../IDEFrame.py:93
+#: ../IDEFrame.py:123
+msgid "Create a new comment"
+msgstr "Créer un nouveau copmmentaire"
+
+#: ../IDEFrame.py:87
+#: ../IDEFrame.py:117
+#: ../IDEFrame.py:150
+msgid "Create a new connection"
+msgstr "Créer une nouvelle connexion"
+
+#: ../IDEFrame.py:105
+#: ../IDEFrame.py:156
+msgid "Create a new contact"
+msgstr "Créer un nouveau contact"
+
+#: ../IDEFrame.py:138
+msgid "Create a new divergence"
+msgstr "Créer une nouvelle divergence"
+
+#: ../dialogs/SFCDivergenceDialog.py:36
+msgid "Create a new divergence or convergence"
+msgstr "Créer une nouvelle divergence ou convergence"
+
+#: ../IDEFrame.py:126
+msgid "Create a new initial step"
+msgstr "Créer une nouvelle étape initiale"
+
+#: ../IDEFrame.py:141
+msgid "Create a new jump"
+msgstr "Créer un nouveau renvoi"
+
+#: ../IDEFrame.py:96
+#: ../IDEFrame.py:153
+msgid "Create a new power rail"
+msgstr "Créer une nouvelle barre d'alimentation"
+
+#: ../IDEFrame.py:99
+msgid "Create a new rung"
+msgstr "Créer un nouvel échelon"
+
+#: ../IDEFrame.py:129
+msgid "Create a new step"
+msgstr "Créer une nouvelle étape"
+
+#: ../IDEFrame.py:132
+#: ../dialogs/PouTransitionDialog.py:42
+msgid "Create a new transition"
+msgstr "Créer une nouvelle transition"
+
+#: ../IDEFrame.py:81
+#: ../IDEFrame.py:111
+#: ../IDEFrame.py:144
+msgid "Create a new variable"
+msgstr "Créer une nouvelle variable"
+
+#: ../IDEFrame.py:346
+#: ../IDEFrame.py:400
+#: ../editors/Viewer.py:535
+msgid "Cut"
+msgstr "Couper"
+
+#: ../editors/ResourceEditor.py:71
+msgid "Cyclic"
+msgstr "Périodique"
+
+#: ../plcopen/iec_std.csv:42
+#: ../plcopen/iec_std.csv:44
+#: ../plcopen/iec_std.csv:46
+#: ../plcopen/iec_std.csv:50
+#: ../plcopen/iec_std.csv:52
+#: ../plcopen/iec_std.csv:54
+#: ../plcopen/iec_std.csv:56
+#: ../plcopen/iec_std.csv:58
+#: ../plcopen/iec_std.csv:60
+msgid "DEPRECATED"
+msgstr "OBSOLETE"
+
#: ../canfestival/SlaveEditor.py:50
#: ../canfestival/NetworkEditor.py:80
msgid "DS-301 Profile"
@@ -372,73 +1249,262 @@
msgid "DS-302 Profile"
msgstr "Profil DS-302"
-#: ../ProjectController.py:1406
+#: ../dialogs/SearchInProjectDialog.py:43
+msgid "Data Type"
+msgstr "Type de donnée"
+
+#: ../PLCControler.py:95
+msgid "Data Types"
+msgstr "Types de données"
+
+#: ../plcopen/iec_std.csv:16
+msgid "Data type conversion"
+msgstr "Conversion entre types de donnée"
+
+#: ../plcopen/iec_std.csv:44
+#: ../plcopen/iec_std.csv:45
+msgid "Date addition"
+msgstr "Addition de dates"
+
+#: ../plcopen/iec_std.csv:56
+#: ../plcopen/iec_std.csv:57
+#: ../plcopen/iec_std.csv:58
+#: ../plcopen/iec_std.csv:59
+msgid "Date and time subtraction"
+msgstr "Soustraction entre horodatage"
+
+#: ../plcopen/iec_std.csv:50
+#: ../plcopen/iec_std.csv:51
+msgid "Date subtraction"
+msgstr "Soustraction de date"
+
+#: ../dialogs/DurationEditorDialog.py:43
+msgid "Days:"
+msgstr "Jours :"
+
+#: ../ProjectController.py:1405
msgid "Debug connect matching running PLC\n"
msgstr "L'automate connecté correspond au project ouvert.\n"
-#: ../ProjectController.py:1409
+#: ../ProjectController.py:1408
msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
msgstr "L'automate connecté ne correspond pas au project ouvert - Arrêter/transférez/démarrer pour pouvoir débogguer.\n"
-#: ../ProjectController.py:1123
+#: ../controls/PouInstanceVariablesPanel.py:52
+msgid "Debug instance"
+msgstr "Déboguer l'instance"
+
+#: ../editors/Viewer.py:3222
+#, python-format
+msgid "Debug: %s"
+msgstr "Déboggage : %s"
+
+#: ../ProjectController.py:1122
#, python-format
msgid "Debug: Unknown variable '%s'\n"
msgstr "Débogage : Variable '%s' inconnue\n"
-#: ../ProjectController.py:1121
+#: ../ProjectController.py:1120
#, python-format
msgid "Debug: Unsupported type to debug '%s'\n"
msgstr "Débogage : Type non supporté dans le débogage '%'\n"
-#: ../ProjectController.py:1286
+#: ../IDEFrame.py:608
+msgid "Debugger"
+msgstr "Déboggueur"
+
+#: ../ProjectController.py:1285
msgid "Debugger disabled\n"
msgstr "Débogueur désactivé\n"
-#: ../ProjectController.py:1298
+#: ../ProjectController.py:1297
msgid "Debugger stopped.\n"
msgstr "Débogueur désactivé\n"
-#: ../Beremiz.py:956
+#: ../IDEFrame.py:1990
+#: ../Beremiz.py:958
+#: ../editors/Viewer.py:511
msgid "Delete"
msgstr "Supprimer"
-#: ../util/FileManagementPanel.py:371
+#: ../editors/Viewer.py:454
+msgid "Delete Divergence Branch"
+msgstr "Supprimer une branche de divergence"
+
+#: ../editors/FileManagementPanel.py:371
msgid "Delete File"
msgstr "Supprimer un fichier"
-#: ../ProjectController.py:1513
+#: ../editors/Viewer.py:443
+msgid "Delete Wire Segment"
+msgstr "Supprimer un segment de fil"
+
+#: ../controls/CustomEditableListBox.py:41
+msgid "Delete item"
+msgstr "Supprimer un élément"
+
+#: ../plcopen/iec_std.csv:88
+msgid "Deletion (within)"
+msgstr "Suppression (au milieu)"
+
+#: ../editors/DataTypeEditor.py:146
+msgid "Derivation Type:"
+msgstr "Type de dérivation :"
+
+#: ../plcopen/structures.py:264
+msgid ""
+"Derivative\n"
+"The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
+msgstr ""
+"Dérivée\n"
+"Le Function Block derivative produit une sortie XOUT proportionnelle au rapport de changement de l'entrée XIN."
+
+#: ../controls/VariablePanel.py:360
+msgid "Description:"
+msgstr "Description :"
+
+#: ../editors/DataTypeEditor.py:314
+#: ../dialogs/ArrayTypeDialog.py:61
+msgid "Dimensions:"
+msgstr "Dimensions :"
+
+#: ../dialogs/FindInPouDialog.py:61
+msgid "Direction"
+msgstr "Direction"
+
+#: ../dialogs/BrowseLocationsDialog.py:78
+msgid "Direction:"
+msgstr "Direction :"
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Directly"
+msgstr "Direct"
+
+#: ../ProjectController.py:1512
msgid "Disconnect"
msgstr "Déconnecter"
-#: ../ProjectController.py:1515
+#: ../ProjectController.py:1514
msgid "Disconnect from PLC"
msgstr "Déconnecter l'automate"
-#: ../util/FileManagementPanel.py:370
+#: ../editors/Viewer.py:496
+msgid "Divergence"
+msgstr "Divergence"
+
+#: ../plcopen/iec_std.csv:36
+msgid "Division"
+msgstr "Division"
+
+#: ../editors/FileManagementPanel.py:370
#, python-format
msgid "Do you really want to delete the file '%s'?"
msgstr "Êtes-vous sûr de vouloir supprimer le fichier '%s' ?"
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Documentation"
+msgstr "Documentation"
+
+#: ../PLCOpenEditor.py:351
+msgid "Done"
+msgstr "Terminé"
+
+#: ../plcopen/structures.py:227
+msgid ""
+"Down-counter\n"
+"The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
+msgstr ""
+"Compteur décrémental\n"
+"Le compteur décrémental peut être utilisé pour signaler lorsque le compteur atteint zéro en partant d'une valeur prédéfinie."
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Duration"
+msgstr "Durée"
+
#: ../canfestival/canfestival.py:118
msgid "EDS files (*.eds)|*.eds|All files|*.*"
msgstr "Fichiers EDS (*.eds)|*.eds|Tous les fichiers|*.*"
+#: ../editors/Viewer.py:510
+msgid "Edit Block"
+msgstr "Editer le block"
+
+#: ../dialogs/LDElementDialog.py:41
+msgid "Edit Coil Values"
+msgstr "Editer les valeurs du relai"
+
+#: ../dialogs/LDElementDialog.py:38
+msgid "Edit Contact Values"
+msgstr "Editer les valeurs du contact"
+
+#: ../dialogs/DurationEditorDialog.py:59
+msgid "Edit Duration"
+msgstr "Editer une durée"
+
+#: ../dialogs/SFCStepDialog.py:35
+msgid "Edit Step"
+msgstr "Editer l'étape"
+
#: ../wxglade_hmi/wxglade_hmi.py:12
msgid "Edit a WxWidgets GUI with WXGlade"
msgstr "Editer une IHM WxWidgets à l'aide de WXGlade"
-#: ../util/FileManagementPanel.py:284
+#: ../dialogs/ActionBlockDialog.py:122
+msgid "Edit action block properties"
+msgstr "Editer les propriétés du block d'actions"
+
+#: ../dialogs/ArrayTypeDialog.py:45
+msgid "Edit array type properties"
+msgstr "Editer les propriétés d'un type de données tableau"
+
+#: ../editors/Viewer.py:2112
+#: ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630
+#: ../editors/Viewer.py:2632
+msgid "Edit comment"
+msgstr "Editer le commentaire"
+
+#: ../editors/FileManagementPanel.py:284
msgid "Edit file"
msgstr "Editer un fichier"
-#: ../ProjectController.py:1527
+#: ../controls/CustomEditableListBox.py:39
+msgid "Edit item"
+msgstr "Editer l'élément"
+
+#: ../editors/Viewer.py:2594
+msgid "Edit jump target"
+msgstr "Editer la cible du renvoi"
+
+#: ../ProjectController.py:1526
msgid "Edit raw IEC code added to code generated by PLCGenerator"
msgstr "Editer le code IEC ajouté au code généré par PLCGenerator"
-#: ../ProjectController.py:1014
+#: ../editors/SFCViewer.py:725
+msgid "Edit step name"
+msgstr "Editer le nom de l'étape"
+
+#: ../dialogs/SFCTransitionDialog.py:38
+msgid "Edit transition"
+msgstr "Editer la transition"
+
+#: ../IDEFrame.py:580
+msgid "Editor ToolBar"
+msgstr "Barre d'outils d'édition"
+
+#: ../ProjectController.py:1013
msgid "Editor selection"
msgstr "Selection d'un éditeur"
+#: ../editors/DataTypeEditor.py:341
+msgid "Elements :"
+msgstr "Eléments :"
+
+#: ../IDEFrame.py:343
+msgid "Enable Undo/Redo"
+msgstr "Activer Défaire/Refaire"
+
#: ../Beremiz_service.py:380
msgid "Enter a name "
msgstr "Saisissez un nom"
@@ -451,25 +1517,77 @@
msgid "Enter the IP of the interface to bind"
msgstr "Saisissez l'adresse IP de l'interface à lier"
+#: ../editors/DataTypeEditor.py:52
+msgid "Enumerated"
+msgstr "Enumération"
+
+#: ../plcopen/iec_std.csv:77
+msgid "Equal to"
+msgstr "Egal à"
+
#: ../Beremiz_service.py:270
#: ../Beremiz_service.py:394
-#: ../Beremiz.py:1081
-#: ../ProjectController.py:222
-#: ../util/BrowseValuesLibraryDialog.py:83
-#: ../util/FileManagementPanel.py:210
+#: ../controls/VariablePanel.py:330
+#: ../controls/VariablePanel.py:678
+#: ../controls/DebugVariablePanel.py:164
+#: ../IDEFrame.py:1083
+#: ../IDEFrame.py:1672
+#: ../IDEFrame.py:1709
+#: ../IDEFrame.py:1714
+#: ../IDEFrame.py:1728
+#: ../IDEFrame.py:1733
+#: ../IDEFrame.py:2422
+#: ../Beremiz.py:1083
+#: ../PLCOpenEditor.py:358
+#: ../PLCOpenEditor.py:363
+#: ../PLCOpenEditor.py:531
+#: ../PLCOpenEditor.py:541
+#: ../editors/TextViewer.py:376
+#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:548
+#: ../editors/DataTypeEditor.py:572
+#: ../editors/DataTypeEditor.py:577
+#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:719
+#: ../editors/DataTypeEditor.py:726
+#: ../editors/Viewer.py:366
+#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:883
+#: ../editors/FileManagementPanel.py:210
+#: ../ProjectController.py:221
+#: ../dialogs/PouNameDialog.py:53
+#: ../dialogs/PouTransitionDialog.py:107
+#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/ProjectDialog.py:71
+#: ../dialogs/SFCStepNameDialog.py:59
+#: ../dialogs/ConnectionDialog.py:152
+#: ../dialogs/FBDVariableDialog.py:201
+#: ../dialogs/PouActionDialog.py:104
+#: ../dialogs/BrowseValuesLibraryDialog.py:83
+#: ../dialogs/PouDialog.py:132
+#: ../dialogs/SFCTransitionDialog.py:147
+#: ../dialogs/DurationEditorDialog.py:121
+#: ../dialogs/DurationEditorDialog.py:163
+#: ../dialogs/SearchInProjectDialog.py:157
+#: ../dialogs/SFCStepDialog.py:130
+#: ../dialogs/ArrayTypeDialog.py:97
+#: ../dialogs/ArrayTypeDialog.py:103
+#: ../dialogs/FBDBlockDialog.py:164
+#: ../dialogs/ForceVariableDialog.py:169
msgid "Error"
msgstr "Erreur"
-#: ../ProjectController.py:588
+#: ../ProjectController.py:587
msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
msgstr "Erreur : Au moins une configuration ou une ressource doit être déclarée dans l'automate !\n"
-#: ../ProjectController.py:580
+#: ../ProjectController.py:579
#, python-format
msgid "Error : IEC to C compiler returned %d\n"
msgstr "Erreur : Le compilateur d'IEC en C a retourné %d\n"
-#: ../ProjectController.py:521
+#: ../ProjectController.py:520
#, python-format
msgid ""
"Error in ST/IL/SFC code generator :\n"
@@ -495,56 +1613,264 @@
msgid "Error: No PLC built\n"
msgstr "Erreur : Aucun automate compilé\n"
-#: ../ProjectController.py:1379
+#: ../ProjectController.py:1378
#, python-format
msgid "Exception while connecting %s!\n"
msgstr "Une exception est apparu au cours de la connexion %s !\n"
+#: ../dialogs/FBDBlockDialog.py:95
+msgid "Execution Control:"
+msgstr "Contrôle d'exécution :"
+
+#: ../dialogs/FBDVariableDialog.py:76
+#: ../dialogs/FBDBlockDialog.py:87
+msgid "Execution Order:"
+msgstr "Ordre d'exécution :"
+
#: ../features.py:10
msgid "Experimental web based HMI"
msgstr "IHM expérimentale utilisant les technologies web"
+#: ../plcopen/iec_std.csv:38
+msgid "Exponent"
+msgstr "Exposant"
+
+#: ../plcopen/iec_std.csv:26
+msgid "Exponentiation"
+msgstr "Exponentiel"
+
#: ../canfestival/canfestival.py:128
msgid "Export CanOpen slave to EDS file"
msgstr "Exporter un esclave CANopen sous la forme d'un fichier EDS"
+#: ../editors/GraphicViewer.py:144
+msgid "Export graph values to clipboard"
+msgstr "Exporter les valeurs du graphique vers le presse-papier"
+
#: ../canfestival/canfestival.py:127
msgid "Export slave"
msgstr "Exporter un esclave"
-#: ../ProjectController.py:592
+#: ../dialogs/FBDVariableDialog.py:69
+msgid "Expression:"
+msgstr "Expression :"
+
+#: ../controls/VariablePanel.py:77
+msgid "External"
+msgstr "Externe"
+
+#: ../ProjectController.py:591
msgid "Extracting Located Variables...\n"
msgstr "Extraction des variables adressées en cours...\n"
-#: ../ProjectController.py:1446
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "FBD"
+msgstr "FBD"
+
+#: ../ProjectController.py:1445
msgid "Failed : Must build before transfer.\n"
msgstr "Echec : Le projet doit être compilé avant d'être transféré.\n"
-#: ../ProjectController.py:901
+#: ../editors/Viewer.py:405
+#: ../dialogs/LDElementDialog.py:84
+msgid "Falling Edge"
+msgstr "Front descendant"
+
+#: ../plcopen/structures.py:217
+msgid ""
+"Falling edge detector\n"
+"The output produces a single pulse when a falling edge is detected."
+msgstr ""
+"Détecteur de front descendant\n"
+"La sortie produit une impulsion unique lorsqu'un front descendant est détecté."
+
+#: ../ProjectController.py:900
msgid "Fatal : cannot get builder.\n"
msgstr "Erreur fatale : impossible de trouver un compilateur.\n"
-#: ../util/FileManagementPanel.py:209
+#: ../dialogs/DurationEditorDialog.py:160
+#, python-format
+msgid "Field %s hasn't a valid value!"
+msgstr "Le champ %s n'a pas une valeur valide !"
+
+#: ../dialogs/DurationEditorDialog.py:162
+#, python-format
+msgid "Fields %s haven't a valid value!"
+msgstr "Les champs %s n'ont pas une valeur valide !"
+
+#: ../editors/FileManagementPanel.py:209
#, python-format
msgid "File '%s' already exists!"
msgstr "Le fichier '%s' existe déjà !"
+#: ../IDEFrame.py:353
+#: ../dialogs/FindInPouDialog.py:30
+#: ../dialogs/FindInPouDialog.py:99
+msgid "Find"
+msgstr "Rechercher"
+
+#: ../IDEFrame.py:355
+msgid "Find Next"
+msgstr "Recherche suivante"
+
+#: ../IDEFrame.py:357
+msgid "Find Previous"
+msgstr "Recherche précédente"
+
+#: ../plcopen/iec_std.csv:90
+msgid "Find position"
+msgstr "Trouver la position"
+
+#: ../dialogs/FindInPouDialog.py:51
+msgid "Find:"
+msgstr "Rechercher :"
+
#: ../connectors/PYRO/__init__.py:125
msgid "Force runtime reload\n"
msgstr "Redémarrage du runtime forcé\n"
-#: ../ProjectController.py:511
+#: ../controls/DebugVariablePanel.py:295
+#: ../editors/Viewer.py:1353
+msgid "Force value"
+msgstr "Forcer la valeur"
+
+#: ../dialogs/ForceVariableDialog.py:152
+msgid "Forcing Variable Value"
+msgstr "Forcer la valeur de la variable"
+
+#: ../dialogs/PouTransitionDialog.py:97
+#: ../dialogs/ProjectDialog.py:70
+#: ../dialogs/PouActionDialog.py:94
+#: ../dialogs/PouDialog.py:114
+#: ../dialogs/SFCTransitionDialog.py:147
+#, python-format
+msgid "Form isn't complete. %s must be filled!"
+msgstr "Le formulaire est incomplet. %s doit être complété !"
+
+#: ../dialogs/ConnectionDialog.py:142
+#: ../dialogs/FBDBlockDialog.py:154
+msgid "Form isn't complete. Name must be filled!"
+msgstr "Le formulaire est incomplet. Le nom doit être complété !"
+
+#: ../dialogs/SearchInProjectDialog.py:145
+msgid "Form isn't complete. Pattern to search must be filled!"
+msgstr "Le formulaire est incomplet. Le modèle à chercher doit être complété !"
+
+#: ../dialogs/FBDBlockDialog.py:152
+msgid "Form isn't complete. Valid block type must be selected!"
+msgstr "Le formulaire est incomplet. Un type de bloc valide doit être sélectionné !"
+
+#: ../dialogs/FindInPouDialog.py:67
+msgid "Forward"
+msgstr "Vers le bas"
+
+#: ../dialogs/SearchInProjectDialog.py:44
+msgid "Function"
+msgstr "Fonction"
+
+#: ../IDEFrame.py:329
+msgid "Function &Block"
+msgstr "&Bloc Fonctionnel"
+
+#: ../IDEFrame.py:1969
+#: ../dialogs/SearchInProjectDialog.py:45
+msgid "Function Block"
+msgstr "Bloc fonctionnel"
+
+#: ../controls/VariablePanel.py:741
+msgid "Function Block Types"
+msgstr "Types de blocs fonctionnels"
+
+#: ../PLCControler.py:94
+msgid "Function Blocks"
+msgstr "Blocs fonctionnels"
+
+#: ../editors/Viewer.py:236
+msgid "Function Blocks can't be used in Functions!"
+msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des functions !"
+
+#: ../editors/Viewer.py:238
+msgid "Function Blocks can't be used in Transitions!"
+msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des transitions"
+
+#: ../PLCControler.py:2055
+#, python-format
+msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
+msgstr "Le bloc fonctionnel \"%s\" ne peuvent être collés dans une function !"
+
+#: ../PLCControler.py:94
+msgid "Functions"
+msgstr "Fonctions"
+
+#: ../PLCOpenEditor.py:138
+msgid "Generate Program"
+msgstr "Générer le program"
+
+#: ../ProjectController.py:510
msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
msgstr "Création du code ST/IL/SFC de l'automate IEC-61131 en cours...\n"
-#: ../util/FileManagementPanel.py:303
+#: ../controls/VariablePanel.py:78
+msgid "Global"
+msgstr "Globale"
+
+#: ../editors/GraphicViewer.py:131
+msgid "Go to current value"
+msgstr "Aller à la valeur actuelle"
+
+#: ../controls/ProjectPropertiesPanel.py:173
+msgid "Graphics"
+msgstr "Graphiques"
+
+#: ../plcopen/iec_std.csv:75
+msgid "Greater than"
+msgstr "Supérieur à"
+
+#: ../plcopen/iec_std.csv:76
+msgid "Greater than or equal to"
+msgstr "Supérieur ou égal à"
+
+#: ../controls/ProjectPropertiesPanel.py:134
+msgid "Grid Resolution:"
+msgstr "Résolution de la grille :"
+
+#: ../controls/ProjectPropertiesPanel.py:120
+msgid "Height:"
+msgstr "Hauteur :"
+
+#: ../editors/FileManagementPanel.py:303
msgid "Home Directory:"
msgstr "Répertoire utilisateur :"
-#: ../ProjectController.py:828
+#: ../controls/ProjectPropertiesPanel.py:150
+msgid "Horizontal:"
+msgstr "Horizontal :"
+
+#: ../dialogs/DurationEditorDialog.py:44
+msgid "Hours:"
+msgstr "Heures :"
+
+#: ../plcopen/structures.py:279
+msgid ""
+"Hysteresis\n"
+"The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
+msgstr ""
+"Hystérésis\n"
+"Le bloc functionnel hystérésis fourni un booléen en sortie suivant une courbe d'hystérésis entre les deux entrées réelles (REAL) XIN1 et XIN2."
+
+#: ../ProjectController.py:827
msgid "IEC-61131-3 code generation failed !\n"
msgstr "La création du code IEC-61131-3 a échouée !\n"
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "IL"
+msgstr "IL"
+
#: ../Beremiz_service.py:356
#: ../Beremiz_service.py:357
msgid "IP is not valid!"
@@ -555,17 +1881,177 @@
msgid "Import SVG"
msgstr "Importer un SVG"
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/FBDVariableDialog.py:34
+msgid "InOut"
+msgstr "Entrée-Sortie"
+
+#: ../controls/VariablePanel.py:263
+#, python-format
+msgid "Incompatible data types between \"%s\" and \"%s\""
+msgstr "Types de donnée imcompatible entre \"%s\" et \"%s\""
+
+#: ../controls/VariablePanel.py:274
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"%s\""
+msgstr "Taille de donnée incompatible entre \"%s\" et \"%s\""
+
+#: ../controls/VariablePanel.py:270
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"BOOL\""
+msgstr "Taille de donnée incompatible entre \"%s\" et \"BOOL\""
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Indicator"
+msgstr "Indicateur"
+
+#: ../editors/Viewer.py:492
+msgid "Initial Step"
+msgstr "Étape initiale"
+
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+msgid "Initial Value"
+msgstr "Valeur initiale"
+
+#: ../editors/DataTypeEditor.py:178
+#: ../editors/DataTypeEditor.py:209
+#: ../editors/DataTypeEditor.py:265
+#: ../editors/DataTypeEditor.py:303
+msgid "Initial Value:"
+msgstr "Valeur initiale :"
+
#: ../svgui/svgui.py:21
msgid "Inkscape"
msgstr "Inkscape"
+#: ../dialogs/ActionBlockDialog.py:41
+#: ../dialogs/SFCTransitionDialog.py:66
+#: ../dialogs/SFCTransitionDialog.py:137
+msgid "Inline"
+msgstr "Inline"
+
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/BrowseLocationsDialog.py:36
+#: ../dialogs/FBDVariableDialog.py:33
+#: ../dialogs/SFCStepDialog.py:61
+msgid "Input"
+msgstr "Entrée"
+
+#: ../dialogs/FBDBlockDialog.py:78
+msgid "Inputs:"
+msgstr "Entrées :"
+
+#: ../plcopen/iec_std.csv:87
+msgid "Insertion (into)"
+msgstr "Insertion (au milieu)"
+
+#: ../plcopen/plcopen.py:1833
+#, python-format
+msgid "Instance with id %d doesn't exist!"
+msgstr "L'instance dont l'id est %d n'existe pas !"
+
+#: ../editors/ResourceEditor.py:247
+msgid "Instances:"
+msgstr "Instances :"
+
+#: ../plcopen/structures.py:259
+msgid ""
+"Integral\n"
+"The integral function block integrates the value of input XIN over time."
+msgstr ""
+"Intégrale\n"
+"Le bloc fonctionnel INTEGRAL intègre les valeurs de l'entrée XIN en fonction du temps."
+
+#: ../controls/VariablePanel.py:75
+msgid "Interface"
+msgstr "Interface"
+
+#: ../editors/ResourceEditor.py:71
+msgid "Interrupt"
+msgstr "Interruption"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Interval"
+msgstr "Interval"
+
+#: ../PLCControler.py:2032
+#: ../PLCControler.py:2070
+msgid "Invalid plcopen element(s)!!!"
+msgstr "Les éléments plcopen ne sont pas valides !!! "
+
#: ../canfestival/config_utils.py:376
#: ../canfestival/config_utils.py:637
#, python-format
msgid "Invalid type \"%s\"-> %d != %d for location\"%s\""
msgstr "Type invalide \"%s\"-> %d != %d pour cette adresse \"%s\""
-#: ../ProjectController.py:1452
+#: ../dialogs/ForceVariableDialog.py:167
+#, python-format
+msgid "Invalid value \"%s\" for \"%s\" variable!"
+msgstr "Valeur \"%s\" invalide pour une variable de type \"%s\" !"
+
+#: ../controls/DebugVariablePanel.py:153
+#: ../controls/DebugVariablePanel.py:156
+#, python-format
+msgid "Invalid value \"%s\" for debug variable"
+msgstr "Chemin de variable à déboguer \"%s\" invalide"
+
+#: ../controls/VariablePanel.py:244
+#: ../controls/VariablePanel.py:247
+#, python-format
+msgid "Invalid value \"%s\" for variable grid element"
+msgstr "Valeur \"%s\" invalide pour un élément de la grille de variables"
+
+#: ../editors/Viewer.py:221
+#: ../editors/Viewer.py:224
+#, python-format
+msgid "Invalid value \"%s\" for viewer block"
+msgstr "Valeur \"%s\" invalide pour un élément graphique"
+
+#: ../dialogs/DurationEditorDialog.py:121
+msgid ""
+"Invalid value!\n"
+"You must fill a numeric value."
+msgstr ""
+"Valeur invalide !\n"
+"Vous devez rentrer une valeur numérique."
+
+#: ../editors/Viewer.py:497
+msgid "Jump"
+msgstr "Renvoi"
+
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "LD"
+msgstr "LD"
+
+#: ../editors/LDViewer.py:215
+#: ../editors/LDViewer.py:231
+#, python-format
+msgid "Ladder element with id %d is on more than one rung."
+msgstr "L'élément de LD dont l'id est %d apparait dans plusieurs échelons. "
+
+#: ../dialogs/PouTransitionDialog.py:86
+#: ../dialogs/PouActionDialog.py:83
+#: ../dialogs/PouDialog.py:102
+msgid "Language"
+msgstr "Langue"
+
+#: ../controls/ProjectPropertiesPanel.py:186
+msgid "Language (optional):"
+msgstr "Langue (optionnel) :"
+
+#: ../dialogs/PouTransitionDialog.py:60
+#: ../dialogs/PouActionDialog.py:56
+#: ../dialogs/PouDialog.py:71
+msgid "Language:"
+msgstr "Langue :"
+
+#: ../ProjectController.py:1451
msgid "Latest build already matches current target. Transfering anyway...\n"
msgstr "La dernière compilation correspond à la cible actuelle...\n"
@@ -577,22 +2063,63 @@
msgid "Launch a live Python shell"
msgstr "Lancer une console Python"
+#: ../editors/Viewer.py:428
+msgid "Left"
+msgstr "Gauche"
+
+#: ../dialogs/LDPowerRailDialog.py:55
+msgid "Left PowerRail"
+msgstr "Barre d'alimentation à gauche"
+
+#: ../plcopen/iec_std.csv:81
+msgid "Length of string"
+msgstr "Longueur de la chaîne"
+
+#: ../plcopen/iec_std.csv:78
+msgid "Less than"
+msgstr "Inférieur à"
+
+#: ../plcopen/iec_std.csv:79
+msgid "Less than or equal to"
+msgstr "Inférieur ou égal à"
+
+#: ../IDEFrame.py:600
+msgid "Library"
+msgstr "Librairie"
+
+#: ../plcopen/iec_std.csv:73
+msgid "Limitation"
+msgstr "Limitation"
+
#: ../targets/toolchain_gcc.py:142
msgid "Linking :\n"
msgstr "Linkage :\n"
-#: ../util/discovery.py:110
+#: ../controls/VariablePanel.py:77
+#: ../dialogs/DiscoveryDialog.py:110
msgid "Local"
-msgstr "Local"
-
-#: ../ProjectController.py:1354
+msgstr "Locale"
+
+#: ../ProjectController.py:1353
msgid "Local service discovery failed!\n"
msgstr "Echec de la sélection d'un service!\n"
-#: ../Beremiz.py:391
+#: ../controls/VariablePanel.py:58
+msgid "Location"
+msgstr "Adresse"
+
+#: ../dialogs/BrowseLocationsDialog.py:61
+msgid "Locations available:"
+msgstr "Adresses disponibles :"
+
+#: ../Beremiz.py:393
msgid "Log Console"
msgstr "Console de log"
+#: ../plcopen/iec_std.csv:25
+msgid "Logarithm to base 10"
+msgstr "Logarithme de base 10"
+
#: ../connectors/PYRO/__init__.py:55
#, python-format
msgid "MDNS resolution failure for '%s'\n"
@@ -616,30 +2143,223 @@
msgid "Max count (%d) reached for this confnode of type %s "
msgstr "Nombre limite(%d) atteint pour les plugin de type %s"
-#: ../util/FileManagementPanel.py:301
+#: ../plcopen/iec_std.csv:71
+msgid "Maximum"
+msgstr "Maximum"
+
+#: ../editors/DataTypeEditor.py:232
+msgid "Maximum:"
+msgstr "Maximum :"
+
+#: ../dialogs/BrowseLocationsDialog.py:38
+msgid "Memory"
+msgstr "Mémoire"
+
+#: ../IDEFrame.py:568
+msgid "Menu ToolBar"
+msgstr "Barre d'outils du menu principal"
+
+#: ../dialogs/DurationEditorDialog.py:48
+msgid "Microseconds:"
+msgstr "Microsecondes :"
+
+#: ../editors/Viewer.py:433
+msgid "Middle"
+msgstr "Milieu"
+
+#: ../dialogs/DurationEditorDialog.py:47
+msgid "Milliseconds:"
+msgstr "Millisecondes :"
+
+#: ../plcopen/iec_std.csv:72
+msgid "Minimum"
+msgstr "Minimum"
+
+#: ../editors/DataTypeEditor.py:219
+msgid "Minimum:"
+msgstr "Minimum :"
+
+#: ../dialogs/DurationEditorDialog.py:45
+msgid "Minutes:"
+msgstr "Minutes :"
+
+#: ../controls/ProjectPropertiesPanel.py:210
+msgid "Miscellaneous"
+msgstr "Divers"
+
+#: ../dialogs/LDElementDialog.py:59
+msgid "Modifier:"
+msgstr "Modificateur :"
+
+#: ../PLCGenerator.py:703
+#: ../PLCGenerator.py:936
+#, python-format
+msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr "Plusieurs connecteurs trouvés pour le prolongement \"%s\" dans le POU \"%s\""
+
+#: ../dialogs/ActionBlockDialog.py:141
+msgid "Move action down"
+msgstr "Déplacer une action vers le bas"
+
+#: ../dialogs/ActionBlockDialog.py:140
+msgid "Move action up"
+msgstr "Déplacer une action vers le haut"
+
+#: ../controls/DebugVariablePanel.py:185
+msgid "Move debug variable down"
+msgstr "Déplacer une variable à déboguer vers le bas"
+
+#: ../controls/DebugVariablePanel.py:184
+msgid "Move debug variable up"
+msgstr "Déplacer une variable à déboguer vers le haut"
+
+#: ../controls/CustomEditableListBox.py:43
+msgid "Move down"
+msgstr "Déplacer vers le haut"
+
+#: ../editors/DataTypeEditor.py:348
+msgid "Move element down"
+msgstr "Déplcer un élément vers le bas"
+
+#: ../editors/DataTypeEditor.py:347
+msgid "Move element up"
+msgstr "Déplacer un élément vers le haut"
+
+#: ../editors/ResourceEditor.py:254
+msgid "Move instance down"
+msgstr "Déplacer une instance vers le bas"
+
+#: ../editors/ResourceEditor.py:253
+msgid "Move instance up"
+msgstr "Déplacer une instance vers le haut"
+
+#: ../editors/ResourceEditor.py:225
+msgid "Move task down"
+msgstr "Déplcer une tâche vers le bas"
+
+#: ../editors/ResourceEditor.py:224
+msgid "Move task up"
+msgstr "Déplacer une tâche vers le haut"
+
+#: ../IDEFrame.py:75
+#: ../IDEFrame.py:90
+#: ../IDEFrame.py:120
+#: ../IDEFrame.py:161
+msgid "Move the view"
+msgstr "Déplacer la vue"
+
+#: ../controls/CustomEditableListBox.py:42
+msgid "Move up"
+msgstr "Déplacer vers le bas"
+
+#: ../controls/VariablePanel.py:381
+msgid "Move variable down"
+msgstr "Déplacer une variable vers le bas"
+
+#: ../controls/VariablePanel.py:380
+msgid "Move variable up"
+msgstr "Déplacer une variable vers le haut"
+
+#: ../plcopen/iec_std.csv:74
+msgid "Multiplexer (select 1 of N)"
+msgstr "Multipléxeur (sélection 1 sur N)"
+
+#: ../plcopen/iec_std.csv:34
+msgid "Multiplication"
+msgstr "Multiplication"
+
+#: ../editors/FileManagementPanel.py:301
msgid "My Computer:"
msgstr "Poste de travail :"
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:76
+msgid "Name"
+msgstr "Nom"
+
#: ../Beremiz_service.py:381
msgid "Name must not be null!"
msgstr "Le nom ne doit pas être vide !"
-#: ../Beremiz.py:340
+#: ../dialogs/ConnectionDialog.py:65
+#: ../dialogs/FBDVariableDialog.py:89
+#: ../dialogs/LDElementDialog.py:88
+#: ../dialogs/SFCStepDialog.py:51
+#: ../dialogs/FBDBlockDialog.py:70
+msgid "Name:"
+msgstr "Nom :"
+
+#: ../plcopen/iec_std.csv:24
+msgid "Natural logarithm"
+msgstr "Logarithme népérien"
+
+#: ../editors/Viewer.py:403
+#: ../dialogs/LDElementDialog.py:67
+msgid "Negated"
+msgstr "Inversé"
+
+#: ../Beremiz.py:307
+#: ../Beremiz.py:342
+#: ../PLCOpenEditor.py:125
+#: ../PLCOpenEditor.py:167
msgid "New"
msgstr "Nouveau"
-#: ../Beremiz.py:305
-msgid "New\tCTRL+N"
-msgstr "Nouveau\tCTRL+N"
-
-#: ../ProjectController.py:1479
+#: ../controls/CustomEditableListBox.py:40
+msgid "New item"
+msgstr "Nouvel élément"
+
+#: ../editors/Viewer.py:402
+msgid "No Modifier"
+msgstr "Pas de modificateur"
+
+#: ../PLCControler.py:2929
+msgid "No PLC project found"
+msgstr "Pas de projet d'automate trouvé"
+
+#: ../ProjectController.py:1478
msgid "No PLC to transfer (did build succeed ?)\n"
msgstr "Aucun automate à transférer (la compilation a-t-elle réussi ?)\n"
+#: ../PLCGenerator.py:1321
+#, python-format
+msgid "No body defined in \"%s\" POU"
+msgstr "Pas de code défini dans le POU \"%s\""
+
+#: ../PLCGenerator.py:722
+#: ../PLCGenerator.py:945
+#, python-format
+msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr "Pas de connecteur trouvé pour le prolongement \"%s\" dans le POU \"%s\""
+
+#: ../PLCOpenEditor.py:370
+msgid ""
+"No documentation available.\n"
+"Coming soon."
+msgstr ""
+"Pas de documentation.\n"
+"Bientôt disponible."
+
+#: ../PLCGenerator.py:744
+#, python-format
+msgid "No informations found for \"%s\" block"
+msgstr "Aucune information trouvée pour le block \"%s\""
+
+#: ../plcopen/structures.py:167
+msgid "No output variable found"
+msgstr "Pas de variable de sortie trouvée."
+
#: ../Beremiz_service.py:394
msgid "No running PLC"
msgstr "Aucun automate en cours d'exécution"
+#: ../controls/SearchResultPanel.py:169
+msgid "No search results available."
+msgstr "Pas de résultat de recherche disponible."
+
#: ../svgui/svgui.py:98
#, python-format
msgid "No such SVG file: %s\n"
@@ -655,10 +2375,15 @@
msgid "No such index/subindex (%x,%x) in ID : %d (variable %s)"
msgstr "indice et sous-indice inconnu (%x,%x) pour l'ID : %d (variable %s)"
-#: ../util/BrowseValuesLibraryDialog.py:83
+#: ../dialogs/BrowseValuesLibraryDialog.py:83
msgid "No valid value selected!"
msgstr "Aucune valeur valide sélectionnée !"
+#: ../PLCGenerator.py:1319
+#, python-format
+msgid "No variable defined in \"%s\" POU"
+msgstr "Pas de varaibles définies dans le POU \"%s\""
+
#: ../canfestival/SlaveEditor.py:49
#: ../canfestival/NetworkEditor.py:79
msgid "Node infos"
@@ -669,24 +2394,63 @@
msgid "Non existing node ID : %d (variable %s)"
msgstr "Le node ID n'existe pas : %d (variable %s)"
+#: ../controls/VariablePanel.py:69
+msgid "Non-Retain"
+msgstr "Non-Persistante"
+
+#: ../dialogs/LDElementDialog.py:62
+msgid "Normal"
+msgstr "Normal"
+
#: ../canfestival/config_utils.py:383
#, python-format
msgid "Not PDO mappable variable : '%s' (ID:%d,Idx:%x,sIdx:%x))"
msgstr "Variable non mappable dans un PDO : '%s' (ID:%d,Idx:%x,sIdx:%x))"
-#: ../Beremiz.py:341
+#: ../plcopen/iec_std.csv:80
+msgid "Not equal to"
+msgstr "Non égal à"
+
+#: ../dialogs/SFCDivergenceDialog.py:80
+msgid "Number of sequences:"
+msgstr "Nombre de branches :"
+
+#: ../plcopen/iec_std.csv:22
+msgid "Numerical"
+msgstr "Numérique"
+
+#: ../plcopen/structures.py:247
+msgid ""
+"Off-delay timer\n"
+"The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
+msgstr ""
+"Temporisation avec retard à l'extinction\n"
+"La temporisation avec retard à l'extinction peut être utilisé pour retarder le passage de la sortie à l'état faux, d'une période fixe après le passage de l'entrée à l'état faux"
+
+#: ../plcopen/structures.py:242
+msgid ""
+"On-delay timer\n"
+"The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
+msgstr ""
+"Temporisation avec retard à l'allumage\n"
+"La temporisation avec retard à l'allumage peut être utilisé pour retarder le passage de la sortie à l'état vrai, d'une période fixe après le passage de l'entrée à l'état vrai"
+
+#: ../dialogs/SearchInProjectDialog.py:93
+msgid "Only Elements"
+msgstr "Uniquement les éléments"
+
+#: ../Beremiz.py:309
+#: ../Beremiz.py:343
+#: ../PLCOpenEditor.py:127
+#: ../PLCOpenEditor.py:168
msgid "Open"
msgstr "Ouvrir"
-#: ../Beremiz.py:307
-msgid "Open\tCTRL+O"
-msgstr "Ouvrir\tCTRL+O"
-
#: ../svgui/svgui.py:107
msgid "Open Inkscape"
msgstr "Ouverture de Inkscape"
-#: ../ProjectController.py:1531
+#: ../ProjectController.py:1530
msgid "Open a file explorer to manage project files"
msgstr "Ouvrir un explorateur de fichier pour gérer les fichiers de projet"
@@ -694,11 +2458,31 @@
msgid "Open wxGlade"
msgstr "Ouverture de wxGlade"
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Option"
+msgstr "Option"
+
+#: ../dialogs/FindInPouDialog.py:76
+msgid "Options"
+msgstr "Options"
+
+#: ../controls/ProjectPropertiesPanel.py:97
+msgid "Organization (optional):"
+msgstr "Groupe (optionnel) :"
+
#: ../canfestival/SlaveEditor.py:47
#: ../canfestival/NetworkEditor.py:77
msgid "Other Profile"
msgstr "Autre profil"
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/BrowseLocationsDialog.py:37
+#: ../dialogs/FBDVariableDialog.py:35
+#: ../dialogs/SFCStepDialog.py:65
+msgid "Output"
+msgstr "Sortie"
+
#: ../canfestival/SlaveEditor.py:36
#: ../canfestival/NetworkEditor.py:66
msgid "PDO Receive"
@@ -709,19 +2493,109 @@
msgid "PDO Transmit"
msgstr "PDO transmis"
+#: ../plcopen/structures.py:269
+msgid ""
+"PID\n"
+"The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
+msgstr ""
+"PID\n"
+"Le bloc fonctionnel PID (Proportionnel, Intégrale, Dérivée) fournit un controller de boucle fermé classique à trois paramètres."
+
#: ../targets/toolchain_gcc.py:107
msgid "PLC :\n"
msgstr "Automate :\n"
-#: ../ProjectController.py:1097
-#: ../ProjectController.py:1399
+#: ../ProjectController.py:1096
+#: ../ProjectController.py:1398
#, python-format
msgid "PLC is %s\n"
msgstr "L'automate est dans l'état %s\n"
-#: ../Beremiz.py:320
-msgid "Page Setup\tCTRL+ALT+P"
-msgstr "Mise en page\tCTRL+ALT+P"
+#: ../PLCOpenEditor.py:313
+#: ../PLCOpenEditor.py:391
+msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
+msgstr "Fichiers PLCOpen (*.xml)|*.xml|Tous les fichiers|*.*"
+
+#: ../PLCOpenEditor.py:175
+#: ../PLCOpenEditor.py:231
+msgid "PLCOpenEditor"
+msgstr "PLCOpenEditor"
+
+#: ../dialogs/PouDialog.py:98
+msgid "POU Name"
+msgstr "Nom du POU"
+
+#: ../dialogs/PouDialog.py:56
+msgid "POU Name:"
+msgstr "Nom du POU :"
+
+#: ../dialogs/PouDialog.py:100
+msgid "POU Type"
+msgstr "Type du POU"
+
+#: ../dialogs/PouDialog.py:63
+msgid "POU Type:"
+msgstr "Type du POU :"
+
+#: ../Beremiz.py:322
+#: ../PLCOpenEditor.py:141
+msgid "Page Setup"
+msgstr "Mise en page..."
+
+#: ../controls/ProjectPropertiesPanel.py:110
+msgid "Page Size (optional):"
+msgstr "Taille de la page (optionnel) :"
+
+#: ../PLCOpenEditor.py:476
+#, python-format
+msgid "Page: %d"
+msgstr "Page: %d"
+
+#: ../controls/PouInstanceVariablesPanel.py:41
+msgid "Parent instance"
+msgstr "Instance parent"
+
+#: ../IDEFrame.py:350
+#: ../IDEFrame.py:402
+#: ../editors/Viewer.py:537
+msgid "Paste"
+msgstr "Coller"
+
+#: ../IDEFrame.py:1900
+msgid "Paste POU"
+msgstr "Coller un POU"
+
+#: ../dialogs/SearchInProjectDialog.py:64
+msgid "Pattern to search:"
+msgstr "Modèle à rechercher :"
+
+#: ../dialogs/LDPowerRailDialog.py:64
+msgid "Pin number:"
+msgstr "Nombre de pattes :"
+
+#: ../editors/Viewer.py:2289
+#: ../editors/Viewer.py:2594
+#: ../editors/SFCViewer.py:696
+msgid "Please choose a target"
+msgstr "Choisissez une cible"
+
+#: ../editors/Viewer.py:2112
+#: ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630
+#: ../editors/Viewer.py:2632
+msgid "Please enter comment text"
+msgstr "Saisissez le texte du commentaire"
+
+#: ../editors/SFCViewer.py:359
+#: ../editors/SFCViewer.py:381
+#: ../editors/SFCViewer.py:725
+msgid "Please enter step name"
+msgstr "Saisissez le nom de l'étape"
+
+#: ../dialogs/ForceVariableDialog.py:153
+#, python-format
+msgid "Please enter value for a \"%s\" variable:"
+msgstr "Veuillez entrer la valeur pour une variable de type \"%s\" :"
#: ../Beremiz_service.py:366
msgid "Port number must be 0 <= port <= 65535!"
@@ -731,75 +2605,336 @@
msgid "Port number must be an integer!"
msgstr "Le numéro de port doit être un entier !"
-#: ../Beremiz.py:322
-msgid "Preview\tCTRL+SHIFT+P"
-msgstr "Preview\tCTRL+SHIFT+P"
-
-#: ../Beremiz.py:344
+#: ../editors/GraphicViewer.py:105
+msgid "Position:"
+msgstr "Position :"
+
+#: ../editors/Viewer.py:476
+msgid "Power Rail"
+msgstr "Barre d'alimentation"
+
+#: ../dialogs/LDPowerRailDialog.py:36
+msgid "Power Rail Properties"
+msgstr "Propriétés de la barre d'alimentation"
+
+#: ../Beremiz.py:324
+#: ../PLCOpenEditor.py:143
+msgid "Preview"
+msgstr "Aperçu avant impression"
+
+#: ../dialogs/SFCDivergenceDialog.py:93
+#: ../dialogs/LDPowerRailDialog.py:78
+#: ../dialogs/ConnectionDialog.py:78
+#: ../dialogs/FBDVariableDialog.py:97
+#: ../dialogs/SFCTransitionDialog.py:96
+#: ../dialogs/LDElementDialog.py:101
+#: ../dialogs/SFCStepDialog.py:79
+#: ../dialogs/FBDBlockDialog.py:103
+msgid "Preview:"
+msgstr "Aperçu :"
+
+#: ../Beremiz.py:326
+#: ../Beremiz.py:346
+#: ../PLCOpenEditor.py:145
+#: ../PLCOpenEditor.py:171
msgid "Print"
msgstr "Imprimer"
-#: ../Beremiz.py:324
-msgid "Print\tCTRL+P"
-msgstr "Imprimer\tCTRL+P"
-
-#: ../ProjectController.py:1530
+#: ../IDEFrame.py:1155
+msgid "Print preview"
+msgstr "Aperçu avant impression"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Priority"
+msgstr "Priorité"
+
+#: ../dialogs/SFCTransitionDialog.py:83
+msgid "Priority:"
+msgstr "Priorité :"
+
+#: ../controls/ProjectPropertiesPanel.py:80
+msgid "Product Name (required):"
+msgstr "Nom du produit (obligatoire) :"
+
+#: ../controls/ProjectPropertiesPanel.py:82
+msgid "Product Release (optional):"
+msgstr "Publication du produit (optionnel) :"
+
+#: ../controls/ProjectPropertiesPanel.py:81
+msgid "Product Version (required):"
+msgstr "Version du produit (obligatoire) :"
+
+#: ../IDEFrame.py:1972
+#: ../dialogs/SearchInProjectDialog.py:46
+msgid "Program"
+msgstr "Programme"
+
+#: ../PLCOpenEditor.py:360
+msgid "Program was successfully generated!"
+msgstr "Le programme a été généré avec succès !"
+
+#: ../PLCControler.py:95
+msgid "Programs"
+msgstr "Programmes"
+
+#: ../editors/Viewer.py:230
+msgid "Programs can't be used by other POUs!"
+msgstr "Les programmes ne peuvent être utilisés par les autres POUs !"
+
+#: ../controls/ProjectPropertiesPanel.py:84
+#: ../IDEFrame.py:553
+msgid "Project"
+msgstr "Projet"
+
+#: ../controls/SearchResultPanel.py:173
+#, python-format
+msgid "Project '%s':"
+msgstr "Projet '%s' :"
+
+#: ../ProjectController.py:1529
msgid "Project Files"
msgstr "Fichiers de projet"
+#: ../controls/ProjectPropertiesPanel.py:78
+msgid "Project Name (required):"
+msgstr "Nom du projet (obligatoire) :"
+
+#: ../controls/ProjectPropertiesPanel.py:79
+msgid "Project Version (optional):"
+msgstr "Version du projet (optionnel) :"
+
+#: ../PLCControler.py:2916
+msgid ""
+"Project file syntax error:\n"
+"\n"
+msgstr ""
+"Erreur de syntaxe dans le fichier du projet :\n"
+"\n"
+
+#: ../dialogs/ProjectDialog.py:32
+msgid "Project properties"
+msgstr "Propriétés du projet"
+
#: ../ConfigTreeNode.py:506
#, python-format
msgid "Project tree layout do not match confnode.xml %s!=%s "
msgstr "L'organisation du projet ne correspond pas à plugin.xml %s!=%s"
+#: ../PLCControler.py:96
+msgid "Properties"
+msgstr "Propriétés"
+
+#: ../plcopen/structures.py:237
+msgid ""
+"Pulse timer\n"
+"The pulse timer can be used to generate output pulses of a given time duration."
+msgstr ""
+"Temporisation à impulsion\n"
+"La temporisation à impulsion peut être utilisée pour générer sur la sortie des impulsions d'une durée déterminée."
+
#: ../features.py:8
msgid "Python file"
msgstr "Fichier Python"
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Qualifier"
+msgstr "Qualificatif"
+
#: ../Beremiz_service.py:328
+#: ../Beremiz.py:329
+#: ../PLCOpenEditor.py:151
msgid "Quit"
msgstr "Quitter"
-#: ../Beremiz.py:327
-msgid "Quit\tCTRL+Q"
-msgstr "Quitter\tCTRL+Q"
-
-#: ../ProjectController.py:1526
+#: ../plcopen/structures.py:202
+msgid ""
+"RS bistable\n"
+"The RS bistable is a latch where the Reset dominates."
+msgstr ""
+"Bascule RS\n"
+"La bascule RS est une bascule où le Reset est dominant."
+
+#: ../plcopen/structures.py:274
+msgid ""
+"Ramp\n"
+"The RAMP function block is modelled on example given in the standard."
+msgstr ""
+"Rampe\n"
+"Le bloc fonctionnel RAMP est basé sur l'exemple du standard."
+
+#: ../editors/GraphicViewer.py:89
+msgid "Range:"
+msgstr "Echelle :"
+
+#: ../ProjectController.py:1525
msgid "Raw IEC code"
msgstr "Ajout code IEC"
-#: ../Beremiz.py:1037
+#: ../plcopen/structures.py:254
+msgid ""
+"Real time clock\n"
+"The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
+msgstr ""
+"Horloge temps réel\n"
+"L'horloge temps réel est utilisée dans de nombreux cas tels que l'horodatage, la définition des dates et heures dans des rapports de commandes, dans des messages d'alarme et bien d'autres."
+
+#: ../Beremiz.py:1039
#, python-format
msgid "Really delete node '%s'?"
msgstr "Êtes-vous sûr de vouloir supprimer le noeud '%s' ?"
-#: ../util/discovery.py:105
+#: ../IDEFrame.py:340
+#: ../IDEFrame.py:398
+msgid "Redo"
+msgstr "Refaire"
+
+#: ../dialogs/SFCTransitionDialog.py:57
+#: ../dialogs/SFCTransitionDialog.py:135
+msgid "Reference"
+msgstr "Référence"
+
+#: ../IDEFrame.py:408
+#: ../dialogs/DiscoveryDialog.py:105
msgid "Refresh"
msgstr "Actualiser"
-#: ../Beremiz.py:1038
+#: ../dialogs/SearchInProjectDialog.py:73
+msgid "Regular expression"
+msgstr "Expression régulière"
+
+#: ../dialogs/FindInPouDialog.py:91
+msgid "Regular expressions"
+msgstr "Expressions régulières"
+
+#: ../controls/DebugVariablePanel.py:299
+#: ../editors/Viewer.py:1356
+msgid "Release value"
+msgstr "Relacher la valeur"
+
+#: ../plcopen/iec_std.csv:37
+msgid "Remainder (modulo)"
+msgstr "Modulo"
+
+#: ../Beremiz.py:1040
#, python-format
msgid "Remove %s node"
msgstr "Enlever un noeud %s"
-#: ../util/FileManagementPanel.py:281
+#: ../dialogs/ActionBlockDialog.py:139
+msgid "Remove action"
+msgstr "Supprimer une action"
+
+#: ../controls/DebugVariablePanel.py:183
+msgid "Remove debug variable"
+msgstr "Supprimer une variable à déboguer"
+
+#: ../editors/DataTypeEditor.py:346
+msgid "Remove element"
+msgstr "Supprimer un élément"
+
+#: ../editors/FileManagementPanel.py:281
msgid "Remove file from left folder"
msgstr "Supprimer un fichier du dossier de gauche"
+#: ../editors/ResourceEditor.py:252
+msgid "Remove instance"
+msgstr "Supprimer une instance"
+
#: ../canfestival/NetworkEditor.py:87
msgid "Remove slave"
msgstr "Enlever l'esclave"
-#: ../util/FileManagementPanel.py:399
+#: ../editors/ResourceEditor.py:223
+msgid "Remove task"
+msgstr "Supprimer la tâche"
+
+#: ../controls/VariablePanel.py:379
+msgid "Remove variable"
+msgstr "Supprimer une variable"
+
+#: ../IDEFrame.py:1976
+msgid "Rename"
+msgstr "Renommer"
+
+#: ../editors/FileManagementPanel.py:399
msgid "Replace File"
msgstr "Remplacer un fichier"
-#: ../ProjectController.py:1494
+#: ../plcopen/iec_std.csv:89
+msgid "Replacement (within)"
+msgstr "Remplacement (au milieu)"
+
+#: ../dialogs/LDElementDialog.py:76
+msgid "Reset"
+msgstr "Mise à zéro"
+
+#: ../editors/Viewer.py:521
+msgid "Reset Execution Order"
+msgstr "Réinitialiser l'order d'exécution"
+
+#: ../IDEFrame.py:423
+msgid "Reset Perspective"
+msgstr "Réinitialiser l'interface"
+
+#: ../controls/SearchResultPanel.py:105
+msgid "Reset search result"
+msgstr "Réinitialiser le résultat de la recherche"
+
+#: ../editors/GraphicViewer.py:137
+msgid "Reset zoom and offset"
+msgstr "Réinitialisation du zoom et de l'offset"
+
+#: ../PLCControler.py:96
+msgid "Resources"
+msgstr "Ressources"
+
+#: ../controls/VariablePanel.py:67
+msgid "Retain"
+msgstr "Persistante"
+
+#: ../controls/VariablePanel.py:352
+msgid "Return Type:"
+msgstr "Type de retour :"
+
+#: ../editors/Viewer.py:430
+msgid "Right"
+msgstr "Droite"
+
+#: ../dialogs/LDPowerRailDialog.py:60
+msgid "Right PowerRail"
+msgstr "Barre d'alimentation à droite"
+
+#: ../editors/Viewer.py:404
+#: ../dialogs/LDElementDialog.py:80
+msgid "Rising Edge"
+msgstr "Front montant"
+
+#: ../plcopen/structures.py:212
+msgid ""
+"Rising edge detector\n"
+"The output produces a single pulse when a rising edge is detected."
+msgstr ""
+"Détecteur de front montant\n"
+"La sortie produit une impulsion unique lorsqu'un front montant est détecté."
+
+#: ../plcopen/iec_std.csv:65
+msgid "Rotate left"
+msgstr "Rotation à gauche"
+
+#: ../plcopen/iec_std.csv:64
+msgid "Rotate right"
+msgstr "Rotation à droite"
+
+#: ../plcopen/iec_std.csv:17
+msgid "Rounding up/down"
+msgstr "Arrondi"
+
+#: ../ProjectController.py:1493
msgid "Run"
msgstr "Exécuter"
-#: ../ProjectController.py:842
-#: ../ProjectController.py:851
+#: ../ProjectController.py:841
+#: ../ProjectController.py:850
msgid "Runtime extensions C code generation failed !\n"
msgstr "La génération du code des plugins a échoué !\n"
@@ -813,6 +2948,29 @@
msgid "SDO Server"
msgstr "Serveur SDO"
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouDialog.py:36
+msgid "SFC"
+msgstr "SFC"
+
+#: ../plcopen/structures.py:197
+msgid ""
+"SR bistable\n"
+"The SR bistable is a latch where the Set dominates."
+msgstr ""
+"Bascule SR\n"
+"La bascule SR est une bascule où le Set est dominant."
+
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "ST"
+msgstr "ST"
+
+#: ../PLCOpenEditor.py:347
+msgid "ST files (*.st)|*.st|All files|*.*"
+msgstr "Fichiers ST (*.st)|*.st|Tous les fichiers|*.*"
+
#: ../svgui/svgui.py:92
msgid "SVG files (*.svg)|*.svg|All files|*.*"
msgstr "Fichiers SVG (*.svg)|*.svg|Tous les fichiers|*.*"
@@ -821,31 +2979,101 @@
msgid "SVGUI"
msgstr "SVGUI"
-#: ../Beremiz.py:342
+#: ../Beremiz.py:313
+#: ../Beremiz.py:344
+#: ../PLCOpenEditor.py:134
+#: ../PLCOpenEditor.py:169
msgid "Save"
msgstr "Enregistrer"
-#: ../Beremiz.py:311
-msgid "Save\tCTRL+S"
-msgstr "Enregistrer\tCTRL+S"
-
-#: ../Beremiz.py:343
+#: ../Beremiz.py:345
+#: ../PLCOpenEditor.py:136
+#: ../PLCOpenEditor.py:170
msgid "Save As..."
msgstr "Enregistrer sous..."
-#: ../Beremiz.py:313
-msgid "Save as\tCTRL+SHIFT+S"
-msgstr "Enregistrer sous...\tCTRL+SHIFT+S"
-
-#: ../ProjectController.py:1014
+#: ../Beremiz.py:315
+msgid "Save as"
+msgstr "Enregistrer sous..."
+
+#: ../dialogs/SearchInProjectDialog.py:76
+msgid "Scope"
+msgstr "Contexte"
+
+#: ../IDEFrame.py:592
+#: ../dialogs/SearchInProjectDialog.py:105
+msgid "Search"
+msgstr "Rechercher"
+
+#: ../IDEFrame.py:360
+#: ../IDEFrame.py:404
+#: ../dialogs/SearchInProjectDialog.py:52
+msgid "Search in Project"
+msgstr "Rechercher dans le projet"
+
+#: ../dialogs/DurationEditorDialog.py:46
+msgid "Seconds:"
+msgstr "Secondes :"
+
+#: ../IDEFrame.py:366
+msgid "Select All"
+msgstr "Tout sélectionner"
+
+#: ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Select a variable class:"
+msgstr "Sélectionner une direction pour la variable :"
+
+#: ../ProjectController.py:1013
msgid "Select an editor:"
msgstr "Sélectionner un éditeur :"
-#: ../util/discovery.py:84
+#: ../controls/PouInstanceVariablesPanel.py:197
+msgid "Select an instance"
+msgstr "Sélectionnez une instance"
+
+#: ../IDEFrame.py:576
+msgid "Select an object"
+msgstr "Sélectionner un objet"
+
+#: ../plcopen/iec_std.csv:70
+msgid "Selection"
+msgstr "Sélection"
+
+#: ../dialogs/SFCDivergenceDialog.py:62
+msgid "Selection Convergence"
+msgstr "Convergence simple"
+
+#: ../dialogs/SFCDivergenceDialog.py:55
+msgid "Selection Divergence"
+msgstr "Divergence simple"
+
+#: ../plcopen/structures.py:207
+msgid ""
+"Semaphore\n"
+"The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
+msgstr ""
+"Sémaphore\n"
+"La sémaphore fournit un mécanisme permettant à des éléments du programme d'accéder de façon exclusive à certaines resources."
+
+#: ../dialogs/DiscoveryDialog.py:84
msgid "Services available:"
msgstr "Services disponibles:"
-#: ../ProjectController.py:1520
+#: ../dialogs/LDElementDialog.py:72
+msgid "Set"
+msgstr "Mise à 1"
+
+#: ../plcopen/iec_std.csv:62
+msgid "Shift left"
+msgstr "Décalage à gauche"
+
+#: ../plcopen/iec_std.csv:63
+msgid "Shift right"
+msgstr "Décalage à droite"
+
+#: ../ProjectController.py:1519
msgid "Show IEC code generated by PLCGenerator"
msgstr "Afficher le code IEC généré par PLCGenerator"
@@ -857,29 +3085,57 @@
msgid "Show Master generated by config_utils"
msgstr "Afficher le maître généré par config_utils"
-#: ../ProjectController.py:1518
+#: ../ProjectController.py:1517
msgid "Show code"
msgstr "Afficher le code"
+#: ../dialogs/SFCDivergenceDialog.py:74
+msgid "Simultaneous Convergence"
+msgstr "Convergence double"
+
+#: ../dialogs/SFCDivergenceDialog.py:68
+msgid "Simultaneous Divergence"
+msgstr "Divergence double"
+
+#: ../plcopen/iec_std.csv:27
+msgid "Sine"
+msgstr "Sinus"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Single"
+msgstr "Evènement"
+
+#: ../plcopen/iec_std.csv:23
+msgid "Square root (base 2)"
+msgstr "Racine carré (base 2)"
+
+#: ../plcopen/structures.py:193
+msgid "Standard function blocks"
+msgstr "Blocs fonctionnels standards"
+
#: ../Beremiz_service.py:319
-#: ../ProjectController.py:1496
+#: ../ProjectController.py:1495
msgid "Start PLC"
msgstr "Démarrer l'automate"
-#: ../ProjectController.py:820
+#: ../ProjectController.py:819
#, python-format
msgid "Start build in %s\n"
msgstr "Début de la compilation dans %s\n"
-#: ../ProjectController.py:1315
+#: ../ProjectController.py:1314
msgid "Starting PLC\n"
msgstr "Démarrer l'automate\n"
-#: ../Beremiz.py:401
+#: ../Beremiz.py:403
msgid "Status ToolBar"
msgstr "Barre d'outils de statut"
-#: ../ProjectController.py:1499
+#: ../editors/Viewer.py:493
+msgid "Step"
+msgstr "Étape"
+
+#: ../ProjectController.py:1498
msgid "Stop"
msgstr "Arrêter"
@@ -887,19 +3143,51 @@
msgid "Stop PLC"
msgstr "Arrêter l'automate"
-#: ../ProjectController.py:1501
+#: ../ProjectController.py:1500
msgid "Stop Running PLC"
msgstr "Arrêter l'automate en cours d'exécution"
-#: ../ProjectController.py:1293
+#: ../ProjectController.py:1292
msgid "Stopping debugger...\n"
msgstr "Arrêt du débogage en cours\n"
-#: ../ProjectController.py:916
+#: ../editors/DataTypeEditor.py:52
+msgid "Structure"
+msgstr "Structure"
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Subrange"
+msgstr "Sous-ensemble"
+
+#: ../plcopen/iec_std.csv:35
+msgid "Subtraction"
+msgstr "Soustraction"
+
+#: ../ProjectController.py:915
msgid "Successfully built.\n"
msgstr "Compilé avec succès.\n"
-#: ../util/FileManagementPanel.py:398
+#: ../dialogs/SearchInProjectDialog.py:154
+msgid "Syntax error in regular expression of pattern to search!"
+msgstr "Erreur de syntaxe dans l'expression régulière du modèle à rechercher !"
+
+#: ../plcopen/iec_std.csv:29
+msgid "Tangent"
+msgstr "Tangente"
+
+#: ../editors/ResourceEditor.py:76
+msgid "Task"
+msgstr "Tâche"
+
+#: ../editors/ResourceEditor.py:218
+msgid "Tasks:"
+msgstr "Tâches :"
+
+#: ../controls/VariablePanel.py:78
+msgid "Temp"
+msgstr "Temporaire"
+
+#: ../editors/FileManagementPanel.py:398
#, python-format
msgid ""
"The file '%s' already exist.\n"
@@ -908,32 +3196,167 @@
"Le fichier '%s' existe déjà.\n"
"Voulez-vous le remplacer ?"
-#: ../Beremiz.py:553
+#: ../editors/LDViewer.py:879
+msgid "The group of block must be coherent!"
+msgstr "Le groupe de blocs doit être cohérent !"
+
+#: ../IDEFrame.py:1091
+#: ../Beremiz.py:555
msgid "There are changes, do you want to save?"
-msgstr "Le projet a été modifié, voulez-vous l'enregistrer ?"
-
-#: ../ProjectController.py:1508
+msgstr "Le projet a été modifié. Voulez-vous l'enregistrer ?"
+
+#: ../IDEFrame.py:1709
+#: ../IDEFrame.py:1728
+#, python-format
+msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr "Un POU a pour nom \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
+
+#: ../IDEFrame.py:1178
+msgid ""
+"There was a problem printing.\n"
+"Perhaps your current printer is not set correctly?"
+msgstr ""
+"Un problème est apparu lors de l'impression.\n"
+"Peut-être que votre imprimante n'est pas correctement configurée ?"
+
+#: ../editors/LDViewer.py:888
+msgid "This option isn't available yet!"
+msgstr "Cette option n'a pas encore disponible"
+
+#: ../editors/GraphicViewer.py:278
+msgid "Tick"
+msgstr "Tick"
+
+#: ../plcopen/iec_std.csv:40
+msgid "Time"
+msgstr "Temps"
+
+#: ../plcopen/iec_std.csv:40
+#: ../plcopen/iec_std.csv:41
+msgid "Time addition"
+msgstr "Addition de durée"
+
+#: ../plcopen/iec_std.csv:86
+msgid "Time concatenation"
+msgstr "Concaténation de date et de durée"
+
+#: ../plcopen/iec_std.csv:60
+#: ../plcopen/iec_std.csv:61
+msgid "Time division"
+msgstr "Division de durée"
+
+#: ../plcopen/iec_std.csv:46
+#: ../plcopen/iec_std.csv:47
+msgid "Time multiplication"
+msgstr "Multiplication de durée"
+
+#: ../plcopen/iec_std.csv:48
+#: ../plcopen/iec_std.csv:49
+msgid "Time subtraction"
+msgstr "Soustraction de durée"
+
+#: ../plcopen/iec_std.csv:42
+#: ../plcopen/iec_std.csv:43
+msgid "Time-of-day addition"
+msgstr "Addition d'horodatage"
+
+#: ../plcopen/iec_std.csv:52
+#: ../plcopen/iec_std.csv:53
+#: ../plcopen/iec_std.csv:54
+#: ../plcopen/iec_std.csv:55
+msgid "Time-of-day subtraction"
+msgstr "Soustraction d'horodatage"
+
+#: ../editors/Viewer.py:432
+msgid "Top"
+msgstr "Haut"
+
+#: ../ProjectController.py:1507
msgid "Transfer"
msgstr "Transférer"
-#: ../ProjectController.py:1510
+#: ../ProjectController.py:1509
msgid "Transfer PLC"
msgstr "Transférer l'automate"
-#: ../ProjectController.py:1475
+#: ../ProjectController.py:1474
msgid "Transfer completed successfully.\n"
msgstr "Transfert effectué avec succès.\n"
-#: ../ProjectController.py:1477
+#: ../ProjectController.py:1476
msgid "Transfer failed\n"
msgstr "Le transfert a échoué\n"
+#: ../editors/Viewer.py:494
+msgid "Transition"
+msgstr "Transition"
+
+#: ../PLCGenerator.py:1212
+#, python-format
+msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
+msgstr "Le code de la transition \"%s\" doit contenir une variable de sortie ou un relai dont la référence est son nom"
+
+#: ../dialogs/PouTransitionDialog.py:84
+msgid "Transition Name"
+msgstr "Nom de la transition"
+
+#: ../dialogs/PouTransitionDialog.py:53
+msgid "Transition Name:"
+msgstr "Nom de la transition :"
+
+#: ../PLCGenerator.py:1301
+#, python-format
+msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
+msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en sortie dans le POU \"%s\" !"
+
+#: ../PLCGenerator.py:1292
+#, python-format
+msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
+msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en entrée dans le POU \"%s\" !"
+
+#: ../plcopen/plcopen.py:1442
+#, python-format
+msgid "Transition with name %s doesn't exist!"
+msgstr "La transition nommée %s n'existe pas !"
+
+#: ../PLCControler.py:95
+msgid "Transitions"
+msgstr "Transitions"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Triggering"
+msgstr "Activation"
+
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+#: ../editors/ResourceEditor.py:76
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Type"
+msgstr "Type"
+
#: ../canfestival/config_utils.py:335
#: ../canfestival/config_utils.py:617
#, python-format
msgid "Type conflict for location \"%s\""
msgstr "Conflit entre types pour l'adresse \"%s\""
+#: ../plcopen/iec_std.csv:16
+msgid "Type conversion"
+msgstr "Conversion de type"
+
+#: ../editors/DataTypeEditor.py:155
+msgid "Type infos:"
+msgstr "Propriétés du type :"
+
+#: ../dialogs/SFCDivergenceDialog.py:51
+#: ../dialogs/LDPowerRailDialog.py:51
+#: ../dialogs/ConnectionDialog.py:52
+#: ../dialogs/SFCTransitionDialog.py:53
+#: ../dialogs/FBDBlockDialog.py:48
+msgid "Type:"
+msgstr "Type :"
+
#: ../canfestival/config_utils.py:455
#: ../canfestival/config_utils.py:469
#, python-format
@@ -945,32 +3368,147 @@
msgid "Unable to get Xenomai's %s \n"
msgstr "Unable to get Xenomai's %s \n"
-#: ../ProjectController.py:255
+#: ../PLCGenerator.py:865
+#: ../PLCGenerator.py:924
+#, python-format
+msgid "Undefined block type \"%s\" in \"%s\" POU"
+msgstr "Type de block \"%s\" indéfini dans le POU \"%s\""
+
+#: ../PLCGenerator.py:240
+#, python-format
+msgid "Undefined pou type \"%s\""
+msgstr "Type de POU \"%s\" indéterminé !"
+
+#: ../IDEFrame.py:338
+#: ../IDEFrame.py:397
+msgid "Undo"
+msgstr "Défaire"
+
+#: ../ProjectController.py:254
msgid "Unknown"
msgstr "Inconnu"
+#: ../editors/Viewer.py:336
+#, python-format
+msgid "Unknown variable \"%s\" for this POU!"
+msgstr "Variable \"%s\" inconnue dans ce POU !"
+
+#: ../ProjectController.py:251
#: ../ProjectController.py:252
-#: ../ProjectController.py:253
msgid "Unnamed"
msgstr "SansNom"
+#: ../PLCControler.py:305
+#, python-format
+msgid "Unnamed%d"
+msgstr "Sansnom%d"
+
+#: ../controls/VariablePanel.py:272
+#, python-format
+msgid "Unrecognized data size \"%s\""
+msgstr "Taille de donnée \"%s\" non identifié !"
+
+#: ../plcopen/structures.py:222
+msgid ""
+"Up-counter\n"
+"The up-counter can be used to signal when a count has reached a maximum value."
+msgstr ""
+"Compteur incrémental\n"
+"Le compteur incrémental peut être utilisé pour signaler lorsque le compteur a atteint la valeur maximale."
+
+#: ../plcopen/structures.py:232
+msgid ""
+"Up-down counter\n"
+"The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
+msgstr ""
+"Compteur bidirectionnel\n"
+"Le compteur bidirectionnel a deux entrées CU et CD. Il peut être utilisé pour compter de façon incrémentale ou décrémentale sur l'une ou l'autre des entrées."
+
+#: ../controls/VariablePanel.py:709
+#: ../editors/DataTypeEditor.py:623
+msgid "User Data Types"
+msgstr "Types de donnée du projet"
+
#: ../canfestival/SlaveEditor.py:38
#: ../canfestival/NetworkEditor.py:68
msgid "User Type"
msgstr "Type utilisateur"
+#: ../PLCControler.py:94
+msgid "User-defined POUs"
+msgstr "POUs du projet"
+
+#: ../controls/DebugVariablePanel.py:40
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Value"
+msgstr "Valeur"
+
+#: ../editors/GraphicViewer.py:278
+msgid "Values"
+msgstr "Valeurs"
+
+#: ../editors/DataTypeEditor.py:252
+msgid "Values:"
+msgstr "Valeurs"
+
+#: ../controls/DebugVariablePanel.py:40
+#: ../editors/Viewer.py:466
+#: ../dialogs/ActionBlockDialog.py:41
+msgid "Variable"
+msgstr "Variable"
+
+#: ../dialogs/FBDVariableDialog.py:47
+msgid "Variable Properties"
+msgstr "Propriétés de la variable"
+
+#: ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Variable class"
+msgstr "Direction de la variable"
+
+#: ../editors/TextViewer.py:374
+#: ../editors/Viewer.py:338
+msgid "Variable don't belong to this POU!"
+msgstr "La variable n'appartient pas à ce POU !"
+
+#: ../controls/VariablePanel.py:77
+msgid "Variables"
+msgstr "Variables"
+
+#: ../controls/ProjectPropertiesPanel.py:151
+msgid "Vertical:"
+msgstr "Vertical :"
+
#: ../wxglade_hmi/wxglade_hmi.py:11
msgid "WXGLADE GUI"
msgstr "IHM WXGlade"
-#: ../ProjectController.py:1277
+#: ../ProjectController.py:1276
msgid "Waiting debugger to recover...\n"
msgstr "En attente de la mise en route du déboggueur...\n"
-#: ../ProjectController.py:516
+#: ../editors/LDViewer.py:888
+#: ../dialogs/PouDialog.py:126
+msgid "Warning"
+msgstr "Attention"
+
+#: ../ProjectController.py:515
msgid "Warnings in ST/IL/SFC code generator :\n"
msgstr "Mises en garde du generateur de code ST/IL/SFC :\n"
+#: ../dialogs/SearchInProjectDialog.py:85
+msgid "Whole Project"
+msgstr "Tout le projet"
+
+#: ../controls/ProjectPropertiesPanel.py:119
+msgid "Width:"
+msgstr "Longueur :"
+
+#: ../dialogs/FindInPouDialog.py:86
+msgid "Wrap search"
+msgstr "Boucler"
+
#: ../features.py:9
msgid "WxGlade GUI"
msgstr "Interface WxGlade"
@@ -991,7 +3529,7 @@
"Vous n'avez pas les permissions d'écriture.\n"
"Ouvrir wxGlade tout de même ?"
-#: ../ProjectController.py:221
+#: ../ProjectController.py:220
msgid ""
"You must have permission to work on the project\n"
"Work on a project copy ?"
@@ -999,11 +3537,84 @@
"Vous n'avez pas la permission de travailler sur le projet.\n"
"Travailler sur une copie du projet ?"
+#: ../editors/LDViewer.py:883
+msgid "You must select the block or group of blocks around which a branch should be added!"
+msgstr "Vous devez sélectionné le bloc ou le group autour duquel un ebranche doit être ajoutée !"
+
+#: ../editors/LDViewer.py:663
+msgid "You must select the wire where a contact should be added!"
+msgstr "Vous devez sélectionner le fil sur lequel le contact doit être ajouté !"
+
+#: ../dialogs/PouNameDialog.py:45
+#: ../dialogs/SFCStepNameDialog.py:47
+#: ../dialogs/SFCStepDialog.py:118
+msgid "You must type a name!"
+msgstr "Vous devez saisir un nom !"
+
+#: ../dialogs/ForceVariableDialog.py:165
+msgid "You must type a value!"
+msgstr "Vous devez saisir une valeur !"
+
+#: ../IDEFrame.py:414
+msgid "Zoom"
+msgstr "Zoom"
+
+#: ../editors/GraphicViewer.py:97
+msgid "Zoom:"
+msgstr "Zoom :"
+
+#: ../PLCOpenEditor.py:356
+#, python-format
+msgid "error: %s\n"
+msgstr "erreur: %s\n"
+
#: ../util/ProcessLogger.py:161
#, python-format
msgid "exited with status %s (pid %s)\n"
msgstr "a quitté avec le status %s (pid %s)\n"
+#: ../PLCOpenEditor.py:508
+#: ../PLCOpenEditor.py:510
+msgid "file : "
+msgstr "fichier :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "function"
+msgstr "fonction"
+
+#: ../PLCOpenEditor.py:511
+msgid "function : "
+msgstr "fonction :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "functionBlock"
+msgstr "Bloc fonctionnel"
+
+#: ../PLCOpenEditor.py:511
+msgid "line : "
+msgstr "ligne :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "program"
+msgstr "programme"
+
+#: ../plcopen/iec_std.csv:84
+msgid "string from the middle"
+msgstr "Caractères du milieu"
+
+#: ../plcopen/iec_std.csv:82
+msgid "string left of"
+msgstr "Caractères à gauche de"
+
+#: ../plcopen/iec_std.csv:83
+msgid "string right of"
+msgstr "Caractères à droite de"
+
+#: ../PLCOpenEditor.py:354
+#, python-format
+msgid "warning: %s\n"
+msgstr "attention: %s\n"
+
#: Extra XSD strings
msgid "CanFestivalSlaveNode"
msgstr "Noeud esclave CanFestival"
@@ -1050,9 +3661,6 @@
msgid "BaseParams"
msgstr "Paramètres de base"
-msgid "Name"
-msgstr "Nom"
-
msgid "IEC_Channel"
msgstr "Numéro IEC"
@@ -1092,14 +3700,74 @@
msgid "Disable_Extensions"
msgstr "Disable_Extensions"
-#~ msgid "&Properties"
-#~ msgstr "&Propriétés"
-
-#~ msgid ", "
-#~ msgstr ", "
-
-#~ msgid ". "
-#~ msgstr ". "
+#, fuzzy
+#~ msgid "Close Project\tCTRL+SHIFT+W"
+#~ msgstr ""
+#~ "#-#-#-#-# Beremiz_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Fermer le project\tCTRL+SHIFT+W\n"
+#~ "#-#-#-#-# PLCOpenEditor_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Fermer le projet\tCTRL+SHIFT+W"
+
+#~ msgid "New\tCTRL+N"
+#~ msgstr "Nouveau\tCTRL+N"
+
+#~ msgid "Open\tCTRL+O"
+#~ msgstr "Ouvrir\tCTRL+O"
+
+#, fuzzy
+#~ msgid "Preview\tCTRL+SHIFT+P"
+#~ msgstr ""
+#~ "#-#-#-#-# Beremiz_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Preview\tCTRL+SHIFT+P\n"
+#~ "#-#-#-#-# PLCOpenEditor_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Aperçu avant impression\tCTRL+SHIFT+S"
+
+#, fuzzy
+#~ msgid "Print\tCTRL+P"
+#~ msgstr ""
+#~ "#-#-#-#-# Beremiz_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Imprimer\tCTRL+P\n"
+#~ "#-#-#-#-# PLCOpenEditor_fr_FR.po (PACKAGE VERSION) #-#-#-#-#\n"
+#~ "Imprimer...\tCTRL+Q"
+
+#~ msgid "Quit\tCTRL+Q"
+#~ msgstr "Quitter\tCTRL+Q"
+
+#~ msgid "Save\tCTRL+S"
+#~ msgstr "Enregistrer\tCTRL+S"
+
+#~ msgid "Save as\tCTRL+SHIFT+S"
+#~ msgstr "Enregistrer sous...\tCTRL+SHIFT+S"
+
+#~ msgid "Copy\tCTRL+C"
+#~ msgstr "Copier\tCtrl+C"
+
+#~ msgid "Cut\tCTRL+X"
+#~ msgstr "Couper\tCTRL+X"
+
+#~ msgid "Find\tCTRL+F"
+#~ msgstr "Rechercher...\tCTRL+Z"
+
+#~ msgid "PLCOpenEditor\tF1"
+#~ msgstr "PLCOpenEditor\tF1"
+
+#~ msgid "Paste\tCTRL+V"
+#~ msgstr "Coller\tCTRL+V"
+
+#~ msgid "Redo\tCTRL+Y"
+#~ msgstr "Refaire\tCTRL+Y"
+
+#~ msgid "Refresh\tCTRL+R"
+#~ msgstr "Actualiser\tCTRL+R"
+
+#~ msgid "Save As...\tCTRL+SHIFT+S"
+#~ msgstr "Enregistrer sous...\tCTRL+SHIFT+S"
+
+#~ msgid "Search in Project\tCTRL+SHIFT+F"
+#~ msgstr "Rechercher dans le projet\tCTRL+SHIFT+F"
+
+#~ msgid "Undo\tCTRL+Z"
+#~ msgstr "Défaire\tCTRL+Z"
#~ msgid "Add a sub confnode"
#~ msgstr "Add a sub confnode"
@@ -1134,11 +3802,83 @@
#~ msgid "Wrong URI, please check it !\n"
#~ msgstr "URI inconnue, veuillez vérifier l'adresse !\n"
-#~ msgid "file : "
-#~ msgstr "fichier :"
-
-#~ msgid "function : "
-#~ msgstr "fonction :"
-
-#~ msgid "line : "
-#~ msgstr "ligne :"
+#~ msgid "Add a new data type"
+#~ msgstr "Ajouter un nouveau type de données"
+
+#~ msgid "Add new configuration"
+#~ msgstr "Ajouter une nouvelle configuration"
+
+#~ msgid "Add new resource"
+#~ msgstr "Ajouter une nouvelle resource"
+
+#~ msgid "Block Types"
+#~ msgstr "Types de blocs"
+
+#~ msgid "CSV Log"
+#~ msgstr "Log CVS"
+
+#~ msgid "Delete Task"
+#~ msgstr "Supprimer une tâche"
+
+#~ msgid "Graphic Panel"
+#~ msgstr "Graphique"
+
+#~ msgid "Instances"
+#~ msgstr "Instances"
+
+#~ msgid "Invalid value \"%s\" for location"
+#~ msgstr "Adresse \"%s\" invalide "
+
+#~ msgid "Please enter configuration name"
+#~ msgstr "Saisissez le nom de la configuration"
+
+#~ msgid "Please enter data type name"
+#~ msgstr "Saisissez le nom du type de donnée"
+
+#~ msgid "Please enter resource name"
+#~ msgstr "Saisissez le nom de la ressource"
+
+#~ msgid "Please enter text"
+#~ msgstr "Saisissez le texte"
+
+#~ msgid "Plugins"
+#~ msgstr "Plugins"
+
+#~ msgid "Types"
+#~ msgstr "Types"
+
+#~ msgid "Create a new POU from"
+#~ msgstr "Créer un nouveau POU à partir de"
+
+#~ msgid "Please enter POU name"
+#~ msgstr "Saisissez le nom du POU"
+
+#~ msgid "Scaling:"
+#~ msgstr "Echelle :"
+
+#~ msgid "X Scale:"
+#~ msgstr "Echelle X :"
+
+#~ msgid "Y Scale:"
+#~ msgstr "Echelle Y :"
+
+#~ msgid "No"
+#~ msgstr "Non"
+
+#~ msgid "Yes"
+#~ msgstr "Oui"
+
+#, fuzzy
+#~ msgid "A pou with \"%s\" as name exists!"
+#~ msgstr "Un POU nommé \"%s\" existe déjà !"
+
+#~ msgid "Close\tCTRL+Q"
+#~ msgstr "Fermer\tCTRL+Q"
+
+#~ msgid ""
+#~ "A variable is defined with \"%s\" as name. It can generate a conflict. Do "
+#~ "you wish to continue?"
+#~ msgstr "Une variable"
+
+#~ msgid "Create A New POU From"
+#~ msgstr "Créer un nouveau POU à partir de"
--- a/i18n/Beremiz_ko_KR.po Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/Beremiz_ko_KR.po Fri Sep 07 16:45:55 2012 +0200
@@ -7,8 +7,8 @@
msgstr ""
"Project-Id-Version: Beremiz_Korean_Version\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-08-24 18:28+0200\n"
-"PO-Revision-Date: 2012-08-27 17:19+0100\n"
+"POT-Creation-Date: 2012-09-07 01:17+0200\n"
+"PO-Revision-Date: 2012-09-07 01:38+0100\n"
"Last-Translator: Laurent BESSARD <laurent.bessard@gmail.com>\n"
"Language-Team: LinuxIT <lij3105@gmail.com>\n"
"Language: \n"
@@ -18,8 +18,32 @@
"X-Poedit-Language: Korean\n"
"X-Poedit-Country: KOREA, REPUBLIC OF\n"
"X-Poedit-SourceCharset: utf-8\n"
-
-#: ../Beremiz.py:1069
+"X-Poedit-Language: Korean\n"
+"X-Poedit-Country: KOREA, REPUBLIC OF\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: ../PLCOpenEditor.py:520
+msgid ""
+"\n"
+"An error has occurred.\n"
+"\n"
+"Click OK to save an error report.\n"
+"\n"
+"Please be kind enough to send this file to:\n"
+"edouard.tisserant@gmail.com\n"
+"\n"
+"Error:\n"
+msgstr ""
+"\n"
+"알 수 없는 에러가 발생했습니다.\n"
+"\n"
+"OK 버튼을 눌러 에러 리포트를 저장하세요.\n"
+"\n"
+"edouard.tisserant@gmail.com 주소로 에러 리포트를 첨부하여 보내주세요\n"
+"\n"
+"에러:\n"
+
+#: ../Beremiz.py:1071
#, python-format
msgid ""
"\n"
@@ -44,85 +68,620 @@
"\n"
"에러 위치:\n"
-#: ../ProjectController.py:891
+#: ../controls/VariablePanel.py:77
+msgid " External"
+msgstr " 외부"
+
+#: ../controls/VariablePanel.py:76
+msgid " InOut"
+msgstr " 입출력"
+
+#: ../controls/VariablePanel.py:76
+msgid " Input"
+msgstr " 입력"
+
+#: ../controls/VariablePanel.py:77
+msgid " Local"
+msgstr " 로컬"
+
+#: ../controls/VariablePanel.py:76
+msgid " Output"
+msgstr " 출력"
+
+#: ../controls/VariablePanel.py:78
+msgid " Temp"
+msgstr " 임시"
+
+#: ../PLCOpenEditor.py:530
+msgid " : "
+msgstr " :"
+
+#: ../dialogs/PouTransitionDialog.py:94
+#: ../dialogs/PouActionDialog.py:91
+#: ../dialogs/PouDialog.py:111
+#: ../dialogs/SFCTransitionDialog.py:144
+#, python-format
+msgid " and %s"
+msgstr " , %s"
+
+#: ../ProjectController.py:890
msgid " generation failed !\n"
msgstr "생성 실패!\n"
-#: ../Beremiz.py:892
+#: ../plcopen/plcopen.py:1051
+#, python-format
+msgid "\"%s\" Data Type doesn't exist !!!"
+msgstr "\"%s\" 존재하지 않는 데이터 타입!!!"
+
+#: ../plcopen/plcopen.py:1069
+#, python-format
+msgid "\"%s\" POU already exists !!!"
+msgstr "\"%s\" POU가 이미 존재합니다!!!"
+
+#: ../plcopen/plcopen.py:1090
+#, python-format
+msgid "\"%s\" POU doesn't exist !!!"
+msgstr "\"%s\" POU가 존재하지 않습니다!!!"
+
+#: ../editors/Viewer.py:234
+#, python-format
+msgid "\"%s\" can't use itself!"
+msgstr "\"%s\": 자신을 사용 할 수 없습니다!"
+
+#: ../IDEFrame.py:1706
+#: ../IDEFrame.py:1725
+#, python-format
+msgid "\"%s\" config already exists!"
+msgstr "\"%s\" 설정(config)이 이미 존재합니다!"
+
+#: ../plcopen/plcopen.py:315
+#, python-format
+msgid "\"%s\" configuration already exists !!!"
+msgstr "\"%s\" 설정(configuration)이 이미 존재합니다!"
+
+#: ../IDEFrame.py:1660
+#, python-format
+msgid "\"%s\" data type already exists!"
+msgstr "\"%s\" 데이터 타입이 이미존재합니다!"
+
+#: ../PLCControler.py:2040
+#: ../PLCControler.py:2044
+#, python-format
+msgid "\"%s\" element can't be pasted here!!!"
+msgstr "\"%s\" 항목을 이곳에 붙여넣기 할 수 없습니다!!!"
+
+#: ../editors/TextViewer.py:305
+#: ../editors/TextViewer.py:325
+#: ../editors/Viewer.py:252
+#: ../dialogs/PouTransitionDialog.py:105
+#: ../dialogs/ConnectionDialog.py:150
+#: ../dialogs/PouActionDialog.py:102
+#: ../dialogs/FBDBlockDialog.py:162
+#, python-format
+msgid "\"%s\" element for this pou already exists!"
+msgstr "이 POU에 \"%s\" 항목이 이미 존재합니다!"
+
+#: ../Beremiz.py:894
#, python-format
msgid "\"%s\" folder is not a valid Beremiz project\n"
msgstr "\"%s\" 베레미즈 프로젝트 폴더가 아닙니다\n"
+#: ../plcopen/structures.py:106
+#, python-format
+msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
+msgstr "\"%s\" 펑션이 취소 되었습니다 \"%s\" POU: 연결된 입력 없음"
+
+#: ../controls/VariablePanel.py:656
+#: ../IDEFrame.py:1651
+#: ../editors/DataTypeEditor.py:548
+#: ../editors/DataTypeEditor.py:577
+#: ../dialogs/PouNameDialog.py:49
+#: ../dialogs/PouTransitionDialog.py:101
+#: ../dialogs/SFCStepNameDialog.py:51
+#: ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/FBDVariableDialog.py:199
+#: ../dialogs/PouActionDialog.py:98
+#: ../dialogs/PouDialog.py:118
+#: ../dialogs/SFCStepDialog.py:122
+#: ../dialogs/FBDBlockDialog.py:158
+#, python-format
+msgid "\"%s\" is a keyword. It can't be used!"
+msgstr "\"%s\" 는 키워드 입니다. 사용 할 수 없습니다!"
+
+#: ../editors/Viewer.py:240
+#, python-format
+msgid "\"%s\" is already used by \"%s\"!"
+msgstr "\"%s\" 는 \"%s\" 에서 이미 사용중입니다!"
+
+#: ../plcopen/plcopen.py:2786
+#, python-format
+msgid "\"%s\" is an invalid value!"
+msgstr "\"%s\" 유효하지 않은 값입니다!"
+
+#: ../PLCOpenEditor.py:362
+#: ../PLCOpenEditor.py:399
+#, python-format
+msgid "\"%s\" is not a valid folder!"
+msgstr "\"%s\" 유효하지 않은 폴더입니다!"
+
+#: ../controls/VariablePanel.py:654
+#: ../IDEFrame.py:1649
+#: ../editors/DataTypeEditor.py:572
+#: ../dialogs/PouNameDialog.py:47
+#: ../dialogs/PouTransitionDialog.py:99
+#: ../dialogs/SFCStepNameDialog.py:49
+#: ../dialogs/ConnectionDialog.py:144
+#: ../dialogs/PouActionDialog.py:96
+#: ../dialogs/PouDialog.py:116
+#: ../dialogs/SFCStepDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:156
+#, python-format
+msgid "\"%s\" is not a valid identifier!"
+msgstr "\"%s\"는 유효하지 않은 식별자입니다!"
+
+#: ../IDEFrame.py:214
+#: ../IDEFrame.py:2445
+#: ../IDEFrame.py:2464
+#, python-format
+msgid "\"%s\" is used by one or more POUs. It can't be removed!"
+msgstr "\"%s\" 는 현재 하나 이상의 POU에서 사용중입니다. 제거할 수 없습니다!"
+
+#: ../controls/VariablePanel.py:311
+#: ../IDEFrame.py:1669
+#: ../editors/TextViewer.py:303
+#: ../editors/TextViewer.py:323
+#: ../editors/TextViewer.py:360
+#: ../editors/Viewer.py:250
+#: ../editors/Viewer.py:295
+#: ../editors/Viewer.py:312
+#: ../dialogs/ConnectionDialog.py:148
+#: ../dialogs/PouDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:160
+#, python-format
+msgid "\"%s\" pou already exists!"
+msgstr "\"%s\" POU는 이미 존재합니다!"
+
+#: ../plcopen/plcopen.py:346
+#, python-format
+msgid "\"%s\" resource already exists in \"%s\" configuration !!!"
+msgstr "\"%s\" 리소스는 \"%s\" 설정(configuration)에 이미 존재합니다 !!!"
+
+#: ../plcopen/plcopen.py:362
+#, python-format
+msgid "\"%s\" resource doesn't exist in \"%s\" configuration !!!"
+msgstr "\"%s\" 리소스를 \"%s\" 설정(configuration)에서 찾을 수 없습니다 !!!"
+
+#: ../dialogs/SFCStepNameDialog.py:57
+#: ../dialogs/SFCStepDialog.py:128
+#, python-format
+msgid "\"%s\" step already exists!"
+msgstr "\"%s\" 스텝이 이미 생성되었습니다!"
+
+#: ../editors/DataTypeEditor.py:543
+#, python-format
+msgid "\"%s\" value already defined!"
+msgstr "\"%s\" 이미 정의된 값이 있습니다!"
+
+#: ../editors/DataTypeEditor.py:719
+#: ../dialogs/ArrayTypeDialog.py:97
+#, python-format
+msgid "\"%s\" value isn't a valid array dimension!"
+msgstr "\"%s\" 값은 올바른 배열 차원이 아닙니다!"
+
+#: ../editors/DataTypeEditor.py:726
+#: ../dialogs/ArrayTypeDialog.py:103
+#, python-format
+msgid ""
+"\"%s\" value isn't a valid array dimension!\n"
+"Right value must be greater than left value."
+msgstr ""
+"\"%s\" 올바른 데이터를 입력하세요!\n"
+"우측의 데이터는 좌측보다 커야 합니다."
+
+#: ../PLCControler.py:793
+#, python-format
+msgid "%s \"%s\" can't be pasted as a %s."
+msgstr "%s \"%s\" 붙여넣기 할 수 없는 형식 : %s."
+
+#: ../PLCControler.py:1422
+#, fuzzy, python-format
+msgid "%s Data Types"
+msgstr "데이터 타입(Data Types)"
+
+#: ../editors/GraphicViewer.py:278
+#, python-format
+msgid "%s Graphics"
+msgstr "%s 그래픽"
+
+#: ../PLCControler.py:1417
+#, fuzzy, python-format
+msgid "%s POUs"
+msgstr "POU 붙여넣기"
+
#: ../canfestival/SlaveEditor.py:42
#: ../canfestival/NetworkEditor.py:72
#, python-format
msgid "%s Profile"
msgstr "%s 프로필"
-#: ../Beremiz.py:308
+#: ../plcopen/plcopen.py:1780
+#: ../plcopen/plcopen.py:1790
+#: ../plcopen/plcopen.py:1800
+#: ../plcopen/plcopen.py:1810
+#: ../plcopen/plcopen.py:1819
+#, python-format
+msgid "%s body don't have instances!"
+msgstr "%s 인스턴스를 찾을 수 없습니다!"
+
+#: ../plcopen/plcopen.py:1842
+#: ../plcopen/plcopen.py:1849
+#, python-format
+msgid "%s body don't have text!"
+msgstr "%s 텍스트를 찾을 수 없습니다!"
+
+#: ../IDEFrame.py:364
+msgid "&Add Element"
+msgstr "&구성원 추가"
+
+#: ../IDEFrame.py:334
+msgid "&Configuration"
+msgstr "&설정(Configuration)"
+
+#: ../IDEFrame.py:325
+msgid "&Data Type"
+msgstr "&데이터 타입"
+
+#: ../IDEFrame.py:368
+msgid "&Delete"
+msgstr "&삭제"
+
+#: ../IDEFrame.py:317
+msgid "&Display"
+msgstr "&보기"
+
+#: ../IDEFrame.py:316
+#, fuzzy
+msgid "&Edit"
+msgstr "수정"
+
+#: ../IDEFrame.py:315
+msgid "&File"
+msgstr "&파일"
+
+#: ../IDEFrame.py:327
+msgid "&Function"
+msgstr "&함수"
+
+#: ../IDEFrame.py:318
+msgid "&Help"
+msgstr "&도움말"
+
+#: ../IDEFrame.py:331
+msgid "&Program"
+msgstr "&프로그램"
+
+#: ../PLCOpenEditor.py:148
+#, fuzzy
+msgid "&Properties"
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"&속성\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"&프로젝트 속성"
+
+#: ../Beremiz.py:310
msgid "&Recent Projects"
msgstr "&최근 프로젝트"
-#: ../Beremiz.py:350
+#: ../Beremiz.py:352
msgid "&Resource"
msgstr ""
+#: ../controls/SearchResultPanel.py:237
+#, python-format
+msgid "'%s' - %d match in project"
+msgstr "'%s' - %d 개의 검색된 결과"
+
+#: ../controls/SearchResultPanel.py:239
+#, python-format
+msgid "'%s' - %d matches in project"
+msgstr "'%s' - %d 개의 검색된 결과들"
+
#: ../connectors/PYRO/__init__.py:51
#, python-format
msgid "'%s' is located at %s\n"
msgstr ""
-#: ../ProjectController.py:1269
+#: ../controls/SearchResultPanel.py:289
+#, python-format
+msgid "(%d matches)"
+msgstr ""
+
+#: ../PLCOpenEditor.py:508
+#: ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:511
+msgid ", "
+msgstr ", "
+
+#: ../dialogs/PouTransitionDialog.py:96
+#: ../dialogs/PouActionDialog.py:93
+#: ../dialogs/PouDialog.py:113
+#: ../dialogs/SFCTransitionDialog.py:146
+#, python-format
+msgid ", %s"
+msgstr ", %s"
+
+#: ../PLCOpenEditor.py:506
+msgid ". "
+msgstr ". "
+
+#: ../ProjectController.py:1268
msgid "... debugger recovered\n"
msgstr "... 디버거 복구\n"
+#: ../IDEFrame.py:1672
+#: ../IDEFrame.py:1714
+#: ../IDEFrame.py:1733
+#: ../dialogs/PouDialog.py:122
+#, python-format
+msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr "POU 구성원의 이름 \"%s\"은 오류를 발생시킬 수 있습니다. 계속 하시겠습니까?"
+
+#: ../controls/VariablePanel.py:658
+#: ../IDEFrame.py:1684
+#: ../IDEFrame.py:1695
+#: ../dialogs/PouNameDialog.py:51
+#: ../dialogs/PouTransitionDialog.py:103
+#: ../dialogs/SFCStepNameDialog.py:53
+#: ../dialogs/PouActionDialog.py:100
+#: ../dialogs/SFCStepDialog.py:124
+#, python-format
+msgid "A POU named \"%s\" already exists!"
+msgstr "이미 생성된 POU 이름입니다 : \"%s\""
+
#: ../ConfigTreeNode.py:371
#, fuzzy, python-format
msgid "A child named \"%s\" already exist -> \"%s\"\n"
msgstr "입력 하신 \"%s\" 이 중복 되는 이름입니다. -> \"%s\"\n"
-#: ../Beremiz.py:360
+#: ../dialogs/BrowseLocationsDialog.py:175
+msgid "A location must be selected!"
+msgstr "위치를 지정해야 합니다!"
+
+#: ../controls/VariablePanel.py:660
+#: ../IDEFrame.py:1686
+#: ../IDEFrame.py:1697
+#: ../dialogs/SFCStepNameDialog.py:55
+#: ../dialogs/SFCStepDialog.py:126
+#, python-format
+msgid "A variable with \"%s\" as name already exists in this pou!"
+msgstr "변수 \"%s\"는 이미 POU에 정의 되어 있습니다!"
+
+#: ../Beremiz.py:362
+#: ../PLCOpenEditor.py:181
+#, fuzzy
msgid "About"
-msgstr "베레미즈는..."
-
-#: ../Beremiz.py:929
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"베레미즈는...\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"베레미즈란"
+
+#: ../Beremiz.py:931
msgid "About Beremiz"
msgstr "베레미즈"
+#: ../PLCOpenEditor.py:376
+msgid "About PLCOpenEditor"
+msgstr "PLC 오픈에디터..."
+
+#: ../plcopen/iec_std.csv:22
+msgid "Absolute number"
+msgstr "절대값 연산"
+
+#: ../dialogs/ActionBlockDialog.py:41
+#: ../dialogs/SFCStepDialog.py:69
+msgid "Action"
+msgstr "액션(Action)"
+
+#: ../editors/Viewer.py:495
+#, fuzzy
+msgid "Action Block"
+msgstr "함수 블럭(Function Block)"
+
+#: ../dialogs/PouActionDialog.py:81
+msgid "Action Name"
+msgstr "액션 명"
+
+#: ../dialogs/PouActionDialog.py:49
+msgid "Action Name:"
+msgstr "액션 명:"
+
+#: ../plcopen/plcopen.py:1480
+#, python-format
+msgid "Action with name %s doesn't exist!"
+msgstr "액션 명 %s는 이미 존재합니다!"
+
+#: ../PLCControler.py:95
+msgid "Actions"
+msgstr "액션(Actions)"
+
+#: ../dialogs/ActionBlockDialog.py:134
+msgid "Actions:"
+msgstr "액션(Actions):"
+
#: ../canfestival/SlaveEditor.py:54
#: ../canfestival/NetworkEditor.py:84
+#: ../editors/Viewer.py:527
msgid "Add"
msgstr "추가"
+#: ../IDEFrame.py:1925
+#: ../IDEFrame.py:1956
+msgid "Add Action"
+msgstr "액션 추가"
+
#: ../features.py:7
msgid "Add C code accessing located variables synchronously"
msgstr "동기적으로 위치한 변수를 액세스하는 C 코드를 추가합니다"
-#: ../util/discovery.py:115
+#: ../IDEFrame.py:1908
+msgid "Add Configuration"
+msgstr "설정(Configuration) 추가"
+
+#: ../IDEFrame.py:1888
+msgid "Add DataType"
+msgstr "데이터 타입 추가"
+
+#: ../editors/Viewer.py:453
+msgid "Add Divergence Branch"
+msgstr "Branch 추가"
+
+#: ../dialogs/DiscoveryDialog.py:115
msgid "Add IP"
msgstr ""
+#: ../IDEFrame.py:1896
+msgid "Add POU"
+msgstr "POU 추가"
+
#: ../features.py:8
msgid "Add Python code executed asynchronously"
msgstr "비동기적으로 실행되는 파이썬 코드를 추가합니다"
+#: ../IDEFrame.py:1936
+#: ../IDEFrame.py:1982
+msgid "Add Resource"
+msgstr "리소스 추가"
+
+#: ../IDEFrame.py:1914
+#: ../IDEFrame.py:1953
+msgid "Add Transition"
+msgstr "트랜지션(Transition) 추가"
+
+#: ../editors/Viewer.py:442
+msgid "Add Wire Segment"
+msgstr "와이어 세그먼트(Wire Segment) 추가"
+
+#: ../editors/SFCViewer.py:359
+msgid "Add a new initial step"
+msgstr "새로운 초기 스텝 추가"
+
+#: ../editors/Viewer.py:2289
+#: ../editors/SFCViewer.py:696
+msgid "Add a new jump"
+msgstr "새로운 점프 추가"
+
+#: ../editors/SFCViewer.py:381
+msgid "Add a new step"
+msgstr "새로운 스텝 추가"
+
#: ../features.py:9
msgid "Add a simple WxGlade based GUI."
msgstr "간단 WxGlade GUI를 추가합니다"
+#: ../dialogs/ActionBlockDialog.py:138
+#, fuzzy
+msgid "Add action"
+msgstr "액션 추가"
+
+#: ../editors/DataTypeEditor.py:345
+#, fuzzy
+msgid "Add element"
+msgstr "구성원(Element) 추가"
+
+#: ../editors/ResourceEditor.py:251
+#, fuzzy
+msgid "Add instance"
+msgstr "인스턴스 추가"
+
#: ../canfestival/NetworkEditor.py:86
msgid "Add slave"
msgstr "슬레이브 추가"
-#: ../util/FileManagementPanel.py:35
+#: ../editors/ResourceEditor.py:222
+#, fuzzy
+msgid "Add task"
+msgstr "태스크 추가"
+
+#: ../controls/VariablePanel.py:378
+#, fuzzy
+msgid "Add variable"
+msgstr "변수"
+
+#: ../plcopen/iec_std.csv:33
+msgid "Addition"
+msgstr "추가"
+
+#: ../plcopen/structures.py:250
+msgid "Additional function blocks"
+msgstr "추가적 함수 블록"
+
+#: ../editors/Viewer.py:1395
+msgid "Alignment"
+msgstr "정렬"
+
+#: ../controls/VariablePanel.py:75
+#: ../dialogs/BrowseLocationsDialog.py:35
+#: ../dialogs/BrowseLocationsDialog.py:116
+msgid "All"
+msgstr "모두"
+
+#: ../editors/FileManagementPanel.py:35
#, fuzzy
msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
msgstr "SVG 파일 (*.svg)|*svg|모든 파일|*.*"
-#: ../ProjectController.py:1336
+#: ../ProjectController.py:1335
msgid "Already connected. Please disconnect\n"
msgstr "이미 접속중입니다. 연결을 해제 하세요\n"
+#: ../editors/DataTypeEditor.py:587
+#, python-format
+msgid "An element named \"%s\" already exists in this structure!"
+msgstr "항목 \"%s\" 는 이미 구조체 안에 존재 합니다!"
+
+#: ../plcopen/iec_std.csv:31
+msgid "Arc cosine"
+msgstr "Arc cosine"
+
+#: ../plcopen/iec_std.csv:30
+msgid "Arc sine"
+msgstr "Arc sine"
+
+#: ../plcopen/iec_std.csv:32
+msgid "Arc tangent"
+msgstr "Arc tangent"
+
+#: ../plcopen/iec_std.csv:33
+msgid "Arithmetic"
+msgstr "산술 연산"
+
+#: ../controls/VariablePanel.py:729
+#: ../editors/DataTypeEditor.py:52
+msgid "Array"
+msgstr "배열"
+
+#: ../plcopen/iec_std.csv:39
+msgid "Assignment"
+msgstr "할당"
+
+#: ../dialogs/FBDVariableDialog.py:197
+msgid "At least a variable or an expression must be selected!"
+msgstr "변수 또는 표현식이 필요합니다!"
+
+#: ../controls/ProjectPropertiesPanel.py:99
+msgid "Author"
+msgstr "작성자"
+
+#: ../controls/ProjectPropertiesPanel.py:96
+msgid "Author Name (optional):"
+msgstr "작성자 이름(옵션):"
+
+#: ../dialogs/FindInPouDialog.py:72
+msgid "Backward"
+msgstr ""
+
#: ../util/Zeroconf.py:599
msgid "Bad domain name (circular) at "
msgstr "도메인 이름이 올바르지 않습니다(circular 네임):"
@@ -137,36 +696,93 @@
msgid "Bad location size : %s"
msgstr "잘못된 로케이션 사이즈: %s"
-#: ../Beremiz.py:484
+#: ../editors/DataTypeEditor.py:168
+#: ../editors/DataTypeEditor.py:198
+#: ../editors/DataTypeEditor.py:290
+#: ../dialogs/ArrayTypeDialog.py:55
+msgid "Base Type:"
+msgstr "기본 타입:"
+
+#: ../controls/VariablePanel.py:699
+#: ../editors/DataTypeEditor.py:617
+msgid "Base Types"
+msgstr "기본 타입"
+
+#: ../Beremiz.py:486
msgid "Beremiz"
msgstr "베레미즈"
-#: ../util/BrowseValuesLibraryDialog.py:37
+#: ../plcopen/iec_std.csv:70
+msgid "Binary selection (1 of 2)"
+msgstr "바이너리 선택 (1 또는 2)"
+
+#: ../plcopen/iec_std.csv:62
+msgid "Bit-shift"
+msgstr "비트-쉬프트"
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise"
+msgstr "비트 연산"
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise AND"
+msgstr "비트 연산 AND"
+
+#: ../plcopen/iec_std.csv:67
+msgid "Bitwise OR"
+msgstr "비트 연산 OR"
+
+#: ../plcopen/iec_std.csv:68
+msgid "Bitwise XOR"
+msgstr "비트 연산 XOR"
+
+#: ../plcopen/iec_std.csv:69
+msgid "Bitwise inverting"
+msgstr "비트 연산 반전 (Invert)"
+
+#: ../editors/Viewer.py:465
+#, fuzzy
+msgid "Block"
+msgstr "블럭 수정"
+
+#: ../dialogs/FBDBlockDialog.py:38
+msgid "Block Properties"
+msgstr "블럭 속성"
+
+#: ../editors/Viewer.py:434
+msgid "Bottom"
+msgstr "하단"
+
+#: ../dialogs/BrowseValuesLibraryDialog.py:37
#, fuzzy, python-format
msgid "Browse %s values library"
msgstr "%s 라이브러리 탐색"
-#: ../ProjectController.py:1485
+#: ../dialogs/BrowseLocationsDialog.py:55
+msgid "Browse Locations"
+msgstr "위치 탐색"
+
+#: ../ProjectController.py:1484
msgid "Build"
msgstr "빌드"
-#: ../ProjectController.py:1052
+#: ../ProjectController.py:1051
msgid "Build directory already clean\n"
msgstr "빌드 디렉토리가 이미 비어 있습니다\n"
-#: ../ProjectController.py:1486
+#: ../ProjectController.py:1485
msgid "Build project into build folder"
msgstr "현재 작성된 프로젝트를 빌드 폴더에 생성합니다"
-#: ../ProjectController.py:911
+#: ../ProjectController.py:910
msgid "C Build crashed !\n"
msgstr "C 파일 빌드 과정에 문제가 있습니다!\n"
-#: ../ProjectController.py:908
+#: ../ProjectController.py:907
msgid "C Build failed.\n"
msgstr "C 파일 빌드에 실패 했습니다\n"
-#: ../ProjectController.py:896
+#: ../ProjectController.py:895
msgid "C code generated successfully.\n"
msgstr "C 코드가 성공적으로 생성되었습니다\n"
@@ -183,6 +799,36 @@
msgid "CANopen support"
msgstr "CANopen 지원"
+#: ../plcopen/plcopen.py:1722
+#: ../plcopen/plcopen.py:1736
+#: ../plcopen/plcopen.py:1757
+#: ../plcopen/plcopen.py:1773
+msgid "Can only generate execution order on FBD networks!"
+msgstr "FBD 네트워크 상태에서만 실행 순서를 생성할 수 있습니다!"
+
+#: ../controls/VariablePanel.py:256
+msgid "Can only give a location to local or global variables"
+msgstr "로컬 또는 전역 변수만 위치 지정 가능합니다"
+
+#: ../PLCOpenEditor.py:357
+#, python-format
+msgid "Can't generate program to file %s!"
+msgstr "프로그램을 %s 파일로 생성할 수 없습니다!"
+
+#: ../controls/VariablePanel.py:254
+msgid "Can't give a location to a function block instance"
+msgstr "함수 블럭 인스턴스에는 위치를 지정할 수 없습니다"
+
+#: ../PLCOpenEditor.py:397
+#, python-format
+msgid "Can't save project to file %s!"
+msgstr "프로젝트를 %s로 저장할 수 없습니다!"
+
+#: ../controls/VariablePanel.py:298
+#, fuzzy
+msgid "Can't set an initial value to a function block instance"
+msgstr "함수 블럭 인스턴스에는 위치를 지정할 수 없습니다"
+
#: ../ConfigTreeNode.py:470
#, python-format
msgid "Cannot create child %s of type %s "
@@ -197,7 +843,7 @@
msgid "Cannot get PLC status - connection failed.\n"
msgstr "현재 PLC 상태를 알 수 없습니다 - 접속 실패.\n"
-#: ../ProjectController.py:716
+#: ../ProjectController.py:715
msgid "Cannot open/parse VARIABLES.csv!\n"
msgstr "VARIABLES.csv 파일을 열거나 파싱할 수 없습니다!\n"
@@ -206,6 +852,15 @@
msgid "Cannot set bit offset for non bool '%s' variable (ID:%d,Idx:%x,sIdx:%x))"
msgstr "'%s' 변수의 비트 옵셋 설정 실패 (ID:%d,ldx:%x,sldx:%x))"
+#: ../dialogs/FindInPouDialog.py:81
+#: ../dialogs/SearchInProjectDialog.py:67
+msgid "Case sensitive"
+msgstr "대소문자 구별"
+
+#: ../editors/Viewer.py:429
+msgid "Center"
+msgstr "중앙"
+
#: ../Beremiz_service.py:322
msgid "Change IP of interface to bind"
msgstr "바인드 인터페이스 IP 변경"
@@ -214,6 +869,10 @@
msgid "Change Name"
msgstr "이름 변경"
+#: ../IDEFrame.py:1974
+msgid "Change POU Type To"
+msgstr "POU 타입 변경"
+
#: ../Beremiz_service.py:325
msgid "Change Port Number"
msgstr "포트 번호 변경"
@@ -222,25 +881,31 @@
msgid "Change working directory"
msgstr "작업 디렉토리 변경"
+#: ../plcopen/iec_std.csv:81
+msgid "Character string"
+msgstr "문자열"
+
#: ../svgui/svgui.py:92
msgid "Choose a SVG file"
msgstr "SVG 파일 선택"
-#: ../ProjectController.py:354
+#: ../ProjectController.py:353
msgid "Choose a directory to save project"
msgstr "프로젝트 저장 디렉토리 선택"
#: ../canfestival/canfestival.py:118
-#, fuzzy
+#: ../PLCOpenEditor.py:313
+#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:391
msgid "Choose a file"
-msgstr "SVG 파일 선택"
-
-#: ../Beremiz.py:829
-#: ../Beremiz.py:864
+msgstr "파일 선택"
+
+#: ../Beremiz.py:831
+#: ../Beremiz.py:866
msgid "Choose a project"
msgstr "프로젝트 선택"
-#: ../util/BrowseValuesLibraryDialog.py:42
+#: ../dialogs/BrowseValuesLibraryDialog.py:42
#, fuzzy, python-format
msgid "Choose a value for %s:"
msgstr "%s 선택:"
@@ -249,51 +914,112 @@
msgid "Choose a working directory "
msgstr "작업 디렉토리 선택"
-#: ../ProjectController.py:282
+#: ../ProjectController.py:281
msgid "Chosen folder doesn't contain a program. It's not a valid project!"
msgstr "베레미즈 프로젝트 폴더를 선택해 주세요. 선택하신 폴더는 프로젝트 폴더가 아닙니다!"
-#: ../ProjectController.py:248
+#: ../ProjectController.py:247
msgid "Chosen folder isn't empty. You can't use it for a new project!"
msgstr "선택된 폴더는 비어있지 않습니다. 새 프로젝트 폴더로 사용 할 수 없습니다!"
-#: ../ProjectController.py:1489
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Class"
+msgstr "클래스"
+
+#: ../controls/VariablePanel.py:369
+msgid "Class Filter:"
+msgstr "클래스 필터:"
+
+#: ../dialogs/FBDVariableDialog.py:62
+msgid "Class:"
+msgstr "클래스:"
+
+#: ../ProjectController.py:1488
msgid "Clean"
msgstr "클린"
-#: ../ProjectController.py:1491
+#: ../ProjectController.py:1490
msgid "Clean project build folder"
msgstr "프로젝트 빌드 폴더를 비웁니다"
-#: ../ProjectController.py:1049
+#: ../ProjectController.py:1048
msgid "Cleaning the build directory\n"
msgstr "빌드 디렉토리를 비우는 중\n"
-#: ../Beremiz.py:596
+#: ../IDEFrame.py:411
+#, fuzzy
+msgid "Clear Errors"
+msgstr "에러 화면 클리어\tCTRL+K"
+
+#: ../editors/Viewer.py:520
+msgid "Clear Execution Order"
+msgstr "실행 순서 클리어"
+
+#: ../editors/GraphicViewer.py:125
+msgid "Clear the graph values"
+msgstr ""
+
+#: ../Beremiz.py:598
+#: ../PLCOpenEditor.py:221
msgid "Close Application"
msgstr "어플리케이션 닫기"
-#: ../Beremiz.py:550
+#: ../IDEFrame.py:1089
+#: ../Beremiz.py:319
+#: ../Beremiz.py:552
+#: ../PLCOpenEditor.py:131
msgid "Close Project"
msgstr "프로젝트 닫기"
#: ../Beremiz.py:317
-msgid "Close Project\tCTRL+SHIFT+W"
-msgstr "프로젝트 닫기\tCTRL+SHIFT+W"
-
-#: ../Beremiz.py:315
-msgid "Close Tab\tCTRL+W"
+#: ../PLCOpenEditor.py:129
+#, fuzzy
+msgid "Close Tab"
msgstr "탭 닫기\tCTRL+W"
-#: ../ProjectController.py:539
+#: ../editors/Viewer.py:481
+msgid "Coil"
+msgstr ""
+
+#: ../editors/Viewer.py:501
+#: ../editors/LDViewer.py:503
+msgid "Comment"
+msgstr "코멘트"
+
+#: ../controls/ProjectPropertiesPanel.py:94
+msgid "Company Name (required):"
+msgstr "회사명(필수):"
+
+#: ../controls/ProjectPropertiesPanel.py:95
+msgid "Company URL (optional):"
+msgstr "회사 URL(옵션):"
+
+#: ../plcopen/iec_std.csv:75
+msgid "Comparison"
+msgstr "비교연산"
+
+#: ../ProjectController.py:538
msgid "Compiling IEC Program into C code...\n"
msgstr "IEC 프로그램을 C코드로 컴파일링 중...\n"
-#: ../ProjectController.py:1504
+#: ../plcopen/iec_std.csv:85
+msgid "Concatenation"
+msgstr "문자열 연결(concatenation)"
+
+#: ../dialogs/SearchInProjectDialog.py:47
+msgid "Configuration"
+msgstr "설정(Configuration)"
+
+#: ../PLCControler.py:96
+msgid "Configurations"
+msgstr "구성(Configurations)"
+
+#: ../ProjectController.py:1503
msgid "Connect"
msgstr "연결하기"
-#: ../ProjectController.py:1505
+#: ../ProjectController.py:1504
msgid "Connect to the target PLC"
msgstr "타겟 PLC와 연결"
@@ -302,11 +1028,20 @@
msgid "Connecting to URI : %s\n"
msgstr "URI 주소로 연결중 : %s\n"
-#: ../ProjectController.py:1360
+#: ../editors/Viewer.py:467
+#: ../dialogs/SFCTransitionDialog.py:76
+msgid "Connection"
+msgstr "연결"
+
+#: ../dialogs/ConnectionDialog.py:37
+msgid "Connection Properties"
+msgstr "연결 속성"
+
+#: ../ProjectController.py:1359
msgid "Connection canceled!\n"
msgstr "연결 취소!\n"
-#: ../ProjectController.py:1385
+#: ../ProjectController.py:1384
#, python-format
msgid "Connection failed to %s!\n"
msgstr "%s 연결에 실패 하였습니다!\n"
@@ -316,14 +1051,69 @@
msgid "Connection to '%s' failed.\n"
msgstr "%s 의 C 컴파일 작업이 실패 했습니다\n"
-#: ../util/FileManagementPanel.py:283
+#: ../dialogs/ConnectionDialog.py:56
+msgid "Connector"
+msgstr "연결자(Connector)"
+
+#: ../dialogs/SFCStepDialog.py:58
+msgid "Connectors:"
+msgstr "연결자(Connectors):"
+
+#: ../controls/VariablePanel.py:65
+msgid "Constant"
+msgstr "상수"
+
+#: ../editors/Viewer.py:477
+#, fuzzy
+msgid "Contact"
+msgstr "지속 커넥터"
+
+#: ../controls/ProjectPropertiesPanel.py:197
+msgid "Content Description (optional):"
+msgstr "컨텐츠 설명(옵션):"
+
+#: ../dialogs/ConnectionDialog.py:61
+msgid "Continuation"
+msgstr "지속 커넥터"
+
+#: ../plcopen/iec_std.csv:18
+msgid "Conversion from BCD"
+msgstr "BCD에서 변환"
+
+#: ../plcopen/iec_std.csv:19
+msgid "Conversion to BCD"
+msgstr "BCD로 변환"
+
+#: ../plcopen/iec_std.csv:21
+msgid "Conversion to date"
+msgstr "날짜로 변환"
+
+#: ../plcopen/iec_std.csv:20
+msgid "Conversion to time-of-day"
+msgstr "시간으로 변환"
+
+#: ../IDEFrame.py:348
+#: ../IDEFrame.py:401
+#: ../editors/Viewer.py:536
+msgid "Copy"
+msgstr "복사하기"
+
+#: ../IDEFrame.py:1961
+msgid "Copy POU"
+msgstr "POU 복사"
+
+#: ../editors/FileManagementPanel.py:283
msgid "Copy file from left folder to right"
msgstr ""
-#: ../util/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:282
msgid "Copy file from right folder to left"
msgstr ""
+#: ../plcopen/iec_std.csv:28
+msgid "Cosine"
+msgstr "Cosine"
+
#: ../ConfigTreeNode.py:582
#, python-format
msgid ""
@@ -351,15 +1141,20 @@
"플러그인 파라메터를 불러 올 수 없습니다 %s:\n"
"%s"
-#: ../ProjectController.py:1318
+#: ../PLCControler.py:765
+#: ../PLCControler.py:802
+msgid "Couldn't paste non-POU object."
+msgstr "POU 오브젝트만 붙여넣기 가능합니다"
+
+#: ../ProjectController.py:1317
msgid "Couldn't start PLC !\n"
msgstr "PLC 를 시작 할 수 없습니다!\n"
-#: ../ProjectController.py:1326
+#: ../ProjectController.py:1325
msgid "Couldn't stop PLC !\n"
msgstr "PLC 를 정지 할 수 없습니다!\n"
-#: ../ProjectController.py:1296
+#: ../ProjectController.py:1295
msgid "Couldn't stop debugger.\n"
msgstr "디버거를 정지 할 수 없습니다.\n"
@@ -367,6 +1162,111 @@
msgid "Create HMI"
msgstr "HMI 생성"
+#: ../dialogs/PouDialog.py:43
+msgid "Create a new POU"
+msgstr "새로운 POU 생성"
+
+#: ../dialogs/PouActionDialog.py:38
+msgid "Create a new action"
+msgstr "새로운 액션 생성"
+
+#: ../IDEFrame.py:135
+msgid "Create a new action block"
+msgstr "새로운 액션 블럭 생성"
+
+#: ../IDEFrame.py:84
+#: ../IDEFrame.py:114
+#: ../IDEFrame.py:147
+msgid "Create a new block"
+msgstr "새로운 블럭 생성"
+
+#: ../IDEFrame.py:108
+msgid "Create a new branch"
+msgstr "새로운 분기(Branch) 생성"
+
+#: ../IDEFrame.py:102
+msgid "Create a new coil"
+msgstr "새로운 코일 생성"
+
+#: ../IDEFrame.py:78
+#: ../IDEFrame.py:93
+#: ../IDEFrame.py:123
+msgid "Create a new comment"
+msgstr "새로운 코멘트 생성"
+
+#: ../IDEFrame.py:87
+#: ../IDEFrame.py:117
+#: ../IDEFrame.py:150
+msgid "Create a new connection"
+msgstr "새로운 연결 생성"
+
+#: ../IDEFrame.py:105
+#: ../IDEFrame.py:156
+msgid "Create a new contact"
+msgstr "새로운 접점 생성"
+
+#: ../IDEFrame.py:138
+msgid "Create a new divergence"
+msgstr "새로운 분기(divergence) 생성"
+
+#: ../dialogs/SFCDivergenceDialog.py:36
+msgid "Create a new divergence or convergence"
+msgstr "새로운 분기(divergence) 또는 합류(convergence) 생성"
+
+#: ../IDEFrame.py:126
+msgid "Create a new initial step"
+msgstr "새로운 이니셜 스텝 생성"
+
+#: ../IDEFrame.py:141
+msgid "Create a new jump"
+msgstr "새로운 점프 생성"
+
+#: ../IDEFrame.py:96
+#: ../IDEFrame.py:153
+msgid "Create a new power rail"
+msgstr "새로운 전원 레일 생성"
+
+#: ../IDEFrame.py:99
+msgid "Create a new rung"
+msgstr "새로운 Rung 생성"
+
+#: ../IDEFrame.py:129
+msgid "Create a new step"
+msgstr "새로운 스텝 생성"
+
+#: ../IDEFrame.py:132
+#: ../dialogs/PouTransitionDialog.py:42
+msgid "Create a new transition"
+msgstr "새로운 트랜지션 생성"
+
+#: ../IDEFrame.py:81
+#: ../IDEFrame.py:111
+#: ../IDEFrame.py:144
+msgid "Create a new variable"
+msgstr "새로운 변수 생성"
+
+#: ../IDEFrame.py:346
+#: ../IDEFrame.py:400
+#: ../editors/Viewer.py:535
+msgid "Cut"
+msgstr "잘라내기"
+
+#: ../editors/ResourceEditor.py:71
+msgid "Cyclic"
+msgstr "주기적"
+
+#: ../plcopen/iec_std.csv:42
+#: ../plcopen/iec_std.csv:44
+#: ../plcopen/iec_std.csv:46
+#: ../plcopen/iec_std.csv:50
+#: ../plcopen/iec_std.csv:52
+#: ../plcopen/iec_std.csv:54
+#: ../plcopen/iec_std.csv:56
+#: ../plcopen/iec_std.csv:58
+#: ../plcopen/iec_std.csv:60
+msgid "DEPRECATED"
+msgstr "사용중지됨"
+
#: ../canfestival/SlaveEditor.py:50
#: ../canfestival/NetworkEditor.py:80
msgid "DS-301 Profile"
@@ -377,75 +1277,270 @@
msgid "DS-302 Profile"
msgstr "DS-302 프로필"
-#: ../ProjectController.py:1406
+#: ../dialogs/SearchInProjectDialog.py:43
+msgid "Data Type"
+msgstr "데이터 타입(Data Type)"
+
+#: ../PLCControler.py:95
+msgid "Data Types"
+msgstr "데이터 타입(Data Types)"
+
+#: ../plcopen/iec_std.csv:16
+msgid "Data type conversion"
+msgstr "데이터 타입 변환"
+
+#: ../plcopen/iec_std.csv:44
+#: ../plcopen/iec_std.csv:45
+msgid "Date addition"
+msgstr "날짜 추가"
+
+#: ../plcopen/iec_std.csv:56
+#: ../plcopen/iec_std.csv:57
+#: ../plcopen/iec_std.csv:58
+#: ../plcopen/iec_std.csv:59
+msgid "Date and time subtraction"
+msgstr "날짜, 시간 뺄셈"
+
+#: ../plcopen/iec_std.csv:50
+#: ../plcopen/iec_std.csv:51
+msgid "Date subtraction"
+msgstr "날짜 뺄셈"
+
+#: ../dialogs/DurationEditorDialog.py:43
+msgid "Days:"
+msgstr ""
+
+#: ../ProjectController.py:1405
msgid "Debug connect matching running PLC\n"
msgstr "디버그 작동중인 PLC와 매칭 되었습니다\n"
-#: ../ProjectController.py:1409
+#: ../ProjectController.py:1408
msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
msgstr "PLC와 디버거가 연결되지 않았습니다 - 정지/전송/시작을 다시 시도하세요\n"
-#: ../ProjectController.py:1123
+#: ../controls/PouInstanceVariablesPanel.py:52
+#, fuzzy
+msgid "Debug instance"
+msgstr "인스턴스 삭제"
+
+#: ../editors/Viewer.py:3222
+#, python-format
+msgid "Debug: %s"
+msgstr "디버그: %s"
+
+#: ../ProjectController.py:1122
#, fuzzy, python-format
msgid "Debug: Unknown variable '%s'\n"
msgstr "디버그 : 알 수 없는 변수 %s\n"
-#: ../ProjectController.py:1121
+#: ../ProjectController.py:1120
#, fuzzy, python-format
msgid "Debug: Unsupported type to debug '%s'\n"
msgstr "디버그 : 지원하지 않는 타입 %s\n"
-#: ../ProjectController.py:1286
+#: ../IDEFrame.py:608
+msgid "Debugger"
+msgstr "디버거"
+
+#: ../ProjectController.py:1285
msgid "Debugger disabled\n"
msgstr "디버거 사용 불가\n"
-#: ../ProjectController.py:1298
+#: ../ProjectController.py:1297
msgid "Debugger stopped.\n"
msgstr "디버거 정지.\n"
-#: ../Beremiz.py:956
+#: ../IDEFrame.py:1990
+#: ../Beremiz.py:958
+#: ../editors/Viewer.py:511
msgid "Delete"
-msgstr ""
-
-#: ../util/FileManagementPanel.py:371
+msgstr "삭제"
+
+#: ../editors/Viewer.py:454
+msgid "Delete Divergence Branch"
+msgstr "파생된 Branch 삭제 (Divergence Branch)"
+
+#: ../editors/FileManagementPanel.py:371
msgid "Delete File"
msgstr ""
-#: ../ProjectController.py:1513
+#: ../editors/Viewer.py:443
+msgid "Delete Wire Segment"
+msgstr "와이어 세그먼트 삭제"
+
+#: ../controls/CustomEditableListBox.py:41
+msgid "Delete item"
+msgstr "항목 삭제"
+
+#: ../plcopen/iec_std.csv:88
+msgid "Deletion (within)"
+msgstr "내부 삭제"
+
+#: ../editors/DataTypeEditor.py:146
+msgid "Derivation Type:"
+msgstr "미분 타입"
+
+#: ../plcopen/structures.py:264
+msgid ""
+"Derivative\n"
+"The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
+msgstr ""
+"미분\n"
+"미분 함수 블럭은 입력 XIN의 변화의 속도에 비례하여 출력 XOUT을 생성합니다"
+
+#: ../controls/VariablePanel.py:360
+#, fuzzy
+msgid "Description:"
+msgstr "방향:"
+
+#: ../editors/DataTypeEditor.py:314
+#: ../dialogs/ArrayTypeDialog.py:61
+msgid "Dimensions:"
+msgstr "넓이:"
+
+#: ../dialogs/FindInPouDialog.py:61
+#, fuzzy
+msgid "Direction"
+msgstr "방향:"
+
+#: ../dialogs/BrowseLocationsDialog.py:78
+msgid "Direction:"
+msgstr "방향:"
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Directly"
+msgstr "직접"
+
+#: ../ProjectController.py:1512
msgid "Disconnect"
msgstr "연결 해제"
-#: ../ProjectController.py:1515
+#: ../ProjectController.py:1514
msgid "Disconnect from PLC"
msgstr "PLC 연결 해제"
-#: ../util/FileManagementPanel.py:370
+#: ../editors/Viewer.py:496
+#, fuzzy
+msgid "Divergence"
+msgstr "벡터 발산(Divergence) 선택"
+
+#: ../plcopen/iec_std.csv:36
+msgid "Division"
+msgstr "분할"
+
+#: ../editors/FileManagementPanel.py:370
#, python-format
msgid "Do you really want to delete the file '%s'?"
msgstr ""
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Documentation"
+msgstr "도움문서"
+
+#: ../PLCOpenEditor.py:351
+msgid "Done"
+msgstr "완료"
+
+#: ../plcopen/structures.py:227
+msgid ""
+"Down-counter\n"
+"The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
+msgstr ""
+"감산 카운터\n"
+"감산 카운터는 사용자가 설정한 값으로부터 감소하여 0이 될 때 신호를 발생시킵니다."
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Duration"
+msgstr "지속시간"
+
#: ../canfestival/canfestival.py:118
#, fuzzy
msgid "EDS files (*.eds)|*.eds|All files|*.*"
msgstr "SVG 파일 (*.svg)|*svg|모든 파일|*.*"
+#: ../editors/Viewer.py:510
+msgid "Edit Block"
+msgstr "블럭 수정"
+
+#: ../dialogs/LDElementDialog.py:41
+msgid "Edit Coil Values"
+msgstr "코일 데이터 수정"
+
+#: ../dialogs/LDElementDialog.py:38
+msgid "Edit Contact Values"
+msgstr "접접 데이터 수정"
+
+#: ../dialogs/DurationEditorDialog.py:59
+#, fuzzy
+msgid "Edit Duration"
+msgstr "트랜지션 수정"
+
+#: ../dialogs/SFCStepDialog.py:35
+msgid "Edit Step"
+msgstr "스텝 수정"
+
#: ../wxglade_hmi/wxglade_hmi.py:12
msgid "Edit a WxWidgets GUI with WXGlade"
msgstr "WXGlade를 이용하여 GUI 수정"
-#: ../util/FileManagementPanel.py:284
+#: ../dialogs/ActionBlockDialog.py:122
+msgid "Edit action block properties"
+msgstr "액션 블럭 속성 수정"
+
+#: ../dialogs/ArrayTypeDialog.py:45
+#, fuzzy
+msgid "Edit array type properties"
+msgstr "액션 블럭 속성 수정"
+
+#: ../editors/Viewer.py:2112
+#: ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630
+#: ../editors/Viewer.py:2632
+msgid "Edit comment"
+msgstr "코멘트 수정"
+
+#: ../editors/FileManagementPanel.py:284
#, fuzzy
msgid "Edit file"
msgstr "C 파일 수정"
-#: ../ProjectController.py:1527
+#: ../controls/CustomEditableListBox.py:39
+msgid "Edit item"
+msgstr "항목 수정"
+
+#: ../editors/Viewer.py:2594
+msgid "Edit jump target"
+msgstr "점프 타겟 수정"
+
+#: ../ProjectController.py:1526
msgid "Edit raw IEC code added to code generated by PLCGenerator"
msgstr "PLCGenerator로 생성된 IEC 코드 수정"
-#: ../ProjectController.py:1014
+#: ../editors/SFCViewer.py:725
+msgid "Edit step name"
+msgstr "스텝 이름 수정"
+
+#: ../dialogs/SFCTransitionDialog.py:38
+msgid "Edit transition"
+msgstr "트랜지션 수정"
+
+#: ../IDEFrame.py:580
+msgid "Editor ToolBar"
+msgstr ""
+
+#: ../ProjectController.py:1013
msgid "Editor selection"
msgstr ""
+#: ../editors/DataTypeEditor.py:341
+msgid "Elements :"
+msgstr "구성원:"
+
+#: ../IDEFrame.py:343
+msgid "Enable Undo/Redo"
+msgstr "되돌리기/되돌리기 취소 활성화"
+
#: ../Beremiz_service.py:380
msgid "Enter a name "
msgstr "이름 입력"
@@ -458,25 +1553,77 @@
msgid "Enter the IP of the interface to bind"
msgstr "바인드 인터페이스 IP 입력"
+#: ../editors/DataTypeEditor.py:52
+msgid "Enumerated"
+msgstr "열거형 데이터"
+
+#: ../plcopen/iec_std.csv:77
+msgid "Equal to"
+msgstr "같은 값 일때"
+
#: ../Beremiz_service.py:270
#: ../Beremiz_service.py:394
-#: ../Beremiz.py:1081
-#: ../ProjectController.py:222
-#: ../util/BrowseValuesLibraryDialog.py:83
-#: ../util/FileManagementPanel.py:210
+#: ../controls/VariablePanel.py:330
+#: ../controls/VariablePanel.py:678
+#: ../controls/DebugVariablePanel.py:164
+#: ../IDEFrame.py:1083
+#: ../IDEFrame.py:1672
+#: ../IDEFrame.py:1709
+#: ../IDEFrame.py:1714
+#: ../IDEFrame.py:1728
+#: ../IDEFrame.py:1733
+#: ../IDEFrame.py:2422
+#: ../Beremiz.py:1083
+#: ../PLCOpenEditor.py:358
+#: ../PLCOpenEditor.py:363
+#: ../PLCOpenEditor.py:531
+#: ../PLCOpenEditor.py:541
+#: ../editors/TextViewer.py:376
+#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:548
+#: ../editors/DataTypeEditor.py:572
+#: ../editors/DataTypeEditor.py:577
+#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:719
+#: ../editors/DataTypeEditor.py:726
+#: ../editors/Viewer.py:366
+#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:883
+#: ../editors/FileManagementPanel.py:210
+#: ../ProjectController.py:221
+#: ../dialogs/PouNameDialog.py:53
+#: ../dialogs/PouTransitionDialog.py:107
+#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/ProjectDialog.py:71
+#: ../dialogs/SFCStepNameDialog.py:59
+#: ../dialogs/ConnectionDialog.py:152
+#: ../dialogs/FBDVariableDialog.py:201
+#: ../dialogs/PouActionDialog.py:104
+#: ../dialogs/BrowseValuesLibraryDialog.py:83
+#: ../dialogs/PouDialog.py:132
+#: ../dialogs/SFCTransitionDialog.py:147
+#: ../dialogs/DurationEditorDialog.py:121
+#: ../dialogs/DurationEditorDialog.py:163
+#: ../dialogs/SearchInProjectDialog.py:157
+#: ../dialogs/SFCStepDialog.py:130
+#: ../dialogs/ArrayTypeDialog.py:97
+#: ../dialogs/ArrayTypeDialog.py:103
+#: ../dialogs/FBDBlockDialog.py:164
+#: ../dialogs/ForceVariableDialog.py:169
msgid "Error"
msgstr "에러"
-#: ../ProjectController.py:588
+#: ../ProjectController.py:587
msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
msgstr "에러 : PLC 프로그램은 하나 이상의 설정과 리소스가 반드시 선언되어야 합니다!\n"
-#: ../ProjectController.py:580
+#: ../ProjectController.py:579
#, python-format
msgid "Error : IEC to C compiler returned %d\n"
msgstr "에러 : IEC -> C 컴파일러 %d\n"
-#: ../ProjectController.py:521
+#: ../ProjectController.py:520
#, python-format
msgid ""
"Error in ST/IL/SFC code generator :\n"
@@ -502,56 +1649,268 @@
msgid "Error: No PLC built\n"
msgstr "에러 : PLC 빌드 미생성\n"
-#: ../ProjectController.py:1379
+#: ../ProjectController.py:1378
#, python-format
msgid "Exception while connecting %s!\n"
msgstr "%s접속중 예외 상황이 발생했습니다!\n"
+#: ../dialogs/FBDBlockDialog.py:95
+msgid "Execution Control:"
+msgstr "실행 제어:"
+
+#: ../dialogs/FBDVariableDialog.py:76
+#: ../dialogs/FBDBlockDialog.py:87
+msgid "Execution Order:"
+msgstr "실행 순서:"
+
#: ../features.py:10
msgid "Experimental web based HMI"
msgstr "웹 기반의 HMI(실험중)"
+#: ../plcopen/iec_std.csv:38
+msgid "Exponent"
+msgstr "지수"
+
+#: ../plcopen/iec_std.csv:26
+msgid "Exponentiation"
+msgstr "지수화"
+
#: ../canfestival/canfestival.py:128
msgid "Export CanOpen slave to EDS file"
msgstr ""
+#: ../editors/GraphicViewer.py:144
+msgid "Export graph values to clipboard"
+msgstr ""
+
#: ../canfestival/canfestival.py:127
msgid "Export slave"
msgstr ""
-#: ../ProjectController.py:592
+#: ../dialogs/FBDVariableDialog.py:69
+msgid "Expression:"
+msgstr "표현식:"
+
+#: ../controls/VariablePanel.py:77
+msgid "External"
+msgstr "외부"
+
+#: ../ProjectController.py:591
msgid "Extracting Located Variables...\n"
msgstr "위치 변수(located variables) 추출 중...\n"
-#: ../ProjectController.py:1446
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "FBD"
+msgstr "FBD"
+
+#: ../ProjectController.py:1445
msgid "Failed : Must build before transfer.\n"
msgstr "실패 : 빌드 후에 전송 하세요.\n"
-#: ../ProjectController.py:901
+#: ../editors/Viewer.py:405
+#: ../dialogs/LDElementDialog.py:84
+msgid "Falling Edge"
+msgstr "폴링 엣지"
+
+#: ../plcopen/structures.py:217
+msgid ""
+"Falling edge detector\n"
+"The output produces a single pulse when a falling edge is detected."
+msgstr ""
+"폴링 엣지 검출\n"
+"출력부의 폴링 엣지를 검출합니다"
+
+#: ../ProjectController.py:900
msgid "Fatal : cannot get builder.\n"
msgstr "치명적 오류 : 빌드 파일 생성 프로그램을 찾을 수 없습니다.\n"
-#: ../util/FileManagementPanel.py:209
+#: ../dialogs/DurationEditorDialog.py:160
+#, fuzzy, python-format
+msgid "Field %s hasn't a valid value!"
+msgstr "\"%s\" 유효하지 않은 값입니다!"
+
+#: ../dialogs/DurationEditorDialog.py:162
+#, fuzzy, python-format
+msgid "Fields %s haven't a valid value!"
+msgstr "\"%s\" 유효하지 않은 값입니다!"
+
+#: ../editors/FileManagementPanel.py:209
#, fuzzy, python-format
msgid "File '%s' already exists!"
msgstr "입력 하신 \"%s\" 이 중복 되는 이름입니다. -> \"%s\"\n"
+#: ../IDEFrame.py:353
+#: ../dialogs/FindInPouDialog.py:30
+#: ../dialogs/FindInPouDialog.py:99
+msgid "Find"
+msgstr ""
+
+#: ../IDEFrame.py:355
+#, fuzzy
+msgid "Find Next"
+msgstr "인쇄\tCTRL+P"
+
+#: ../IDEFrame.py:357
+#, fuzzy
+msgid "Find Previous"
+msgstr "인쇄 페이지 미리보기\tCTRL+SHIFT+P"
+
+#: ../plcopen/iec_std.csv:90
+msgid "Find position"
+msgstr "위치 찾기"
+
+#: ../dialogs/FindInPouDialog.py:51
+msgid "Find:"
+msgstr ""
+
#: ../connectors/PYRO/__init__.py:125
msgid "Force runtime reload\n"
msgstr "실행환경 강제 리로딩\n"
-#: ../ProjectController.py:511
+#: ../controls/DebugVariablePanel.py:295
+#: ../editors/Viewer.py:1353
+msgid "Force value"
+msgstr "강제 데이터 입력"
+
+#: ../dialogs/ForceVariableDialog.py:152
+msgid "Forcing Variable Value"
+msgstr "강제 변수 데이터"
+
+#: ../dialogs/PouTransitionDialog.py:97
+#: ../dialogs/ProjectDialog.py:70
+#: ../dialogs/PouActionDialog.py:94
+#: ../dialogs/PouDialog.py:114
+#: ../dialogs/SFCTransitionDialog.py:147
+#, python-format
+msgid "Form isn't complete. %s must be filled!"
+msgstr "형식이 완성되지 않았습니다. %s 를 입력하세요!"
+
+#: ../dialogs/ConnectionDialog.py:142
+#: ../dialogs/FBDBlockDialog.py:154
+msgid "Form isn't complete. Name must be filled!"
+msgstr "형식이 완성되지 않았습니다. 이름을 입력하세요"
+
+#: ../dialogs/SearchInProjectDialog.py:145
+msgid "Form isn't complete. Pattern to search must be filled!"
+msgstr "형식이 완성되지 않았습니다. 검색어를 입력하세요!"
+
+#: ../dialogs/FBDBlockDialog.py:152
+msgid "Form isn't complete. Valid block type must be selected!"
+msgstr "형식이 완성되지 않았습니다. 블럭 타입을 선택하세요!"
+
+#: ../dialogs/FindInPouDialog.py:67
+msgid "Forward"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:44
+msgid "Function"
+msgstr "함수"
+
+#: ../IDEFrame.py:329
+msgid "Function &Block"
+msgstr "함수 &블록"
+
+#: ../IDEFrame.py:1969
+#: ../dialogs/SearchInProjectDialog.py:45
+msgid "Function Block"
+msgstr "함수 블럭(Function Block)"
+
+#: ../controls/VariablePanel.py:741
+msgid "Function Block Types"
+msgstr "함수 블럭 타입"
+
+#: ../PLCControler.py:94
+msgid "Function Blocks"
+msgstr "함수 블럭(Function Blocks)"
+
+#: ../editors/Viewer.py:236
+msgid "Function Blocks can't be used in Functions!"
+msgstr "함수 블럭을 함수에서 사용할 수 없습니다!"
+
+#: ../editors/Viewer.py:238
+msgid "Function Blocks can't be used in Transitions!"
+msgstr "함수 블럭을 트랜지션에서 사용할 수 없습니다!"
+
+#: ../PLCControler.py:2055
+#, python-format
+msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
+msgstr "함수 블럭 \"%s\" 을 함수에 붙여 넣기 할 수 없습니다!!!"
+
+#: ../PLCControler.py:94
+msgid "Functions"
+msgstr "함수 (Functions)"
+
+#: ../PLCOpenEditor.py:138
+#, fuzzy
+msgid "Generate Program"
+msgstr "프로그램 생성\tCTRL+G"
+
+#: ../ProjectController.py:510
msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
msgstr "IEC-61131 기반의 ST/IL/SFC 코드 생성중...\n"
-#: ../util/FileManagementPanel.py:303
+#: ../controls/VariablePanel.py:78
+msgid "Global"
+msgstr "글로벌"
+
+#: ../editors/GraphicViewer.py:131
+#, fuzzy
+msgid "Go to current value"
+msgstr "강제 데이터 입력"
+
+#: ../controls/ProjectPropertiesPanel.py:173
+msgid "Graphics"
+msgstr "그래픽"
+
+#: ../plcopen/iec_std.csv:75
+msgid "Greater than"
+msgstr "큰 값 일때"
+
+#: ../plcopen/iec_std.csv:76
+msgid "Greater than or equal to"
+msgstr "크거나 같은 값 일때"
+
+#: ../controls/ProjectPropertiesPanel.py:134
+msgid "Grid Resolution:"
+msgstr "격자 해상도:"
+
+#: ../controls/ProjectPropertiesPanel.py:120
+msgid "Height:"
+msgstr "높이:"
+
+#: ../editors/FileManagementPanel.py:303
msgid "Home Directory:"
msgstr ""
-#: ../ProjectController.py:828
+#: ../controls/ProjectPropertiesPanel.py:150
+msgid "Horizontal:"
+msgstr "가로:"
+
+#: ../dialogs/DurationEditorDialog.py:44
+msgid "Hours:"
+msgstr ""
+
+#: ../plcopen/structures.py:279
+msgid ""
+"Hysteresis\n"
+"The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
+msgstr ""
+"이력(Hysteresis)\n"
+"이력 펑션 블럭은 두 부동 소수점(REAL) 입력 XIN1, XIN2의 차이에 의한 이력(Hysteresis) 참,거짓(불린) 출력을 제공합니다"
+
+#: ../ProjectController.py:827
msgid "IEC-61131-3 code generation failed !\n"
msgstr "IEC-61131-3 코드 생성 실패!\n"
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "IL"
+msgstr "IL"
+
#: ../Beremiz_service.py:356
#: ../Beremiz_service.py:357
msgid "IP is not valid!"
@@ -562,17 +1921,176 @@
msgid "Import SVG"
msgstr "SVG 가져오기"
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/FBDVariableDialog.py:34
+msgid "InOut"
+msgstr "입출력"
+
+#: ../controls/VariablePanel.py:263
+#, python-format
+msgid "Incompatible data types between \"%s\" and \"%s\""
+msgstr "\"%s\" 와 \"%s\"간의 데이터 타입이 호환되지 않습니다"
+
+#: ../controls/VariablePanel.py:274
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"%s\""
+msgstr "\"%s\"와 \"%s\"간의 데이터 크기가 호환되지 않습니다"
+
+#: ../controls/VariablePanel.py:270
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"BOOL\""
+msgstr "\"%s\"와 \"BOOL\"간의 데이터 크기가 호환되지 않습니다"
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Indicator"
+msgstr "지시기(Indicator)"
+
+#: ../editors/Viewer.py:492
+#, fuzzy
+msgid "Initial Step"
+msgstr "초기 값"
+
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+msgid "Initial Value"
+msgstr "초기 값"
+
+#: ../editors/DataTypeEditor.py:178
+#: ../editors/DataTypeEditor.py:209
+#: ../editors/DataTypeEditor.py:265
+#: ../editors/DataTypeEditor.py:303
+msgid "Initial Value:"
+msgstr "초기 값:"
+
#: ../svgui/svgui.py:21
msgid "Inkscape"
msgstr "Inkscape"
+#: ../dialogs/ActionBlockDialog.py:41
+#: ../dialogs/SFCTransitionDialog.py:66
+#: ../dialogs/SFCTransitionDialog.py:137
+msgid "Inline"
+msgstr "인라인"
+
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/BrowseLocationsDialog.py:36
+#: ../dialogs/FBDVariableDialog.py:33
+#: ../dialogs/SFCStepDialog.py:61
+msgid "Input"
+msgstr "입력"
+
+#: ../dialogs/FBDBlockDialog.py:78
+msgid "Inputs:"
+msgstr "입력(Inputs):"
+
+#: ../plcopen/iec_std.csv:87
+msgid "Insertion (into)"
+msgstr "대상에 삽입"
+
+#: ../plcopen/plcopen.py:1833
+#, python-format
+msgid "Instance with id %d doesn't exist!"
+msgstr "ID %d의 인스턴스가 존재하지 않습니다!"
+
+#: ../editors/ResourceEditor.py:247
+msgid "Instances:"
+msgstr "인스턴스:"
+
+#: ../plcopen/structures.py:259
+msgid ""
+"Integral\n"
+"The integral function block integrates the value of input XIN over time."
+msgstr ""
+"적분\n"
+"적분 함수 블럭은 시간에 따른 입력 XIN의 데이터를 적분합니다"
+
+#: ../controls/VariablePanel.py:75
+msgid "Interface"
+msgstr "인터페이스"
+
+#: ../editors/ResourceEditor.py:71
+msgid "Interrupt"
+msgstr "인터럽트"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Interval"
+msgstr "간격 (Interval)"
+
+#: ../PLCControler.py:2032
+#: ../PLCControler.py:2070
+msgid "Invalid plcopen element(s)!!!"
+msgstr "알 수 없는 plcopen 항목입니다!!!"
+
#: ../canfestival/config_utils.py:376
#: ../canfestival/config_utils.py:637
#, python-format
msgid "Invalid type \"%s\"-> %d != %d for location\"%s\""
msgstr "타입 에러 \"%s\" -> %d != %d 위치 \"%s\""
-#: ../ProjectController.py:1452
+#: ../dialogs/ForceVariableDialog.py:167
+#, python-format
+msgid "Invalid value \"%s\" for \"%s\" variable!"
+msgstr "\"%s\"값은 \"%s\" 변수에 적합하지 않습니다!"
+
+#: ../controls/DebugVariablePanel.py:153
+#: ../controls/DebugVariablePanel.py:156
+#, python-format
+msgid "Invalid value \"%s\" for debug variable"
+msgstr "\"%s\"값은 디버그 변수에 적합하지 않습니다!"
+
+#: ../controls/VariablePanel.py:244
+#: ../controls/VariablePanel.py:247
+#, fuzzy, python-format
+msgid "Invalid value \"%s\" for variable grid element"
+msgstr "\"%s\"값은 \"%s\" 변수에 적합하지 않습니다!"
+
+#: ../editors/Viewer.py:221
+#: ../editors/Viewer.py:224
+#, python-format
+msgid "Invalid value \"%s\" for viewer block"
+msgstr "\"%s\" 값은 뷰어 블록에 적합하지 않습니다!"
+
+#: ../dialogs/DurationEditorDialog.py:121
+msgid ""
+"Invalid value!\n"
+"You must fill a numeric value."
+msgstr ""
+
+#: ../editors/Viewer.py:497
+msgid "Jump"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "LD"
+msgstr "LD"
+
+#: ../editors/LDViewer.py:215
+#: ../editors/LDViewer.py:231
+#, python-format
+msgid "Ladder element with id %d is on more than one rung."
+msgstr "래더 항목 ID %d이 하나 이상의 Rung에 존재합니다"
+
+#: ../dialogs/PouTransitionDialog.py:86
+#: ../dialogs/PouActionDialog.py:83
+#: ../dialogs/PouDialog.py:102
+msgid "Language"
+msgstr "언어"
+
+#: ../controls/ProjectPropertiesPanel.py:186
+msgid "Language (optional):"
+msgstr "언어(옵션):"
+
+#: ../dialogs/PouTransitionDialog.py:60
+#: ../dialogs/PouActionDialog.py:56
+#: ../dialogs/PouDialog.py:71
+msgid "Language:"
+msgstr "언어:"
+
+#: ../ProjectController.py:1451
msgid "Latest build already matches current target. Transfering anyway...\n"
msgstr "최근 빌드가 이미 타겟과 일치합니다. 전송합니다...\n"
@@ -584,22 +2102,63 @@
msgid "Launch a live Python shell"
msgstr "Live Python Shell 실행"
+#: ../editors/Viewer.py:428
+msgid "Left"
+msgstr "좌측"
+
+#: ../dialogs/LDPowerRailDialog.py:55
+msgid "Left PowerRail"
+msgstr "좌측 전원 레일"
+
+#: ../plcopen/iec_std.csv:81
+msgid "Length of string"
+msgstr "문자열 길이"
+
+#: ../plcopen/iec_std.csv:78
+msgid "Less than"
+msgstr "작은 값 일때"
+
+#: ../plcopen/iec_std.csv:79
+msgid "Less than or equal to"
+msgstr "작거나 같은 값 일때"
+
+#: ../IDEFrame.py:600
+msgid "Library"
+msgstr "라이브러리"
+
+#: ../plcopen/iec_std.csv:73
+msgid "Limitation"
+msgstr "한도"
+
#: ../targets/toolchain_gcc.py:142
msgid "Linking :\n"
msgstr "링크 중 : \n"
-#: ../util/discovery.py:110
+#: ../controls/VariablePanel.py:77
+#: ../dialogs/DiscoveryDialog.py:110
msgid "Local"
msgstr "로컬"
-#: ../ProjectController.py:1354
+#: ../ProjectController.py:1353
msgid "Local service discovery failed!\n"
msgstr ""
-#: ../Beremiz.py:391
+#: ../controls/VariablePanel.py:58
+msgid "Location"
+msgstr "위치"
+
+#: ../dialogs/BrowseLocationsDialog.py:61
+msgid "Locations available:"
+msgstr "가능한 위치:"
+
+#: ../Beremiz.py:393
msgid "Log Console"
msgstr "로그 콘솔"
+#: ../plcopen/iec_std.csv:25
+msgid "Logarithm to base 10"
+msgstr "상용로그(상용대수)"
+
#: ../connectors/PYRO/__init__.py:55
#, python-format
msgid "MDNS resolution failure for '%s'\n"
@@ -623,31 +2182,237 @@
msgid "Max count (%d) reached for this confnode of type %s "
msgstr "플러그인 최대 카운트 (%d)에 도달했습니다. %s"
-#: ../util/FileManagementPanel.py:301
+#: ../plcopen/iec_std.csv:71
+msgid "Maximum"
+msgstr "최대값"
+
+#: ../editors/DataTypeEditor.py:232
+msgid "Maximum:"
+msgstr "최대값:"
+
+#: ../dialogs/BrowseLocationsDialog.py:38
+msgid "Memory"
+msgstr "메모리"
+
+#: ../IDEFrame.py:568
+#, fuzzy
+msgid "Menu ToolBar"
+msgstr "툴바"
+
+#: ../dialogs/DurationEditorDialog.py:48
+msgid "Microseconds:"
+msgstr ""
+
+#: ../editors/Viewer.py:433
+msgid "Middle"
+msgstr "중간"
+
+#: ../dialogs/DurationEditorDialog.py:47
+msgid "Milliseconds:"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:72
+msgid "Minimum"
+msgstr "최소값"
+
+#: ../editors/DataTypeEditor.py:219
+msgid "Minimum:"
+msgstr "최소값:"
+
+#: ../dialogs/DurationEditorDialog.py:45
+msgid "Minutes:"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:210
+msgid "Miscellaneous"
+msgstr "기타"
+
+#: ../dialogs/LDElementDialog.py:59
+msgid "Modifier:"
+msgstr "수정자:"
+
+#: ../PLCGenerator.py:703
+#: ../PLCGenerator.py:936
+#, python-format
+msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr "\"%s\"에 대응하는 하나 이상의 연결이 \"%s\" POU에서 발견 되었습니다"
+
+#: ../dialogs/ActionBlockDialog.py:141
+#, fuzzy
+msgid "Move action down"
+msgstr "하단 이동"
+
+#: ../dialogs/ActionBlockDialog.py:140
+#, fuzzy
+msgid "Move action up"
+msgstr "상단 이동"
+
+#: ../controls/DebugVariablePanel.py:185
+#, fuzzy
+msgid "Move debug variable down"
+msgstr "출력 변수를 찾을 수 없습니다"
+
+#: ../controls/DebugVariablePanel.py:184
+#, fuzzy
+msgid "Move debug variable up"
+msgstr "출력 변수를 찾을 수 없습니다"
+
+#: ../controls/CustomEditableListBox.py:43
+msgid "Move down"
+msgstr "하단 이동"
+
+#: ../editors/DataTypeEditor.py:348
+#, fuzzy
+msgid "Move element down"
+msgstr "하단 이동"
+
+#: ../editors/DataTypeEditor.py:347
+#, fuzzy
+msgid "Move element up"
+msgstr "상단 이동"
+
+#: ../editors/ResourceEditor.py:254
+#, fuzzy
+msgid "Move instance down"
+msgstr "하단 이동"
+
+#: ../editors/ResourceEditor.py:253
+#, fuzzy
+msgid "Move instance up"
+msgstr "상단 이동"
+
+#: ../editors/ResourceEditor.py:225
+#, fuzzy
+msgid "Move task down"
+msgstr "하단 이동"
+
+#: ../editors/ResourceEditor.py:224
+#, fuzzy
+msgid "Move task up"
+msgstr "상단 이동"
+
+#: ../IDEFrame.py:75
+#: ../IDEFrame.py:90
+#: ../IDEFrame.py:120
+#: ../IDEFrame.py:161
+msgid "Move the view"
+msgstr "화면을 드래그하여 이동"
+
+#: ../controls/CustomEditableListBox.py:42
+msgid "Move up"
+msgstr "상단 이동"
+
+#: ../controls/VariablePanel.py:381
+#, fuzzy
+msgid "Move variable down"
+msgstr "하단 이동"
+
+#: ../controls/VariablePanel.py:380
+#, fuzzy
+msgid "Move variable up"
+msgstr "상단 이동"
+
+#: ../plcopen/iec_std.csv:74
+msgid "Multiplexer (select 1 of N)"
+msgstr "멀티플렉서(MUX, 단일 출력선)"
+
+#: ../plcopen/iec_std.csv:34
+msgid "Multiplication"
+msgstr "곱셈"
+
+#: ../editors/FileManagementPanel.py:301
#, fuzzy
msgid "My Computer:"
msgstr "컴파일러"
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:76
+msgid "Name"
+msgstr "이름"
+
#: ../Beremiz_service.py:381
msgid "Name must not be null!"
msgstr "이름은 널(null)이 되어서는 안됩니다!"
-#: ../Beremiz.py:340
+#: ../dialogs/ConnectionDialog.py:65
+#: ../dialogs/FBDVariableDialog.py:89
+#: ../dialogs/LDElementDialog.py:88
+#: ../dialogs/SFCStepDialog.py:51
+#: ../dialogs/FBDBlockDialog.py:70
+msgid "Name:"
+msgstr "이름:"
+
+#: ../plcopen/iec_std.csv:24
+msgid "Natural logarithm"
+msgstr "자연 로그"
+
+#: ../editors/Viewer.py:403
+#: ../dialogs/LDElementDialog.py:67
+msgid "Negated"
+msgstr "역 방향(Negate)"
+
+#: ../Beremiz.py:307
+#: ../Beremiz.py:342
+#: ../PLCOpenEditor.py:125
+#: ../PLCOpenEditor.py:167
msgid "New"
msgstr "새로 만들기"
-#: ../Beremiz.py:305
-msgid "New\tCTRL+N"
-msgstr "새로 만들기\tCTRL+N"
-
-#: ../ProjectController.py:1479
+#: ../controls/CustomEditableListBox.py:40
+msgid "New item"
+msgstr "새로운 아이템"
+
+#: ../editors/Viewer.py:402
+msgid "No Modifier"
+msgstr "수정자 없음"
+
+#: ../PLCControler.py:2929
+msgid "No PLC project found"
+msgstr "PLC 프로젝트를 찾을 수 없습니다"
+
+#: ../ProjectController.py:1478
msgid "No PLC to transfer (did build succeed ?)\n"
msgstr "전송할 PLC 파일이 없습니다 (빌드 성공 여부를 확인하세요)\n"
+#: ../PLCGenerator.py:1321
+#, python-format
+msgid "No body defined in \"%s\" POU"
+msgstr "\"%s\" POU에 바디(body)가 정의되어 있지 않습니다"
+
+#: ../PLCGenerator.py:722
+#: ../PLCGenerator.py:945
+#, python-format
+msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr "\"%s\"에 대응하는 연결을 \"%s\" POU에서 찾을 수 없습니다"
+
+#: ../PLCOpenEditor.py:370
+msgid ""
+"No documentation available.\n"
+"Coming soon."
+msgstr ""
+"현재 지원되는 도움 문서가 없습니다.\n"
+"지원 예정"
+
+#: ../PLCGenerator.py:744
+#, python-format
+msgid "No informations found for \"%s\" block"
+msgstr "\"%s\"블럭에 대한 정보를 찾을 수 없습니다"
+
+#: ../plcopen/structures.py:167
+msgid "No output variable found"
+msgstr "출력 변수를 찾을 수 없습니다"
+
#: ../Beremiz_service.py:394
msgid "No running PLC"
msgstr "실행중인 PLC가 없습니다"
+#: ../controls/SearchResultPanel.py:169
+msgid "No search results available."
+msgstr "검색된 결과가 없습니다"
+
#: ../svgui/svgui.py:98
#, python-format
msgid "No such SVG file: %s\n"
@@ -663,10 +2428,15 @@
msgid "No such index/subindex (%x,%x) in ID : %d (variable %s)"
msgstr "인덱스/서브 인덱스 (%x,%x) ID 위치: %d (변수 %s)"
-#: ../util/BrowseValuesLibraryDialog.py:83
+#: ../dialogs/BrowseValuesLibraryDialog.py:83
msgid "No valid value selected!"
msgstr "유효한 데이터 값을 선택하세요!"
+#: ../PLCGenerator.py:1319
+#, python-format
+msgid "No variable defined in \"%s\" POU"
+msgstr "\"%s\" POU에 정의된 변수가 없습니다"
+
#: ../canfestival/SlaveEditor.py:49
#: ../canfestival/NetworkEditor.py:79
msgid "Node infos"
@@ -677,24 +2447,68 @@
msgid "Non existing node ID : %d (variable %s)"
msgstr "Node ID : %d(변수%s)를 찾을 수 없습니다"
+#: ../controls/VariablePanel.py:69
+msgid "Non-Retain"
+msgstr "유지 안함(No Retain)"
+
+#: ../dialogs/LDElementDialog.py:62
+msgid "Normal"
+msgstr "기본(Normal)"
+
#: ../canfestival/config_utils.py:383
#, python-format
msgid "Not PDO mappable variable : '%s' (ID:%d,Idx:%x,sIdx:%x))"
msgstr "PDO 맵핑 변수가 아닙니다 : '%s'(ID:%d,ldx:%x,sldx:%x))"
-#: ../Beremiz.py:341
+#: ../plcopen/iec_std.csv:80
+msgid "Not equal to"
+msgstr "같지 않을 때"
+
+#: ../dialogs/SFCDivergenceDialog.py:80
+msgid "Number of sequences:"
+msgstr "시퀀스 넘버:"
+
+#: ../plcopen/iec_std.csv:22
+msgid "Numerical"
+msgstr "수치(Numeric)"
+
+#: ../plcopen/structures.py:247
+msgid ""
+"Off-delay timer\n"
+"The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
+msgstr ""
+"오프 딜레이 타이머\n"
+"오프 딜레이 타이머는 입력이 FALSE로 진행되는 주기를 수정하여 출력을 FALSE로 셋팅합니다"
+
+#: ../plcopen/structures.py:242
+msgid ""
+"On-delay timer\n"
+"The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
+msgstr ""
+"온 딜레이 타이머\n"
+"온 딜레이 타이머는 입력이 TRUE로 진행되는 주기를 수정하여 출력을 TRUE로 셋팅합니다"
+
+#: ../dialogs/SearchInProjectDialog.py:93
+msgid "Only Elements"
+msgstr "구성원 내부 검색"
+
+#: ../Beremiz.py:309
+#: ../Beremiz.py:343
+#: ../PLCOpenEditor.py:127
+#: ../PLCOpenEditor.py:168
+#, fuzzy
msgid "Open"
-msgstr "프로젝트 열기"
-
-#: ../Beremiz.py:307
-msgid "Open\tCTRL+O"
-msgstr "프로젝트 열기\tCTRL+O"
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"프로젝트 열기\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"파일 열기"
#: ../svgui/svgui.py:107
msgid "Open Inkscape"
msgstr "잉크스케이프 열기"
-#: ../ProjectController.py:1531
+#: ../ProjectController.py:1530
msgid "Open a file explorer to manage project files"
msgstr ""
@@ -702,11 +2516,32 @@
msgid "Open wxGlade"
msgstr "WxGlade 열기"
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+msgid "Option"
+msgstr "옵션"
+
+#: ../dialogs/FindInPouDialog.py:76
+#, fuzzy
+msgid "Options"
+msgstr "옵션"
+
+#: ../controls/ProjectPropertiesPanel.py:97
+msgid "Organization (optional):"
+msgstr "구성단체(옵션):"
+
#: ../canfestival/SlaveEditor.py:47
#: ../canfestival/NetworkEditor.py:77
msgid "Other Profile"
msgstr "다른 프로필"
+#: ../controls/VariablePanel.py:76
+#: ../dialogs/BrowseLocationsDialog.py:37
+#: ../dialogs/FBDVariableDialog.py:35
+#: ../dialogs/SFCStepDialog.py:65
+msgid "Output"
+msgstr "출력"
+
#: ../canfestival/SlaveEditor.py:36
#: ../canfestival/NetworkEditor.py:66
msgid "PDO Receive"
@@ -717,19 +2552,110 @@
msgid "PDO Transmit"
msgstr "전송 PDO"
+#: ../plcopen/structures.py:269
+msgid ""
+"PID\n"
+"The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
+msgstr ""
+"PID\n"
+"PID 펑션 블럭은 폐쇄 루프 제어를 위한 다음과 같은 비례상수, 미분, 적분등의 기본 제어 규칙을 제공합니다"
+
#: ../targets/toolchain_gcc.py:107
msgid "PLC :\n"
msgstr "PLC :\n"
-#: ../ProjectController.py:1097
-#: ../ProjectController.py:1399
+#: ../ProjectController.py:1096
+#: ../ProjectController.py:1398
#, python-format
msgid "PLC is %s\n"
msgstr "현재 PLC %s\n"
-#: ../Beremiz.py:320
-msgid "Page Setup\tCTRL+ALT+P"
-msgstr "인쇄 페이지 설정\tCTRL+ALT+P"
+#: ../PLCOpenEditor.py:313
+#: ../PLCOpenEditor.py:391
+msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
+msgstr "PLCOpen files (*.xml)|*.xml|모든 파일|*.*"
+
+#: ../PLCOpenEditor.py:175
+#: ../PLCOpenEditor.py:231
+msgid "PLCOpenEditor"
+msgstr "PLCOpenEditor"
+
+#: ../dialogs/PouDialog.py:98
+msgid "POU Name"
+msgstr "POU 이름"
+
+#: ../dialogs/PouDialog.py:56
+msgid "POU Name:"
+msgstr "POU 이름:"
+
+#: ../dialogs/PouDialog.py:100
+msgid "POU Type"
+msgstr "POU 타입"
+
+#: ../dialogs/PouDialog.py:63
+msgid "POU Type:"
+msgstr "POU 타입:"
+
+#: ../Beremiz.py:322
+#: ../PLCOpenEditor.py:141
+msgid "Page Setup"
+msgstr "인쇄 페이지 설정"
+
+#: ../controls/ProjectPropertiesPanel.py:110
+msgid "Page Size (optional):"
+msgstr "페이지 크기(옵션):"
+
+#: ../PLCOpenEditor.py:476
+#, python-format
+msgid "Page: %d"
+msgstr "인쇄 페이지: %d"
+
+#: ../controls/PouInstanceVariablesPanel.py:41
+#, fuzzy
+msgid "Parent instance"
+msgstr "인스턴스 삭제"
+
+#: ../IDEFrame.py:350
+#: ../IDEFrame.py:402
+#: ../editors/Viewer.py:537
+msgid "Paste"
+msgstr "붙여넣기"
+
+#: ../IDEFrame.py:1900
+msgid "Paste POU"
+msgstr "POU 붙여넣기"
+
+#: ../dialogs/SearchInProjectDialog.py:64
+msgid "Pattern to search:"
+msgstr "검색할 문자열 패턴:"
+
+#: ../dialogs/LDPowerRailDialog.py:64
+msgid "Pin number:"
+msgstr "핀 넘버:"
+
+#: ../editors/Viewer.py:2289
+#: ../editors/Viewer.py:2594
+#: ../editors/SFCViewer.py:696
+msgid "Please choose a target"
+msgstr "타겟을 선택하세요"
+
+#: ../editors/Viewer.py:2112
+#: ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630
+#: ../editors/Viewer.py:2632
+msgid "Please enter comment text"
+msgstr "코멘트를 입력하세요"
+
+#: ../editors/SFCViewer.py:359
+#: ../editors/SFCViewer.py:381
+#: ../editors/SFCViewer.py:725
+msgid "Please enter step name"
+msgstr "스텝 이름을 입력하세요"
+
+#: ../dialogs/ForceVariableDialog.py:153
+#, python-format
+msgid "Please enter value for a \"%s\" variable:"
+msgstr "\"%s\" 변수의 데이터를 입력하세요"
#: ../Beremiz_service.py:366
msgid "Port number must be 0 <= port <= 65535!"
@@ -739,75 +2665,341 @@
msgid "Port number must be an integer!"
msgstr "포트 번호는 숫자로만 입력하세요!"
-#: ../Beremiz.py:322
-msgid "Preview\tCTRL+SHIFT+P"
-msgstr "인쇄 미리보기\tnCTRL+SHIFT+P"
-
-#: ../Beremiz.py:344
+#: ../editors/GraphicViewer.py:105
+msgid "Position:"
+msgstr "위치:"
+
+#: ../editors/Viewer.py:476
+#, fuzzy
+msgid "Power Rail"
+msgstr "좌측 전원 레일"
+
+#: ../dialogs/LDPowerRailDialog.py:36
+msgid "Power Rail Properties"
+msgstr "전원 레일 속성"
+
+#: ../Beremiz.py:324
+#: ../PLCOpenEditor.py:143
+msgid "Preview"
+msgstr "인쇄 페이지 미리보기"
+
+#: ../dialogs/SFCDivergenceDialog.py:93
+#: ../dialogs/LDPowerRailDialog.py:78
+#: ../dialogs/ConnectionDialog.py:78
+#: ../dialogs/FBDVariableDialog.py:97
+#: ../dialogs/SFCTransitionDialog.py:96
+#: ../dialogs/LDElementDialog.py:101
+#: ../dialogs/SFCStepDialog.py:79
+#: ../dialogs/FBDBlockDialog.py:103
+msgid "Preview:"
+msgstr "인쇄 페이지 미리보기:"
+
+#: ../Beremiz.py:326
+#: ../Beremiz.py:346
+#: ../PLCOpenEditor.py:145
+#: ../PLCOpenEditor.py:171
msgid "Print"
msgstr "인쇄"
-#: ../Beremiz.py:324
-msgid "Print\tCTRL+P"
-msgstr "인쇄\tCTRL+P"
-
-#: ../ProjectController.py:1530
+#: ../IDEFrame.py:1155
+msgid "Print preview"
+msgstr "인쇄 미리보기"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Priority"
+msgstr "우선권"
+
+#: ../dialogs/SFCTransitionDialog.py:83
+msgid "Priority:"
+msgstr "우선권:"
+
+#: ../controls/ProjectPropertiesPanel.py:80
+msgid "Product Name (required):"
+msgstr "제품 이름(필수):"
+
+#: ../controls/ProjectPropertiesPanel.py:82
+msgid "Product Release (optional):"
+msgstr "제품 출시 번호(옵션):"
+
+#: ../controls/ProjectPropertiesPanel.py:81
+msgid "Product Version (required):"
+msgstr "제품 버젼(필수):"
+
+#: ../IDEFrame.py:1972
+#: ../dialogs/SearchInProjectDialog.py:46
+msgid "Program"
+msgstr "프로그램"
+
+#: ../PLCOpenEditor.py:360
+msgid "Program was successfully generated!"
+msgstr "프로그램이 성공적으로 생성되었습니다!"
+
+#: ../PLCControler.py:95
+msgid "Programs"
+msgstr "프로그램(Programs)"
+
+#: ../editors/Viewer.py:230
+msgid "Programs can't be used by other POUs!"
+msgstr "다른 POU에서 사용할 수 없는 프로그램입니다!"
+
+#: ../controls/ProjectPropertiesPanel.py:84
+#: ../IDEFrame.py:553
+msgid "Project"
+msgstr "프로젝트"
+
+#: ../controls/SearchResultPanel.py:173
+#, fuzzy, python-format
+msgid "Project '%s':"
+msgstr "프로젝트"
+
+#: ../ProjectController.py:1529
msgid "Project Files"
msgstr ""
+#: ../controls/ProjectPropertiesPanel.py:78
+msgid "Project Name (required):"
+msgstr "프로젝트 명(필수):"
+
+#: ../controls/ProjectPropertiesPanel.py:79
+msgid "Project Version (optional):"
+msgstr "프로젝트 버젼(옵션):"
+
+#: ../PLCControler.py:2916
+msgid ""
+"Project file syntax error:\n"
+"\n"
+msgstr ""
+"프로젝트 파일 구문 오류:\n"
+"\n"
+
+#: ../dialogs/ProjectDialog.py:32
+msgid "Project properties"
+msgstr "프로젝트 속성"
+
#: ../ConfigTreeNode.py:506
#, python-format
msgid "Project tree layout do not match confnode.xml %s!=%s "
msgstr "프로젝트 레이아웃이 포함된 %s!=%s confnode.xml파일이 손상되거나 일치 하지 않습니다"
+#: ../PLCControler.py:96
+msgid "Properties"
+msgstr "속성"
+
+#: ../plcopen/structures.py:237
+msgid ""
+"Pulse timer\n"
+"The pulse timer can be used to generate output pulses of a given time duration."
+msgstr ""
+"펄스 타이머\n"
+"펄스 타이머 펑션 블럭은 사용자가 설정한 시간동안 출력 신호를 생성하는데 사용합니다"
+
#: ../features.py:8
msgid "Python file"
msgstr "파이썬 파일"
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Qualifier"
+msgstr "한정자"
+
#: ../Beremiz_service.py:328
+#: ../Beremiz.py:329
+#: ../PLCOpenEditor.py:151
msgid "Quit"
msgstr "프로그램 종료"
-#: ../Beremiz.py:327
-msgid "Quit\tCTRL+Q"
-msgstr "프로그램 종료\tCTRL+Q"
-
-#: ../ProjectController.py:1526
+#: ../plcopen/structures.py:202
+msgid ""
+"RS bistable\n"
+"The RS bistable is a latch where the Reset dominates."
+msgstr ""
+"RS 쌍안정 회로\n"
+"RS 쌍안정(bistable) 회로 함수은 리셋 동작시 래치가 발생됩니다"
+
+#: ../plcopen/structures.py:274
+#, fuzzy
+msgid ""
+"Ramp\n"
+"The RAMP function block is modelled on example given in the standard."
+msgstr ""
+"램프(Ramp)\n"
+"램프(Ramp) 함수 블럭은 정지(Holdback) 기능이 추가된 기본 예제 모델입니다"
+
+#: ../editors/GraphicViewer.py:89
+msgid "Range:"
+msgstr "범위:"
+
+#: ../ProjectController.py:1525
msgid "Raw IEC code"
msgstr "IEC 코드"
-#: ../Beremiz.py:1037
+#: ../plcopen/structures.py:254
+msgid ""
+"Real time clock\n"
+"The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
+msgstr ""
+
+#: ../Beremiz.py:1039
#, fuzzy, python-format
msgid "Really delete node '%s'?"
msgstr "플러그인을 삭제하시겠습니까?"
-#: ../util/discovery.py:105
+#: ../IDEFrame.py:340
+#: ../IDEFrame.py:398
+msgid "Redo"
+msgstr "되돌리기 취소"
+
+#: ../dialogs/SFCTransitionDialog.py:57
+#: ../dialogs/SFCTransitionDialog.py:135
+msgid "Reference"
+msgstr "레퍼런스"
+
+#: ../IDEFrame.py:408
+#: ../dialogs/DiscoveryDialog.py:105
msgid "Refresh"
msgstr "새로고침"
-#: ../Beremiz.py:1038
+#: ../dialogs/SearchInProjectDialog.py:73
+msgid "Regular expression"
+msgstr "정규 표현식"
+
+#: ../dialogs/FindInPouDialog.py:91
+#, fuzzy
+msgid "Regular expressions"
+msgstr "정규 표현식"
+
+#: ../controls/DebugVariablePanel.py:299
+#: ../editors/Viewer.py:1356
+msgid "Release value"
+msgstr "강제 데이터 입력 해제"
+
+#: ../plcopen/iec_std.csv:37
+msgid "Remainder (modulo)"
+msgstr "잔여 (모듈)"
+
+#: ../Beremiz.py:1040
#, fuzzy, python-format
msgid "Remove %s node"
msgstr "플러그인 제거"
-#: ../util/FileManagementPanel.py:281
+#: ../dialogs/ActionBlockDialog.py:139
+#, fuzzy
+msgid "Remove action"
+msgstr "선택"
+
+#: ../controls/DebugVariablePanel.py:183
+#, fuzzy
+msgid "Remove debug variable"
+msgstr "새로운 변수 생성"
+
+#: ../editors/DataTypeEditor.py:346
+msgid "Remove element"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:281
msgid "Remove file from left folder"
msgstr ""
+#: ../editors/ResourceEditor.py:252
+#, fuzzy
+msgid "Remove instance"
+msgstr "인스턴스 삭제"
+
#: ../canfestival/NetworkEditor.py:87
msgid "Remove slave"
msgstr "슬레이브 제거"
-#: ../util/FileManagementPanel.py:399
+#: ../editors/ResourceEditor.py:223
+msgid "Remove task"
+msgstr ""
+
+#: ../controls/VariablePanel.py:379
+#, fuzzy
+msgid "Remove variable"
+msgstr "새로운 변수 생성"
+
+#: ../IDEFrame.py:1976
+msgid "Rename"
+msgstr "이름 변경"
+
+#: ../editors/FileManagementPanel.py:399
msgid "Replace File"
msgstr ""
-#: ../ProjectController.py:1494
+#: ../plcopen/iec_std.csv:89
+msgid "Replacement (within)"
+msgstr "내부 교체"
+
+#: ../dialogs/LDElementDialog.py:76
+msgid "Reset"
+msgstr "리셋 코일(Unlatch)"
+
+#: ../editors/Viewer.py:521
+msgid "Reset Execution Order"
+msgstr "실행 순서 초기화"
+
+#: ../IDEFrame.py:423
+msgid "Reset Perspective"
+msgstr ""
+
+#: ../controls/SearchResultPanel.py:105
+msgid "Reset search result"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:137
+msgid "Reset zoom and offset"
+msgstr ""
+
+#: ../PLCControler.py:96
+msgid "Resources"
+msgstr "리소스"
+
+#: ../controls/VariablePanel.py:67
+msgid "Retain"
+msgstr "유지 변수"
+
+#: ../controls/VariablePanel.py:352
+msgid "Return Type:"
+msgstr "반환(Return) 타입:"
+
+#: ../editors/Viewer.py:430
+msgid "Right"
+msgstr "우측"
+
+#: ../dialogs/LDPowerRailDialog.py:60
+msgid "Right PowerRail"
+msgstr "우측 전원 레일"
+
+#: ../editors/Viewer.py:404
+#: ../dialogs/LDElementDialog.py:80
+msgid "Rising Edge"
+msgstr "라이징 엣지"
+
+#: ../plcopen/structures.py:212
+msgid ""
+"Rising edge detector\n"
+"The output produces a single pulse when a rising edge is detected."
+msgstr ""
+"라이징 엣지 검출\n"
+"출력부의 라이징 엣지를 검출합니다"
+
+#: ../plcopen/iec_std.csv:65
+msgid "Rotate left"
+msgstr "좌측으로 회전 (rotate)"
+
+#: ../plcopen/iec_std.csv:64
+msgid "Rotate right"
+msgstr "우측으로 회전 (rotate)"
+
+#: ../plcopen/iec_std.csv:17
+msgid "Rounding up/down"
+msgstr "라운딩 업/다운"
+
+#: ../ProjectController.py:1493
msgid "Run"
msgstr "실행"
-#: ../ProjectController.py:842
-#: ../ProjectController.py:851
+#: ../ProjectController.py:841
+#: ../ProjectController.py:850
msgid "Runtime extensions C code generation failed !\n"
msgstr "확장 C 코드의 생성에 실패하였습니다 ! \n"
@@ -821,6 +3013,29 @@
msgid "SDO Server"
msgstr "SDO 서버"
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouDialog.py:36
+msgid "SFC"
+msgstr "SFC"
+
+#: ../plcopen/structures.py:197
+msgid ""
+"SR bistable\n"
+"The SR bistable is a latch where the Set dominates."
+msgstr ""
+"SR 쌍안정\n"
+"SR 쌍안정 셋팅시 래치 스위치가 동작합니다"
+
+#: ../dialogs/PouTransitionDialog.py:35
+#: ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "ST"
+msgstr "ST"
+
+#: ../PLCOpenEditor.py:347
+msgid "ST files (*.st)|*.st|All files|*.*"
+msgstr "ST 파일 (*.st)|*.st|모든 파일|*.*"
+
#: ../svgui/svgui.py:92
msgid "SVG files (*.svg)|*.svg|All files|*.*"
msgstr "SVG 파일 (*.svg)|*svg|모든 파일|*.*"
@@ -829,31 +3044,118 @@
msgid "SVGUI"
msgstr "SVGUI"
-#: ../Beremiz.py:342
+#: ../Beremiz.py:313
+#: ../Beremiz.py:344
+#: ../PLCOpenEditor.py:134
+#: ../PLCOpenEditor.py:169
+#, fuzzy
msgid "Save"
-msgstr "프로젝트 저장"
-
-#: ../Beremiz.py:311
-msgid "Save\tCTRL+S"
-msgstr "프로젝트 저장\tCTRL+S"
-
-#: ../Beremiz.py:343
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"프로젝트 저장\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"파일 저장"
+
+#: ../Beremiz.py:345
+#: ../PLCOpenEditor.py:136
+#: ../PLCOpenEditor.py:170
+#, fuzzy
msgid "Save As..."
-msgstr "다른 이름으로 저장"
-
-#: ../Beremiz.py:313
-msgid "Save as\tCTRL+SHIFT+S"
-msgstr "다른 이름으로 저장\tCTRL+SHIFT+S"
-
-#: ../ProjectController.py:1014
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"다른 이름으로 저장\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"다른 이름으로 저장..."
+
+#: ../Beremiz.py:315
+#, fuzzy
+msgid "Save as"
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"프로젝트 저장\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"파일 저장"
+
+#: ../dialogs/SearchInProjectDialog.py:76
+msgid "Scope"
+msgstr "검색 범위"
+
+#: ../IDEFrame.py:592
+#: ../dialogs/SearchInProjectDialog.py:105
+msgid "Search"
+msgstr "검색"
+
+#: ../IDEFrame.py:360
+#: ../IDEFrame.py:404
+#: ../dialogs/SearchInProjectDialog.py:52
+msgid "Search in Project"
+msgstr "프로젝트 내부 검색"
+
+#: ../dialogs/DurationEditorDialog.py:46
+msgid "Seconds:"
+msgstr ""
+
+#: ../IDEFrame.py:366
+#, fuzzy
+msgid "Select All"
+msgstr "모두 선택\tCTRL+A"
+
+#: ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Select a variable class:"
+msgstr "변수 클래스 선택:"
+
+#: ../ProjectController.py:1013
msgid "Select an editor:"
msgstr ""
-#: ../util/discovery.py:84
+#: ../controls/PouInstanceVariablesPanel.py:197
+#, fuzzy
+msgid "Select an instance"
+msgstr "인스턴스 삭제"
+
+#: ../IDEFrame.py:576
+msgid "Select an object"
+msgstr "오브젝트 선택"
+
+#: ../plcopen/iec_std.csv:70
+msgid "Selection"
+msgstr "선택"
+
+#: ../dialogs/SFCDivergenceDialog.py:62
+msgid "Selection Convergence"
+msgstr "수렴(Convergence) 선택"
+
+#: ../dialogs/SFCDivergenceDialog.py:55
+msgid "Selection Divergence"
+msgstr "벡터 발산(Divergence) 선택"
+
+#: ../plcopen/structures.py:207
+msgid ""
+"Semaphore\n"
+"The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
+msgstr ""
+"세마포어(semaphore)\n"
+"세마포어 함수 블럭은 소프트웨어적으로 동시에 두 개 이상의 프로그램 요소가 실행될 수 있는 다중 프로그래밍 환경에서 한순간에 반드시 하나의 작업에 의해 접근되어야 하는 임계 영역 및 상호 배제 원리를 지키기 위해 사용됩니다"
+
+#: ../dialogs/DiscoveryDialog.py:84
msgid "Services available:"
msgstr "서비스 이용 가능:"
-#: ../ProjectController.py:1520
+#: ../dialogs/LDElementDialog.py:72
+msgid "Set"
+msgstr "셋팅 코일(Latch)"
+
+#: ../plcopen/iec_std.csv:62
+msgid "Shift left"
+msgstr "좌측으로 이동(Shift)"
+
+#: ../plcopen/iec_std.csv:63
+msgid "Shift right"
+msgstr "우측이로 이동(Shift)"
+
+#: ../ProjectController.py:1519
msgid "Show IEC code generated by PLCGenerator"
msgstr "PLCGenerator가 생성한 IEC 코드 보기"
@@ -865,29 +3167,58 @@
msgid "Show Master generated by config_utils"
msgstr "config_util(설정 유틸리티)에 의해 생성된 마스터 보기"
-#: ../ProjectController.py:1518
+#: ../ProjectController.py:1517
msgid "Show code"
msgstr "코드 보기"
+#: ../dialogs/SFCDivergenceDialog.py:74
+msgid "Simultaneous Convergence"
+msgstr "동시 수렴"
+
+#: ../dialogs/SFCDivergenceDialog.py:68
+msgid "Simultaneous Divergence"
+msgstr "동시 발산"
+
+#: ../plcopen/iec_std.csv:27
+msgid "Sine"
+msgstr "Sine"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Single"
+msgstr "싱글"
+
+#: ../plcopen/iec_std.csv:23
+msgid "Square root (base 2)"
+msgstr "제곱근 (SQRT: base 2)"
+
+#: ../plcopen/structures.py:193
+msgid "Standard function blocks"
+msgstr "기본 함수 블럭"
+
#: ../Beremiz_service.py:319
-#: ../ProjectController.py:1496
+#: ../ProjectController.py:1495
msgid "Start PLC"
msgstr "PLC 시작"
-#: ../ProjectController.py:820
+#: ../ProjectController.py:819
#, python-format
msgid "Start build in %s\n"
msgstr "%s에서 빌드를 시작합니다\n"
-#: ../ProjectController.py:1315
+#: ../ProjectController.py:1314
msgid "Starting PLC\n"
msgstr "PLC 시작중\n"
-#: ../Beremiz.py:401
+#: ../Beremiz.py:403
msgid "Status ToolBar"
msgstr ""
-#: ../ProjectController.py:1499
+#: ../editors/Viewer.py:493
+#, fuzzy
+msgid "Step"
+msgstr "스텝 수정"
+
+#: ../ProjectController.py:1498
msgid "Stop"
msgstr "정지"
@@ -895,51 +3226,224 @@
msgid "Stop PLC"
msgstr "PLC 정지"
-#: ../ProjectController.py:1501
+#: ../ProjectController.py:1500
msgid "Stop Running PLC"
msgstr "작동중인 PLC 정지"
-#: ../ProjectController.py:1293
+#: ../ProjectController.py:1292
msgid "Stopping debugger...\n"
msgstr "디버거 정지중...\n"
-#: ../ProjectController.py:916
+#: ../editors/DataTypeEditor.py:52
+msgid "Structure"
+msgstr "구조"
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Subrange"
+msgstr "서브레인지"
+
+#: ../plcopen/iec_std.csv:35
+msgid "Subtraction"
+msgstr "뺄셈 연산"
+
+#: ../ProjectController.py:915
msgid "Successfully built.\n"
msgstr "성공적으로 빌드 완료 되었습니다\n"
-#: ../util/FileManagementPanel.py:398
+#: ../dialogs/SearchInProjectDialog.py:154
+msgid "Syntax error in regular expression of pattern to search!"
+msgstr "정규 표현식에 적합하지 않은 검색어 입니다!"
+
+#: ../plcopen/iec_std.csv:29
+msgid "Tangent"
+msgstr "Tangent"
+
+#: ../editors/ResourceEditor.py:76
+msgid "Task"
+msgstr "태스크"
+
+#: ../editors/ResourceEditor.py:218
+msgid "Tasks:"
+msgstr "태스크:"
+
+#: ../controls/VariablePanel.py:78
+msgid "Temp"
+msgstr "임시"
+
+#: ../editors/FileManagementPanel.py:398
#, python-format
msgid ""
"The file '%s' already exist.\n"
"Do you want to replace it?"
msgstr ""
-#: ../Beremiz.py:553
+#: ../editors/LDViewer.py:879
+msgid "The group of block must be coherent!"
+msgstr "블럭 그룹은 일관성을 가져야 합니다!"
+
+#: ../IDEFrame.py:1091
+#: ../Beremiz.py:555
+#, fuzzy
msgid "There are changes, do you want to save?"
-msgstr "프로젝트가 수정되었습니다. 저장 하시겠습니까?"
-
-#: ../ProjectController.py:1508
+msgstr ""
+"#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"프로젝트가 수정되었습니다. 저장 하시겠습니까?\n"
+"#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+"프로젝트 파일이 변경되었습니다. 저장 하시겠습니까?"
+
+#: ../IDEFrame.py:1709
+#: ../IDEFrame.py:1728
+#, python-format
+msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr "\"%s\" POU 에 문제가 있을 수 있습니다. 계속 하시겠습니까?"
+
+#: ../IDEFrame.py:1178
+msgid ""
+"There was a problem printing.\n"
+"Perhaps your current printer is not set correctly?"
+msgstr ""
+"인쇄 작업에 문제가 있습니다.\n"
+"프린터 설정을 확인하세요"
+
+#: ../editors/LDViewer.py:888
+msgid "This option isn't available yet!"
+msgstr "선택하신 옵션은 지원되지 않습니다!"
+
+#: ../editors/GraphicViewer.py:278
+msgid "Tick"
+msgstr "틱(Tick)"
+
+#: ../plcopen/iec_std.csv:40
+msgid "Time"
+msgstr "시간 연산"
+
+#: ../plcopen/iec_std.csv:40
+#: ../plcopen/iec_std.csv:41
+msgid "Time addition"
+msgstr "시간 더하기 연산"
+
+#: ../plcopen/iec_std.csv:86
+msgid "Time concatenation"
+msgstr "시간 연결 연산"
+
+#: ../plcopen/iec_std.csv:60
+#: ../plcopen/iec_std.csv:61
+msgid "Time division"
+msgstr "시간 나누기 연산"
+
+#: ../plcopen/iec_std.csv:46
+#: ../plcopen/iec_std.csv:47
+msgid "Time multiplication"
+msgstr "시간 곱하기 연산"
+
+#: ../plcopen/iec_std.csv:48
+#: ../plcopen/iec_std.csv:49
+msgid "Time subtraction"
+msgstr "시간 뺄셈 연산"
+
+#: ../plcopen/iec_std.csv:42
+#: ../plcopen/iec_std.csv:43
+msgid "Time-of-day addition"
+msgstr "하루의 시간 더하기"
+
+#: ../plcopen/iec_std.csv:52
+#: ../plcopen/iec_std.csv:53
+#: ../plcopen/iec_std.csv:54
+#: ../plcopen/iec_std.csv:55
+msgid "Time-of-day subtraction"
+msgstr "하루의 시간 빼기"
+
+#: ../editors/Viewer.py:432
+msgid "Top"
+msgstr "상단"
+
+#: ../ProjectController.py:1507
msgid "Transfer"
msgstr "전송"
-#: ../ProjectController.py:1510
+#: ../ProjectController.py:1509
msgid "Transfer PLC"
msgstr "PLC에 전송"
-#: ../ProjectController.py:1475
+#: ../ProjectController.py:1474
msgid "Transfer completed successfully.\n"
msgstr "전송이 성공적으로 완료되었습니다\n"
-#: ../ProjectController.py:1477
+#: ../ProjectController.py:1476
msgid "Transfer failed\n"
msgstr "전송 실패\n"
+#: ../editors/Viewer.py:494
+#, fuzzy
+msgid "Transition"
+msgstr "트랜지션"
+
+#: ../PLCGenerator.py:1212
+#, python-format
+msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:84
+msgid "Transition Name"
+msgstr "트랜지션 이름"
+
+#: ../dialogs/PouTransitionDialog.py:53
+msgid "Transition Name:"
+msgstr "트랜지션 이름:"
+
+#: ../PLCGenerator.py:1301
+#, python-format
+msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
+msgstr "\"%s\" 트랜지션의 내용이 POU 다음 스텝 \"%s\"와 연결되지 않았습니다"
+
+#: ../PLCGenerator.py:1292
+#, python-format
+msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
+msgstr "\"%s\" 트랜지션의 내용이 POU 이전 스텝 \"%s\"와 연결되지 않았습니다"
+
+#: ../plcopen/plcopen.py:1442
+#, python-format
+msgid "Transition with name %s doesn't exist!"
+msgstr "%s 트랜지션 이름이 존재 하지 않습니다!"
+
+#: ../PLCControler.py:95
+msgid "Transitions"
+msgstr "트랜지션"
+
+#: ../editors/ResourceEditor.py:67
+msgid "Triggering"
+msgstr "트리거링 스위치"
+
+#: ../controls/VariablePanel.py:58
+#: ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+#: ../editors/ResourceEditor.py:76
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Type"
+msgstr "타입"
+
#: ../canfestival/config_utils.py:335
#: ../canfestival/config_utils.py:617
#, python-format
msgid "Type conflict for location \"%s\""
msgstr "\"%s\" 타입 에러"
+#: ../plcopen/iec_std.csv:16
+msgid "Type conversion"
+msgstr "변환 타입"
+
+#: ../editors/DataTypeEditor.py:155
+msgid "Type infos:"
+msgstr "타입 정보:"
+
+#: ../dialogs/SFCDivergenceDialog.py:51
+#: ../dialogs/LDPowerRailDialog.py:51
+#: ../dialogs/ConnectionDialog.py:52
+#: ../dialogs/SFCTransitionDialog.py:53
+#: ../dialogs/FBDBlockDialog.py:48
+msgid "Type:"
+msgstr "타입:"
+
#: ../canfestival/config_utils.py:455
#: ../canfestival/config_utils.py:469
#, python-format
@@ -951,32 +3455,147 @@
msgid "Unable to get Xenomai's %s \n"
msgstr "Xenomai의 %s 를 가져 올 수 없습니다\n"
-#: ../ProjectController.py:255
+#: ../PLCGenerator.py:865
+#: ../PLCGenerator.py:924
+#, fuzzy, python-format
+msgid "Undefined block type \"%s\" in \"%s\" POU"
+msgstr "\"%s\" 의 POU 타입이 정의되지 않았습니다"
+
+#: ../PLCGenerator.py:240
+#, python-format
+msgid "Undefined pou type \"%s\""
+msgstr "\"%s\" 의 POU 타입이 정의되지 않았습니다"
+
+#: ../IDEFrame.py:338
+#: ../IDEFrame.py:397
+msgid "Undo"
+msgstr "되돌리기"
+
+#: ../ProjectController.py:254
msgid "Unknown"
msgstr ""
+#: ../editors/Viewer.py:336
+#, python-format
+msgid "Unknown variable \"%s\" for this POU!"
+msgstr "\"%s\" 변수를 현재 POU에서 알 수 없습니다!"
+
+#: ../ProjectController.py:251
#: ../ProjectController.py:252
-#: ../ProjectController.py:253
msgid "Unnamed"
msgstr ""
+#: ../PLCControler.py:305
+#, python-format
+msgid "Unnamed%d"
+msgstr "이름 없음%d"
+
+#: ../controls/VariablePanel.py:272
+#, python-format
+msgid "Unrecognized data size \"%s\""
+msgstr "\"%s\"의 데이터 크기를 알 수 없습니다"
+
+#: ../plcopen/structures.py:222
+msgid ""
+"Up-counter\n"
+"The up-counter can be used to signal when a count has reached a maximum value."
+msgstr ""
+"업 카운터\n"
+"업 카운터는 시그널이 사용자가 설정한 최대값에 도달할 때까지 동작합니다"
+
+#: ../plcopen/structures.py:232
+msgid ""
+"Up-down counter\n"
+"The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
+msgstr ""
+"업 다운 카운터\n"
+"업 다운 카운터는 CU, CD 두 개의 입력부를 가지고 있습니다. 입력부는 동시에 동작하며 하나는 카운터 상승, 다른 하나는 카운터 감소로 동작합니다"
+
+#: ../controls/VariablePanel.py:709
+#: ../editors/DataTypeEditor.py:623
+msgid "User Data Types"
+msgstr "사용자 데이터 타입"
+
#: ../canfestival/SlaveEditor.py:38
#: ../canfestival/NetworkEditor.py:68
msgid "User Type"
msgstr "사용자 타입"
+#: ../PLCControler.py:94
+msgid "User-defined POUs"
+msgstr "사용자 정의 POU"
+
+#: ../controls/DebugVariablePanel.py:40
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Value"
+msgstr "데이터 값"
+
+#: ../editors/GraphicViewer.py:278
+msgid "Values"
+msgstr "데이터 값"
+
+#: ../editors/DataTypeEditor.py:252
+msgid "Values:"
+msgstr "데이터 값:"
+
+#: ../controls/DebugVariablePanel.py:40
+#: ../editors/Viewer.py:466
+#: ../dialogs/ActionBlockDialog.py:41
+msgid "Variable"
+msgstr "변수"
+
+#: ../dialogs/FBDVariableDialog.py:47
+msgid "Variable Properties"
+msgstr "변수 속성"
+
+#: ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Variable class"
+msgstr "변수 클래스"
+
+#: ../editors/TextViewer.py:374
+#: ../editors/Viewer.py:338
+msgid "Variable don't belong to this POU!"
+msgstr "POU에 속하지 않는 변수 입니다!"
+
+#: ../controls/VariablePanel.py:77
+msgid "Variables"
+msgstr "변수"
+
+#: ../controls/ProjectPropertiesPanel.py:151
+msgid "Vertical:"
+msgstr "수직:"
+
#: ../wxglade_hmi/wxglade_hmi.py:11
msgid "WXGLADE GUI"
msgstr "WXGLADE GUI"
-#: ../ProjectController.py:1277
+#: ../ProjectController.py:1276
msgid "Waiting debugger to recover...\n"
msgstr "디버거 복구 대기중...\n"
-#: ../ProjectController.py:516
+#: ../editors/LDViewer.py:888
+#: ../dialogs/PouDialog.py:126
+msgid "Warning"
+msgstr "경고"
+
+#: ../ProjectController.py:515
msgid "Warnings in ST/IL/SFC code generator :\n"
msgstr "ST/IL/SFC 코드 생성기 경고:\n"
+#: ../dialogs/SearchInProjectDialog.py:85
+msgid "Whole Project"
+msgstr "프로젝트 전체 검색"
+
+#: ../controls/ProjectPropertiesPanel.py:119
+msgid "Width:"
+msgstr "폭:"
+
+#: ../dialogs/FindInPouDialog.py:86
+msgid "Wrap search"
+msgstr ""
+
#: ../features.py:9
msgid "WxGlade GUI"
msgstr "WxGlade GUI"
@@ -997,7 +3616,7 @@
"현재 쓰기 권한이 없습니다\n"
"그래도 WxGlade를 열까요?"
-#: ../ProjectController.py:221
+#: ../ProjectController.py:220
msgid ""
"You must have permission to work on the project\n"
"Work on a project copy ?"
@@ -1005,11 +3624,85 @@
"프로젝트 작업에는 권한이 필요합니다\n"
"사본으로 작업하시겠습니까?"
+#: ../editors/LDViewer.py:883
+msgid "You must select the block or group of blocks around which a branch should be added!"
+msgstr "Branch를 추가할 블럭 또는 블럭 그룹을 선택해야 합니다!"
+
+#: ../editors/LDViewer.py:663
+msgid "You must select the wire where a contact should be added!"
+msgstr "접점에 추가될 와이어를 선택해야 합니다!"
+
+#: ../dialogs/PouNameDialog.py:45
+#: ../dialogs/SFCStepNameDialog.py:47
+#: ../dialogs/SFCStepDialog.py:118
+msgid "You must type a name!"
+msgstr "이름을 입력하세요!"
+
+#: ../dialogs/ForceVariableDialog.py:165
+msgid "You must type a value!"
+msgstr "데이터 값을 입력하세요!"
+
+#: ../IDEFrame.py:414
+msgid "Zoom"
+msgstr "확대(Zoom)"
+
+#: ../editors/GraphicViewer.py:97
+#, fuzzy
+msgid "Zoom:"
+msgstr "확대(Zoom)"
+
+#: ../PLCOpenEditor.py:356
+#, python-format
+msgid "error: %s\n"
+msgstr "에러: %s\n"
+
#: ../util/ProcessLogger.py:161
#, python-format
msgid "exited with status %s (pid %s)\n"
msgstr "종료함: 종료 상태 번호 %s(PID %s)\n"
+#: ../PLCOpenEditor.py:508
+#: ../PLCOpenEditor.py:510
+msgid "file : "
+msgstr "파일 :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "function"
+msgstr "함수"
+
+#: ../PLCOpenEditor.py:511
+msgid "function : "
+msgstr "함수 :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "functionBlock"
+msgstr "함수 블럭"
+
+#: ../PLCOpenEditor.py:511
+msgid "line : "
+msgstr "라인 :"
+
+#: ../dialogs/PouDialog.py:31
+msgid "program"
+msgstr "프로그램"
+
+#: ../plcopen/iec_std.csv:84
+msgid "string from the middle"
+msgstr "중간 문자열"
+
+#: ../plcopen/iec_std.csv:82
+msgid "string left of"
+msgstr "좌측 문자열"
+
+#: ../plcopen/iec_std.csv:83
+msgid "string right of"
+msgstr "우측 문자열"
+
+#: ../PLCOpenEditor.py:354
+#, python-format
+msgid "warning: %s\n"
+msgstr "경고: %s\n"
+
#: Extra XSD strings
msgid "CanFestivalSlaveNode"
msgstr "CANFestival 슬레이브 노드"
@@ -1056,9 +3749,6 @@
msgid "BaseParams"
msgstr "베이스 파라메터"
-msgid "Name"
-msgstr "이름"
-
msgid "IEC_Channel"
msgstr "IEC_Channel"
@@ -1098,18 +3788,73 @@
msgid "Disable_Extensions"
msgstr "확장 모듈 제거 (Extension 제거)"
+#~ msgid "Close Project\tCTRL+SHIFT+W"
+#~ msgstr "프로젝트 닫기\tCTRL+SHIFT+W"
+
+#~ msgid "New\tCTRL+N"
+#~ msgstr "새로 만들기\tCTRL+N"
+
+#~ msgid "Open\tCTRL+O"
+#~ msgstr "프로젝트 열기\tCTRL+O"
+
+#~ msgid "Page Setup\tCTRL+ALT+P"
+#~ msgstr "인쇄 페이지 설정\tCTRL+ALT+P"
+
+#, fuzzy
+#~ msgid "Preview\tCTRL+SHIFT+P"
+#~ msgstr ""
+#~ "#-#-#-#-# Beremiz_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+#~ "인쇄 미리보기\tnCTRL+SHIFT+P\n"
+#~ "#-#-#-#-# PLCOpenEditor_ko_KR.po (Beremiz_Korean_Version) #-#-#-#-#\n"
+#~ "인쇄 페이지 미리보기\tCTRL+SHIFT+P"
+
+#~ msgid "Print\tCTRL+P"
+#~ msgstr "인쇄\tCTRL+P"
+
+#~ msgid "Quit\tCTRL+Q"
+#~ msgstr "프로그램 종료\tCTRL+Q"
+
+#~ msgid "Save\tCTRL+S"
+#~ msgstr "프로젝트 저장\tCTRL+S"
+
+#~ msgid "Save as\tCTRL+SHIFT+S"
+#~ msgstr "다른 이름으로 저장\tCTRL+SHIFT+S"
+
+#~ msgid "Copy\tCTRL+C"
+#~ msgstr "복사\tCTRL+C"
+
+#~ msgid "Cut\tCTRL+X"
+#~ msgstr "잘라내기\tCTRL+X"
+
+#, fuzzy
+#~ msgid "Find\tCTRL+F"
+#~ msgstr "되돌리기\tCTRL+Z"
+
+#~ msgid "PLCOpenEditor\tF1"
+#~ msgstr "PLCOpenEditor\tF1"
+
+#~ msgid "Paste\tCTRL+V"
+#~ msgstr "붙여넣기\tCTRL+V"
+
+#~ msgid "Redo\tCTRL+Y"
+#~ msgstr "되돌리기 취소\tCTRL+Y"
+
+#~ msgid "Refresh\tCTRL+R"
+#~ msgstr "새로 고침\tCTRL+R"
+
+#~ msgid "Save As...\tCTRL+SHIFT+S"
+#~ msgstr "다른 이름으로 저장...\tCTRL+SHIFT+S"
+
+#, fuzzy
+#~ msgid "Search in Project\tCTRL+SHIFT+F"
+#~ msgstr "프로젝트 내부 검색\tCTRL+F"
+
+#~ msgid "Undo\tCTRL+Z"
+#~ msgstr "되돌리기\tCTRL+Z"
+
#~ msgid "&ConfNode"
#~ msgstr "&플러그인"
-#~ msgid "&Properties"
-#~ msgstr "&속성"
-
-#~ msgid ", "
-#~ msgstr ", "
-
-#~ msgid ". "
-#~ msgstr ". "
-
#~ msgid "Add ConfNode"
#~ msgstr "플러그인 추가"
@@ -1146,11 +3891,56 @@
#~ msgid "Wrong URI, please check it !\n"
#~ msgstr "URI 타입 주소를 확인하세요!\n"
-#~ msgid "file : "
-#~ msgstr "파일 :"
-
-#~ msgid "function : "
-#~ msgstr "함수 :"
-
-#~ msgid "line : "
-#~ msgstr "라인 :"
+#~ msgid "Add a new data type"
+#~ msgstr "새로운 데이터 타입 추가"
+
+#~ msgid "Add new configuration"
+#~ msgstr "새로운 설정(configuration) 추가"
+
+#~ msgid "Add new resource"
+#~ msgstr "새로운 리소스 추가"
+
+#~ msgid "Block Types"
+#~ msgstr "블럭 타입"
+
+#~ msgid "CSV Log"
+#~ msgstr "CSV 로그"
+
+#~ msgid "Delete Task"
+#~ msgstr "태스크 삭제"
+
+#~ msgid "Display"
+#~ msgstr "디스플레이"
+
+#~ msgid "File"
+#~ msgstr "파일"
+
+#~ msgid "Graphic Panel"
+#~ msgstr "그래픽 패널"
+
+#~ msgid "Help"
+#~ msgstr "도움말"
+
+#~ msgid "Instances"
+#~ msgstr "인스턴스"
+
+#~ msgid "Invalid value \"%s\" for location"
+#~ msgstr "\"%s\"값은 위치에 적합하지 않습니다!"
+
+#~ msgid "Please enter configuration name"
+#~ msgstr "설정(configuration) 이름을 입력하세요"
+
+#~ msgid "Please enter data type name"
+#~ msgstr "데이터 타입 이름을 입력하세요"
+
+#~ msgid "Please enter resource name"
+#~ msgstr "리소스 이름을 입력하세요"
+
+#~ msgid "Please enter text"
+#~ msgstr "텍스트를 입력하세요"
+
+#~ msgid "Plugins"
+#~ msgstr "플러그인"
+
+#~ msgid "Types"
+#~ msgstr "타입(Types)"
--- a/i18n/README Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/README Fri Sep 07 16:45:55 2012 +0200
@@ -9,4 +9,4 @@
To generate app.fil:
- find .. -name "*.py" -exec grep -q '_(' {} ';' -print -o -name "*XSD*" -print | grep -v '/build/' | grep -v '/pyjs/' > app.fil
+ find .. -name "*.py" -exec grep -q '_(' {} ';' -print -o -name "*XSD*" -print -o -name "*.csv" -print | grep -v '/build/' | grep -v '/pyjs/' > app.fil
--- a/i18n/app.fil Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/app.fil Fri Sep 07 16:45:55 2012 +0200
@@ -4,18 +4,50 @@
../canfestival/config_utils.py
../canfestival/canfestival.py
../canfestival/NetworkEditor.py
-../tests/python/project_files/DirTreeCtrl.py
../Beremiz_service.py
-../ProjectNodeEditor.py
-../ConfTreeNodeEditor.py
+../controls/LocationCellEditor.py
+../controls/CustomTree.py
+../controls/VariablePanel.py
+../controls/CustomGrid.py
+../controls/SearchResultPanel.py
+../controls/LibraryPanel.py
+../controls/TextCtrlAutoComplete.py
+../controls/DebugVariablePanel.py
+../controls/PouInstanceVariablesPanel.py
+../controls/DurationCellEditor.py
+../controls/CustomTable.py
+../controls/CustomEditableListBox.py
+../controls/ProjectPropertiesPanel.py
+../IDEFrame.py
../Beremiz.py
../svgui/svgui.py
../svgui/svgui_server.py
../svgui/svguilib.py
+../xmlclass/xmlclass.py
+../xmlclass/xsdschema.py
+../PLCOpenEditor.py
../c_ext/CFileEditor.py
../c_ext/c_ext.py
../ConfigTreeNode.py
../wxglade_hmi/wxglade_hmi.py
+../PLCGenerator.py
+../docutil/dochtml.py
+../editors/ProjectNodeEditor.py
+../editors/ConfTreeNodeEditor.py
+../editors/TextViewer.py
+../editors/DataTypeEditor.py
+../editors/GraphicViewer.py
+../editors/ResourceEditor.py
+../editors/Viewer.py
+../editors/LDViewer.py
+../editors/SFCViewer.py
+../editors/EditorPanel.py
+../editors/FileManagementPanel.py
+../graphics/GraphicCommons.py
+../graphics/LD_Objects.py
+../graphics/SFC_Objects.py
+../graphics/FBD_Objects.py
+../PLCControler.py
../connectors/__init__.py
../connectors/PYRO/__init__.py
../targets/toolchain_gcc.py
@@ -28,13 +60,35 @@
../features.py
../ProjectController.py
../POULibrary.py
+../dialogs/PouNameDialog.py
+../dialogs/SFCDivergenceDialog.py
+../dialogs/LDPowerRailDialog.py
+../dialogs/PouTransitionDialog.py
+../dialogs/BrowseLocationsDialog.py
+../dialogs/DiscoveryDialog.py
+../dialogs/ProjectDialog.py
+../dialogs/SFCStepNameDialog.py
+../dialogs/ConnectionDialog.py
+../dialogs/FBDVariableDialog.py
+../dialogs/PouActionDialog.py
+../dialogs/BrowseValuesLibraryDialog.py
+../dialogs/PouDialog.py
+../dialogs/ActionBlockDialog.py
+../dialogs/SFCTransitionDialog.py
+../dialogs/LDElementDialog.py
+../dialogs/DurationEditorDialog.py
+../dialogs/FindInPouDialog.py
+../dialogs/SearchInProjectDialog.py
+../dialogs/SFCStepDialog.py
+../dialogs/ArrayTypeDialog.py
+../dialogs/FBDBlockDialog.py
+../dialogs/ForceVariableDialog.py
../util/ProcessLogger.py
-../util/BrowseValuesLibraryDialog.py
../util/MiniTextControler.py
-../util/TextCtrlAutoComplete.py
-../util/discovery.py
-../util/FileManagementPanel.py
../util/Zeroconf.py
../util/misc.py
+../plcopen/iec_std.csv
+../plcopen/structures.py
+../plcopen/plcopen.py
../py_ext/PythonFileCTNMixin.py
../py_ext/PythonEditor.py
--- a/i18n/messages.pot Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/messages.pot Fri Sep 07 16:45:55 2012 +0200
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-08-24 18:28+0200\n"
+"POT-Creation-Date: 2012-09-07 01:17+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +17,20 @@
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ../Beremiz.py:1069
+#: ../PLCOpenEditor.py:520
+msgid ""
+"\n"
+"An error has occurred.\n"
+"\n"
+"Click OK to save an error report.\n"
+"\n"
+"Please be kind enough to send this file to:\n"
+"edouard.tisserant@gmail.com\n"
+"\n"
+"Error:\n"
+msgstr ""
+
+#: ../Beremiz.py:1071
#, python-format
msgid ""
"\n"
@@ -32,82 +45,545 @@
"Traceback:\n"
msgstr ""
-#: ../ProjectController.py:891
+#: ../controls/VariablePanel.py:77
+msgid " External"
+msgstr ""
+
+#: ../controls/VariablePanel.py:76
+msgid " InOut"
+msgstr ""
+
+#: ../controls/VariablePanel.py:76
+msgid " Input"
+msgstr ""
+
+#: ../controls/VariablePanel.py:77
+msgid " Local"
+msgstr ""
+
+#: ../controls/VariablePanel.py:76
+msgid " Output"
+msgstr ""
+
+#: ../controls/VariablePanel.py:78
+msgid " Temp"
+msgstr ""
+
+#: ../PLCOpenEditor.py:530
+msgid " : "
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:94 ../dialogs/PouActionDialog.py:91
+#: ../dialogs/PouDialog.py:111 ../dialogs/SFCTransitionDialog.py:144
+#, python-format
+msgid " and %s"
+msgstr ""
+
+#: ../ProjectController.py:890
msgid " generation failed !\n"
msgstr ""
-#: ../Beremiz.py:892
+#: ../plcopen/plcopen.py:1051
+#, python-format
+msgid "\"%s\" Data Type doesn't exist !!!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1069
+#, python-format
+msgid "\"%s\" POU already exists !!!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1090
+#, python-format
+msgid "\"%s\" POU doesn't exist !!!"
+msgstr ""
+
+#: ../editors/Viewer.py:234
+#, python-format
+msgid "\"%s\" can't use itself!"
+msgstr ""
+
+#: ../IDEFrame.py:1706 ../IDEFrame.py:1725
+#, python-format
+msgid "\"%s\" config already exists!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:315
+#, python-format
+msgid "\"%s\" configuration already exists !!!"
+msgstr ""
+
+#: ../IDEFrame.py:1660
+#, python-format
+msgid "\"%s\" data type already exists!"
+msgstr ""
+
+#: ../PLCControler.py:2040 ../PLCControler.py:2044
+#, python-format
+msgid "\"%s\" element can't be pasted here!!!"
+msgstr ""
+
+#: ../editors/TextViewer.py:305 ../editors/TextViewer.py:325
+#: ../editors/Viewer.py:252 ../dialogs/PouTransitionDialog.py:105
+#: ../dialogs/ConnectionDialog.py:150 ../dialogs/PouActionDialog.py:102
+#: ../dialogs/FBDBlockDialog.py:162
+#, python-format
+msgid "\"%s\" element for this pou already exists!"
+msgstr ""
+
+#: ../Beremiz.py:894
#, python-format
msgid "\"%s\" folder is not a valid Beremiz project\n"
msgstr ""
+#: ../plcopen/structures.py:106
+#, python-format
+msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
+msgstr ""
+
+#: ../controls/VariablePanel.py:656 ../IDEFrame.py:1651
+#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:577
+#: ../dialogs/PouNameDialog.py:49 ../dialogs/PouTransitionDialog.py:101
+#: ../dialogs/SFCStepNameDialog.py:51 ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/FBDVariableDialog.py:199 ../dialogs/PouActionDialog.py:98
+#: ../dialogs/PouDialog.py:118 ../dialogs/SFCStepDialog.py:122
+#: ../dialogs/FBDBlockDialog.py:158
+#, python-format
+msgid "\"%s\" is a keyword. It can't be used!"
+msgstr ""
+
+#: ../editors/Viewer.py:240
+#, python-format
+msgid "\"%s\" is already used by \"%s\"!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:2786
+#, python-format
+msgid "\"%s\" is an invalid value!"
+msgstr ""
+
+#: ../PLCOpenEditor.py:362 ../PLCOpenEditor.py:399
+#, python-format
+msgid "\"%s\" is not a valid folder!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:654 ../IDEFrame.py:1649
+#: ../editors/DataTypeEditor.py:572 ../dialogs/PouNameDialog.py:47
+#: ../dialogs/PouTransitionDialog.py:99 ../dialogs/SFCStepNameDialog.py:49
+#: ../dialogs/ConnectionDialog.py:144 ../dialogs/PouActionDialog.py:96
+#: ../dialogs/PouDialog.py:116 ../dialogs/SFCStepDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:156
+#, python-format
+msgid "\"%s\" is not a valid identifier!"
+msgstr ""
+
+#: ../IDEFrame.py:214 ../IDEFrame.py:2445 ../IDEFrame.py:2464
+#, python-format
+msgid "\"%s\" is used by one or more POUs. It can't be removed!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:311 ../IDEFrame.py:1669
+#: ../editors/TextViewer.py:303 ../editors/TextViewer.py:323
+#: ../editors/TextViewer.py:360 ../editors/Viewer.py:250
+#: ../editors/Viewer.py:295 ../editors/Viewer.py:312
+#: ../dialogs/ConnectionDialog.py:148 ../dialogs/PouDialog.py:120
+#: ../dialogs/FBDBlockDialog.py:160
+#, python-format
+msgid "\"%s\" pou already exists!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:346
+#, python-format
+msgid "\"%s\" resource already exists in \"%s\" configuration !!!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:362
+#, python-format
+msgid "\"%s\" resource doesn't exist in \"%s\" configuration !!!"
+msgstr ""
+
+#: ../dialogs/SFCStepNameDialog.py:57 ../dialogs/SFCStepDialog.py:128
+#, python-format
+msgid "\"%s\" step already exists!"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:543
+#, python-format
+msgid "\"%s\" value already defined!"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:719 ../dialogs/ArrayTypeDialog.py:97
+#, python-format
+msgid "\"%s\" value isn't a valid array dimension!"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:726 ../dialogs/ArrayTypeDialog.py:103
+#, python-format
+msgid ""
+"\"%s\" value isn't a valid array dimension!\n"
+"Right value must be greater than left value."
+msgstr ""
+
+#: ../PLCControler.py:793
+#, python-format
+msgid "%s \"%s\" can't be pasted as a %s."
+msgstr ""
+
+#: ../PLCControler.py:1422
+#, python-format
+msgid "%s Data Types"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:278
+#, python-format
+msgid "%s Graphics"
+msgstr ""
+
+#: ../PLCControler.py:1417
+#, python-format
+msgid "%s POUs"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:42 ../canfestival/NetworkEditor.py:72
#, python-format
msgid "%s Profile"
msgstr ""
-#: ../Beremiz.py:308
+#: ../plcopen/plcopen.py:1780 ../plcopen/plcopen.py:1790
+#: ../plcopen/plcopen.py:1800 ../plcopen/plcopen.py:1810
+#: ../plcopen/plcopen.py:1819
+#, python-format
+msgid "%s body don't have instances!"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1842 ../plcopen/plcopen.py:1849
+#, python-format
+msgid "%s body don't have text!"
+msgstr ""
+
+#: ../IDEFrame.py:364
+msgid "&Add Element"
+msgstr ""
+
+#: ../IDEFrame.py:334
+msgid "&Configuration"
+msgstr ""
+
+#: ../IDEFrame.py:325
+msgid "&Data Type"
+msgstr ""
+
+#: ../IDEFrame.py:368
+msgid "&Delete"
+msgstr ""
+
+#: ../IDEFrame.py:317
+msgid "&Display"
+msgstr ""
+
+#: ../IDEFrame.py:316
+msgid "&Edit"
+msgstr ""
+
+#: ../IDEFrame.py:315
+msgid "&File"
+msgstr ""
+
+#: ../IDEFrame.py:327
+msgid "&Function"
+msgstr ""
+
+#: ../IDEFrame.py:318
+msgid "&Help"
+msgstr ""
+
+#: ../IDEFrame.py:331
+msgid "&Program"
+msgstr ""
+
+#: ../PLCOpenEditor.py:148
+msgid "&Properties"
+msgstr ""
+
+#: ../Beremiz.py:310
msgid "&Recent Projects"
msgstr ""
-#: ../Beremiz.py:350
+#: ../Beremiz.py:352
msgid "&Resource"
msgstr ""
+#: ../controls/SearchResultPanel.py:237
+#, python-format
+msgid "'%s' - %d match in project"
+msgstr ""
+
+#: ../controls/SearchResultPanel.py:239
+#, python-format
+msgid "'%s' - %d matches in project"
+msgstr ""
+
#: ../connectors/PYRO/__init__.py:51
#, python-format
msgid "'%s' is located at %s\n"
msgstr ""
-#: ../ProjectController.py:1269
+#: ../controls/SearchResultPanel.py:289
+#, python-format
+msgid "(%d matches)"
+msgstr ""
+
+#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510 ../PLCOpenEditor.py:511
+msgid ", "
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:96 ../dialogs/PouActionDialog.py:93
+#: ../dialogs/PouDialog.py:113 ../dialogs/SFCTransitionDialog.py:146
+#, python-format
+msgid ", %s"
+msgstr ""
+
+#: ../PLCOpenEditor.py:506
+msgid ". "
+msgstr ""
+
+#: ../ProjectController.py:1268
msgid "... debugger recovered\n"
msgstr ""
+#: ../IDEFrame.py:1672 ../IDEFrame.py:1714 ../IDEFrame.py:1733
+#: ../dialogs/PouDialog.py:122
+#, python-format
+msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr ""
+
+#: ../controls/VariablePanel.py:658 ../IDEFrame.py:1684 ../IDEFrame.py:1695
+#: ../dialogs/PouNameDialog.py:51 ../dialogs/PouTransitionDialog.py:103
+#: ../dialogs/SFCStepNameDialog.py:53 ../dialogs/PouActionDialog.py:100
+#: ../dialogs/SFCStepDialog.py:124
+#, python-format
+msgid "A POU named \"%s\" already exists!"
+msgstr ""
+
#: ../ConfigTreeNode.py:371
#, python-format
msgid "A child named \"%s\" already exist -> \"%s\"\n"
msgstr ""
-#: ../Beremiz.py:360
+#: ../dialogs/BrowseLocationsDialog.py:175
+msgid "A location must be selected!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:660 ../IDEFrame.py:1686 ../IDEFrame.py:1697
+#: ../dialogs/SFCStepNameDialog.py:55 ../dialogs/SFCStepDialog.py:126
+#, python-format
+msgid "A variable with \"%s\" as name already exists in this pou!"
+msgstr ""
+
+#: ../Beremiz.py:362 ../PLCOpenEditor.py:181
msgid "About"
msgstr ""
-#: ../Beremiz.py:929
+#: ../Beremiz.py:931
msgid "About Beremiz"
msgstr ""
+#: ../PLCOpenEditor.py:376
+msgid "About PLCOpenEditor"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:22
+msgid "Absolute number"
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:41 ../dialogs/SFCStepDialog.py:69
+msgid "Action"
+msgstr ""
+
+#: ../editors/Viewer.py:495
+msgid "Action Block"
+msgstr ""
+
+#: ../dialogs/PouActionDialog.py:81
+msgid "Action Name"
+msgstr ""
+
+#: ../dialogs/PouActionDialog.py:49
+msgid "Action Name:"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1480
+#, python-format
+msgid "Action with name %s doesn't exist!"
+msgstr ""
+
+#: ../PLCControler.py:95
+msgid "Actions"
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:134
+msgid "Actions:"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:54 ../canfestival/NetworkEditor.py:84
+#: ../editors/Viewer.py:527
msgid "Add"
msgstr ""
+#: ../IDEFrame.py:1925 ../IDEFrame.py:1956
+msgid "Add Action"
+msgstr ""
+
#: ../features.py:7
msgid "Add C code accessing located variables synchronously"
msgstr ""
-#: ../util/discovery.py:115
+#: ../IDEFrame.py:1908
+msgid "Add Configuration"
+msgstr ""
+
+#: ../IDEFrame.py:1888
+msgid "Add DataType"
+msgstr ""
+
+#: ../editors/Viewer.py:453
+msgid "Add Divergence Branch"
+msgstr ""
+
+#: ../dialogs/DiscoveryDialog.py:115
msgid "Add IP"
msgstr ""
+#: ../IDEFrame.py:1896
+msgid "Add POU"
+msgstr ""
+
#: ../features.py:8
msgid "Add Python code executed asynchronously"
msgstr ""
+#: ../IDEFrame.py:1936 ../IDEFrame.py:1982
+msgid "Add Resource"
+msgstr ""
+
+#: ../IDEFrame.py:1914 ../IDEFrame.py:1953
+msgid "Add Transition"
+msgstr ""
+
+#: ../editors/Viewer.py:442
+msgid "Add Wire Segment"
+msgstr ""
+
+#: ../editors/SFCViewer.py:359
+msgid "Add a new initial step"
+msgstr ""
+
+#: ../editors/Viewer.py:2289 ../editors/SFCViewer.py:696
+msgid "Add a new jump"
+msgstr ""
+
+#: ../editors/SFCViewer.py:381
+msgid "Add a new step"
+msgstr ""
+
#: ../features.py:9
msgid "Add a simple WxGlade based GUI."
msgstr ""
+#: ../dialogs/ActionBlockDialog.py:138
+msgid "Add action"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:345
+msgid "Add element"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:251
+msgid "Add instance"
+msgstr ""
+
#: ../canfestival/NetworkEditor.py:86
msgid "Add slave"
msgstr ""
-#: ../util/FileManagementPanel.py:35
+#: ../editors/ResourceEditor.py:222
+msgid "Add task"
+msgstr ""
+
+#: ../controls/VariablePanel.py:378
+msgid "Add variable"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:33
+msgid "Addition"
+msgstr ""
+
+#: ../plcopen/structures.py:250
+msgid "Additional function blocks"
+msgstr ""
+
+#: ../editors/Viewer.py:1395
+msgid "Alignment"
+msgstr ""
+
+#: ../controls/VariablePanel.py:75 ../dialogs/BrowseLocationsDialog.py:35
+#: ../dialogs/BrowseLocationsDialog.py:116
+msgid "All"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:35
msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
msgstr ""
-#: ../ProjectController.py:1336
+#: ../ProjectController.py:1335
msgid "Already connected. Please disconnect\n"
msgstr ""
+#: ../editors/DataTypeEditor.py:587
+#, python-format
+msgid "An element named \"%s\" already exists in this structure!"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:31
+msgid "Arc cosine"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:30
+msgid "Arc sine"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:32
+msgid "Arc tangent"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:33
+msgid "Arithmetic"
+msgstr ""
+
+#: ../controls/VariablePanel.py:729 ../editors/DataTypeEditor.py:52
+msgid "Array"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:39
+msgid "Assignment"
+msgstr ""
+
+#: ../dialogs/FBDVariableDialog.py:197
+msgid "At least a variable or an expression must be selected!"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:99
+msgid "Author"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:96
+msgid "Author Name (optional):"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:72
+msgid "Backward"
+msgstr ""
+
#: ../util/Zeroconf.py:599
msgid "Bad domain name (circular) at "
msgstr ""
@@ -121,36 +597,89 @@
msgid "Bad location size : %s"
msgstr ""
-#: ../Beremiz.py:484
+#: ../editors/DataTypeEditor.py:168 ../editors/DataTypeEditor.py:198
+#: ../editors/DataTypeEditor.py:290 ../dialogs/ArrayTypeDialog.py:55
+msgid "Base Type:"
+msgstr ""
+
+#: ../controls/VariablePanel.py:699 ../editors/DataTypeEditor.py:617
+msgid "Base Types"
+msgstr ""
+
+#: ../Beremiz.py:486
msgid "Beremiz"
msgstr ""
-#: ../util/BrowseValuesLibraryDialog.py:37
+#: ../plcopen/iec_std.csv:70
+msgid "Binary selection (1 of 2)"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:62
+msgid "Bit-shift"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:66
+msgid "Bitwise AND"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:67
+msgid "Bitwise OR"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:68
+msgid "Bitwise XOR"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:69
+msgid "Bitwise inverting"
+msgstr ""
+
+#: ../editors/Viewer.py:465
+msgid "Block"
+msgstr ""
+
+#: ../dialogs/FBDBlockDialog.py:38
+msgid "Block Properties"
+msgstr ""
+
+#: ../editors/Viewer.py:434
+msgid "Bottom"
+msgstr ""
+
+#: ../dialogs/BrowseValuesLibraryDialog.py:37
#, python-format
msgid "Browse %s values library"
msgstr ""
+#: ../dialogs/BrowseLocationsDialog.py:55
+msgid "Browse Locations"
+msgstr ""
+
+#: ../ProjectController.py:1484
+msgid "Build"
+msgstr ""
+
+#: ../ProjectController.py:1051
+msgid "Build directory already clean\n"
+msgstr ""
+
#: ../ProjectController.py:1485
-msgid "Build"
-msgstr ""
-
-#: ../ProjectController.py:1052
-msgid "Build directory already clean\n"
-msgstr ""
-
-#: ../ProjectController.py:1486
msgid "Build project into build folder"
msgstr ""
-#: ../ProjectController.py:911
+#: ../ProjectController.py:910
msgid "C Build crashed !\n"
msgstr ""
-#: ../ProjectController.py:908
+#: ../ProjectController.py:907
msgid "C Build failed.\n"
msgstr ""
-#: ../ProjectController.py:896
+#: ../ProjectController.py:895
msgid "C code generated successfully.\n"
msgstr ""
@@ -167,6 +696,33 @@
msgid "CANopen support"
msgstr ""
+#: ../plcopen/plcopen.py:1722 ../plcopen/plcopen.py:1736
+#: ../plcopen/plcopen.py:1757 ../plcopen/plcopen.py:1773
+msgid "Can only generate execution order on FBD networks!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:256
+msgid "Can only give a location to local or global variables"
+msgstr ""
+
+#: ../PLCOpenEditor.py:357
+#, python-format
+msgid "Can't generate program to file %s!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:254
+msgid "Can't give a location to a function block instance"
+msgstr ""
+
+#: ../PLCOpenEditor.py:397
+#, python-format
+msgid "Can't save project to file %s!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:298
+msgid "Can't set an initial value to a function block instance"
+msgstr ""
+
#: ../ConfigTreeNode.py:470
#, python-format
msgid "Cannot create child %s of type %s "
@@ -181,7 +737,7 @@
msgid "Cannot get PLC status - connection failed.\n"
msgstr ""
-#: ../ProjectController.py:716
+#: ../ProjectController.py:715
msgid "Cannot open/parse VARIABLES.csv!\n"
msgstr ""
@@ -190,6 +746,14 @@
msgid "Cannot set bit offset for non bool '%s' variable (ID:%d,Idx:%x,sIdx:%x))"
msgstr ""
+#: ../dialogs/FindInPouDialog.py:81 ../dialogs/SearchInProjectDialog.py:67
+msgid "Case sensitive"
+msgstr ""
+
+#: ../editors/Viewer.py:429
+msgid "Center"
+msgstr ""
+
#: ../Beremiz_service.py:322
msgid "Change IP of interface to bind"
msgstr ""
@@ -198,6 +762,10 @@
msgid "Change Name"
msgstr ""
+#: ../IDEFrame.py:1974
+msgid "Change POU Type To"
+msgstr ""
+
#: ../Beremiz_service.py:325
msgid "Change Port Number"
msgstr ""
@@ -206,23 +774,28 @@
msgid "Change working directory"
msgstr ""
+#: ../plcopen/iec_std.csv:81
+msgid "Character string"
+msgstr ""
+
#: ../svgui/svgui.py:92
msgid "Choose a SVG file"
msgstr ""
-#: ../ProjectController.py:354
+#: ../ProjectController.py:353
msgid "Choose a directory to save project"
msgstr ""
-#: ../canfestival/canfestival.py:118
+#: ../canfestival/canfestival.py:118 ../PLCOpenEditor.py:313
+#: ../PLCOpenEditor.py:347 ../PLCOpenEditor.py:391
msgid "Choose a file"
msgstr ""
-#: ../Beremiz.py:829 ../Beremiz.py:864
+#: ../Beremiz.py:831 ../Beremiz.py:866
msgid "Choose a project"
msgstr ""
-#: ../util/BrowseValuesLibraryDialog.py:42
+#: ../dialogs/BrowseValuesLibraryDialog.py:42
#, python-format
msgid "Choose a value for %s:"
msgstr ""
@@ -231,51 +804,104 @@
msgid "Choose a working directory "
msgstr ""
-#: ../ProjectController.py:282
+#: ../ProjectController.py:281
msgid "Chosen folder doesn't contain a program. It's not a valid project!"
msgstr ""
-#: ../ProjectController.py:248
+#: ../ProjectController.py:247
msgid "Chosen folder isn't empty. You can't use it for a new project!"
msgstr ""
-#: ../ProjectController.py:1489
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+msgid "Class"
+msgstr ""
+
+#: ../controls/VariablePanel.py:369
+msgid "Class Filter:"
+msgstr ""
+
+#: ../dialogs/FBDVariableDialog.py:62
+msgid "Class:"
+msgstr ""
+
+#: ../ProjectController.py:1488
msgid "Clean"
msgstr ""
-#: ../ProjectController.py:1491
+#: ../ProjectController.py:1490
msgid "Clean project build folder"
msgstr ""
-#: ../ProjectController.py:1049
+#: ../ProjectController.py:1048
msgid "Cleaning the build directory\n"
msgstr ""
-#: ../Beremiz.py:596
+#: ../IDEFrame.py:411
+msgid "Clear Errors"
+msgstr ""
+
+#: ../editors/Viewer.py:520
+msgid "Clear Execution Order"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:125
+msgid "Clear the graph values"
+msgstr ""
+
+#: ../Beremiz.py:598 ../PLCOpenEditor.py:221
msgid "Close Application"
msgstr ""
-#: ../Beremiz.py:550
+#: ../IDEFrame.py:1089 ../Beremiz.py:319 ../Beremiz.py:552
+#: ../PLCOpenEditor.py:131
msgid "Close Project"
msgstr ""
-#: ../Beremiz.py:317
-msgid "Close Project\tCTRL+SHIFT+W"
-msgstr ""
-
-#: ../Beremiz.py:315
-msgid "Close Tab\tCTRL+W"
-msgstr ""
-
-#: ../ProjectController.py:539
+#: ../Beremiz.py:317 ../PLCOpenEditor.py:129
+msgid "Close Tab"
+msgstr ""
+
+#: ../editors/Viewer.py:481
+msgid "Coil"
+msgstr ""
+
+#: ../editors/Viewer.py:501 ../editors/LDViewer.py:503
+msgid "Comment"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:94
+msgid "Company Name (required):"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:95
+msgid "Company URL (optional):"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:75
+msgid "Comparison"
+msgstr ""
+
+#: ../ProjectController.py:538
msgid "Compiling IEC Program into C code...\n"
msgstr ""
+#: ../plcopen/iec_std.csv:85
+msgid "Concatenation"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:47
+msgid "Configuration"
+msgstr ""
+
+#: ../PLCControler.py:96
+msgid "Configurations"
+msgstr ""
+
+#: ../ProjectController.py:1503
+msgid "Connect"
+msgstr ""
+
#: ../ProjectController.py:1504
-msgid "Connect"
-msgstr ""
-
-#: ../ProjectController.py:1505
msgid "Connect to the target PLC"
msgstr ""
@@ -284,11 +910,19 @@
msgid "Connecting to URI : %s\n"
msgstr ""
-#: ../ProjectController.py:1360
+#: ../editors/Viewer.py:467 ../dialogs/SFCTransitionDialog.py:76
+msgid "Connection"
+msgstr ""
+
+#: ../dialogs/ConnectionDialog.py:37
+msgid "Connection Properties"
+msgstr ""
+
+#: ../ProjectController.py:1359
msgid "Connection canceled!\n"
msgstr ""
-#: ../ProjectController.py:1385
+#: ../ProjectController.py:1384
#, python-format
msgid "Connection failed to %s!\n"
msgstr ""
@@ -298,14 +932,66 @@
msgid "Connection to '%s' failed.\n"
msgstr ""
-#: ../util/FileManagementPanel.py:283
+#: ../dialogs/ConnectionDialog.py:56
+msgid "Connector"
+msgstr ""
+
+#: ../dialogs/SFCStepDialog.py:58
+msgid "Connectors:"
+msgstr ""
+
+#: ../controls/VariablePanel.py:65
+msgid "Constant"
+msgstr ""
+
+#: ../editors/Viewer.py:477
+msgid "Contact"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:197
+msgid "Content Description (optional):"
+msgstr ""
+
+#: ../dialogs/ConnectionDialog.py:61
+msgid "Continuation"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:18
+msgid "Conversion from BCD"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:19
+msgid "Conversion to BCD"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:21
+msgid "Conversion to date"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:20
+msgid "Conversion to time-of-day"
+msgstr ""
+
+#: ../IDEFrame.py:348 ../IDEFrame.py:401 ../editors/Viewer.py:536
+msgid "Copy"
+msgstr ""
+
+#: ../IDEFrame.py:1961
+msgid "Copy POU"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:283
msgid "Copy file from left folder to right"
msgstr ""
-#: ../util/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:282
msgid "Copy file from right folder to left"
msgstr ""
+#: ../plcopen/iec_std.csv:28
+msgid "Cosine"
+msgstr ""
+
#: ../ConfigTreeNode.py:582
#, python-format
msgid ""
@@ -327,15 +1013,19 @@
" %s"
msgstr ""
-#: ../ProjectController.py:1318
+#: ../PLCControler.py:765 ../PLCControler.py:802
+msgid "Couldn't paste non-POU object."
+msgstr ""
+
+#: ../ProjectController.py:1317
msgid "Couldn't start PLC !\n"
msgstr ""
-#: ../ProjectController.py:1326
+#: ../ProjectController.py:1325
msgid "Couldn't stop PLC !\n"
msgstr ""
-#: ../ProjectController.py:1296
+#: ../ProjectController.py:1295
msgid "Couldn't stop debugger.\n"
msgstr ""
@@ -343,6 +1033,94 @@
msgid "Create HMI"
msgstr ""
+#: ../dialogs/PouDialog.py:43
+msgid "Create a new POU"
+msgstr ""
+
+#: ../dialogs/PouActionDialog.py:38
+msgid "Create a new action"
+msgstr ""
+
+#: ../IDEFrame.py:135
+msgid "Create a new action block"
+msgstr ""
+
+#: ../IDEFrame.py:84 ../IDEFrame.py:114 ../IDEFrame.py:147
+msgid "Create a new block"
+msgstr ""
+
+#: ../IDEFrame.py:108
+msgid "Create a new branch"
+msgstr ""
+
+#: ../IDEFrame.py:102
+msgid "Create a new coil"
+msgstr ""
+
+#: ../IDEFrame.py:78 ../IDEFrame.py:93 ../IDEFrame.py:123
+msgid "Create a new comment"
+msgstr ""
+
+#: ../IDEFrame.py:87 ../IDEFrame.py:117 ../IDEFrame.py:150
+msgid "Create a new connection"
+msgstr ""
+
+#: ../IDEFrame.py:105 ../IDEFrame.py:156
+msgid "Create a new contact"
+msgstr ""
+
+#: ../IDEFrame.py:138
+msgid "Create a new divergence"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:36
+msgid "Create a new divergence or convergence"
+msgstr ""
+
+#: ../IDEFrame.py:126
+msgid "Create a new initial step"
+msgstr ""
+
+#: ../IDEFrame.py:141
+msgid "Create a new jump"
+msgstr ""
+
+#: ../IDEFrame.py:96 ../IDEFrame.py:153
+msgid "Create a new power rail"
+msgstr ""
+
+#: ../IDEFrame.py:99
+msgid "Create a new rung"
+msgstr ""
+
+#: ../IDEFrame.py:129
+msgid "Create a new step"
+msgstr ""
+
+#: ../IDEFrame.py:132 ../dialogs/PouTransitionDialog.py:42
+msgid "Create a new transition"
+msgstr ""
+
+#: ../IDEFrame.py:81 ../IDEFrame.py:111 ../IDEFrame.py:144
+msgid "Create a new variable"
+msgstr ""
+
+#: ../IDEFrame.py:346 ../IDEFrame.py:400 ../editors/Viewer.py:535
+msgid "Cut"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:71
+msgid "Cyclic"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:42 ../plcopen/iec_std.csv:44
+#: ../plcopen/iec_std.csv:46 ../plcopen/iec_std.csv:50
+#: ../plcopen/iec_std.csv:52 ../plcopen/iec_std.csv:54
+#: ../plcopen/iec_std.csv:56 ../plcopen/iec_std.csv:58
+#: ../plcopen/iec_std.csv:60
+msgid "DEPRECATED"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:50 ../canfestival/NetworkEditor.py:80
msgid "DS-301 Profile"
msgstr ""
@@ -351,73 +1129,248 @@
msgid "DS-302 Profile"
msgstr ""
-#: ../ProjectController.py:1406
+#: ../dialogs/SearchInProjectDialog.py:43
+msgid "Data Type"
+msgstr ""
+
+#: ../PLCControler.py:95
+msgid "Data Types"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:16
+msgid "Data type conversion"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:44 ../plcopen/iec_std.csv:45
+msgid "Date addition"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:56 ../plcopen/iec_std.csv:57
+#: ../plcopen/iec_std.csv:58 ../plcopen/iec_std.csv:59
+msgid "Date and time subtraction"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:50 ../plcopen/iec_std.csv:51
+msgid "Date subtraction"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:43
+msgid "Days:"
+msgstr ""
+
+#: ../ProjectController.py:1405
msgid "Debug connect matching running PLC\n"
msgstr ""
-#: ../ProjectController.py:1409
+#: ../ProjectController.py:1408
msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
msgstr ""
-#: ../ProjectController.py:1123
+#: ../controls/PouInstanceVariablesPanel.py:52
+msgid "Debug instance"
+msgstr ""
+
+#: ../editors/Viewer.py:3222
+#, python-format
+msgid "Debug: %s"
+msgstr ""
+
+#: ../ProjectController.py:1122
#, python-format
msgid "Debug: Unknown variable '%s'\n"
msgstr ""
-#: ../ProjectController.py:1121
+#: ../ProjectController.py:1120
#, python-format
msgid "Debug: Unsupported type to debug '%s'\n"
msgstr ""
-#: ../ProjectController.py:1286
+#: ../IDEFrame.py:608
+msgid "Debugger"
+msgstr ""
+
+#: ../ProjectController.py:1285
msgid "Debugger disabled\n"
msgstr ""
-#: ../ProjectController.py:1298
+#: ../ProjectController.py:1297
msgid "Debugger stopped.\n"
msgstr ""
-#: ../Beremiz.py:956
+#: ../IDEFrame.py:1990 ../Beremiz.py:958 ../editors/Viewer.py:511
msgid "Delete"
msgstr ""
-#: ../util/FileManagementPanel.py:371
+#: ../editors/Viewer.py:454
+msgid "Delete Divergence Branch"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:371
msgid "Delete File"
msgstr ""
-#: ../ProjectController.py:1513
+#: ../editors/Viewer.py:443
+msgid "Delete Wire Segment"
+msgstr ""
+
+#: ../controls/CustomEditableListBox.py:41
+msgid "Delete item"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:88
+msgid "Deletion (within)"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:146
+msgid "Derivation Type:"
+msgstr ""
+
+#: ../plcopen/structures.py:264
+msgid ""
+"Derivative\n"
+"The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
+msgstr ""
+
+#: ../controls/VariablePanel.py:360
+msgid "Description:"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:314 ../dialogs/ArrayTypeDialog.py:61
+msgid "Dimensions:"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:61
+msgid "Direction"
+msgstr ""
+
+#: ../dialogs/BrowseLocationsDialog.py:78
+msgid "Direction:"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Directly"
+msgstr ""
+
+#: ../ProjectController.py:1512
msgid "Disconnect"
msgstr ""
-#: ../ProjectController.py:1515
+#: ../ProjectController.py:1514
msgid "Disconnect from PLC"
msgstr ""
-#: ../util/FileManagementPanel.py:370
+#: ../editors/Viewer.py:496
+msgid "Divergence"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:36
+msgid "Division"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:370
#, python-format
msgid "Do you really want to delete the file '%s'?"
msgstr ""
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+msgid "Documentation"
+msgstr ""
+
+#: ../PLCOpenEditor.py:351
+msgid "Done"
+msgstr ""
+
+#: ../plcopen/structures.py:227
+msgid ""
+"Down-counter\n"
+"The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Duration"
+msgstr ""
+
#: ../canfestival/canfestival.py:118
msgid "EDS files (*.eds)|*.eds|All files|*.*"
msgstr ""
+#: ../editors/Viewer.py:510
+msgid "Edit Block"
+msgstr ""
+
+#: ../dialogs/LDElementDialog.py:41
+msgid "Edit Coil Values"
+msgstr ""
+
+#: ../dialogs/LDElementDialog.py:38
+msgid "Edit Contact Values"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:59
+msgid "Edit Duration"
+msgstr ""
+
+#: ../dialogs/SFCStepDialog.py:35
+msgid "Edit Step"
+msgstr ""
+
#: ../wxglade_hmi/wxglade_hmi.py:12
msgid "Edit a WxWidgets GUI with WXGlade"
msgstr ""
-#: ../util/FileManagementPanel.py:284
+#: ../dialogs/ActionBlockDialog.py:122
+msgid "Edit action block properties"
+msgstr ""
+
+#: ../dialogs/ArrayTypeDialog.py:45
+msgid "Edit array type properties"
+msgstr ""
+
+#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+msgid "Edit comment"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:284
msgid "Edit file"
msgstr ""
-#: ../ProjectController.py:1527
+#: ../controls/CustomEditableListBox.py:39
+msgid "Edit item"
+msgstr ""
+
+#: ../editors/Viewer.py:2594
+msgid "Edit jump target"
+msgstr ""
+
+#: ../ProjectController.py:1526
msgid "Edit raw IEC code added to code generated by PLCGenerator"
msgstr ""
-#: ../ProjectController.py:1014
+#: ../editors/SFCViewer.py:725
+msgid "Edit step name"
+msgstr ""
+
+#: ../dialogs/SFCTransitionDialog.py:38
+msgid "Edit transition"
+msgstr ""
+
+#: ../IDEFrame.py:580
+msgid "Editor ToolBar"
+msgstr ""
+
+#: ../ProjectController.py:1013
msgid "Editor selection"
msgstr ""
+#: ../editors/DataTypeEditor.py:341
+msgid "Elements :"
+msgstr ""
+
+#: ../IDEFrame.py:343
+msgid "Enable Undo/Redo"
+msgstr ""
+
#: ../Beremiz_service.py:380
msgid "Enter a name "
msgstr ""
@@ -430,22 +1383,52 @@
msgid "Enter the IP of the interface to bind"
msgstr ""
-#: ../Beremiz_service.py:270 ../Beremiz_service.py:394 ../Beremiz.py:1081
-#: ../ProjectController.py:222 ../util/BrowseValuesLibraryDialog.py:83
-#: ../util/FileManagementPanel.py:210
+#: ../editors/DataTypeEditor.py:52
+msgid "Enumerated"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:77
+msgid "Equal to"
+msgstr ""
+
+#: ../Beremiz_service.py:270 ../Beremiz_service.py:394
+#: ../controls/VariablePanel.py:330 ../controls/VariablePanel.py:678
+#: ../controls/DebugVariablePanel.py:164 ../IDEFrame.py:1083
+#: ../IDEFrame.py:1672 ../IDEFrame.py:1709 ../IDEFrame.py:1714
+#: ../IDEFrame.py:1728 ../IDEFrame.py:1733 ../IDEFrame.py:2422
+#: ../Beremiz.py:1083 ../PLCOpenEditor.py:358 ../PLCOpenEditor.py:363
+#: ../PLCOpenEditor.py:531 ../PLCOpenEditor.py:541
+#: ../editors/TextViewer.py:376 ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:572
+#: ../editors/DataTypeEditor.py:577 ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:719 ../editors/DataTypeEditor.py:726
+#: ../editors/Viewer.py:366 ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:879 ../editors/LDViewer.py:883
+#: ../editors/FileManagementPanel.py:210 ../ProjectController.py:221
+#: ../dialogs/PouNameDialog.py:53 ../dialogs/PouTransitionDialog.py:107
+#: ../dialogs/BrowseLocationsDialog.py:175 ../dialogs/ProjectDialog.py:71
+#: ../dialogs/SFCStepNameDialog.py:59 ../dialogs/ConnectionDialog.py:152
+#: ../dialogs/FBDVariableDialog.py:201 ../dialogs/PouActionDialog.py:104
+#: ../dialogs/BrowseValuesLibraryDialog.py:83 ../dialogs/PouDialog.py:132
+#: ../dialogs/SFCTransitionDialog.py:147
+#: ../dialogs/DurationEditorDialog.py:121
+#: ../dialogs/DurationEditorDialog.py:163
+#: ../dialogs/SearchInProjectDialog.py:157 ../dialogs/SFCStepDialog.py:130
+#: ../dialogs/ArrayTypeDialog.py:97 ../dialogs/ArrayTypeDialog.py:103
+#: ../dialogs/FBDBlockDialog.py:164 ../dialogs/ForceVariableDialog.py:169
msgid "Error"
msgstr ""
-#: ../ProjectController.py:588
+#: ../ProjectController.py:587
msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
msgstr ""
-#: ../ProjectController.py:580
+#: ../ProjectController.py:579
#, python-format
msgid "Error : IEC to C compiler returned %d\n"
msgstr ""
-#: ../ProjectController.py:521
+#: ../ProjectController.py:520
#, python-format
msgid ""
"Error in ST/IL/SFC code generator :\n"
@@ -469,56 +1452,250 @@
msgid "Error: No PLC built\n"
msgstr ""
-#: ../ProjectController.py:1379
+#: ../ProjectController.py:1378
#, python-format
msgid "Exception while connecting %s!\n"
msgstr ""
+#: ../dialogs/FBDBlockDialog.py:95
+msgid "Execution Control:"
+msgstr ""
+
+#: ../dialogs/FBDVariableDialog.py:76 ../dialogs/FBDBlockDialog.py:87
+msgid "Execution Order:"
+msgstr ""
+
#: ../features.py:10
msgid "Experimental web based HMI"
msgstr ""
+#: ../plcopen/iec_std.csv:38
+msgid "Exponent"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:26
+msgid "Exponentiation"
+msgstr ""
+
#: ../canfestival/canfestival.py:128
msgid "Export CanOpen slave to EDS file"
msgstr ""
+#: ../editors/GraphicViewer.py:144
+msgid "Export graph values to clipboard"
+msgstr ""
+
#: ../canfestival/canfestival.py:127
msgid "Export slave"
msgstr ""
-#: ../ProjectController.py:592
+#: ../dialogs/FBDVariableDialog.py:69
+msgid "Expression:"
+msgstr ""
+
+#: ../controls/VariablePanel.py:77
+msgid "External"
+msgstr ""
+
+#: ../ProjectController.py:591
msgid "Extracting Located Variables...\n"
msgstr ""
-#: ../ProjectController.py:1446
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35 ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "FBD"
+msgstr ""
+
+#: ../ProjectController.py:1445
msgid "Failed : Must build before transfer.\n"
msgstr ""
-#: ../ProjectController.py:901
+#: ../editors/Viewer.py:405 ../dialogs/LDElementDialog.py:84
+msgid "Falling Edge"
+msgstr ""
+
+#: ../plcopen/structures.py:217
+msgid ""
+"Falling edge detector\n"
+"The output produces a single pulse when a falling edge is detected."
+msgstr ""
+
+#: ../ProjectController.py:900
msgid "Fatal : cannot get builder.\n"
msgstr ""
-#: ../util/FileManagementPanel.py:209
+#: ../dialogs/DurationEditorDialog.py:160
+#, python-format
+msgid "Field %s hasn't a valid value!"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:162
+#, python-format
+msgid "Fields %s haven't a valid value!"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:209
#, python-format
msgid "File '%s' already exists!"
msgstr ""
+#: ../IDEFrame.py:353 ../dialogs/FindInPouDialog.py:30
+#: ../dialogs/FindInPouDialog.py:99
+msgid "Find"
+msgstr ""
+
+#: ../IDEFrame.py:355
+msgid "Find Next"
+msgstr ""
+
+#: ../IDEFrame.py:357
+msgid "Find Previous"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:90
+msgid "Find position"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:51
+msgid "Find:"
+msgstr ""
+
#: ../connectors/PYRO/__init__.py:125
msgid "Force runtime reload\n"
msgstr ""
-#: ../ProjectController.py:511
+#: ../controls/DebugVariablePanel.py:295 ../editors/Viewer.py:1353
+msgid "Force value"
+msgstr ""
+
+#: ../dialogs/ForceVariableDialog.py:152
+msgid "Forcing Variable Value"
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:97 ../dialogs/ProjectDialog.py:70
+#: ../dialogs/PouActionDialog.py:94 ../dialogs/PouDialog.py:114
+#: ../dialogs/SFCTransitionDialog.py:147
+#, python-format
+msgid "Form isn't complete. %s must be filled!"
+msgstr ""
+
+#: ../dialogs/ConnectionDialog.py:142 ../dialogs/FBDBlockDialog.py:154
+msgid "Form isn't complete. Name must be filled!"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:145
+msgid "Form isn't complete. Pattern to search must be filled!"
+msgstr ""
+
+#: ../dialogs/FBDBlockDialog.py:152
+msgid "Form isn't complete. Valid block type must be selected!"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:67
+msgid "Forward"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:44
+msgid "Function"
+msgstr ""
+
+#: ../IDEFrame.py:329
+msgid "Function &Block"
+msgstr ""
+
+#: ../IDEFrame.py:1969 ../dialogs/SearchInProjectDialog.py:45
+msgid "Function Block"
+msgstr ""
+
+#: ../controls/VariablePanel.py:741
+msgid "Function Block Types"
+msgstr ""
+
+#: ../PLCControler.py:94
+msgid "Function Blocks"
+msgstr ""
+
+#: ../editors/Viewer.py:236
+msgid "Function Blocks can't be used in Functions!"
+msgstr ""
+
+#: ../editors/Viewer.py:238
+msgid "Function Blocks can't be used in Transitions!"
+msgstr ""
+
+#: ../PLCControler.py:2055
+#, python-format
+msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
+msgstr ""
+
+#: ../PLCControler.py:94
+msgid "Functions"
+msgstr ""
+
+#: ../PLCOpenEditor.py:138
+msgid "Generate Program"
+msgstr ""
+
+#: ../ProjectController.py:510
msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
msgstr ""
-#: ../util/FileManagementPanel.py:303
+#: ../controls/VariablePanel.py:78
+msgid "Global"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:131
+msgid "Go to current value"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:173
+msgid "Graphics"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:75
+msgid "Greater than"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:76
+msgid "Greater than or equal to"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:134
+msgid "Grid Resolution:"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:120
+msgid "Height:"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:303
msgid "Home Directory:"
msgstr ""
-#: ../ProjectController.py:828
+#: ../controls/ProjectPropertiesPanel.py:150
+msgid "Horizontal:"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:44
+msgid "Hours:"
+msgstr ""
+
+#: ../plcopen/structures.py:279
+msgid ""
+"Hysteresis\n"
+"The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
+msgstr ""
+
+#: ../ProjectController.py:827
msgid "IEC-61131-3 code generation failed !\n"
msgstr ""
+#: ../dialogs/PouTransitionDialog.py:35 ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "IL"
+msgstr ""
+
#: ../Beremiz_service.py:356 ../Beremiz_service.py:357
msgid "IP is not valid!"
msgstr ""
@@ -527,16 +1704,157 @@
msgid "Import SVG"
msgstr ""
+#: ../controls/VariablePanel.py:76 ../dialogs/FBDVariableDialog.py:34
+msgid "InOut"
+msgstr ""
+
+#: ../controls/VariablePanel.py:263
+#, python-format
+msgid "Incompatible data types between \"%s\" and \"%s\""
+msgstr ""
+
+#: ../controls/VariablePanel.py:274
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"%s\""
+msgstr ""
+
+#: ../controls/VariablePanel.py:270
+#, python-format
+msgid "Incompatible size of data between \"%s\" and \"BOOL\""
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Indicator"
+msgstr ""
+
+#: ../editors/Viewer.py:492
+msgid "Initial Step"
+msgstr ""
+
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48
+msgid "Initial Value"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:178 ../editors/DataTypeEditor.py:209
+#: ../editors/DataTypeEditor.py:265 ../editors/DataTypeEditor.py:303
+msgid "Initial Value:"
+msgstr ""
+
#: ../svgui/svgui.py:21
msgid "Inkscape"
msgstr ""
+#: ../dialogs/ActionBlockDialog.py:41 ../dialogs/SFCTransitionDialog.py:66
+#: ../dialogs/SFCTransitionDialog.py:137
+msgid "Inline"
+msgstr ""
+
+#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:36
+#: ../dialogs/FBDVariableDialog.py:33 ../dialogs/SFCStepDialog.py:61
+msgid "Input"
+msgstr ""
+
+#: ../dialogs/FBDBlockDialog.py:78
+msgid "Inputs:"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:87
+msgid "Insertion (into)"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1833
+#, python-format
+msgid "Instance with id %d doesn't exist!"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:247
+msgid "Instances:"
+msgstr ""
+
+#: ../plcopen/structures.py:259
+msgid ""
+"Integral\n"
+"The integral function block integrates the value of input XIN over time."
+msgstr ""
+
+#: ../controls/VariablePanel.py:75
+msgid "Interface"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:71
+msgid "Interrupt"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:67
+msgid "Interval"
+msgstr ""
+
+#: ../PLCControler.py:2032 ../PLCControler.py:2070
+msgid "Invalid plcopen element(s)!!!"
+msgstr ""
+
#: ../canfestival/config_utils.py:376 ../canfestival/config_utils.py:637
#, python-format
msgid "Invalid type \"%s\"-> %d != %d for location\"%s\""
msgstr ""
-#: ../ProjectController.py:1452
+#: ../dialogs/ForceVariableDialog.py:167
+#, python-format
+msgid "Invalid value \"%s\" for \"%s\" variable!"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:153 ../controls/DebugVariablePanel.py:156
+#, python-format
+msgid "Invalid value \"%s\" for debug variable"
+msgstr ""
+
+#: ../controls/VariablePanel.py:244 ../controls/VariablePanel.py:247
+#, python-format
+msgid "Invalid value \"%s\" for variable grid element"
+msgstr ""
+
+#: ../editors/Viewer.py:221 ../editors/Viewer.py:224
+#, python-format
+msgid "Invalid value \"%s\" for viewer block"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:121
+msgid ""
+"Invalid value!\n"
+"You must fill a numeric value."
+msgstr ""
+
+#: ../editors/Viewer.py:497
+msgid "Jump"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:143
+#: ../dialogs/PouTransitionDialog.py:35 ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "LD"
+msgstr ""
+
+#: ../editors/LDViewer.py:215 ../editors/LDViewer.py:231
+#, python-format
+msgid "Ladder element with id %d is on more than one rung."
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:86 ../dialogs/PouActionDialog.py:83
+#: ../dialogs/PouDialog.py:102
+msgid "Language"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:186
+msgid "Language (optional):"
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:60 ../dialogs/PouActionDialog.py:56
+#: ../dialogs/PouDialog.py:71
+msgid "Language:"
+msgstr ""
+
+#: ../ProjectController.py:1451
msgid "Latest build already matches current target. Transfering anyway...\n"
msgstr ""
@@ -548,22 +1866,62 @@
msgid "Launch a live Python shell"
msgstr ""
+#: ../editors/Viewer.py:428
+msgid "Left"
+msgstr ""
+
+#: ../dialogs/LDPowerRailDialog.py:55
+msgid "Left PowerRail"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:81
+msgid "Length of string"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:78
+msgid "Less than"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:79
+msgid "Less than or equal to"
+msgstr ""
+
+#: ../IDEFrame.py:600
+msgid "Library"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:73
+msgid "Limitation"
+msgstr ""
+
#: ../targets/toolchain_gcc.py:142
msgid "Linking :\n"
msgstr ""
-#: ../util/discovery.py:110
+#: ../controls/VariablePanel.py:77 ../dialogs/DiscoveryDialog.py:110
msgid "Local"
msgstr ""
-#: ../ProjectController.py:1354
+#: ../ProjectController.py:1353
msgid "Local service discovery failed!\n"
msgstr ""
-#: ../Beremiz.py:391
+#: ../controls/VariablePanel.py:58
+msgid "Location"
+msgstr ""
+
+#: ../dialogs/BrowseLocationsDialog.py:61
+msgid "Locations available:"
+msgstr ""
+
+#: ../Beremiz.py:393
msgid "Log Console"
msgstr ""
+#: ../plcopen/iec_std.csv:25
+msgid "Logarithm to base 10"
+msgstr ""
+
#: ../connectors/PYRO/__init__.py:55
#, python-format
msgid "MDNS resolution failure for '%s'\n"
@@ -586,30 +1944,209 @@
msgid "Max count (%d) reached for this confnode of type %s "
msgstr ""
-#: ../util/FileManagementPanel.py:301
+#: ../plcopen/iec_std.csv:71
+msgid "Maximum"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:232
+msgid "Maximum:"
+msgstr ""
+
+#: ../dialogs/BrowseLocationsDialog.py:38
+msgid "Memory"
+msgstr ""
+
+#: ../IDEFrame.py:568
+msgid "Menu ToolBar"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:48
+msgid "Microseconds:"
+msgstr ""
+
+#: ../editors/Viewer.py:433
+msgid "Middle"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:47
+msgid "Milliseconds:"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:72
+msgid "Minimum"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:219
+msgid "Minimum:"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:45
+msgid "Minutes:"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:210
+msgid "Miscellaneous"
+msgstr ""
+
+#: ../dialogs/LDElementDialog.py:59
+msgid "Modifier:"
+msgstr ""
+
+#: ../PLCGenerator.py:703 ../PLCGenerator.py:936
+#, python-format
+msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:141
+msgid "Move action down"
+msgstr ""
+
+#: ../dialogs/ActionBlockDialog.py:140
+msgid "Move action up"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:185
+msgid "Move debug variable down"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:184
+msgid "Move debug variable up"
+msgstr ""
+
+#: ../controls/CustomEditableListBox.py:43
+msgid "Move down"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:348
+msgid "Move element down"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:347
+msgid "Move element up"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:254
+msgid "Move instance down"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:253
+msgid "Move instance up"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:225
+msgid "Move task down"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:224
+msgid "Move task up"
+msgstr ""
+
+#: ../IDEFrame.py:75 ../IDEFrame.py:90 ../IDEFrame.py:120 ../IDEFrame.py:161
+msgid "Move the view"
+msgstr ""
+
+#: ../controls/CustomEditableListBox.py:42
+msgid "Move up"
+msgstr ""
+
+#: ../controls/VariablePanel.py:381
+msgid "Move variable down"
+msgstr ""
+
+#: ../controls/VariablePanel.py:380
+msgid "Move variable up"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:74
+msgid "Multiplexer (select 1 of N)"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:34
+msgid "Multiplication"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:301
msgid "My Computer:"
msgstr ""
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:76
+msgid "Name"
+msgstr ""
+
#: ../Beremiz_service.py:381
msgid "Name must not be null!"
msgstr ""
-#: ../Beremiz.py:340
+#: ../dialogs/ConnectionDialog.py:65 ../dialogs/FBDVariableDialog.py:89
+#: ../dialogs/LDElementDialog.py:88 ../dialogs/SFCStepDialog.py:51
+#: ../dialogs/FBDBlockDialog.py:70
+msgid "Name:"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:24
+msgid "Natural logarithm"
+msgstr ""
+
+#: ../editors/Viewer.py:403 ../dialogs/LDElementDialog.py:67
+msgid "Negated"
+msgstr ""
+
+#: ../Beremiz.py:307 ../Beremiz.py:342 ../PLCOpenEditor.py:125
+#: ../PLCOpenEditor.py:167
msgid "New"
msgstr ""
-#: ../Beremiz.py:305
-msgid "New\tCTRL+N"
-msgstr ""
-
-#: ../ProjectController.py:1479
+#: ../controls/CustomEditableListBox.py:40
+msgid "New item"
+msgstr ""
+
+#: ../editors/Viewer.py:402
+msgid "No Modifier"
+msgstr ""
+
+#: ../PLCControler.py:2929
+msgid "No PLC project found"
+msgstr ""
+
+#: ../ProjectController.py:1478
msgid "No PLC to transfer (did build succeed ?)\n"
msgstr ""
+#: ../PLCGenerator.py:1321
+#, python-format
+msgid "No body defined in \"%s\" POU"
+msgstr ""
+
+#: ../PLCGenerator.py:722 ../PLCGenerator.py:945
+#, python-format
+msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
+msgstr ""
+
+#: ../PLCOpenEditor.py:370
+msgid ""
+"No documentation available.\n"
+"Coming soon."
+msgstr ""
+
+#: ../PLCGenerator.py:744
+#, python-format
+msgid "No informations found for \"%s\" block"
+msgstr ""
+
+#: ../plcopen/structures.py:167
+msgid "No output variable found"
+msgstr ""
+
#: ../Beremiz_service.py:394
msgid "No running PLC"
msgstr ""
+#: ../controls/SearchResultPanel.py:169
+msgid "No search results available."
+msgstr ""
+
#: ../svgui/svgui.py:98
#, python-format
msgid "No such SVG file: %s\n"
@@ -625,10 +2162,15 @@
msgid "No such index/subindex (%x,%x) in ID : %d (variable %s)"
msgstr ""
-#: ../util/BrowseValuesLibraryDialog.py:83
+#: ../dialogs/BrowseValuesLibraryDialog.py:83
msgid "No valid value selected!"
msgstr ""
+#: ../PLCGenerator.py:1319
+#, python-format
+msgid "No variable defined in \"%s\" POU"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:49 ../canfestival/NetworkEditor.py:79
msgid "Node infos"
msgstr ""
@@ -638,24 +2180,57 @@
msgid "Non existing node ID : %d (variable %s)"
msgstr ""
+#: ../controls/VariablePanel.py:69
+msgid "Non-Retain"
+msgstr ""
+
+#: ../dialogs/LDElementDialog.py:62
+msgid "Normal"
+msgstr ""
+
#: ../canfestival/config_utils.py:383
#, python-format
msgid "Not PDO mappable variable : '%s' (ID:%d,Idx:%x,sIdx:%x))"
msgstr ""
-#: ../Beremiz.py:341
+#: ../plcopen/iec_std.csv:80
+msgid "Not equal to"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:80
+msgid "Number of sequences:"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:22
+msgid "Numerical"
+msgstr ""
+
+#: ../plcopen/structures.py:247
+msgid ""
+"Off-delay timer\n"
+"The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
+msgstr ""
+
+#: ../plcopen/structures.py:242
+msgid ""
+"On-delay timer\n"
+"The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:93
+msgid "Only Elements"
+msgstr ""
+
+#: ../Beremiz.py:309 ../Beremiz.py:343 ../PLCOpenEditor.py:127
+#: ../PLCOpenEditor.py:168
msgid "Open"
msgstr ""
-#: ../Beremiz.py:307
-msgid "Open\tCTRL+O"
-msgstr ""
-
#: ../svgui/svgui.py:107
msgid "Open Inkscape"
msgstr ""
-#: ../ProjectController.py:1531
+#: ../ProjectController.py:1530
msgid "Open a file explorer to manage project files"
msgstr ""
@@ -663,10 +2238,27 @@
msgid "Open wxGlade"
msgstr ""
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+msgid "Option"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:76
+msgid "Options"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:97
+msgid "Organization (optional):"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:47 ../canfestival/NetworkEditor.py:77
msgid "Other Profile"
msgstr ""
+#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:37
+#: ../dialogs/FBDVariableDialog.py:35 ../dialogs/SFCStepDialog.py:65
+msgid "Output"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:36 ../canfestival/NetworkEditor.py:66
msgid "PDO Receive"
msgstr ""
@@ -675,17 +2267,96 @@
msgid "PDO Transmit"
msgstr ""
+#: ../plcopen/structures.py:269
+msgid ""
+"PID\n"
+"The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
+msgstr ""
+
#: ../targets/toolchain_gcc.py:107
msgid "PLC :\n"
msgstr ""
-#: ../ProjectController.py:1097 ../ProjectController.py:1399
+#: ../ProjectController.py:1096 ../ProjectController.py:1398
#, python-format
msgid "PLC is %s\n"
msgstr ""
-#: ../Beremiz.py:320
-msgid "Page Setup\tCTRL+ALT+P"
+#: ../PLCOpenEditor.py:313 ../PLCOpenEditor.py:391
+msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
+msgstr ""
+
+#: ../PLCOpenEditor.py:175 ../PLCOpenEditor.py:231
+msgid "PLCOpenEditor"
+msgstr ""
+
+#: ../dialogs/PouDialog.py:98
+msgid "POU Name"
+msgstr ""
+
+#: ../dialogs/PouDialog.py:56
+msgid "POU Name:"
+msgstr ""
+
+#: ../dialogs/PouDialog.py:100
+msgid "POU Type"
+msgstr ""
+
+#: ../dialogs/PouDialog.py:63
+msgid "POU Type:"
+msgstr ""
+
+#: ../Beremiz.py:322 ../PLCOpenEditor.py:141
+msgid "Page Setup"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:110
+msgid "Page Size (optional):"
+msgstr ""
+
+#: ../PLCOpenEditor.py:476
+#, python-format
+msgid "Page: %d"
+msgstr ""
+
+#: ../controls/PouInstanceVariablesPanel.py:41
+msgid "Parent instance"
+msgstr ""
+
+#: ../IDEFrame.py:350 ../IDEFrame.py:402 ../editors/Viewer.py:537
+msgid "Paste"
+msgstr ""
+
+#: ../IDEFrame.py:1900
+msgid "Paste POU"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:64
+msgid "Pattern to search:"
+msgstr ""
+
+#: ../dialogs/LDPowerRailDialog.py:64
+msgid "Pin number:"
+msgstr ""
+
+#: ../editors/Viewer.py:2289 ../editors/Viewer.py:2594
+#: ../editors/SFCViewer.py:696
+msgid "Please choose a target"
+msgstr ""
+
+#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
+#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+msgid "Please enter comment text"
+msgstr ""
+
+#: ../editors/SFCViewer.py:359 ../editors/SFCViewer.py:381
+#: ../editors/SFCViewer.py:725
+msgid "Please enter step name"
+msgstr ""
+
+#: ../dialogs/ForceVariableDialog.py:153
+#, python-format
+msgid "Please enter value for a \"%s\" variable:"
msgstr ""
#: ../Beremiz_service.py:366
@@ -696,74 +2367,307 @@
msgid "Port number must be an integer!"
msgstr ""
-#: ../Beremiz.py:322
-msgid "Preview\tCTRL+SHIFT+P"
-msgstr ""
-
-#: ../Beremiz.py:344
+#: ../editors/GraphicViewer.py:105
+msgid "Position:"
+msgstr ""
+
+#: ../editors/Viewer.py:476
+msgid "Power Rail"
+msgstr ""
+
+#: ../dialogs/LDPowerRailDialog.py:36
+msgid "Power Rail Properties"
+msgstr ""
+
+#: ../Beremiz.py:324 ../PLCOpenEditor.py:143
+msgid "Preview"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:93 ../dialogs/LDPowerRailDialog.py:78
+#: ../dialogs/ConnectionDialog.py:78 ../dialogs/FBDVariableDialog.py:97
+#: ../dialogs/SFCTransitionDialog.py:96 ../dialogs/LDElementDialog.py:101
+#: ../dialogs/SFCStepDialog.py:79 ../dialogs/FBDBlockDialog.py:103
+msgid "Preview:"
+msgstr ""
+
+#: ../Beremiz.py:326 ../Beremiz.py:346 ../PLCOpenEditor.py:145
+#: ../PLCOpenEditor.py:171
msgid "Print"
msgstr ""
-#: ../Beremiz.py:324
-msgid "Print\tCTRL+P"
-msgstr ""
-
-#: ../ProjectController.py:1530
+#: ../IDEFrame.py:1155
+msgid "Print preview"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:67
+msgid "Priority"
+msgstr ""
+
+#: ../dialogs/SFCTransitionDialog.py:83
+msgid "Priority:"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:80
+msgid "Product Name (required):"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:82
+msgid "Product Release (optional):"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:81
+msgid "Product Version (required):"
+msgstr ""
+
+#: ../IDEFrame.py:1972 ../dialogs/SearchInProjectDialog.py:46
+msgid "Program"
+msgstr ""
+
+#: ../PLCOpenEditor.py:360
+msgid "Program was successfully generated!"
+msgstr ""
+
+#: ../PLCControler.py:95
+msgid "Programs"
+msgstr ""
+
+#: ../editors/Viewer.py:230
+msgid "Programs can't be used by other POUs!"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:84 ../IDEFrame.py:553
+msgid "Project"
+msgstr ""
+
+#: ../controls/SearchResultPanel.py:173
+#, python-format
+msgid "Project '%s':"
+msgstr ""
+
+#: ../ProjectController.py:1529
msgid "Project Files"
msgstr ""
+#: ../controls/ProjectPropertiesPanel.py:78
+msgid "Project Name (required):"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:79
+msgid "Project Version (optional):"
+msgstr ""
+
+#: ../PLCControler.py:2916
+msgid ""
+"Project file syntax error:\n"
+"\n"
+msgstr ""
+
+#: ../dialogs/ProjectDialog.py:32
+msgid "Project properties"
+msgstr ""
+
#: ../ConfigTreeNode.py:506
#, python-format
msgid "Project tree layout do not match confnode.xml %s!=%s "
msgstr ""
+#: ../PLCControler.py:96
+msgid "Properties"
+msgstr ""
+
+#: ../plcopen/structures.py:237
+msgid ""
+"Pulse timer\n"
+"The pulse timer can be used to generate output pulses of a given time duration."
+msgstr ""
+
#: ../features.py:8
msgid "Python file"
msgstr ""
-#: ../Beremiz_service.py:328
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Qualifier"
+msgstr ""
+
+#: ../Beremiz_service.py:328 ../Beremiz.py:329 ../PLCOpenEditor.py:151
msgid "Quit"
msgstr ""
-#: ../Beremiz.py:327
-msgid "Quit\tCTRL+Q"
-msgstr ""
-
-#: ../ProjectController.py:1526
+#: ../plcopen/structures.py:202
+msgid ""
+"RS bistable\n"
+"The RS bistable is a latch where the Reset dominates."
+msgstr ""
+
+#: ../plcopen/structures.py:274
+msgid ""
+"Ramp\n"
+"The RAMP function block is modelled on example given in the standard."
+msgstr ""
+
+#: ../editors/GraphicViewer.py:89
+msgid "Range:"
+msgstr ""
+
+#: ../ProjectController.py:1525
msgid "Raw IEC code"
msgstr ""
-#: ../Beremiz.py:1037
+#: ../plcopen/structures.py:254
+msgid ""
+"Real time clock\n"
+"The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
+msgstr ""
+
+#: ../Beremiz.py:1039
#, python-format
msgid "Really delete node '%s'?"
msgstr ""
-#: ../util/discovery.py:105
+#: ../IDEFrame.py:340 ../IDEFrame.py:398
+msgid "Redo"
+msgstr ""
+
+#: ../dialogs/SFCTransitionDialog.py:57 ../dialogs/SFCTransitionDialog.py:135
+msgid "Reference"
+msgstr ""
+
+#: ../IDEFrame.py:408 ../dialogs/DiscoveryDialog.py:105
msgid "Refresh"
msgstr ""
-#: ../Beremiz.py:1038
+#: ../dialogs/SearchInProjectDialog.py:73
+msgid "Regular expression"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:91
+msgid "Regular expressions"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:299 ../editors/Viewer.py:1356
+msgid "Release value"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:37
+msgid "Remainder (modulo)"
+msgstr ""
+
+#: ../Beremiz.py:1040
#, python-format
msgid "Remove %s node"
msgstr ""
-#: ../util/FileManagementPanel.py:281
+#: ../dialogs/ActionBlockDialog.py:139
+msgid "Remove action"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:183
+msgid "Remove debug variable"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:346
+msgid "Remove element"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:281
msgid "Remove file from left folder"
msgstr ""
+#: ../editors/ResourceEditor.py:252
+msgid "Remove instance"
+msgstr ""
+
#: ../canfestival/NetworkEditor.py:87
msgid "Remove slave"
msgstr ""
-#: ../util/FileManagementPanel.py:399
+#: ../editors/ResourceEditor.py:223
+msgid "Remove task"
+msgstr ""
+
+#: ../controls/VariablePanel.py:379
+msgid "Remove variable"
+msgstr ""
+
+#: ../IDEFrame.py:1976
+msgid "Rename"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:399
msgid "Replace File"
msgstr ""
-#: ../ProjectController.py:1494
+#: ../plcopen/iec_std.csv:89
+msgid "Replacement (within)"
+msgstr ""
+
+#: ../dialogs/LDElementDialog.py:76
+msgid "Reset"
+msgstr ""
+
+#: ../editors/Viewer.py:521
+msgid "Reset Execution Order"
+msgstr ""
+
+#: ../IDEFrame.py:423
+msgid "Reset Perspective"
+msgstr ""
+
+#: ../controls/SearchResultPanel.py:105
+msgid "Reset search result"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:137
+msgid "Reset zoom and offset"
+msgstr ""
+
+#: ../PLCControler.py:96
+msgid "Resources"
+msgstr ""
+
+#: ../controls/VariablePanel.py:67
+msgid "Retain"
+msgstr ""
+
+#: ../controls/VariablePanel.py:352
+msgid "Return Type:"
+msgstr ""
+
+#: ../editors/Viewer.py:430
+msgid "Right"
+msgstr ""
+
+#: ../dialogs/LDPowerRailDialog.py:60
+msgid "Right PowerRail"
+msgstr ""
+
+#: ../editors/Viewer.py:404 ../dialogs/LDElementDialog.py:80
+msgid "Rising Edge"
+msgstr ""
+
+#: ../plcopen/structures.py:212
+msgid ""
+"Rising edge detector\n"
+"The output produces a single pulse when a rising edge is detected."
+msgstr ""
+
+#: ../plcopen/iec_std.csv:65
+msgid "Rotate left"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:64
+msgid "Rotate right"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:17
+msgid "Rounding up/down"
+msgstr ""
+
+#: ../ProjectController.py:1493
msgid "Run"
msgstr ""
-#: ../ProjectController.py:842 ../ProjectController.py:851
+#: ../ProjectController.py:841 ../ProjectController.py:850
msgid "Runtime extensions C code generation failed !\n"
msgstr ""
@@ -775,6 +2679,25 @@
msgid "SDO Server"
msgstr ""
+#: ../controls/ProjectPropertiesPanel.py:143 ../dialogs/PouDialog.py:36
+msgid "SFC"
+msgstr ""
+
+#: ../plcopen/structures.py:197
+msgid ""
+"SR bistable\n"
+"The SR bistable is a latch where the Set dominates."
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:35 ../dialogs/PouActionDialog.py:31
+#: ../dialogs/PouDialog.py:36
+msgid "ST"
+msgstr ""
+
+#: ../PLCOpenEditor.py:347
+msgid "ST files (*.st)|*.st|All files|*.*"
+msgstr ""
+
#: ../svgui/svgui.py:92
msgid "SVG files (*.svg)|*.svg|All files|*.*"
msgstr ""
@@ -783,31 +2706,92 @@
msgid "SVGUI"
msgstr ""
-#: ../Beremiz.py:342
+#: ../Beremiz.py:313 ../Beremiz.py:344 ../PLCOpenEditor.py:134
+#: ../PLCOpenEditor.py:169
msgid "Save"
msgstr ""
-#: ../Beremiz.py:311
-msgid "Save\tCTRL+S"
-msgstr ""
-
-#: ../Beremiz.py:343
+#: ../Beremiz.py:345 ../PLCOpenEditor.py:136 ../PLCOpenEditor.py:170
msgid "Save As..."
msgstr ""
-#: ../Beremiz.py:313
-msgid "Save as\tCTRL+SHIFT+S"
-msgstr ""
-
-#: ../ProjectController.py:1014
+#: ../Beremiz.py:315
+msgid "Save as"
+msgstr ""
+
+#: ../dialogs/SearchInProjectDialog.py:76
+msgid "Scope"
+msgstr ""
+
+#: ../IDEFrame.py:592 ../dialogs/SearchInProjectDialog.py:105
+msgid "Search"
+msgstr ""
+
+#: ../IDEFrame.py:360 ../IDEFrame.py:404
+#: ../dialogs/SearchInProjectDialog.py:52
+msgid "Search in Project"
+msgstr ""
+
+#: ../dialogs/DurationEditorDialog.py:46
+msgid "Seconds:"
+msgstr ""
+
+#: ../IDEFrame.py:366
+msgid "Select All"
+msgstr ""
+
+#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Select a variable class:"
+msgstr ""
+
+#: ../ProjectController.py:1013
msgid "Select an editor:"
msgstr ""
-#: ../util/discovery.py:84
+#: ../controls/PouInstanceVariablesPanel.py:197
+msgid "Select an instance"
+msgstr ""
+
+#: ../IDEFrame.py:576
+msgid "Select an object"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:70
+msgid "Selection"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:62
+msgid "Selection Convergence"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:55
+msgid "Selection Divergence"
+msgstr ""
+
+#: ../plcopen/structures.py:207
+msgid ""
+"Semaphore\n"
+"The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
+msgstr ""
+
+#: ../dialogs/DiscoveryDialog.py:84
msgid "Services available:"
msgstr ""
-#: ../ProjectController.py:1520
+#: ../dialogs/LDElementDialog.py:72
+msgid "Set"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:62
+msgid "Shift left"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:63
+msgid "Shift right"
+msgstr ""
+
+#: ../ProjectController.py:1519
msgid "Show IEC code generated by PLCGenerator"
msgstr ""
@@ -819,28 +2803,56 @@
msgid "Show Master generated by config_utils"
msgstr ""
-#: ../ProjectController.py:1518
+#: ../ProjectController.py:1517
msgid "Show code"
msgstr ""
-#: ../Beremiz_service.py:319 ../ProjectController.py:1496
+#: ../dialogs/SFCDivergenceDialog.py:74
+msgid "Simultaneous Convergence"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:68
+msgid "Simultaneous Divergence"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:27
+msgid "Sine"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:67
+msgid "Single"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:23
+msgid "Square root (base 2)"
+msgstr ""
+
+#: ../plcopen/structures.py:193
+msgid "Standard function blocks"
+msgstr ""
+
+#: ../Beremiz_service.py:319 ../ProjectController.py:1495
msgid "Start PLC"
msgstr ""
-#: ../ProjectController.py:820
+#: ../ProjectController.py:819
#, python-format
msgid "Start build in %s\n"
msgstr ""
-#: ../ProjectController.py:1315
+#: ../ProjectController.py:1314
msgid "Starting PLC\n"
msgstr ""
-#: ../Beremiz.py:401
+#: ../Beremiz.py:403
msgid "Status ToolBar"
msgstr ""
-#: ../ProjectController.py:1499
+#: ../editors/Viewer.py:493
+msgid "Step"
+msgstr ""
+
+#: ../ProjectController.py:1498
msgid "Stop"
msgstr ""
@@ -848,50 +2860,202 @@
msgid "Stop PLC"
msgstr ""
-#: ../ProjectController.py:1501
+#: ../ProjectController.py:1500
msgid "Stop Running PLC"
msgstr ""
-#: ../ProjectController.py:1293
+#: ../ProjectController.py:1292
msgid "Stopping debugger...\n"
msgstr ""
-#: ../ProjectController.py:916
+#: ../editors/DataTypeEditor.py:52
+msgid "Structure"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:52
+msgid "Subrange"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:35
+msgid "Subtraction"
+msgstr ""
+
+#: ../ProjectController.py:915
msgid "Successfully built.\n"
msgstr ""
-#: ../util/FileManagementPanel.py:398
+#: ../dialogs/SearchInProjectDialog.py:154
+msgid "Syntax error in regular expression of pattern to search!"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:29
+msgid "Tangent"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:76
+msgid "Task"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:218
+msgid "Tasks:"
+msgstr ""
+
+#: ../controls/VariablePanel.py:78
+msgid "Temp"
+msgstr ""
+
+#: ../editors/FileManagementPanel.py:398
#, python-format
msgid ""
"The file '%s' already exist.\n"
"Do you want to replace it?"
msgstr ""
-#: ../Beremiz.py:553
+#: ../editors/LDViewer.py:879
+msgid "The group of block must be coherent!"
+msgstr ""
+
+#: ../IDEFrame.py:1091 ../Beremiz.py:555
msgid "There are changes, do you want to save?"
msgstr ""
-#: ../ProjectController.py:1508
+#: ../IDEFrame.py:1709 ../IDEFrame.py:1728
+#, python-format
+msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
+msgstr ""
+
+#: ../IDEFrame.py:1178
+msgid ""
+"There was a problem printing.\n"
+"Perhaps your current printer is not set correctly?"
+msgstr ""
+
+#: ../editors/LDViewer.py:888
+msgid "This option isn't available yet!"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:278
+msgid "Tick"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:40
+msgid "Time"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:40 ../plcopen/iec_std.csv:41
+msgid "Time addition"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:86
+msgid "Time concatenation"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:60 ../plcopen/iec_std.csv:61
+msgid "Time division"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:46 ../plcopen/iec_std.csv:47
+msgid "Time multiplication"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:48 ../plcopen/iec_std.csv:49
+msgid "Time subtraction"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:42 ../plcopen/iec_std.csv:43
+msgid "Time-of-day addition"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:52 ../plcopen/iec_std.csv:53
+#: ../plcopen/iec_std.csv:54 ../plcopen/iec_std.csv:55
+msgid "Time-of-day subtraction"
+msgstr ""
+
+#: ../editors/Viewer.py:432
+msgid "Top"
+msgstr ""
+
+#: ../ProjectController.py:1507
msgid "Transfer"
msgstr ""
-#: ../ProjectController.py:1510
+#: ../ProjectController.py:1509
msgid "Transfer PLC"
msgstr ""
-#: ../ProjectController.py:1475
+#: ../ProjectController.py:1474
msgid "Transfer completed successfully.\n"
msgstr ""
-#: ../ProjectController.py:1477
+#: ../ProjectController.py:1476
msgid "Transfer failed\n"
msgstr ""
+#: ../editors/Viewer.py:494
+msgid "Transition"
+msgstr ""
+
+#: ../PLCGenerator.py:1212
+#, python-format
+msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:84
+msgid "Transition Name"
+msgstr ""
+
+#: ../dialogs/PouTransitionDialog.py:53
+msgid "Transition Name:"
+msgstr ""
+
+#: ../PLCGenerator.py:1301
+#, python-format
+msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
+msgstr ""
+
+#: ../PLCGenerator.py:1292
+#, python-format
+msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
+msgstr ""
+
+#: ../plcopen/plcopen.py:1442
+#, python-format
+msgid "Transition with name %s doesn't exist!"
+msgstr ""
+
+#: ../PLCControler.py:95
+msgid "Transitions"
+msgstr ""
+
+#: ../editors/ResourceEditor.py:67
+msgid "Triggering"
+msgstr ""
+
+#: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
+#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:76
+#: ../dialogs/ActionBlockDialog.py:37
+msgid "Type"
+msgstr ""
+
#: ../canfestival/config_utils.py:335 ../canfestival/config_utils.py:617
#, python-format
msgid "Type conflict for location \"%s\""
msgstr ""
+#: ../plcopen/iec_std.csv:16
+msgid "Type conversion"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:155
+msgid "Type infos:"
+msgstr ""
+
+#: ../dialogs/SFCDivergenceDialog.py:51 ../dialogs/LDPowerRailDialog.py:51
+#: ../dialogs/ConnectionDialog.py:52 ../dialogs/SFCTransitionDialog.py:53
+#: ../dialogs/FBDBlockDialog.py:48
+msgid "Type:"
+msgstr ""
+
#: ../canfestival/config_utils.py:455 ../canfestival/config_utils.py:469
#, python-format
msgid "Unable to define PDO mapping for node %02x"
@@ -902,30 +3066,133 @@
msgid "Unable to get Xenomai's %s \n"
msgstr ""
-#: ../ProjectController.py:255
+#: ../PLCGenerator.py:865 ../PLCGenerator.py:924
+#, python-format
+msgid "Undefined block type \"%s\" in \"%s\" POU"
+msgstr ""
+
+#: ../PLCGenerator.py:240
+#, python-format
+msgid "Undefined pou type \"%s\""
+msgstr ""
+
+#: ../IDEFrame.py:338 ../IDEFrame.py:397
+msgid "Undo"
+msgstr ""
+
+#: ../ProjectController.py:254
msgid "Unknown"
msgstr ""
-#: ../ProjectController.py:252 ../ProjectController.py:253
+#: ../editors/Viewer.py:336
+#, python-format
+msgid "Unknown variable \"%s\" for this POU!"
+msgstr ""
+
+#: ../ProjectController.py:251 ../ProjectController.py:252
msgid "Unnamed"
msgstr ""
+#: ../PLCControler.py:305
+#, python-format
+msgid "Unnamed%d"
+msgstr ""
+
+#: ../controls/VariablePanel.py:272
+#, python-format
+msgid "Unrecognized data size \"%s\""
+msgstr ""
+
+#: ../plcopen/structures.py:222
+msgid ""
+"Up-counter\n"
+"The up-counter can be used to signal when a count has reached a maximum value."
+msgstr ""
+
+#: ../plcopen/structures.py:232
+msgid ""
+"Up-down counter\n"
+"The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
+msgstr ""
+
+#: ../controls/VariablePanel.py:709 ../editors/DataTypeEditor.py:623
+msgid "User Data Types"
+msgstr ""
+
#: ../canfestival/SlaveEditor.py:38 ../canfestival/NetworkEditor.py:68
msgid "User Type"
msgstr ""
+#: ../PLCControler.py:94
+msgid "User-defined POUs"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:40 ../dialogs/ActionBlockDialog.py:37
+msgid "Value"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:278
+msgid "Values"
+msgstr ""
+
+#: ../editors/DataTypeEditor.py:252
+msgid "Values:"
+msgstr ""
+
+#: ../controls/DebugVariablePanel.py:40 ../editors/Viewer.py:466
+#: ../dialogs/ActionBlockDialog.py:41
+msgid "Variable"
+msgstr ""
+
+#: ../dialogs/FBDVariableDialog.py:47
+msgid "Variable Properties"
+msgstr ""
+
+#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
+#: ../editors/Viewer.py:277
+msgid "Variable class"
+msgstr ""
+
+#: ../editors/TextViewer.py:374 ../editors/Viewer.py:338
+msgid "Variable don't belong to this POU!"
+msgstr ""
+
+#: ../controls/VariablePanel.py:77
+msgid "Variables"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:151
+msgid "Vertical:"
+msgstr ""
+
#: ../wxglade_hmi/wxglade_hmi.py:11
msgid "WXGLADE GUI"
msgstr ""
-#: ../ProjectController.py:1277
+#: ../ProjectController.py:1276
msgid "Waiting debugger to recover...\n"
msgstr ""
-#: ../ProjectController.py:516
+#: ../editors/LDViewer.py:888 ../dialogs/PouDialog.py:126
+msgid "Warning"
+msgstr ""
+
+#: ../ProjectController.py:515
msgid "Warnings in ST/IL/SFC code generator :\n"
msgstr ""
+#: ../dialogs/SearchInProjectDialog.py:85
+msgid "Whole Project"
+msgstr ""
+
+#: ../controls/ProjectPropertiesPanel.py:119
+msgid "Width:"
+msgstr ""
+
+#: ../dialogs/FindInPouDialog.py:86
+msgid "Wrap search"
+msgstr ""
+
#: ../features.py:9
msgid "WxGlade GUI"
msgstr ""
@@ -942,17 +3209,88 @@
"Open wxGlade anyway ?"
msgstr ""
-#: ../ProjectController.py:221
+#: ../ProjectController.py:220
msgid ""
"You must have permission to work on the project\n"
"Work on a project copy ?"
msgstr ""
+#: ../editors/LDViewer.py:883
+msgid "You must select the block or group of blocks around which a branch should be added!"
+msgstr ""
+
+#: ../editors/LDViewer.py:663
+msgid "You must select the wire where a contact should be added!"
+msgstr ""
+
+#: ../dialogs/PouNameDialog.py:45 ../dialogs/SFCStepNameDialog.py:47
+#: ../dialogs/SFCStepDialog.py:118
+msgid "You must type a name!"
+msgstr ""
+
+#: ../dialogs/ForceVariableDialog.py:165
+msgid "You must type a value!"
+msgstr ""
+
+#: ../IDEFrame.py:414
+msgid "Zoom"
+msgstr ""
+
+#: ../editors/GraphicViewer.py:97
+msgid "Zoom:"
+msgstr ""
+
+#: ../PLCOpenEditor.py:356
+#, python-format
+msgid "error: %s\n"
+msgstr ""
+
#: ../util/ProcessLogger.py:161
#, python-format
msgid "exited with status %s (pid %s)\n"
msgstr ""
+#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510
+msgid "file : "
+msgstr ""
+
+#: ../dialogs/PouDialog.py:31
+msgid "function"
+msgstr ""
+
+#: ../PLCOpenEditor.py:511
+msgid "function : "
+msgstr ""
+
+#: ../dialogs/PouDialog.py:31
+msgid "functionBlock"
+msgstr ""
+
+#: ../PLCOpenEditor.py:511
+msgid "line : "
+msgstr ""
+
+#: ../dialogs/PouDialog.py:31
+msgid "program"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:84
+msgid "string from the middle"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:82
+msgid "string left of"
+msgstr ""
+
+#: ../plcopen/iec_std.csv:83
+msgid "string right of"
+msgstr ""
+
+#: ../PLCOpenEditor.py:354
+#, python-format
+msgid "warning: %s\n"
+msgstr ""
+
#: Extra XSD strings
msgid "CanFestivalSlaveNode"
@@ -1000,9 +3338,6 @@
msgid "BaseParams"
msgstr ""
-msgid "Name"
-msgstr ""
-
msgid "IEC_Channel"
msgstr ""
--- a/i18n/mki18n.py Wed Sep 05 11:17:52 2012 +0200
+++ b/i18n/mki18n.py Fri Sep 07 16:45:55 2012 +0200
@@ -154,6 +154,9 @@
os.system(cmd)
appfil_file = open("app.fil", 'r')
+ messages_file = open("messages.pot", 'r')
+ messages = messages_file.read()
+ messages_file.close()
messages_file = open("messages.pot", 'a')
messages_file.write("""
#: Extra XSD strings
@@ -162,10 +165,10 @@
for filepath in appfil_file.xreadlines():
code_file = open(filepath.strip(), 'r')
for match in XSD_STRING_MODEL.finditer(code_file.read()):
- word = match.group(1)
- if not words_found.get(word, False):
- words_found[word] = True
- messages_file.write("""
+ word = match.group(1)
+ if not words_found.get(word, False) and messages.find("msgid \"%s\"\nmsgstr \"\"" % word) == -1:
+ words_found[word] = True
+ messages_file.write("""
msgid "%s"
msgstr ""
"""%word)
Binary file images/ACTION.png has changed
Binary file images/ACTIONS.png has changed
Binary file images/BLOCK.png has changed
Binary file images/COIL.png has changed
Binary file images/COMMENT.png has changed
Binary file images/CONFIGURATION.png has changed
Binary file images/CONFIGURATIONS.png has changed
Binary file images/CONNECTOR.png has changed
Binary file images/CONTACT.png has changed
Binary file images/DATATYPE.png has changed
Binary file images/DATATYPES.png has changed
Binary file images/F.png has changed
Binary file images/FB.png has changed
Binary file images/FBD.png has changed
Binary file images/FUNCTION.png has changed
Binary file images/FUNCTIONBLOCK.png has changed
Binary file images/GRAPH.png has changed
Binary file images/IL.png has changed
Binary file images/IO_VARIABLE.png has changed
Binary file images/JUMP.png has changed
Binary file images/LD.png has changed
Binary file images/PROGRAM.png has changed
Binary file images/PROJECT.png has changed
Binary file images/PROPERTIES.png has changed
Binary file images/RESOURCE.png has changed
Binary file images/RESOURCES.png has changed
Binary file images/SFC.png has changed
Binary file images/ST.png has changed
Binary file images/STEP.png has changed
Binary file images/TRANSITION.png has changed
Binary file images/TRANSITIONS.png has changed
Binary file images/VAR_INOUT.png has changed
Binary file images/VAR_INPUT.png has changed
Binary file images/VAR_LOCAL.png has changed
Binary file images/VAR_OUTPUT.png has changed
Binary file images/aboutlogo.png has changed
Binary file images/add_action.png has changed
Binary file images/add_block.png has changed
Binary file images/add_branch.png has changed
Binary file images/add_coil.png has changed
Binary file images/add_comment.png has changed
Binary file images/add_connection.png has changed
Binary file images/add_contact.png has changed
Binary file images/add_divergence.png has changed
Binary file images/add_element.png has changed
Binary file images/add_initial_step.png has changed
Binary file images/add_jump.png has changed
Binary file images/add_powerrail.png has changed
Binary file images/add_rung.png has changed
Binary file images/add_step.png has changed
Binary file images/add_transition.png has changed
Binary file images/add_variable.png has changed
Binary file images/add_wire.png has changed
Binary file images/copy.png has changed
Binary file images/current.png has changed
Binary file images/custom_tree_background.png has changed
Binary file images/cut.png has changed
Binary file images/debug_instance.png has changed
Binary file images/down.png has changed
Binary file images/edit.png has changed
Binary file images/export_graph.png has changed
Binary file images/find.png has changed
Binary file images/fit.png has changed
Binary file images/instance_graph.png has changed
Binary file images/move.png has changed
Binary file images/new.png has changed
Binary file images/open.png has changed
Binary file images/paste.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/images/plcopen_icons.svg Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,13644 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:modified="TRUE"
+ version="1.0"
+ inkscape:export-filename="/taf/Pim/workspace_laurent/plcopeneditor/Images/SFC.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docname="icons.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient4327">
+ <stop
+ style="stop-color:#0000ff;stop-opacity:1;"
+ offset="0"
+ id="stop4329" />
+ <stop
+ id="stop4331"
+ offset="0.5"
+ style="stop-color:#0000ff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#0000ff;stop-opacity:0;"
+ offset="1"
+ id="stop4333" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4309">
+ <stop
+ id="stop4311"
+ offset="0"
+ style="stop-color:#fe0000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#fe0000;stop-opacity:1;"
+ offset="0.5"
+ id="stop4313" />
+ <stop
+ id="stop4315"
+ offset="1"
+ style="stop-color:#fe0000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4184">
+ <stop
+ style="stop-color:#d81515;stop-opacity:1;"
+ offset="0"
+ id="stop4186" />
+ <stop
+ id="stop4192"
+ offset="0.5"
+ style="stop-color:#4dd728;stop-opacity:1;" />
+ <stop
+ style="stop-color:#2119b5;stop-opacity:1;"
+ offset="1"
+ id="stop4188" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient27229">
+ <stop
+ style="stop-color:#00fe1a;stop-opacity:1;"
+ offset="0"
+ id="stop27231" />
+ <stop
+ id="stop20982"
+ offset="0.5"
+ style="stop-color:#00fe1a;stop-opacity:1;" />
+ <stop
+ style="stop-color:#7f7f7f;stop-opacity:0;"
+ offset="1"
+ id="stop27233" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient20976">
+ <stop
+ id="stop20978"
+ offset="0"
+ style="stop-color:#7f7f7f;stop-opacity:1;" />
+ <stop
+ id="stop20980"
+ offset="1"
+ style="stop-color:#7f7f7f;stop-opacity:0;" />
+ </linearGradient>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-50 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ id="perspective2460" />
+ <linearGradient
+ id="linearGradient5197">
+ <stop
+ id="stop5199"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop5201"
+ offset="1"
+ style="stop-color:#f7f7f7;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5175">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179" />
+ </linearGradient>
+ <marker
+ inkscape:stockid="TriangleOutS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="TriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path3461"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ transform="scale(0.2)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;">
+ <path
+ id="path3327"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow2Mend"
+ style="overflow:visible;">
+ <path
+ id="path3339"
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ transform="scale(0.6) rotate(180) translate(0,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-50 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ id="perspective22" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5197"
+ id="linearGradient5203"
+ gradientUnits="userSpaceOnUse"
+ x1="40"
+ y1="1"
+ x2="40"
+ y2="16" />
+ <inkscape:perspective
+ id="perspective3259"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3280"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3304"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3330"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3356"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3380"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3401"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3420"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3442"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3464"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3490"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.93933,2.771444e-7,-2.74398e-7,0.930021,-2.994365,-0.94731)"
+ r="12.5625"
+ fy="16.40625"
+ fx="15"
+ cy="16.40625"
+ cx="15"
+ id="radialGradient4594"
+ xlink:href="#linearGradient4588"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4588">
+ <stop
+ id="stop4590"
+ offset="0"
+ style="stop-color:#494949;stop-opacity:1;" />
+ <stop
+ id="stop4592"
+ offset="1"
+ style="stop-color:#262626;stop-opacity:0;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective3517"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3536"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3556"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3576"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3597"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3619"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3762"
+ inkscape:persp3d-origin="300 : 400 : 1"
+ inkscape:vp_z="700 : 600 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="-50 : 600 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175"
+ id="linearGradient3808"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient4156"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient27229"
+ id="linearGradient4158"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4184"
+ id="linearGradient4190"
+ x1="290"
+ y1="11"
+ x2="296"
+ y2="11"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient4305"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4309"
+ id="linearGradient4307"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient4323"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4327"
+ id="linearGradient4325"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient3125"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4309"
+ id="linearGradient3127"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient3147"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4327"
+ id="linearGradient3149"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient3173"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4327"
+ id="linearGradient3175"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient20976"
+ id="linearGradient3177"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,246.5,71)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4309"
+ id="linearGradient3179"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1,0,0,-1,245.5,70)"
+ x1="19"
+ y1="11"
+ x2="19"
+ y2="23" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-2"
+ id="linearGradient3808-6"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-2">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-8" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-9" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-9"
+ id="linearGradient3808-1"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-9">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-9" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-6" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-5"
+ id="linearGradient3808-3"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-5">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-94" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-8" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-3"
+ id="linearGradient3808-8"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-3">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-2" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-3" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-1"
+ id="linearGradient3808-19"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-1">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-5" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-0" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-6"
+ id="linearGradient3808-2"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-6">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-56" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-63" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-34"
+ id="linearGradient3808-9"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-34">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-6" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-2" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5175-97"
+ id="linearGradient3808-97"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="1"
+ x2="15"
+ y2="16"
+ gradientTransform="translate(-40,0)" />
+ <linearGradient
+ id="linearGradient5175-97">
+ <stop
+ style="stop-color:#bdcccd;stop-opacity:1;"
+ offset="0"
+ id="stop5177-3" />
+ <stop
+ style="stop-color:#7979ff;stop-opacity:1;"
+ offset="1"
+ id="stop5179-5" />
+ </linearGradient>
+ <linearGradient
+ x1="72.422"
+ x2="72.422"
+ gradientTransform="matrix(0.14823,0,0,0.14944,5.4508,5.3567)"
+ y1="124.76"
+ gradientUnits="userSpaceOnUse"
+ y2="51.244"
+ id="linearGradient3123">
+ <stop
+ offset="0"
+ style="stop-color:#365f0e"
+ id="stop2492-3" />
+ <stop
+ offset="1"
+ style="stop-color:#84a718"
+ id="stop2494-8" />
+ </linearGradient>
+ <radialGradient
+ gradientTransform="matrix(0,0.35684,-0.3882,0,37.357,-10.114)"
+ r="31"
+ cy="51.695"
+ cx="69.448"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3121">
+ <stop
+ offset="0"
+ style="stop-color:#eef87e"
+ id="stop3244-5" />
+ <stop
+ offset=".26238"
+ style="stop-color:#cde34f"
+ id="stop3246-9" />
+ <stop
+ offset=".66094"
+ style="stop-color:#93b723"
+ id="stop3248-7" />
+ <stop
+ offset="1"
+ style="stop-color:#5a7e0d"
+ id="stop3250-8" />
+ </radialGradient>
+ <linearGradient
+ x1="24.139"
+ y1="7.0479"
+ gradientTransform="matrix(0.2818,0,0,0.2801,12.186,12.022)"
+ x2="24.139"
+ gradientUnits="userSpaceOnUse"
+ y2="39.268"
+ id="linearGradient5530">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop3945" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop3947" />
+ </linearGradient>
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(0.035207,0,0,0.0082353,-0.72485,18.981)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient2447">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050" />
+ <stop
+ offset=".5"
+ id="stop5056" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-0.012049,0,0,0.0082353,10.761,18.981)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060"
+ id="radialGradient2444" />
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(0.012049,0,0,0.0082353,13.239,18.981)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060"
+ id="radialGradient2441" />
+ <linearGradient
+ x1="-51.786"
+ y1="50.786"
+ gradientTransform="matrix(0.39221,0,0,0.44736,29.199,-1.2387)"
+ x2="-51.786"
+ gradientUnits="userSpaceOnUse"
+ y2="2.9062"
+ id="linearGradient2438">
+ <stop
+ offset="0"
+ style="stop-color:#aaa"
+ id="stop3106" />
+ <stop
+ offset="1"
+ style="stop-color:#c8c8c8"
+ id="stop3108" />
+ </linearGradient>
+ <linearGradient
+ x1="25.132"
+ y1=".98521"
+ gradientTransform="matrix(0.48572,0,0,0.47803,0.34283,-0.70595)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ y2="47.013"
+ id="linearGradient2435">
+ <stop
+ offset="0"
+ style="stop-color:#f4f4f4"
+ id="stop3602" />
+ <stop
+ offset="1"
+ style="stop-color:#dbdbdb"
+ id="stop3604" />
+ </linearGradient>
+ <radialGradient
+ r="139.56"
+ gradientTransform="matrix(0.17021,0,0,-0.19072,1.1064,23.717)"
+ cx="92.09"
+ cy="102.7"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2432">
+ <stop
+ offset="0"
+ style="stop-color:#b7b8b9"
+ id="stop41" />
+ <stop
+ offset=".17403"
+ style="stop-color:#ececec"
+ id="stop47" />
+ <stop
+ offset=".23908"
+ style="stop-color:#fafafa;stop-opacity:0"
+ id="stop49" />
+ <stop
+ offset=".30111"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop51" />
+ <stop
+ offset=".53130"
+ style="stop-color:#fafafa;stop-opacity:0"
+ id="stop53" />
+ <stop
+ offset=".84490"
+ style="stop-color:#ebecec;stop-opacity:0"
+ id="stop55" />
+ <stop
+ offset="1"
+ style="stop-color:#e1e2e3;stop-opacity:0"
+ id="stop57" />
+ </radialGradient>
+ <linearGradient
+ x1="24"
+ y1="2"
+ gradientTransform="matrix(0.45454,0,0,0.46512,1.0909,0.33723)"
+ x2="24"
+ gradientUnits="userSpaceOnUse"
+ y2="46.017"
+ id="linearGradient2429">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop3213" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop3215" />
+ </linearGradient>
+ <linearGradient
+ x1="32.892"
+ y1="8.059"
+ gradientTransform="matrix(0.47785,0,0,0.55248,0.37225,-0.076128)"
+ x2="36.358"
+ gradientUnits="userSpaceOnUse"
+ y2="5.4565"
+ id="linearGradient2425">
+ <stop
+ offset="0"
+ style="stop-color:#fefefe"
+ id="stop8591" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop8593" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5060">
+ <stop
+ offset="0"
+ id="stop5062" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064" />
+ </linearGradient>
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient2617">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050-4" />
+ <stop
+ offset=".5"
+ id="stop5056-8" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052-1" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-3"
+ id="radialGradient2619" />
+ <linearGradient
+ id="linearGradient5060-3">
+ <stop
+ offset="0"
+ id="stop5062-3" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064-0" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-3"
+ id="radialGradient2621" />
+ <linearGradient
+ x1="-28.531"
+ y1="17.956"
+ gradientTransform="translate(34.414,-14.501)"
+ x2="-28.531"
+ gradientUnits="userSpaceOnUse"
+ y2="37.503"
+ id="linearGradient3019">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop11113" />
+ <stop
+ offset=".91014"
+ style="stop-color:#cdcdcd"
+ id="stop11115" />
+ <stop
+ offset="1"
+ style="stop-color:#a1a1a1"
+ id="stop11117" />
+ </linearGradient>
+ <linearGradient
+ x1="9.8764"
+ y1="2.6015"
+ gradientTransform="translate(0.036304,2.9e-7)"
+ x2="9.8764"
+ gradientUnits="userSpaceOnUse"
+ y2="23.062"
+ id="linearGradient3021">
+ <stop
+ offset="0"
+ style="stop-color:#c1c1c1"
+ id="stop5159" />
+ <stop
+ offset="1"
+ style="stop-color:#909090"
+ id="stop5161" />
+ </linearGradient>
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.37156,0,0,0.33344,-0.91752,-0.0025171)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ y2="47.013"
+ id="linearGradient3253">
+ <stop
+ offset="0"
+ style="stop-color:#f4f4f4"
+ id="stop3602-4" />
+ <stop
+ offset="1"
+ style="stop-color:#dbdbdb"
+ id="stop3604-6" />
+ </linearGradient>
+ <linearGradient
+ x1="-51.786"
+ y1="50.786"
+ gradientTransform="matrix(0.30004,0,0,0.31205,21.157,-0.37413)"
+ x2="-51.786"
+ gradientUnits="userSpaceOnUse"
+ y2="2.9062"
+ id="linearGradient3255">
+ <stop
+ offset="0"
+ style="stop-color:#8d8f8a"
+ id="stop3933" />
+ <stop
+ offset="1"
+ style="stop-color:#c1c1c1"
+ id="stop3935" />
+ </linearGradient>
+ <linearGradient
+ x1="32.892"
+ y1="8.059"
+ gradientTransform="matrix(0.37071,0,0,0.35485,-0.85666,-0.048951)"
+ x2="36.358"
+ gradientUnits="userSpaceOnUse"
+ y2="5.4565"
+ id="linearGradient3257">
+ <stop
+ offset="0"
+ style="stop-color:#fefefe"
+ id="stop8591-4" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop8593-8" />
+ </linearGradient>
+ <linearGradient
+ x1="17.289"
+ y1="2.1849"
+ gradientTransform="matrix(0.68443,0,0,0.68201,-0.21315,0.15692)"
+ x2="15.18"
+ gradientUnits="userSpaceOnUse"
+ y2="5.8215"
+ id="linearGradient3259">
+ <stop
+ offset="0"
+ style="stop-color:#c0c0c0"
+ id="stop4561" />
+ <stop
+ offset="1"
+ style="stop-color:#949492"
+ id="stop4563" />
+ </linearGradient>
+ <radialGradient
+ r="11.268"
+ gradientTransform="matrix(1.69,0,0,0.78074,-5.4127,6.4032)"
+ cx="7.8186"
+ cy="8.5609"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3293">
+ <stop
+ offset="0"
+ style="stop-color:#f0c178"
+ id="stop3618" />
+ <stop
+ offset=".5"
+ style="stop-color:#e18941"
+ id="stop3270" />
+ <stop
+ offset="1"
+ style="stop-color:#ec4f18"
+ id="stop3620" />
+ </radialGradient>
+ <linearGradient
+ x1="9.7046"
+ y1="20.882"
+ gradientTransform="matrix(0.99458,0,0,0.74406,-0.30296,6.9688)"
+ x2="9.7046"
+ gradientUnits="userSpaceOnUse"
+ y2="4.303"
+ id="linearGradient3295">
+ <stop
+ offset="0"
+ style="stop-color:#bb2b12"
+ id="stop3624" />
+ <stop
+ offset="1"
+ style="stop-color:#cd7233"
+ id="stop3626" />
+ </linearGradient>
+ <radialGradient
+ r="10.273"
+ gradientTransform="matrix(-0.016802,1.0431,-1.7966,-0.016197,14.189,7.2971)"
+ cx="4.02"
+ cy="5.5927"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3004-5-2">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop3754-7-5" />
+ <stop
+ offset=".84754"
+ style="stop-color:#fff"
+ id="stop3760-0-0" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop3756-4-9" />
+ </radialGradient>
+ <linearGradient
+ x1="18.031"
+ y1="16.408"
+ gradientTransform="matrix(0.44503,0,0,0.30102,2.8555,8.5638)"
+ x2="20.055"
+ gradientUnits="userSpaceOnUse"
+ y2="24.628"
+ id="linearGradient3001-8-3">
+ <stop
+ offset="0"
+ style="stop-color:#fff;stop-opacity:.27451"
+ id="stop2687-4-1" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:.078431"
+ id="stop2689-2-2" />
+ </linearGradient>
+ <linearGradient
+ x1="28.671"
+ y1="23.891"
+ gradientTransform="matrix(0,-0.33674,-0.33543,0,20.014,15.582)"
+ x2="1.31"
+ gradientUnits="userSpaceOnUse"
+ y2="23.891"
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#d7e866"
+ id="stop2266" />
+ <stop
+ offset="1"
+ style="stop-color:#8cab2a"
+ id="stop2268" />
+ </linearGradient>
+ <linearGradient
+ x1="8.5273"
+ y1="33.332"
+ gradientTransform="matrix(0,0.39055,-0.38724,0,22.223,-1.7244)"
+ x2="57.411"
+ gradientUnits="userSpaceOnUse"
+ y2="33.332"
+ id="linearGradient2831">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop4224" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop4226" />
+ </linearGradient>
+ <radialGradient
+ r="2.5631"
+ cx="113.07"
+ cy="97.588"
+ gradientUnits="userSpaceOnUse"
+ fy="98"
+ fx="113.67"
+ id="radialGradient4241">
+ <stop
+ offset="0"
+ style="stop-color:#eee"
+ id="stop4243" />
+ <stop
+ offset=".16"
+ style="stop-color:#cecece"
+ id="stop4245" />
+ <stop
+ offset=".4675"
+ style="stop-color:#888"
+ id="stop4247" />
+ <stop
+ offset="1"
+ style="stop-color:#555"
+ id="stop4249" />
+ </radialGradient>
+ <radialGradient
+ r="2.5631"
+ gradientTransform="matrix(2.4957,0,0,2.4957,-223.98,-53.226)"
+ cx="113.07"
+ cy="97.588"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#radialGradient4241"
+ fy="98"
+ fx="113.67"
+ id="radialGradient8498" />
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.10006,-0.023376,0.0082168,0.059717,-5.2741,-0.91135)"
+ cx="127.32"
+ cy="143.83"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4035"
+ id="radialGradient8475" />
+ <linearGradient
+ id="linearGradient4035">
+ <stop
+ offset="0"
+ style="stop-color:#f5f5f5"
+ id="stop4037" />
+ <stop
+ offset=".47026"
+ style="stop-color:#e7e7e7"
+ id="stop4039" />
+ <stop
+ offset=".69349"
+ style="stop-color:#8c8c8c"
+ id="stop4041" />
+ <stop
+ offset=".83543"
+ style="stop-color:#ddd"
+ id="stop4043" />
+ <stop
+ offset="1"
+ style="stop-color:#a8a8a8"
+ id="stop4045" />
+ </linearGradient>
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.092476,0,0,-0.087162,-1.5556,24.366)"
+ cx="142.62"
+ cy="191.85"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient8464">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop7611-3" />
+ <stop
+ offset=".47026"
+ style="stop-color:#e7e7e7"
+ id="stop7677-2" />
+ <stop
+ offset=".67184"
+ style="stop-color:#8c8c8c"
+ id="stop7613-4" />
+ <stop
+ offset=".83543"
+ style="stop-color:#ddd"
+ id="stop7617-3" />
+ <stop
+ offset="1"
+ style="stop-color:#a8a8a8"
+ id="stop7615-6" />
+ </radialGradient>
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.18615,-0.0031402,0.0013777,0.18068,-15.013,-20.636)"
+ cx="141.75"
+ cy="206.43"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4035"
+ id="radialGradient8471" />
+ <radialGradient
+ r="21"
+ gradientTransform="matrix(0.52381,0,1.4569e-8,0.21429,-0.57143,11.5)"
+ cx="24"
+ cy="42"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3248">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop6312-6" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop6314-6" />
+ </radialGradient>
+ <radialGradient
+ r="12"
+ gradientTransform="matrix(1.5194,0.0037157,-0.0030247,1.2368,-3.7373,0.067833)"
+ cx="7.2203"
+ cy="4.2333"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4072">
+ <stop
+ offset="0"
+ style="stop-color:#e6e6e6"
+ id="stop7064-4" />
+ <stop
+ offset="1"
+ style="stop-color:#c8c8c8"
+ id="stop7060-2" />
+ </radialGradient>
+ <linearGradient
+ x1="9.599"
+ y1="13.499"
+ gradientTransform="matrix(0.98925,0,0,1.4696,0.12903,-10.339)"
+ x2="4.459"
+ gradientUnits="userSpaceOnUse"
+ y2="8.3495"
+ id="linearGradient2869">
+ <stop
+ offset="0"
+ style="stop-color:#969696"
+ id="stop3486-2" />
+ <stop
+ offset="1"
+ style="stop-color:#b4b4b4"
+ id="stop3488-0" />
+ </linearGradient>
+ <linearGradient
+ x1="12.277"
+ y1="37.206"
+ gradientTransform="matrix(0.73759,0,0,0.53933,-2.796,2.187)"
+ x2="12.222"
+ gradientUnits="userSpaceOnUse"
+ y2="33.759"
+ id="linearGradient8487">
+ <stop
+ offset="0"
+ style="stop-color:#eee"
+ id="stop4238-4" />
+ <stop
+ offset="1"
+ style="stop-color:#eee;stop-opacity:0"
+ id="stop4240-3" />
+ </linearGradient>
+ <linearGradient
+ x1="7.0625"
+ y1="35.281"
+ gradientTransform="matrix(0.73759,0,0,0.53933,-2.2092,1.4719)"
+ x2="24.688"
+ gradientUnits="userSpaceOnUse"
+ y2="35.281"
+ id="linearGradient8490">
+ <stop
+ offset="0"
+ id="stop6311" />
+ <stop
+ offset="1"
+ style="stop-color:#bbb;stop-opacity:0"
+ id="stop6313" />
+ </linearGradient>
+ <linearGradient
+ x1="53.991"
+ y1="87.896"
+ gradientTransform="matrix(0.19046,0,0,0.24853,0.090799,-3.4847)"
+ x2="53.991"
+ gradientUnits="userSpaceOnUse"
+ y2="104.28"
+ id="linearGradient2872">
+ <stop
+ offset="0"
+ style="stop-color:#7a7a7a"
+ id="stop2223-6" />
+ <stop
+ offset="1"
+ style="stop-color:#474747"
+ id="stop2219-1" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-0.022183,0,0,0.01086,16.259,17.381)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-0"
+ id="radialGradient2877" />
+ <linearGradient
+ id="linearGradient5060-0">
+ <stop
+ offset="0"
+ id="stop5062-9" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064-7" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(0.022183,0,0,0.01086,7.7407,17.381)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-0"
+ id="radialGradient2880" />
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(0.037768,0,0,0.01086,-1.6503,17.381)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient2883">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050-0" />
+ <stop
+ offset=".5"
+ id="stop5056-1" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052-7" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#radialGradient4241"
+ id="radialGradient4747"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.4957,0,0,2.4957,-223.98,-53.226)"
+ cx="113.07"
+ cy="97.588"
+ fx="113.67"
+ fy="98"
+ r="2.5631" />
+ <linearGradient
+ x1="21.478"
+ y1="1.6845"
+ gradientTransform="matrix(0.47644,0,0,0.40577,-36.625,-0.80551)"
+ x2="21.478"
+ gradientUnits="userSpaceOnUse"
+ y2="6.5747"
+ id="linearGradient5605">
+ <stop
+ offset="0"
+ style="stop-color:#eeeeec"
+ id="stop6453" />
+ <stop
+ offset="1"
+ style="stop-color:#fff"
+ id="stop6455" />
+ </linearGradient>
+ <linearGradient
+ x1="28.671"
+ y1="23.891"
+ gradientTransform="matrix(0,-0.33674,-0.33543,0,20.014,15.582)"
+ x2="1.31"
+ gradientUnits="userSpaceOnUse"
+ y2="23.891"
+ id="linearGradient2834-9">
+ <stop
+ offset="0"
+ style="stop-color:#d7e866"
+ id="stop2266-1" />
+ <stop
+ offset="1"
+ style="stop-color:#8cab2a"
+ id="stop2268-1" />
+ </linearGradient>
+ <linearGradient
+ x1="8.5273"
+ y1="33.332"
+ gradientTransform="matrix(0,0.39055,-0.38724,0,22.223,-1.7244)"
+ x2="57.411"
+ gradientUnits="userSpaceOnUse"
+ y2="33.332"
+ id="linearGradient2831-9">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop4224-4" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop4226-7" />
+ </linearGradient>
+ <radialGradient
+ r="2.5631"
+ cx="113.07"
+ cy="97.588"
+ gradientUnits="userSpaceOnUse"
+ fy="98"
+ fx="113.67"
+ id="radialGradient4241-8">
+ <stop
+ offset="0"
+ style="stop-color:#eee"
+ id="stop4243-5" />
+ <stop
+ offset=".16"
+ style="stop-color:#cecece"
+ id="stop4245-2" />
+ <stop
+ offset=".4675"
+ style="stop-color:#888"
+ id="stop4247-3" />
+ <stop
+ offset="1"
+ style="stop-color:#555"
+ id="stop4249-1" />
+ </radialGradient>
+ <radialGradient
+ r="2.5631"
+ gradientTransform="matrix(2.4957,0,0,2.4957,-223.98,-53.226)"
+ cx="113.07"
+ cy="97.588"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#radialGradient4241-8"
+ fy="98"
+ fx="113.67"
+ id="radialGradient8498-1" />
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.10006,-0.023376,0.0082168,0.059717,-5.2741,-0.91135)"
+ cx="127.32"
+ cy="143.83"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4035-7"
+ id="radialGradient8475-7" />
+ <linearGradient
+ id="linearGradient4035-7">
+ <stop
+ offset="0"
+ style="stop-color:#f5f5f5"
+ id="stop4037-6" />
+ <stop
+ offset=".47026"
+ style="stop-color:#e7e7e7"
+ id="stop4039-4" />
+ <stop
+ offset=".69349"
+ style="stop-color:#8c8c8c"
+ id="stop4041-5" />
+ <stop
+ offset=".83543"
+ style="stop-color:#ddd"
+ id="stop4043-0" />
+ <stop
+ offset="1"
+ style="stop-color:#a8a8a8"
+ id="stop4045-2" />
+ </linearGradient>
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.092476,0,0,-0.087162,-1.5556,24.366)"
+ cx="142.62"
+ cy="191.85"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient8464-7">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop7611-3-2" />
+ <stop
+ offset=".47026"
+ style="stop-color:#e7e7e7"
+ id="stop7677-2-3" />
+ <stop
+ offset=".67184"
+ style="stop-color:#8c8c8c"
+ id="stop7613-4-2" />
+ <stop
+ offset=".83543"
+ style="stop-color:#ddd"
+ id="stop7617-3-2" />
+ <stop
+ offset="1"
+ style="stop-color:#a8a8a8"
+ id="stop7615-6-2" />
+ </radialGradient>
+ <radialGradient
+ r="78.728"
+ gradientTransform="matrix(0.18615,-0.0031402,0.0013777,0.18068,-15.013,-20.636)"
+ cx="141.75"
+ cy="206.43"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4035-7"
+ id="radialGradient8471-3" />
+ <radialGradient
+ r="21"
+ gradientTransform="matrix(0.52381,0,1.4569e-8,0.21429,-0.57143,11.5)"
+ cx="24"
+ cy="42"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3248-5">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop6312-6-9" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop6314-6-5" />
+ </radialGradient>
+ <radialGradient
+ r="12"
+ gradientTransform="matrix(1.5194,0.0037157,-0.0030247,1.2368,-3.7373,0.067833)"
+ cx="7.2203"
+ cy="4.2333"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4072-4">
+ <stop
+ offset="0"
+ style="stop-color:#e6e6e6"
+ id="stop7064-4-2" />
+ <stop
+ offset="1"
+ style="stop-color:#c8c8c8"
+ id="stop7060-2-6" />
+ </radialGradient>
+ <linearGradient
+ x1="9.599"
+ y1="13.499"
+ gradientTransform="matrix(0.98925,0,0,1.4696,0.12903,-10.339)"
+ x2="4.459"
+ gradientUnits="userSpaceOnUse"
+ y2="8.3495"
+ id="linearGradient2869-6">
+ <stop
+ offset="0"
+ style="stop-color:#969696"
+ id="stop3486-2-0" />
+ <stop
+ offset="1"
+ style="stop-color:#b4b4b4"
+ id="stop3488-0-6" />
+ </linearGradient>
+ <linearGradient
+ x1="12.277"
+ y1="37.206"
+ gradientTransform="matrix(0.73759,0,0,0.53933,-2.796,2.187)"
+ x2="12.222"
+ gradientUnits="userSpaceOnUse"
+ y2="33.759"
+ id="linearGradient8487-5">
+ <stop
+ offset="0"
+ style="stop-color:#eee"
+ id="stop4238-4-4" />
+ <stop
+ offset="1"
+ style="stop-color:#eee;stop-opacity:0"
+ id="stop4240-3-9" />
+ </linearGradient>
+ <linearGradient
+ x1="7.0625"
+ y1="35.281"
+ gradientTransform="matrix(0.73759,0,0,0.53933,-2.2092,1.4719)"
+ x2="24.688"
+ gradientUnits="userSpaceOnUse"
+ y2="35.281"
+ id="linearGradient8490-6">
+ <stop
+ offset="0"
+ id="stop6311-1" />
+ <stop
+ offset="1"
+ style="stop-color:#bbb;stop-opacity:0"
+ id="stop6313-5" />
+ </linearGradient>
+ <linearGradient
+ x1="53.991"
+ y1="87.896"
+ gradientTransform="matrix(0.19046,0,0,0.24853,0.090799,-3.4847)"
+ x2="53.991"
+ gradientUnits="userSpaceOnUse"
+ y2="104.28"
+ id="linearGradient2872-0">
+ <stop
+ offset="0"
+ style="stop-color:#7a7a7a"
+ id="stop2223-6-9" />
+ <stop
+ offset="1"
+ style="stop-color:#474747"
+ id="stop2219-1-5" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-0.022183,0,0,0.01086,16.259,17.381)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-9"
+ id="radialGradient2877-6" />
+ <linearGradient
+ id="linearGradient5060-9">
+ <stop
+ offset="0"
+ id="stop5062-6" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064-2" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(0.022183,0,0,0.01086,7.7407,17.381)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-9"
+ id="radialGradient2880-4" />
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(0.037768,0,0,0.01086,-1.6503,17.381)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient2883-8">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050-3" />
+ <stop
+ offset=".5"
+ id="stop5056-89" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052-5" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#radialGradient4241-8"
+ id="radialGradient4958"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.4957,0,0,2.4957,-223.98,-53.226)"
+ cx="113.07"
+ cy="97.588"
+ fx="113.67"
+ fy="98"
+ r="2.5631" />
+ <linearGradient
+ x1="12"
+ y1="10"
+ x2="12"
+ gradientUnits="userSpaceOnUse"
+ y2="3"
+ id="linearGradient6666">
+ <stop
+ offset="0"
+ style="stop-color:#787878"
+ id="stop6662" />
+ <stop
+ offset="1"
+ style="stop-color:#787878;stop-opacity:0"
+ id="stop6664" />
+ </linearGradient>
+ <radialGradient
+ r="19.125"
+ gradientTransform="matrix(.62745 0 0 .16993 -3.0588 13.634)"
+ cx="24"
+ cy="41.875"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5591">
+ <stop
+ offset="0"
+ id="stop7614" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop7616" />
+ </radialGradient>
+ <linearGradient
+ x1="20.562"
+ y1="37"
+ gradientTransform="matrix(.48837 0 0 0.5 .27903 1.2499)"
+ x2="20.279"
+ gradientUnits="userSpaceOnUse"
+ y2="20"
+ id="linearGradient5583">
+ <stop
+ offset="0"
+ style="stop-color:#828282"
+ id="stop6868" />
+ <stop
+ offset="1"
+ style="stop-color:#aaa"
+ id="stop6870" />
+ </linearGradient>
+ <linearGradient
+ x1="11.519"
+ y1="20"
+ gradientTransform="matrix(.48837 0 0 0.5 .27903 1.2499)"
+ x2="11.506"
+ gradientUnits="userSpaceOnUse"
+ y2="37"
+ id="linearGradient5581">
+ <stop
+ offset="0"
+ style="stop-color:#fefefe"
+ id="stop8591-40" />
+ <stop
+ offset="1"
+ style="stop-color:#bebebe"
+ id="stop8593-9" />
+ </linearGradient>
+ <radialGradient
+ r="21"
+ gradientTransform="matrix(.46334 .0080418 -.0058515 .28835 .94804 9.495)"
+ cx="3.7591"
+ cy="11.918"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5577">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop6844" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop6846" />
+ </radialGradient>
+ <linearGradient
+ x1="15.601"
+ y1="33"
+ gradientTransform="matrix(.48359 0 0 .45973 .39379 1.5874)"
+ x2="15.335"
+ gradientUnits="userSpaceOnUse"
+ y2="21"
+ id="linearGradient5564">
+ <stop
+ offset="0"
+ style="stop-color:#2e3436"
+ id="stop2368" />
+ <stop
+ offset="1"
+ style="stop-color:#555753"
+ id="stop2370" />
+ </linearGradient>
+ <linearGradient
+ x1="21.919"
+ y1="21"
+ gradientTransform="matrix(.48359 0 0 .45973 .39379 1.5874)"
+ x2="22.008"
+ gradientUnits="userSpaceOnUse"
+ y2="33"
+ id="linearGradient5562">
+ <stop
+ offset="0"
+ style="stop-color:#6e6e6e"
+ id="stop6830" />
+ <stop
+ offset="1"
+ style="stop-color:#1e1e1e"
+ id="stop6832" />
+ </linearGradient>
+ <radialGradient
+ r="13"
+ gradientTransform="matrix(.34490 -.0026213 0.00146 .19209 2.5148 9.5924)"
+ cx="11.537"
+ cy="15.28"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5554">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop2465" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:0"
+ id="stop2467" />
+ </radialGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#radialGradient5591"
+ id="radialGradient5083"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.62745,0,0,0.16993,-3.0588,13.634)"
+ cx="24"
+ cy="41.875"
+ r="19.125" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5581"
+ id="linearGradient5085"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.48837,0,0,0.5,0.27903,1.2499)"
+ x1="11.519"
+ y1="20"
+ x2="11.506"
+ y2="37" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5583"
+ id="linearGradient5087"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.48837,0,0,0.5,0.27903,1.2499)"
+ x1="20.562"
+ y1="37"
+ x2="20.279"
+ y2="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#radialGradient5577"
+ id="radialGradient5089"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.46334,0.0080418,-0.0058515,0.28835,0.94804,9.495)"
+ cx="3.7591"
+ cy="11.918"
+ r="21" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5562"
+ id="linearGradient5091"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.48359,0,0,0.45973,0.39379,1.5874)"
+ x1="21.919"
+ y1="21"
+ x2="22.008"
+ y2="33" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5564"
+ id="linearGradient5093"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.48359,0,0,0.45973,0.39379,1.5874)"
+ x1="15.601"
+ y1="33"
+ x2="15.335"
+ y2="21" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#radialGradient5554"
+ id="radialGradient5095"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3449,-0.0026213,0.00146,0.19209,2.5148,9.5924)"
+ cx="11.537"
+ cy="15.28"
+ r="13" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6666"
+ id="linearGradient5097"
+ gradientUnits="userSpaceOnUse"
+ x1="12"
+ y1="10"
+ x2="12"
+ y2="3" />
+ <linearGradient
+ x1="5.5"
+ y1="-3"
+ x2="14.154"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456"
+ y2="18.844"
+ id="linearGradient2839" />
+ <linearGradient
+ x1="5.5"
+ y1="-3"
+ x2="13"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456"
+ y2="16"
+ id="linearGradient4462" />
+ <linearGradient
+ x1="-47.659"
+ y1="178.97"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-42.818"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0"
+ y2="197.04"
+ id="linearGradient4322" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0">
+ <stop
+ offset="0"
+ style="stop-color:#f0c178"
+ id="stop3618-1-9" />
+ <stop
+ offset=".5"
+ style="stop-color:#e18941"
+ id="stop3270-5-6" />
+ <stop
+ offset="1"
+ style="stop-color:#ec4f18"
+ id="stop3620-9-3" />
+ </linearGradient>
+ <linearGradient
+ x1="-39.666"
+ y1="198.91"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-46.583"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3390-178-986-453-4-5"
+ y2="176.96"
+ id="linearGradient3732" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5">
+ <stop
+ offset="0"
+ style="stop-color:#bb2b12"
+ id="stop3624-8-6" />
+ <stop
+ offset="1"
+ style="stop-color:#cd7233"
+ id="stop3626-1-1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4456">
+ <stop
+ offset="0"
+ style="stop-color:#f6daae"
+ id="stop4458" />
+ <stop
+ offset="1"
+ style="stop-color:#f0c178;stop-opacity:0"
+ id="stop4460" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3390-178-986-453-4-5"
+ id="linearGradient5188"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x1="-39.666"
+ y1="198.91"
+ x2="-46.583"
+ y2="176.96" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0"
+ id="linearGradient5190"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x1="-47.659"
+ y1="178.97"
+ x2="-42.818"
+ y2="197.04" />
+ <linearGradient
+ x1="5.5"
+ y1="-3"
+ x2="14.154"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456-7"
+ y2="18.844"
+ id="linearGradient2839-6" />
+ <linearGradient
+ x1="5.5"
+ y1="-3"
+ x2="13"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456-7"
+ y2="16"
+ id="linearGradient4462-6" />
+ <linearGradient
+ x1="-40.64"
+ y1="182.98"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-46.994"
+ gradientUnits="userSpaceOnUse"
+ y2="198.19"
+ id="linearGradient4324">
+ <stop
+ offset="0"
+ style="stop-color:#d7e866"
+ id="stop3641" />
+ <stop
+ offset=".64406"
+ style="stop-color:#98b438"
+ id="stop3643" />
+ <stop
+ offset="1"
+ style="stop-color:#a1ba49"
+ id="stop3645" />
+ </linearGradient>
+ <linearGradient
+ x1="-40.64"
+ y1="182.98"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-46.994"
+ gradientUnits="userSpaceOnUse"
+ y2="198.19"
+ id="linearGradient4322-5">
+ <stop
+ offset="0"
+ style="stop-color:#d7e866"
+ id="stop3618-1-9-1" />
+ <stop
+ offset=".64198"
+ style="stop-color:#98b438"
+ id="stop3270-5-6-3" />
+ <stop
+ offset="1"
+ style="stop-color:#a1ba49"
+ id="stop3620-9-3-5" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4456-7">
+ <stop
+ offset="0"
+ style="stop-color:#e5f09a"
+ id="stop4458-2" />
+ <stop
+ offset="1"
+ style="stop-color:#d7e866;stop-opacity:0"
+ id="stop4460-5" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_45_"
+ y2="27.836672"
+ x2="74.587158"
+ y1="21.424805"
+ x1="68.175293">
+ <stop
+ offset="0"
+ style="stop-color:#babdb6;stop-opacity:1"
+ id="stop695" />
+ <stop
+ offset="1"
+ style="stop-color:#eeeeec;stop-opacity:1"
+ id="stop697" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.32937464,-0.18440234,0.2272317,0.42503946,63.110644,29.8148)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#XMLID_45_"
+ id="linearGradient6543"
+ y2="22.860907"
+ x2="24.190449"
+ y1="23.843431"
+ x1="22.225399" />
+ <linearGradient
+ gradientTransform="matrix(0.32429489,-0.1897767,0.23187135,0.40393918,63.404032,30.47844)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2229"
+ id="linearGradient6531"
+ y2="27.087946"
+ x2="24.947838"
+ y1="21.144136"
+ x1="21.054403" />
+ <linearGradient
+ gradientTransform="matrix(1.3432519,-0.79619135,0.98307194,1.7344871,-329.42724,256.04227)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#XMLID_897_"
+ id="linearGradient6534"
+ y2="10.711433"
+ x2="296.93979"
+ y1="4.7592773"
+ x1="292.97168" />
+ <linearGradient
+ id="linearGradient2229">
+ <stop
+ offset="0"
+ style="stop-color:#e2e2e2;stop-opacity:1"
+ id="stop2231" />
+ <stop
+ offset="1"
+ style="stop-color:#d8d8d8;stop-opacity:1"
+ id="stop2233" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.30002427,-0.15885894,0.1590255,0.44281237,66.323263,29.078644)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2229"
+ id="linearGradient6538"
+ y2="23.942537"
+ x2="24.32597"
+ y1="6.4603648"
+ x1="20.288025" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_897_"
+ y2="10.711433"
+ x2="296.93979"
+ y1="4.7592773"
+ x1="292.97168">
+ <stop
+ offset="0"
+ style="stop-color:#e8e8e5;stop-opacity:1"
+ id="stop45093" />
+ <stop
+ offset="1"
+ style="stop-color:#f0f0f0;stop-opacity:1"
+ id="stop45095" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(-1.6199036,0.64359508,0.87746896,1.8299675,548.24021,-170.70599)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#XMLID_897_"
+ id="linearGradient6541"
+ y2="9.9224663"
+ x2="296.44699"
+ y1="7.4534159"
+ x1="294.8241" />
+ <linearGradient
+ x1="-55.344"
+ y1="34.058"
+ gradientTransform="matrix(0.4153,0,0,0.43675,24.711,-5.7201)"
+ x2="-51.786"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3931"
+ y2="2.9062"
+ id="linearGradient2908" />
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.51431,0,0,0.46669,-5.8439,-5.2)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3600"
+ y2="47.013"
+ id="linearGradient2906" />
+ <linearGradient
+ x1="-56.333"
+ y1="17.518"
+ gradientTransform="matrix(0.4153,0,0,0.43675,30.711,0.27988)"
+ x2="-47.636"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3931"
+ y2="-.10106"
+ id="linearGradient2992" />
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.51431,0,0,0.46669,0.15615,0.79999)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3600"
+ y2="47.013"
+ id="linearGradient2990" />
+ <linearGradient
+ id="linearGradient3931">
+ <stop
+ offset="0"
+ style="stop-color:#787a75"
+ id="stop3933-0" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop3935-0" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3600">
+ <stop
+ offset="0"
+ style="stop-color:#f4f4f4"
+ id="stop3602-7" />
+ <stop
+ offset="1"
+ style="stop-color:#dbdbdb"
+ id="stop3604-8" />
+ </linearGradient>
+ <linearGradient
+ x1="6.0602"
+ y1="19"
+ x2="5.8041"
+ gradientUnits="userSpaceOnUse"
+ y2="11"
+ id="linearGradient3621">
+ <stop
+ offset="0"
+ style="stop-color:#9d7d53"
+ id="stop3617" />
+ <stop
+ offset="1"
+ style="stop-color:#ad8757"
+ id="stop3619" />
+ </linearGradient>
+ <linearGradient
+ x1="22.452"
+ y1="30.05"
+ gradientTransform="matrix(0.41182,0,0,0.51282,-0.5502,-14.24)"
+ x2="22.452"
+ gradientUnits="userSpaceOnUse"
+ y2="40.186"
+ id="linearGradient2935">
+ <stop
+ offset="0"
+ style="stop-color:#787878"
+ id="stop3261" />
+ <stop
+ offset=".36564"
+ style="stop-color:#828282"
+ id="stop3263" />
+ <stop
+ offset="1"
+ style="stop-color:#464646"
+ id="stop3267" />
+ </linearGradient>
+ <linearGradient
+ x1="321.58"
+ y1="86.05"
+ gradientTransform="matrix(0.41035,0,0,0.51289,-121.99,-42.965)"
+ x2="321.58"
+ gradientUnits="userSpaceOnUse"
+ y2="96.245"
+ id="linearGradient2933">
+ <stop
+ offset="0"
+ style="stop-color:#787878"
+ id="stop3271" />
+ <stop
+ offset=".34447"
+ style="stop-color:#bebebe"
+ id="stop3273" />
+ <stop
+ offset=".37315"
+ style="stop-color:#c8c8c8"
+ id="stop3275" />
+ <stop
+ offset=".48991"
+ style="stop-color:#e6e6e6"
+ id="stop3277" />
+ <stop
+ offset="1"
+ style="stop-color:#8c8c8c"
+ id="stop3279" />
+ </linearGradient>
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.51431,0,0,0.46669,-5.8439,-3.2)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ y2="47.013"
+ id="linearGradient2906-5">
+ <stop
+ offset="0"
+ style="stop-color:#c1a581"
+ id="stop3620-9" />
+ <stop
+ offset="1"
+ style="stop-color:#9b784b"
+ id="stop3622" />
+ </linearGradient>
+ <linearGradient
+ x1="-56.333"
+ y1="17.518"
+ gradientTransform="matrix(0.4153,0,0,0.43675,30.711,0.27988)"
+ x2="-47.636"
+ gradientUnits="userSpaceOnUse"
+ y2="-.10106"
+ id="linearGradient2992-9">
+ <stop
+ offset="0"
+ style="stop-color:#787a75"
+ id="stop3933-3" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop3935-7" />
+ </linearGradient>
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.51431,0,0,0.46669,0.15615,0.79999)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ y2="47.013"
+ id="linearGradient2990-5">
+ <stop
+ offset="0"
+ style="stop-color:#f4f4f4"
+ id="stop3602-8" />
+ <stop
+ offset="1"
+ style="stop-color:#dbdbdb"
+ id="stop3604-61" />
+ </linearGradient>
+ <linearGradient
+ x1="173.1"
+ y1="75.319"
+ gradientTransform="matrix(0.13658,0,0,0.14121,-9.717,7.6392)"
+ x2="173.1"
+ gradientUnits="userSpaceOnUse"
+ y2="11.949"
+ id="linearGradient2700">
+ <stop
+ offset="0"
+ style="stop-color:#343435;stop-opacity:.97647"
+ id="stop2302" />
+ <stop
+ offset="1"
+ style="stop-color:#919295"
+ id="stop2304" />
+ </linearGradient>
+ <linearGradient
+ x1="41.542"
+ y1="68.292"
+ gradientTransform="matrix(0.13658,0,0,0.14121,7.9984,8.7019)"
+ x2="41.485"
+ gradientUnits="userSpaceOnUse"
+ y2="4.5363"
+ id="linearGradient2702">
+ <stop
+ offset="0"
+ style="stop-color:#333"
+ id="stop11106" />
+ <stop
+ offset="1"
+ style="stop-color:#333;stop-opacity:.61224"
+ id="stop11108" />
+ </linearGradient>
+ <radialGradient
+ r="6.6562"
+ gradientTransform="matrix(1.0388,0,0,0.83463,-3.35,0.21686)"
+ cx="15.414"
+ cy="13.078"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2705">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop4469" />
+ <stop
+ offset="1"
+ style="stop-color:#fff;stop-opacity:.24762"
+ id="stop4471" />
+ </radialGradient>
+ <radialGradient
+ r="8.3085"
+ cx="18.241"
+ cy="21.818"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4193">
+ <stop
+ offset="0"
+ style="stop-color:#a1a1a1;stop-opacity:.20784"
+ id="stop4456" />
+ <stop
+ offset="1"
+ style="stop-color:#a1a1a1;stop-opacity:.67843"
+ id="stop4458-7" />
+ </radialGradient>
+ <linearGradient
+ x1="-172.65"
+ y1="99.667"
+ gradientTransform="matrix(0.13658,0,0,0.14121,42.855,6.039)"
+ x2="-166.64"
+ gradientUnits="userSpaceOnUse"
+ y2="93.654"
+ id="linearGradient2710">
+ <stop
+ offset="0"
+ style="stop-color:#242424;stop-opacity:.99216"
+ id="stop11116" />
+ <stop
+ offset="1"
+ style="stop-color:#656565"
+ id="stop11118" />
+ </linearGradient>
+ <linearGradient
+ x1="32.892"
+ y1="8.059"
+ gradientTransform="matrix(0.54163,0,0,0.5203,-0.94021,-0.30186)"
+ x2="36.358"
+ gradientUnits="userSpaceOnUse"
+ y2="5.4565"
+ id="linearGradient4543">
+ <stop
+ offset="0"
+ style="stop-color:#fefefe"
+ id="stop8591-3" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop8593-7" />
+ </linearGradient>
+ <linearGradient
+ x1="17.289"
+ y1="2.1849"
+ x2="15.18"
+ gradientUnits="userSpaceOnUse"
+ y2="5.8215"
+ id="linearGradient4565">
+ <stop
+ offset="0"
+ style="stop-color:#cacaca"
+ id="stop4561-2" />
+ <stop
+ offset="1"
+ style="stop-color:#949492"
+ id="stop4563-2" />
+ </linearGradient>
+ <linearGradient
+ x1="25.132"
+ y1="6.7287"
+ gradientTransform="matrix(0.54288,0,0,0.48891,-1.0291,-0.23377)"
+ x2="25.132"
+ gradientUnits="userSpaceOnUse"
+ y2="47.013"
+ id="linearGradient4545">
+ <stop
+ offset="0"
+ style="stop-color:#f4f4f4"
+ id="stop3602-3" />
+ <stop
+ offset="1"
+ style="stop-color:#dbdbdb"
+ id="stop3604-7" />
+ </linearGradient>
+ <linearGradient
+ x1="-51.786"
+ y1="50.786"
+ gradientTransform="matrix(0.43837,0,0,0.45754,31.224,-0.77865)"
+ x2="-51.786"
+ gradientUnits="userSpaceOnUse"
+ y2="2.9062"
+ id="linearGradient4547">
+ <stop
+ offset="0"
+ style="stop-color:#8d8f8a"
+ id="stop3933-2" />
+ <stop
+ offset="1"
+ style="stop-color:#cbcbcb"
+ id="stop3935-76" />
+ </linearGradient>
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient4532">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050-6" />
+ <stop
+ offset=".5"
+ id="stop5056-0" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052-53" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-04"
+ id="radialGradient4534" />
+ <linearGradient
+ id="linearGradient5060-04">
+ <stop
+ offset="0"
+ id="stop5062-4" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064-1" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-04"
+ id="radialGradient4536" />
+ <linearGradient
+ y1="-8.8818e-16"
+ x2="22"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456-4"
+ y2="9"
+ id="linearGradient3052" />
+ <linearGradient
+ x1="-55.189"
+ y1="183.48"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-31.523"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8"
+ y2="191.52"
+ id="linearGradient3050" />
+ <linearGradient
+ x1="-55.189"
+ y1="182.48"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-31.523"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8"
+ y2="191.52"
+ id="linearGradient3048" />
+ <linearGradient
+ x1="-31.523"
+ y1="191.52"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-55.189"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3390-178-986-453-4-5-0"
+ y2="182.48"
+ id="linearGradient3046" />
+ <linearGradient
+ x1="-31.523"
+ y1="190.51"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-55.189"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3390-178-986-453-4-5-0"
+ y2="182.48"
+ id="linearGradient3044" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8">
+ <stop
+ offset="0"
+ style="stop-color:#f0c178"
+ id="stop3618-1-9-14" />
+ <stop
+ offset=".5"
+ style="stop-color:#e18941"
+ id="stop3270-5-6-8" />
+ <stop
+ offset="1"
+ style="stop-color:#ec4f18"
+ id="stop3620-9-3-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-0">
+ <stop
+ offset="0"
+ style="stop-color:#bb2b12"
+ id="stop3624-8-6-0" />
+ <stop
+ offset="1"
+ style="stop-color:#cd7233"
+ id="stop3626-1-1-2" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4456-4">
+ <stop
+ offset="0"
+ style="stop-color:#f6daae"
+ id="stop4458-9" />
+ <stop
+ offset="1"
+ style="stop-color:#f0c178;stop-opacity:0"
+ id="stop4460-7" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4456-4"
+ id="linearGradient4005"
+ gradientUnits="userSpaceOnUse"
+ y1="-8.8818e-16"
+ x2="22"
+ y2="9" />
+ <linearGradient
+ x1="23.576"
+ y1="25.357"
+ gradientTransform="matrix(0.41578,-0.41749,0.51898,0.51462,-15.747,2.6504)"
+ x2="23.576"
+ gradientUnits="userSpaceOnUse"
+ y2="31.211"
+ id="linearGradient3841">
+ <stop
+ offset="0"
+ style="stop-color:#181818"
+ id="stop2541" />
+ <stop
+ offset=".13483"
+ style="stop-color:#dbdbdb"
+ id="stop2543" />
+ <stop
+ offset=".20224"
+ style="stop-color:#a4a4a4"
+ id="stop2545" />
+ <stop
+ offset=".26966"
+ style="stop-color:#fff"
+ id="stop2547" />
+ <stop
+ offset=".44650"
+ style="stop-color:#8d8d8d"
+ id="stop2549" />
+ <stop
+ offset=".57114"
+ style="stop-color:#959595"
+ id="stop2551" />
+ <stop
+ offset=".72038"
+ style="stop-color:#cecece"
+ id="stop2553" />
+ <stop
+ offset="1"
+ style="stop-color:#181818"
+ id="stop2555" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.868,0.69303)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3843">
+ <stop
+ offset="0"
+ style="stop-color:#565656"
+ id="stop2559" />
+ <stop
+ offset=".5"
+ style="stop-color:#9a9a9a"
+ id="stop2561" />
+ <stop
+ offset="1"
+ style="stop-color:#545454"
+ id="stop2563" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.983,0.80921)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3845">
+ <stop
+ offset="0"
+ style="stop-color:#b1b1b1"
+ id="stop2567" />
+ <stop
+ offset=".5"
+ style="stop-color:#fff"
+ id="stop2569" />
+ <stop
+ offset="1"
+ style="stop-color:#8f8f8f"
+ id="stop2571" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.466,0.28929)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3847">
+ <stop
+ offset="0"
+ style="stop-color:#565656"
+ id="stop2575" />
+ <stop
+ offset=".5"
+ style="stop-color:#9a9a9a"
+ id="stop2577" />
+ <stop
+ offset="1"
+ style="stop-color:#545454"
+ id="stop2579" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.581,0.40547)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3849">
+ <stop
+ offset="0"
+ style="stop-color:#b1b1b1"
+ id="stop2583" />
+ <stop
+ offset=".5"
+ style="stop-color:#fff"
+ id="stop2585" />
+ <stop
+ offset="1"
+ style="stop-color:#8f8f8f"
+ id="stop2587" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.062,-0.11641)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3851">
+ <stop
+ offset="0"
+ style="stop-color:#565656"
+ id="stop2591" />
+ <stop
+ offset=".5"
+ style="stop-color:#9a9a9a"
+ id="stop2593" />
+ <stop
+ offset="1"
+ style="stop-color:#545454"
+ id="stop2595" />
+ </linearGradient>
+ <linearGradient
+ x1="30.038"
+ y1="24.99"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.177,-2.197e-4)"
+ x2="30.038"
+ gradientUnits="userSpaceOnUse"
+ y2="30"
+ id="linearGradient3853">
+ <stop
+ offset="0"
+ style="stop-color:#b1b1b1"
+ id="stop2599" />
+ <stop
+ offset=".5"
+ style="stop-color:#fff"
+ id="stop2601" />
+ <stop
+ offset="1"
+ style="stop-color:#8f8f8f"
+ id="stop2603" />
+ </linearGradient>
+ <linearGradient
+ x1="9"
+ y1="29.057"
+ gradientTransform="matrix(0.40402,-0.40569,0.60738,0.60227,-17.637,0.46249)"
+ x2="9"
+ gradientUnits="userSpaceOnUse"
+ y2="26.03"
+ id="linearGradient3855">
+ <stop
+ offset="0"
+ style="stop-color:#ece5a5"
+ id="stop2607" />
+ <stop
+ offset="1"
+ style="stop-color:#fcfbf2"
+ id="stop2609" />
+ </linearGradient>
+ <linearGradient
+ x1="5.5179"
+ y1="37.372"
+ gradientTransform="matrix(0.37638,0.036153,0.0367,0.37487,-2.2183,-1.1331)"
+ x2="9.5221"
+ gradientUnits="userSpaceOnUse"
+ y2="41.392"
+ id="linearGradient3857">
+ <stop
+ offset="0"
+ style="stop-color:#dbce48"
+ id="stop2613" />
+ <stop
+ offset="1"
+ style="stop-color:#c5b625"
+ id="stop2615" />
+ </linearGradient>
+ <linearGradient
+ x1="-60.97"
+ y1="13.668"
+ gradientTransform="matrix(0.21875,1.3313e-4,-1.3319e-4,0.21865,23.078,-2.7267)"
+ x2="-60.908"
+ gradientUnits="userSpaceOnUse"
+ y2="114.54"
+ id="linearGradient4194">
+ <stop
+ offset="0"
+ style="stop-color:#fff"
+ id="stop3602-2" />
+ <stop
+ offset="1"
+ style="stop-color:#e6e6e6"
+ id="stop3604-60" />
+ </linearGradient>
+ <linearGradient
+ x1="11.536"
+ y1="44.952"
+ gradientTransform="matrix(0.48572,0,0,0.48993,0.34281,0.72163)"
+ x2="11.536"
+ gradientUnits="userSpaceOnUse"
+ y2="-1.0041"
+ id="linearGradient4196">
+ <stop
+ offset="0"
+ style="stop-color:#969696"
+ id="stop3106-9" />
+ <stop
+ offset="1"
+ style="stop-color:#bebebe"
+ id="stop3108-5" />
+ </linearGradient>
+ <linearGradient
+ x1="302.86"
+ y1="366.65"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+ x2="302.86"
+ gradientUnits="userSpaceOnUse"
+ y2="609.51"
+ id="linearGradient4532-7">
+ <stop
+ offset="0"
+ style="stop-opacity:0"
+ id="stop5050-1" />
+ <stop
+ offset=".5"
+ id="stop5056-6" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5052-9" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-30"
+ id="radialGradient4534-7" />
+ <linearGradient
+ id="linearGradient5060-30">
+ <stop
+ offset="0"
+ id="stop5062-2" />
+ <stop
+ offset="1"
+ style="stop-opacity:0"
+ id="stop5064-01" />
+ </linearGradient>
+ <radialGradient
+ r="117.14"
+ gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)"
+ cx="605.71"
+ cy="486.65"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient5060-30"
+ id="radialGradient4536-0" />
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3764">
+ <stop
+ id="stop3766"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop3768"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="177.205"
+ x2="109.303"
+ y1="176.645"
+ x1="112.152"
+ gradientTransform="matrix(0.6168621,0,0,0.6168621,464.44564,636.2255)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5286"
+ xlink:href="#id106-6"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757">
+ <stop
+ id="stop3759"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop3761"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3750">
+ <stop
+ id="stop3752"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop3754"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3743">
+ <stop
+ id="stop3745"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop3747"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3736">
+ <stop
+ id="stop3738"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop3740"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="70.518097"
+ x2="368.80499"
+ y1="70.004303"
+ x1="364.53201"
+ gradientUnits="userSpaceOnUse"
+ id="id106-6">
+ <stop
+ id="stop381-4"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop383-3"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="110.835"
+ x2="116.524"
+ y1="131.308"
+ x1="128.826"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3725">
+ <stop
+ id="stop3727"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3729"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="110.835"
+ x2="116.524"
+ y1="131.308"
+ x1="128.826"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3718">
+ <stop
+ id="stop3720"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3722"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="118.687"
+ x2="128.21201"
+ y1="168.049"
+ x1="139.687"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3711">
+ <stop
+ id="stop3713"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3715"
+ style="stop-color:#843E2A"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="118.687"
+ x2="128.21201"
+ y1="168.049"
+ x1="139.687"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3704">
+ <stop
+ id="stop3706"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3708"
+ style="stop-color:#843E2A"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="118.687"
+ x2="128.21201"
+ y1="168.049"
+ x1="139.687"
+ gradientUnits="userSpaceOnUse"
+ id="id58-9">
+ <stop
+ id="stop247-7"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop249-7"
+ style="stop-color:#843E2A"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="110.835"
+ x2="116.524"
+ y1="131.308"
+ x1="128.826"
+ gradientUnits="userSpaceOnUse"
+ id="id67-8">
+ <stop
+ id="stop260-8"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop262-8"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="139.76199"
+ x2="143.61501"
+ y1="134.62801"
+ x1="91.134003"
+ gradientUnits="userSpaceOnUse"
+ id="id87-8">
+ <stop
+ id="stop326-6"
+ style="stop-color:#43110e;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop328-0"
+ style="stop-color:#E77817"
+ offset="0.560784" />
+ <stop
+ id="stop330-4"
+ style="stop-color:#d5cca3;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="91.515999"
+ x2="85.941597"
+ y1="85.329201"
+ x1="83.996399"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3684">
+ <stop
+ id="stop3686"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3688"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id16-6">
+ <linearGradient
+ y2="91.515999"
+ x2="85.941597"
+ y1="85.329201"
+ x1="83.996399"
+ gradientUnits="userSpaceOnUse"
+ id="id17-4">
+ <stop
+ id="stop74-3"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop76-3"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect78-3"
+ height="10.3911"
+ width="5.0668998"
+ y="83.226997"
+ x="82.435501"
+ style="fill:url(#id17-4)" />
+ </mask>
+ <linearGradient
+ y2="90.978401"
+ x2="98.317703"
+ y1="84.9562"
+ x1="105.893"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3673">
+ <stop
+ id="stop3675"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3677"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id14-8">
+ <linearGradient
+ y2="90.978401"
+ x2="98.317703"
+ y1="84.9562"
+ x1="105.893"
+ gradientUnits="userSpaceOnUse"
+ id="id15-3">
+ <stop
+ id="stop66-1"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop68-8"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect70-9"
+ height="7.5611"
+ width="9.066"
+ y="84.186699"
+ x="97.572502"
+ style="fill:url(#id15-3)" />
+ </mask>
+ <linearGradient
+ y2="95.373398"
+ x2="96.237297"
+ y1="85.097"
+ x1="109.484"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3662">
+ <stop
+ id="stop3664"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3666"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id12-9">
+ <linearGradient
+ y2="95.373398"
+ x2="96.237297"
+ y1="85.097"
+ x1="109.484"
+ gradientUnits="userSpaceOnUse"
+ id="id13-7">
+ <stop
+ id="stop58-5"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop60-3"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect62-8"
+ height="12.2285"
+ width="14.9803"
+ y="84.120903"
+ x="95.370201"
+ style="fill:url(#id13-7)" />
+ </mask>
+ <linearGradient
+ y2="88.3964"
+ x2="45.027302"
+ y1="85.191399"
+ x1="42.6605"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3651">
+ <stop
+ id="stop3653"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3655"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id10-7">
+ <linearGradient
+ y2="88.3964"
+ x2="45.027302"
+ y1="85.191399"
+ x1="42.6605"
+ gradientUnits="userSpaceOnUse"
+ id="id11-4">
+ <stop
+ id="stop50-1"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop52-8"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect54-5"
+ height="5.1496"
+ width="5.0612001"
+ y="84.219101"
+ x="41.313301"
+ style="fill:url(#id11-4)" />
+ </mask>
+ <linearGradient
+ y2="94.052696"
+ x2="46.064999"
+ y1="85.694099"
+ x1="39.1106"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3640">
+ <stop
+ id="stop3642"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3644"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id8-2">
+ <linearGradient
+ y2="94.052696"
+ x2="46.064999"
+ y1="85.694099"
+ x1="39.1106"
+ gradientUnits="userSpaceOnUse"
+ id="id9-1">
+ <stop
+ id="stop42-7"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop44-8"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect46-5"
+ height="12.1484"
+ width="14.1734"
+ y="83.799202"
+ x="35.501099"
+ style="fill:url(#id9-1)" />
+ </mask>
+ <linearGradient
+ y2="97.205902"
+ x2="96.932098"
+ y1="82.476501"
+ x1="96.142097"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3629">
+ <stop
+ id="stop3631"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3633"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id6-1">
+ <linearGradient
+ y2="97.205902"
+ x2="96.932098"
+ y1="82.476501"
+ x1="96.142097"
+ gradientUnits="userSpaceOnUse"
+ id="id7-7">
+ <stop
+ id="stop34-5"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop36-9"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect38-6"
+ height="14.4522"
+ width="30.0107"
+ y="82.615097"
+ x="81.5317"
+ style="fill:url(#id7-7)" />
+ </mask>
+ <linearGradient
+ y2="97.346802"
+ x2="50.137199"
+ y1="82.335602"
+ x1="49.353401"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3618">
+ <stop
+ id="stop3620-3"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3622-3"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id4-4">
+ <linearGradient
+ y2="97.346802"
+ x2="50.137199"
+ y1="82.335602"
+ x1="49.353401"
+ gradientUnits="userSpaceOnUse"
+ id="id5-4">
+ <stop
+ id="stop26-7"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop28-6"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect30-3"
+ height="14.4522"
+ width="29.992701"
+ y="82.615097"
+ x="34.748901"
+ style="fill:url(#id5-4)" />
+ </mask>
+ <linearGradient
+ y2="96.7258"
+ x2="78.758698"
+ y1="89.459801"
+ x1="70.354897"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3607">
+ <stop
+ id="stop3609"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3611"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id2-0">
+ <linearGradient
+ y2="96.7258"
+ x2="78.758698"
+ y1="89.459801"
+ x1="70.354897"
+ gradientUnits="userSpaceOnUse"
+ id="id3-3">
+ <stop
+ id="stop18-0"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop20-4"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect22-4"
+ height="20.8179"
+ width="12.7385"
+ y="82.6838"
+ x="68.1875"
+ style="fill:url(#id3-3)" />
+ </mask>
+ <linearGradient
+ y2="94.792503"
+ x2="65.6744"
+ y1="94.792503"
+ x1="83.948196"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3596">
+ <stop
+ id="stop3598"
+ style="stop-color:#FFF500"
+ offset="0" />
+ <stop
+ id="stop3600"
+ style="stop-color:#F3AE00"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="103.748"
+ x2="81.069901"
+ y1="101.448"
+ x1="75.444702"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3589">
+ <stop
+ id="stop3591"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop3593"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id0-9">
+ <linearGradient
+ y2="103.748"
+ x2="81.069901"
+ y1="101.448"
+ x1="75.444702"
+ gradientUnits="userSpaceOnUse"
+ id="id1-8">
+ <stop
+ id="stop10-4"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop12-8"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect14-1"
+ height="9.8077002"
+ width="8.2557001"
+ y="97.694199"
+ x="74.129402"
+ style="fill:url(#id1-8)" />
+ </mask>
+ <linearGradient
+ y2="94.792503"
+ x2="65.6744"
+ y1="94.792503"
+ x1="83.948196"
+ gradientUnits="userSpaceOnUse"
+ id="id55-1">
+ <stop
+ id="stop228-1"
+ style="stop-color:#FFF500"
+ offset="0" />
+ <stop
+ id="stop230-5"
+ style="stop-color:#F3AE00"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5047"
+ gradientUnits="userSpaceOnUse"
+ x1="128.826"
+ y1="131.308"
+ x2="116.524"
+ y2="110.835">
+ <stop
+ offset="0"
+ style="stop-color:#1A0D0E"
+ id="stop5049" />
+ <stop
+ offset="1"
+ style="stop-color:#c66714;stop-opacity:1"
+ id="stop5051" />
+ </linearGradient>
+ <linearGradient
+ y2="127.002"
+ x2="72.372398"
+ y1="132.248"
+ x1="45.526402"
+ gradientUnits="userSpaceOnUse"
+ id="id97-8">
+ <stop
+ id="stop342-5"
+ style="stop-color:#E77817"
+ offset="0" />
+ <stop
+ id="stop344-6"
+ style="stop-color:#c3bb84;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ fy="130.32201"
+ fx="75.147003"
+ r="27.4161"
+ cy="130.32201"
+ cx="75.147003"
+ gradientUnits="userSpaceOnUse"
+ id="id138-9">
+ <stop
+ id="stop535-6"
+ style="stop-color:#c33b13;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop537-3"
+ style="stop-color:#781E19"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3562">
+ <stop
+ id="stop3564"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3566"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3555">
+ <stop
+ id="stop3557"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3559"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3548">
+ <stop
+ id="stop3550"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop3552"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="id83-8">
+ <stop
+ id="stop314-5"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop316-0"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="111.569"
+ x2="34.2696"
+ y1="111.569"
+ x1="155.966"
+ gradientUnits="userSpaceOnUse"
+ id="id56-9">
+ <stop
+ id="stop233-0"
+ style="stop-color:#e1b000;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop235-8"
+ style="stop-color:#af4d1c;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective3535" />
+ <linearGradient
+ y2="177.205"
+ x2="109.303"
+ y1="176.645"
+ x1="112.152"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4399"
+ xlink:href="#id106"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="176.887"
+ x2="112.361"
+ y1="176.321"
+ x1="115.189"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4397"
+ xlink:href="#id106"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="177.096"
+ x2="115.458"
+ y1="176.571"
+ x1="118.03"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4395"
+ xlink:href="#id106"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="174.303"
+ x2="81.9135"
+ y1="173.877"
+ x1="85.5758"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4393"
+ xlink:href="#id106"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="110.835"
+ x2="116.524"
+ y1="131.308"
+ x1="128.826"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4391"
+ xlink:href="#id67"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="118.687"
+ x2="128.212"
+ y1="168.049"
+ x1="139.687"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4389"
+ xlink:href="#id58"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="139.762"
+ x2="143.615"
+ y1="134.628"
+ x1="91.134"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4387"
+ xlink:href="#id87"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="94.7925"
+ x2="65.6744"
+ y1="94.7925"
+ x1="83.9482"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4385"
+ xlink:href="#id55"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="103.7"
+ x2="75.4122"
+ y1="101.238"
+ x1="81.1023"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4383"
+ xlink:href="#id55"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="127.002"
+ x2="72.3724"
+ y1="132.248"
+ x1="45.5264"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4381"
+ xlink:href="#id97"
+ inkscape:collect="always" />
+ <radialGradient
+ r="27.4161"
+ fy="130.322"
+ fx="75.147"
+ cy="130.322"
+ cx="75.147"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4379"
+ xlink:href="#id138"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4377"
+ xlink:href="#id83"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="175.826"
+ x2="116.497"
+ y1="205.733"
+ x1="126.406"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4375"
+ xlink:href="#id83"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="175.929"
+ x2="123.175"
+ y1="207.462"
+ x1="137.505"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4373"
+ xlink:href="#id83"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="174.005"
+ x2="128.974"
+ y1="206.581"
+ x1="149.606"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4371"
+ xlink:href="#id83"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="111.569"
+ x2="34.2696"
+ y1="111.569"
+ x1="155.966"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4369"
+ xlink:href="#id56"
+ inkscape:collect="always" />
+ <radialGradient
+ fy="-19.9185"
+ fx="248.255"
+ r="0.3552"
+ cy="-19.9185"
+ cx="248.255"
+ xlink:href="#id142"
+ gradientUnits="userSpaceOnUse"
+ id="id144" />
+ <radialGradient
+ fy="-18.0226"
+ fx="243.171"
+ r="0.8707"
+ cy="-18.0226"
+ cx="243.171"
+ xlink:href="#id142"
+ gradientUnits="userSpaceOnUse"
+ id="id143" />
+ <radialGradient
+ fy="-16.7101"
+ fx="241.762"
+ r="0.4905"
+ cy="-16.7101"
+ cx="241.762"
+ gradientUnits="userSpaceOnUse"
+ id="id142">
+ <stop
+ id="stop561"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop563"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-18.8861"
+ fx="247.079"
+ r="22.4989"
+ cy="-18.8861"
+ cx="247.079"
+ gradientUnits="userSpaceOnUse"
+ id="id141">
+ <stop
+ id="stop550"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop552"
+ style="stop-color:#75C5F0"
+ offset="0.631373" />
+ <stop
+ id="stop554"
+ style="stop-color:#007CC3"
+ offset="0.74902" />
+ <stop
+ id="stop556"
+ style="stop-color:#23297A"
+ offset="0.878431" />
+ <stop
+ id="stop558"
+ style="stop-color:#28166F"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-17.7117"
+ fx="246.969"
+ r="17.236"
+ cy="-17.7117"
+ cx="246.969"
+ gradientUnits="userSpaceOnUse"
+ id="id140">
+ <stop
+ id="stop541"
+ style="stop-color:#FFF974"
+ offset="0" />
+ <stop
+ id="stop543"
+ style="stop-color:#EF9A48"
+ offset="0.141176" />
+ <stop
+ id="stop545"
+ style="stop-color:#FFF974"
+ offset="0.760784" />
+ <stop
+ id="stop547"
+ style="stop-color:#E77817"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-6.235"
+ fx="250.876"
+ r="4.4334"
+ cy="-6.235"
+ cx="250.876"
+ xlink:href="#id137"
+ gradientUnits="userSpaceOnUse"
+ id="id139" />
+ <radialGradient
+ fy="130.322"
+ fx="75.147"
+ r="27.4161"
+ cy="130.322"
+ cx="75.147"
+ gradientUnits="userSpaceOnUse"
+ id="id138">
+ <stop
+ id="stop535"
+ style="stop-color:#E77817"
+ offset="0" />
+ <stop
+ id="stop537"
+ style="stop-color:#781E19"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-7.7064"
+ fx="250.339"
+ r="9.106"
+ cy="-7.7064"
+ cx="250.339"
+ gradientUnits="userSpaceOnUse"
+ id="id137">
+ <stop
+ id="stop524"
+ style="stop-color:#2C2065"
+ offset="0" />
+ <stop
+ id="stop526"
+ style="stop-color:#007CC3"
+ offset="0.521569" />
+ <stop
+ id="stop528"
+ style="stop-color:#007CC3"
+ offset="0.588235" />
+ <stop
+ id="stop530"
+ style="stop-color:#94CCDC"
+ offset="0.780392" />
+ <stop
+ id="stop532"
+ style="stop-color:#75C5F0"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-12.32"
+ fx="245.749"
+ r="6.2371"
+ cy="-12.32"
+ cx="245.749"
+ gradientUnits="userSpaceOnUse"
+ id="id136">
+ <stop
+ id="stop517"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop519"
+ style="stop-color:white"
+ offset="0.341176" />
+ <stop
+ id="stop521"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-10.9792"
+ fx="245.932"
+ r="7.417"
+ cy="-10.9792"
+ cx="245.932"
+ xlink:href="#id128"
+ gradientUnits="userSpaceOnUse"
+ id="id135" />
+ <radialGradient
+ fy="-8.0904"
+ fx="248.019"
+ r="12.6211"
+ cy="-8.0904"
+ cx="248.019"
+ gradientUnits="userSpaceOnUse"
+ id="id134">
+ <stop
+ id="stop503"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop505"
+ style="stop-color:white"
+ offset="0.0117647" />
+ <stop
+ id="stop507"
+ style="stop-color:#75C5F0"
+ offset="0.258824" />
+ <stop
+ id="stop509"
+ style="stop-color:#007CC3"
+ offset="0.509804" />
+ <stop
+ id="stop511"
+ style="stop-color:#00579C"
+ offset="0.721569" />
+ <stop
+ id="stop513"
+ style="stop-color:#28166F"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-20.8934"
+ fx="246.586"
+ r="21.1084"
+ cy="-20.8934"
+ cx="246.586"
+ gradientUnits="userSpaceOnUse"
+ id="id133">
+ <stop
+ id="stop492"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop494"
+ style="stop-color:#007CC3"
+ offset="0.329412" />
+ <stop
+ id="stop496"
+ style="stop-color:white"
+ offset="0.611765" />
+ <stop
+ id="stop498"
+ style="stop-color:#007CC3"
+ offset="0.858824" />
+ <stop
+ id="stop500"
+ style="stop-color:#28166F"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-8.8615"
+ fx="245.754"
+ r="10.1282"
+ cy="-8.8615"
+ cx="245.754"
+ xlink:href="#id128"
+ gradientUnits="userSpaceOnUse"
+ id="id132" />
+ <radialGradient
+ fy="-11.1305"
+ fx="244.053"
+ r="5.9542"
+ cy="-11.1305"
+ cx="244.053"
+ gradientUnits="userSpaceOnUse"
+ id="id131">
+ <stop
+ id="stop484"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop486"
+ style="stop-color:white"
+ offset="0.501961" />
+ <stop
+ id="stop488"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-14.8849"
+ fx="251.939"
+ r="39.1796"
+ cy="-14.8849"
+ cx="251.939"
+ gradientUnits="userSpaceOnUse"
+ id="id130">
+ <stop
+ id="stop469"
+ style="stop-color:#28166F"
+ offset="0" />
+ <stop
+ id="stop471"
+ style="stop-color:#28166F"
+ offset="0.121569" />
+ <stop
+ id="stop473"
+ style="stop-color:#007CC3"
+ offset="0.34902" />
+ <stop
+ id="stop475"
+ style="stop-color:#75C5F0"
+ offset="0.501961" />
+ <stop
+ id="stop477"
+ style="stop-color:#0062A6"
+ offset="0.690196" />
+ <stop
+ id="stop479"
+ style="stop-color:#75C5F0"
+ offset="0.839216" />
+ <stop
+ id="stop481"
+ style="stop-color:#28166F"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-0.5511"
+ fx="249.134"
+ r="3.1474"
+ cy="-0.5511"
+ cx="249.134"
+ gradientUnits="userSpaceOnUse"
+ id="id129">
+ <stop
+ id="stop460"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop462"
+ style="stop-color:white"
+ offset="0.258824" />
+ <stop
+ id="stop464"
+ style="stop-color:white"
+ offset="0.521569" />
+ <stop
+ id="stop466"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-10.5384"
+ fx="242.889"
+ r="7.4343"
+ cy="-10.5384"
+ cx="242.889"
+ gradientUnits="userSpaceOnUse"
+ id="id128">
+ <stop
+ id="stop451"
+ style="stop-color:#FFF974"
+ offset="0" />
+ <stop
+ id="stop453"
+ style="stop-color:#FADE69"
+ offset="0.419608" />
+ <stop
+ id="stop455"
+ style="stop-color:#EA9551"
+ offset="0.701961" />
+ <stop
+ id="stop457"
+ style="stop-color:#A94F29"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-23.3266"
+ fx="250.612"
+ r="9.297"
+ cy="-23.3266"
+ cx="250.612"
+ gradientUnits="userSpaceOnUse"
+ id="id127">
+ <stop
+ id="stop440"
+ style="stop-color:#28166F"
+ offset="0" />
+ <stop
+ id="stop442"
+ style="stop-color:#007CC3"
+ offset="0.321569" />
+ <stop
+ id="stop444"
+ style="stop-color:#75C5F0"
+ offset="0.470588" />
+ <stop
+ id="stop446"
+ style="stop-color:#007CC3"
+ offset="0.631373" />
+ <stop
+ id="stop448"
+ style="stop-color:#28166F"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-12.5974"
+ fx="250.954"
+ r="5.3942"
+ cy="-12.5974"
+ cx="250.954"
+ gradientUnits="userSpaceOnUse"
+ id="id126">
+ <stop
+ id="stop433"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop435"
+ style="stop-color:white"
+ offset="0.509804" />
+ <stop
+ id="stop437"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-7.7663"
+ fx="248.524"
+ r="10.8814"
+ cy="-7.7663"
+ cx="248.524"
+ gradientUnits="userSpaceOnUse"
+ id="id125">
+ <stop
+ id="stop424"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop426"
+ style="stop-color:white"
+ offset="0.180392" />
+ <stop
+ id="stop428"
+ style="stop-color:white"
+ offset="0.709804" />
+ <stop
+ id="stop430"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-8.5636"
+ fx="242.193"
+ r="2.9979"
+ cy="-8.5636"
+ cx="242.193"
+ xlink:href="#id122"
+ gradientUnits="userSpaceOnUse"
+ id="id124" />
+ <radialGradient
+ fy="-7.7696"
+ fx="242.968"
+ r="1.0405"
+ cy="-7.7696"
+ cx="242.968"
+ xlink:href="#id122"
+ gradientUnits="userSpaceOnUse"
+ id="id123" />
+ <radialGradient
+ fy="-8.9938"
+ fx="241.173"
+ r="3.865"
+ cy="-8.9938"
+ cx="241.173"
+ gradientUnits="userSpaceOnUse"
+ id="id122">
+ <stop
+ id="stop415"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop417"
+ style="stop-color:white"
+ offset="0.611765" />
+ <stop
+ id="stop419"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-2.9444"
+ fx="242.608"
+ r="10.0329"
+ cy="-2.9444"
+ cx="242.608"
+ gradientUnits="userSpaceOnUse"
+ id="id121">
+ <stop
+ id="stop406"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop408"
+ style="stop-color:white"
+ offset="0.258824" />
+ <stop
+ id="stop410"
+ style="stop-color:white"
+ offset="0.588235" />
+ <stop
+ id="stop412"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ fy="-11.9504"
+ fx="243.155"
+ r="4.4856"
+ cy="-11.9504"
+ cx="243.155"
+ gradientUnits="userSpaceOnUse"
+ id="id120">
+ <stop
+ id="stop399"
+ style="stop-color:white"
+ offset="0" />
+ <stop
+ id="stop401"
+ style="stop-color:white"
+ offset="0.690196" />
+ <stop
+ id="stop403"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ y2="-2.486"
+ x2="245.579"
+ y1="-1.9814"
+ x1="245.695"
+ xlink:href="#id102"
+ gradientUnits="userSpaceOnUse"
+ id="id119" />
+ <linearGradient
+ y2="177.096"
+ x2="115.458"
+ y1="176.571"
+ x1="118.03"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id118" />
+ <linearGradient
+ y2="72.9321"
+ x2="402.262"
+ y1="72.4239"
+ x1="404.753"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id117" />
+ <linearGradient
+ y2="73.0377"
+ x2="396.301"
+ y1="72.4962"
+ x1="399.06"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id116" />
+ <linearGradient
+ y2="177.205"
+ x2="109.303"
+ y1="176.645"
+ x1="112.152"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id115" />
+ <linearGradient
+ y2="176.887"
+ x2="112.361"
+ y1="176.321"
+ x1="115.189"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id114" />
+ <linearGradient
+ y2="72.7303"
+ x2="399.262"
+ y1="72.1814"
+ x1="402.002"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id113" />
+ <linearGradient
+ y2="126.726"
+ x2="231.513"
+ y1="126.463"
+ x1="229.205"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id112" />
+ <linearGradient
+ y2="175.973"
+ x2="88.708"
+ y1="175.474"
+ x1="84.3238"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id111" />
+ <linearGradient
+ y2="71.8443"
+ x2="376.355"
+ y1="71.3611"
+ x1="372.109"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id110" />
+ <linearGradient
+ y2="174.303"
+ x2="81.9135"
+ y1="173.877"
+ x1="85.5758"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id109" />
+ <linearGradient
+ y2="126.004"
+ x2="227.408"
+ y1="125.725"
+ x1="225.086"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id108" />
+ <linearGradient
+ y2="174.603"
+ x2="80.912"
+ y1="174.073"
+ x1="76.5012"
+ xlink:href="#id106"
+ gradientUnits="userSpaceOnUse"
+ id="id107" />
+ <linearGradient
+ y2="70.5181"
+ x2="368.805"
+ y1="70.0043"
+ x1="364.532"
+ gradientUnits="userSpaceOnUse"
+ id="id106">
+ <stop
+ id="stop381"
+ style="stop-color:#EF9BA0"
+ offset="0" />
+ <stop
+ id="stop383"
+ style="stop-color:#DA422B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="4.1303"
+ x2="243.029"
+ y1="1.0795"
+ x1="241.722"
+ gradientUnits="userSpaceOnUse"
+ id="id105">
+ <stop
+ id="stop374"
+ style="stop-color:#DA251D"
+ offset="0" />
+ <stop
+ id="stop376"
+ style="stop-color:#FFF500"
+ offset="0.478431" />
+ <stop
+ id="stop378"
+ style="stop-color:#DA251D"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="173.469"
+ x2="316.434"
+ y1="161.604"
+ x1="296.908"
+ xlink:href="#id86"
+ gradientUnits="userSpaceOnUse"
+ id="id104" />
+ <linearGradient
+ y2="103.7"
+ x2="75.4122"
+ y1="101.238"
+ x1="81.1023"
+ xlink:href="#id55"
+ gradientUnits="userSpaceOnUse"
+ id="id103" />
+ <linearGradient
+ y2="-1.9268"
+ x2="248.736"
+ y1="-1.1579"
+ x1="248.913"
+ gradientUnits="userSpaceOnUse"
+ id="id102">
+ <stop
+ id="stop363"
+ style="stop-color:#064C92"
+ offset="0" />
+ <stop
+ id="stop365"
+ style="stop-color:#75C5F0"
+ offset="0.4" />
+ <stop
+ id="stop367"
+ style="stop-color:#75C5F0"
+ offset="0.580392" />
+ <stop
+ id="stop369"
+ style="stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="-3.3544"
+ x2="247.849"
+ y1="-3.6521"
+ x1="249.337"
+ xlink:href="#id57"
+ gradientUnits="userSpaceOnUse"
+ id="id101" />
+ <linearGradient
+ y2="114.403"
+ x2="468.92"
+ y1="111.391"
+ x1="459.742"
+ xlink:href="#id86"
+ gradientUnits="userSpaceOnUse"
+ id="id100" />
+ <linearGradient
+ y2="-25.1953"
+ x2="250.702"
+ y1="-23.896"
+ x1="252.119"
+ gradientUnits="userSpaceOnUse"
+ id="id99">
+ <stop
+ id="stop354"
+ style="stop-color:#EF9A48"
+ offset="0" />
+ <stop
+ id="stop356"
+ style="stop-color:#FDFFCE"
+ offset="0.501961" />
+ <stop
+ id="stop358"
+ style="stop-color:#EF9A48"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="3.9145"
+ x2="252.326"
+ y1="6.6198"
+ x1="247.456"
+ gradientUnits="userSpaceOnUse"
+ id="id98">
+ <stop
+ id="stop347"
+ style="stop-color:#DA251D"
+ offset="0" />
+ <stop
+ id="stop349"
+ style="stop-color:#FFF500"
+ offset="0.741176" />
+ <stop
+ id="stop351"
+ style="stop-color:#DA251D"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="127.002"
+ x2="72.3724"
+ y1="132.248"
+ x1="45.5264"
+ gradientUnits="userSpaceOnUse"
+ id="id97">
+ <stop
+ id="stop342"
+ style="stop-color:#E77817"
+ offset="0" />
+ <stop
+ id="stop344"
+ style="stop-color:#FFF500"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="97.436"
+ x2="215.964"
+ y1="97.562"
+ x1="206.673"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id96" />
+ <linearGradient
+ y2="17.9741"
+ x2="347.754"
+ y1="18.2055"
+ x1="330.667"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id95" />
+ <linearGradient
+ y2="120.349"
+ x2="59.1755"
+ y1="120.587"
+ x1="41.5347"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id94" />
+ <linearGradient
+ y2="160.832"
+ x2="472.812"
+ y1="141.92"
+ x1="322.107"
+ xlink:href="#id86"
+ gradientUnits="userSpaceOnUse"
+ id="id93" />
+ <linearGradient
+ y2="-10.9095"
+ x2="246.473"
+ y1="-12.6362"
+ x1="246.098"
+ xlink:href="#id79"
+ gradientUnits="userSpaceOnUse"
+ id="id92" />
+ <linearGradient
+ y2="171.826"
+ x2="398.34"
+ y1="172.57"
+ x1="394.901"
+ xlink:href="#id77"
+ gradientUnits="userSpaceOnUse"
+ id="id91" />
+ <linearGradient
+ y2="185.066"
+ x2="386.154"
+ y1="186.248"
+ x1="380.668"
+ xlink:href="#id77"
+ gradientUnits="userSpaceOnUse"
+ id="id90" />
+ <linearGradient
+ y2="179.354"
+ x2="394.165"
+ y1="180.282"
+ x1="389.808"
+ xlink:href="#id77"
+ gradientUnits="userSpaceOnUse"
+ id="id89" />
+ <linearGradient
+ y2="174.005"
+ x2="128.974"
+ y1="206.581"
+ x1="149.606"
+ xlink:href="#id83"
+ gradientUnits="userSpaceOnUse"
+ id="id88" />
+ <linearGradient
+ y2="139.762"
+ x2="143.615"
+ y1="134.628"
+ x1="91.134"
+ gradientUnits="userSpaceOnUse"
+ id="id87">
+ <stop
+ id="stop326"
+ style="stop-color:#781E19"
+ offset="0" />
+ <stop
+ id="stop328"
+ style="stop-color:#E77817"
+ offset="0.560784" />
+ <stop
+ id="stop330"
+ style="stop-color:#FFF500"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="154.192"
+ x2="445.849"
+ y1="144.321"
+ x1="424.41"
+ gradientUnits="userSpaceOnUse"
+ id="id86">
+ <stop
+ id="stop321"
+ style="stop-color:#A5D134"
+ offset="0" />
+ <stop
+ id="stop323"
+ style="stop-color:#D4E426"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="175.929"
+ x2="123.175"
+ y1="207.462"
+ x1="137.505"
+ xlink:href="#id83"
+ gradientUnits="userSpaceOnUse"
+ id="id85" />
+ <linearGradient
+ y2="175.826"
+ x2="116.497"
+ y1="205.733"
+ x1="126.406"
+ xlink:href="#id83"
+ gradientUnits="userSpaceOnUse"
+ id="id84" />
+ <linearGradient
+ y2="175.125"
+ x2="109.982"
+ y1="202.207"
+ x1="117.053"
+ gradientUnits="userSpaceOnUse"
+ id="id83">
+ <stop
+ id="stop314"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop316"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="3.8676"
+ x2="253.16"
+ y1="4.5563"
+ x1="247.651"
+ gradientUnits="userSpaceOnUse"
+ id="id82">
+ <stop
+ id="stop305"
+ style="stop-color:#1F1A17"
+ offset="0" />
+ <stop
+ id="stop307"
+ style="stop-color:white"
+ offset="0.211765" />
+ <stop
+ id="stop309"
+ style="stop-color:white"
+ offset="0.831373" />
+ <stop
+ id="stop311"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="174.82"
+ x2="397.171"
+ y1="177.801"
+ x1="352.994"
+ xlink:href="#id77"
+ gradientUnits="userSpaceOnUse"
+ id="id81" />
+ <linearGradient
+ y2="176.762"
+ x2="354.11"
+ y1="175.482"
+ x1="340.951"
+ xlink:href="#id77"
+ gradientUnits="userSpaceOnUse"
+ id="id80" />
+ <linearGradient
+ y2="-10.2373"
+ x2="245.592"
+ y1="-11.1769"
+ x1="245.388"
+ gradientUnits="userSpaceOnUse"
+ id="id79">
+ <stop
+ id="stop298"
+ style="stop-color:#E77817"
+ offset="0" />
+ <stop
+ id="stop300"
+ style="stop-color:#FFF974"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="-0.2224"
+ x2="242.532"
+ y1="5.0327"
+ x1="244.831"
+ gradientUnits="userSpaceOnUse"
+ id="id78">
+ <stop
+ id="stop289"
+ style="stop-color:#1F1A17"
+ offset="0" />
+ <stop
+ id="stop291"
+ style="stop-color:white"
+ offset="0.2" />
+ <stop
+ id="stop293"
+ style="stop-color:white"
+ offset="0.819608" />
+ <stop
+ id="stop295"
+ style="stop-color:#1F1A17"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="74.11"
+ x2="345.634"
+ y1="80.6882"
+ x1="302.397"
+ gradientUnits="userSpaceOnUse"
+ id="id77">
+ <stop
+ id="stop284"
+ style="stop-color:#D4E426"
+ offset="0" />
+ <stop
+ id="stop286"
+ style="stop-color:#79B82F"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="-21.4598"
+ x2="240.359"
+ y1="-25.5061"
+ x1="243.731"
+ gradientUnits="userSpaceOnUse"
+ id="id76">
+ <stop
+ id="stop273"
+ style="stop-color:#195189"
+ offset="0" />
+ <stop
+ id="stop275"
+ style="stop-color:#0085C5"
+ offset="0.25098" />
+ <stop
+ id="stop277"
+ style="stop-color:#DFEEFA"
+ offset="0.419608" />
+ <stop
+ id="stop279"
+ style="stop-color:#007CC3"
+ offset="0.74902" />
+ <stop
+ id="stop281"
+ style="stop-color:#00569B"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="92.4274"
+ x2="246.159"
+ y1="103.206"
+ x1="252.638"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id75" />
+ <linearGradient
+ y2="8.7607"
+ x2="403.293"
+ y1="28.5869"
+ x1="415.21"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id74" />
+ <linearGradient
+ y2="91.2197"
+ x2="248.604"
+ y1="101.999"
+ x1="255.083"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id73" />
+ <linearGradient
+ y2="108.543"
+ x2="121.164"
+ y1="129.014"
+ x1="133.469"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id72" />
+ <linearGradient
+ y2="10.9815"
+ x2="398.797"
+ y1="30.8071"
+ x1="410.714"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id71" />
+ <linearGradient
+ y2="93.6347"
+ x2="243.715"
+ y1="104.414"
+ x1="250.194"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id70" />
+ <linearGradient
+ y2="6.5386"
+ x2="407.789"
+ y1="26.366"
+ x1="419.706"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id69" />
+ <linearGradient
+ y2="113.128"
+ x2="111.881"
+ y1="133.601"
+ x1="124.184"
+ xlink:href="#id67"
+ gradientUnits="userSpaceOnUse"
+ id="id68" />
+ <linearGradient
+ y2="110.835"
+ x2="116.524"
+ y1="131.308"
+ x1="128.826"
+ gradientUnits="userSpaceOnUse"
+ id="id67">
+ <stop
+ id="stop260"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop262"
+ style="stop-color:#E77817"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="21.2792"
+ x2="413.179"
+ y1="61.828"
+ x1="422.609"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id66" />
+ <linearGradient
+ y2="99.2335"
+ x2="251.534"
+ y1="121.28"
+ x1="256.661"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id65" />
+ <linearGradient
+ y2="123.761"
+ x2="126.73"
+ y1="165.63"
+ x1="136.467"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id64" />
+ <linearGradient
+ y2="129.654"
+ x2="124.016"
+ y1="160.672"
+ x1="131.229"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id63" />
+ <linearGradient
+ y2="102.336"
+ x2="250.105"
+ y1="118.669"
+ x1="253.903"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id62" />
+ <linearGradient
+ y2="96.5614"
+ x2="252.314"
+ y1="122.553"
+ x1="258.358"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id61" />
+ <linearGradient
+ y2="16.3643"
+ x2="414.613"
+ y1="64.1705"
+ x1="425.729"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id60" />
+ <linearGradient
+ y2="26.9847"
+ x2="410.55"
+ y1="57.0259"
+ x1="417.536"
+ xlink:href="#id58"
+ gradientUnits="userSpaceOnUse"
+ id="id59" />
+ <linearGradient
+ y2="118.687"
+ x2="128.212"
+ y1="168.049"
+ x1="139.687"
+ gradientUnits="userSpaceOnUse"
+ id="id58">
+ <stop
+ id="stop247"
+ style="stop-color:#1A0D0E"
+ offset="0" />
+ <stop
+ id="stop249"
+ style="stop-color:#843E2A"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="-3.789"
+ x2="246.436"
+ y1="-3.789"
+ x1="245.074"
+ gradientUnits="userSpaceOnUse"
+ id="id57">
+ <stop
+ id="stop238"
+ style="stop-color:#064C92"
+ offset="0" />
+ <stop
+ id="stop240"
+ style="stop-color:#75C5F0"
+ offset="0.419608" />
+ <stop
+ id="stop242"
+ style="stop-color:#75C5F0"
+ offset="0.580392" />
+ <stop
+ id="stop244"
+ style="stop-color:#054990"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="111.569"
+ x2="34.2696"
+ y1="111.569"
+ x1="155.966"
+ gradientUnits="userSpaceOnUse"
+ id="id56">
+ <stop
+ id="stop233"
+ style="stop-color:#F8C300"
+ offset="0" />
+ <stop
+ id="stop235"
+ style="stop-color:#AF251C"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="94.7925"
+ x2="65.6744"
+ y1="94.7925"
+ x1="83.9482"
+ gradientUnits="userSpaceOnUse"
+ id="id55">
+ <stop
+ id="stop228"
+ style="stop-color:#FFF500"
+ offset="0" />
+ <stop
+ id="stop230"
+ style="stop-color:#F3AE00"
+ offset="1" />
+ </linearGradient>
+ <mask
+ id="id53">
+ <linearGradient
+ y2="-9.9508"
+ x2="373.676"
+ y1="-15.9426"
+ x1="371.792"
+ gradientUnits="userSpaceOnUse"
+ id="id54">
+ <stop
+ id="stop221"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop223"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect225"
+ height="10.0698"
+ width="4.9137"
+ y="-17.9816"
+ x="370.277"
+ style="fill:url(#id54)" />
+ </mask>
+ <mask
+ id="id51">
+ <linearGradient
+ y2="-10.4715"
+ x2="385.662"
+ y1="-16.3039"
+ x1="392.999"
+ gradientUnits="userSpaceOnUse"
+ id="id52">
+ <stop
+ id="stop213"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop215"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect217"
+ height="7.3292"
+ width="8.7865"
+ y="-17.0523"
+ x="384.937"
+ style="fill:url(#id52)" />
+ </mask>
+ <mask
+ id="id49">
+ <linearGradient
+ y2="-6.2149"
+ x2="383.647"
+ y1="-16.1675"
+ x1="396.476"
+ gradientUnits="userSpaceOnUse"
+ id="id50">
+ <stop
+ id="stop205"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop207"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect209"
+ height="11.8497"
+ width="14.5146"
+ y="-17.1161"
+ x="382.804"
+ style="fill:url(#id50)" />
+ </mask>
+ <mask
+ id="id47">
+ <linearGradient
+ y2="-12.972"
+ x2="334.051"
+ y1="-16.076"
+ x1="331.759"
+ gradientUnits="userSpaceOnUse"
+ id="id48">
+ <stop
+ id="stop197"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop199"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect201"
+ height="4.9935"
+ width="4.908"
+ y="-17.0208"
+ x="330.451"
+ style="fill:url(#id48)" />
+ </mask>
+ <mask
+ id="id45">
+ <linearGradient
+ y2="-7.4943"
+ x2="335.056"
+ y1="-15.5887"
+ x1="328.32"
+ gradientUnits="userSpaceOnUse"
+ id="id46">
+ <stop
+ id="stop189"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop191"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect193"
+ height="11.7719"
+ width="13.7331"
+ y="-17.4275"
+ x="324.822"
+ style="fill:url(#id46)" />
+ </mask>
+ <mask
+ id="id43">
+ <linearGradient
+ y2="-4.4401"
+ x2="384.32"
+ y1="-18.7055"
+ x1="383.555"
+ gradientUnits="userSpaceOnUse"
+ id="id44">
+ <stop
+ id="stop181"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop183"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect185"
+ height="13.9968"
+ width="29.0651"
+ y="-18.5712"
+ x="369.405"
+ style="fill:url(#id44)" />
+ </mask>
+ <mask
+ id="id41">
+ <linearGradient
+ y2="-4.3038"
+ x2="339"
+ y1="-18.8418"
+ x1="338.241"
+ gradientUnits="userSpaceOnUse"
+ id="id42">
+ <stop
+ id="stop173"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop175"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect177"
+ height="13.9968"
+ width="29.0477"
+ y="-18.5712"
+ x="324.096"
+ style="fill:url(#id42)" />
+ </mask>
+ <mask
+ id="id39">
+ <linearGradient
+ y2="-4.9052"
+ x2="366.719"
+ y1="-11.9422"
+ x1="358.58"
+ gradientUnits="userSpaceOnUse"
+ id="id40">
+ <stop
+ id="stop165"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop167"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect169"
+ height="20.1644"
+ width="12.3395"
+ y="-18.5059"
+ x="356.48"
+ style="fill:url(#id40)" />
+ </mask>
+ <mask
+ id="id37">
+ <linearGradient
+ y2="1.8957"
+ x2="368.957"
+ y1="-0.3315"
+ x1="363.51"
+ gradientUnits="userSpaceOnUse"
+ id="id38">
+ <stop
+ id="stop157"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop159"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect161"
+ height="9.5011"
+ width="7.9979"
+ y="-3.9685"
+ x="362.235"
+ style="fill:url(#id38)" />
+ </mask>
+ <clipPath
+ id="id36">
+ <path
+ id="path153"
+ d="M246.996 -16.1191l4.2989 -0.0845 0 2.4392 -4.2989 0.0845 0 -2.4392z" />
+ </clipPath>
+ <mask
+ id="id34">
+ <linearGradient
+ y2="82.2542"
+ x2="230.057"
+ y1="78.9964"
+ x1="229.032"
+ gradientUnits="userSpaceOnUse"
+ id="id35">
+ <stop
+ id="stop146"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop148"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect150"
+ height="5.5662"
+ width="2.7627"
+ y="77.8422"
+ x="228.163"
+ style="fill:url(#id35)" />
+ </mask>
+ <mask
+ id="id32">
+ <linearGradient
+ y2="81.9711"
+ x2="236.573"
+ y1="78.8001"
+ x1="240.562"
+ gradientUnits="userSpaceOnUse"
+ id="id33">
+ <stop
+ id="stop138"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop140"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect142"
+ height="4.0761"
+ width="4.8685"
+ y="78.3475"
+ x="236.134"
+ style="fill:url(#id33)" />
+ </mask>
+ <mask
+ id="id30">
+ <linearGradient
+ y2="84.2854"
+ x2="235.478"
+ y1="78.8742"
+ x1="242.453"
+ gradientUnits="userSpaceOnUse"
+ id="id31">
+ <stop
+ id="stop130"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop132"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect134"
+ height="6.5337"
+ width="7.9827"
+ y="78.3129"
+ x="234.974"
+ style="fill:url(#id31)" />
+ </mask>
+ <mask
+ id="id28">
+ <linearGradient
+ y2="80.6116"
+ x2="208.513"
+ y1="78.9238"
+ x1="207.267"
+ gradientUnits="userSpaceOnUse"
+ id="id29">
+ <stop
+ id="stop122"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop124"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect126"
+ height="2.8062"
+ width="2.7598"
+ y="78.3646"
+ x="206.51"
+ style="fill:url(#id29)" />
+ </mask>
+ <mask
+ id="id26">
+ <linearGradient
+ y2="83.5897"
+ x2="209.059"
+ y1="79.1889"
+ x1="205.397"
+ gradientUnits="userSpaceOnUse"
+ id="id27">
+ <stop
+ id="stop114"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop116"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect118"
+ height="6.4915"
+ width="7.5579"
+ y="78.1435"
+ x="203.449"
+ style="fill:url(#id27)" />
+ </mask>
+ <mask
+ id="id24">
+ <linearGradient
+ y2="85.2502"
+ x2="235.844"
+ y1="77.4944"
+ x1="235.428"
+ gradientUnits="userSpaceOnUse"
+ id="id25">
+ <stop
+ id="stop106"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop108"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect110"
+ height="7.6123"
+ width="15.8048"
+ y="77.5661"
+ x="227.733"
+ style="fill:url(#id25)" />
+ </mask>
+ <mask
+ id="id22">
+ <linearGradient
+ y2="85.3244"
+ x2="211.203"
+ y1="77.4202"
+ x1="210.791"
+ gradientUnits="userSpaceOnUse"
+ id="id23">
+ <stop
+ id="stop98"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop100"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect102"
+ height="7.6123"
+ width="15.7954"
+ y="77.5661"
+ x="203.099"
+ style="fill:url(#id23)" />
+ </mask>
+ <mask
+ id="id20">
+ <linearGradient
+ y2="84.9975"
+ x2="226.274"
+ y1="81.1715"
+ x1="221.849"
+ gradientUnits="userSpaceOnUse"
+ id="id21">
+ <stop
+ id="stop90"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop92"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect94"
+ height="10.9979"
+ width="6.7437"
+ y="77.5855"
+ x="220.69"
+ style="fill:url(#id21)" />
+ </mask>
+ <mask
+ id="id18">
+ <linearGradient
+ y2="88.6951"
+ x2="227.491"
+ y1="87.4841"
+ x1="224.529"
+ gradientUnits="userSpaceOnUse"
+ id="id19">
+ <stop
+ id="stop82"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop84"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect86"
+ height="5.2005"
+ width="4.3832"
+ y="85.4893"
+ x="223.819"
+ style="fill:url(#id19)" />
+ </mask>
+ <mask
+ id="id16">
+ <linearGradient
+ y2="91.516"
+ x2="85.9416"
+ y1="85.3292"
+ x1="83.9964"
+ gradientUnits="userSpaceOnUse"
+ id="id17">
+ <stop
+ id="stop74"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop76"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect78"
+ height="10.3911"
+ width="5.0669"
+ y="83.227"
+ x="82.4355"
+ style="fill:url(#id17)" />
+ </mask>
+ <mask
+ id="id14">
+ <linearGradient
+ y2="90.9784"
+ x2="98.3177"
+ y1="84.9562"
+ x1="105.893"
+ gradientUnits="userSpaceOnUse"
+ id="id15">
+ <stop
+ id="stop66"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop68"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect70"
+ height="7.5611"
+ width="9.066"
+ y="84.1867"
+ x="97.5725"
+ style="fill:url(#id15)" />
+ </mask>
+ <mask
+ id="id12">
+ <linearGradient
+ y2="95.3734"
+ x2="96.2373"
+ y1="85.097"
+ x1="109.484"
+ gradientUnits="userSpaceOnUse"
+ id="id13">
+ <stop
+ id="stop58"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop60"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect62"
+ height="12.2285"
+ width="14.9803"
+ y="84.1209"
+ x="95.3702"
+ style="fill:url(#id13)" />
+ </mask>
+ <mask
+ id="id10">
+ <linearGradient
+ y2="88.3964"
+ x2="45.0273"
+ y1="85.1914"
+ x1="42.6605"
+ gradientUnits="userSpaceOnUse"
+ id="id11">
+ <stop
+ id="stop50"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop52"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect54"
+ height="5.1496"
+ width="5.0612"
+ y="84.2191"
+ x="41.3133"
+ style="fill:url(#id11)" />
+ </mask>
+ <mask
+ id="id8">
+ <linearGradient
+ y2="94.0527"
+ x2="46.065"
+ y1="85.6941"
+ x1="39.1106"
+ gradientUnits="userSpaceOnUse"
+ id="id9">
+ <stop
+ id="stop42"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop44"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect46"
+ height="12.1484"
+ width="14.1734"
+ y="83.7992"
+ x="35.5011"
+ style="fill:url(#id9)" />
+ </mask>
+ <mask
+ id="id6">
+ <linearGradient
+ y2="97.2059"
+ x2="96.9321"
+ y1="82.4765"
+ x1="96.1421"
+ gradientUnits="userSpaceOnUse"
+ id="id7">
+ <stop
+ id="stop34"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop36"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect38"
+ height="14.4522"
+ width="30.0107"
+ y="82.6151"
+ x="81.5317"
+ style="fill:url(#id7)" />
+ </mask>
+ <mask
+ id="id4">
+ <linearGradient
+ y2="97.3468"
+ x2="50.1372"
+ y1="82.3356"
+ x1="49.3534"
+ gradientUnits="userSpaceOnUse"
+ id="id5">
+ <stop
+ id="stop26"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop28"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect30"
+ height="14.4522"
+ width="29.9927"
+ y="82.6151"
+ x="34.7489"
+ style="fill:url(#id5)" />
+ </mask>
+ <mask
+ id="id2">
+ <linearGradient
+ y2="96.7258"
+ x2="78.7587"
+ y1="89.4598"
+ x1="70.3549"
+ gradientUnits="userSpaceOnUse"
+ id="id3">
+ <stop
+ id="stop18"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop20"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect22"
+ height="20.8179"
+ width="12.7385"
+ y="82.6838"
+ x="68.1875"
+ style="fill:url(#id3)" />
+ </mask>
+ <mask
+ id="id0">
+ <linearGradient
+ y2="103.748"
+ x2="81.0699"
+ y1="101.448"
+ x1="75.4447"
+ gradientUnits="userSpaceOnUse"
+ id="id1">
+ <stop
+ id="stop10"
+ style="stop-opacity:1; stop-color:white"
+ offset="0" />
+ <stop
+ id="stop12"
+ style="stop-opacity:0; stop-color:white"
+ offset="1" />
+ </linearGradient>
+ <rect
+ id="rect14"
+ height="9.8077"
+ width="8.2557"
+ y="97.6942"
+ x="74.1294"
+ style="fill:url(#id1)" />
+ </mask>
+ <style
+ id="style6"
+ type="text/css">
+
+ .str3 {stroke:#007CC3;stroke-width:0.0762}
+ .str2 {stroke:#0093DD;stroke-width:0.0762}
+ .str0 {stroke:#1F1A17;stroke-width:0.0762}
+ .str9 {stroke:#667AB3;stroke-width:0.0762}
+ .str6 {stroke:#72706F;stroke-width:0.0762}
+ .str4 {stroke:#838281;stroke-width:0.0762}
+ .str7 {stroke:#969594;stroke-width:0.0762}
+ .str1 {stroke:#AAA9A9;stroke-width:0.0762}
+ .str10 {stroke:#C2C1C1;stroke-width:0.0762}
+ .str5 {stroke:#DA251D;stroke-width:0.0762}
+ .str8 {stroke:#E77817;stroke-width:0.0762}
+ .fil3 {fill:none}
+ .fil16 {fill:#0093DD}
+ .fil5 {fill:#1F1A17}
+ .fil2 {fill:#969594}
+ .fil6 {fill:#AAA9A9}
+ .fil4 {fill:#DEDEDD}
+ .fil1 {fill:white}
+ .fil46 {fill:#69312C}
+ .fil15 {fill:url(#id55)}
+ .fil0 {fill:url(#id56)}
+ .fil59 {fill:url(#id57)}
+ .fil19 {fill:url(#id58)}
+ .fil81 {fill:url(#id59)}
+ .fil80 {fill:url(#id60)}
+ .fil32 {fill:url(#id61)}
+ .fil33 {fill:url(#id62)}
+ .fil20 {fill:url(#id63)}
+ .fil21 {fill:url(#id64)}
+ .fil34 {fill:url(#id65)}
+ .fil82 {fill:url(#id66)}
+ .fil22 {fill:url(#id67)}
+ .fil23 {fill:url(#id68)}
+ .fil79 {fill:url(#id69)}
+ .fil36 {fill:url(#id70)}
+ .fil84 {fill:url(#id71)}
+ .fil18 {fill:url(#id72)}
+ .fil31 {fill:url(#id73)}
+ .fil83 {fill:url(#id74)}
+ .fil35 {fill:url(#id75)}
+ .fil51 {fill:url(#id76)}
+ .fil77 {fill:url(#id77)}
+ .fil53 {fill:url(#id78)}
+ .fil65 {fill:url(#id79)}
+ .fil76 {fill:url(#id80)}
+ .fil96 {fill:url(#id81)}
+ .fil55 {fill:url(#id82)}
+ .fil10 {fill:url(#id83)}
+ .fil9 {fill:url(#id84)}
+ .fil8 {fill:url(#id85)}
+ .fil92 {fill:url(#id86)}
+ .fil17 {fill:url(#id87)}
+ .fil7 {fill:url(#id88)}
+ .fil95 {fill:url(#id89)}
+ .fil94 {fill:url(#id90)}
+ .fil97 {fill:url(#id91)}
+ .fil69 {fill:url(#id92)}
+ .fil85 {fill:url(#id93)}
+ .fil13 {fill:url(#id94)}
+ .fil78 {fill:url(#id95)}
+ .fil30 {fill:url(#id96)}
+ .fil12 {fill:url(#id97)}
+ .fil54 {fill:url(#id98)}
+ .fil48 {fill:url(#id99)}
+ .fil91 {fill:url(#id100)}
+ .fil68 {fill:url(#id101)}
+ .fil60 {fill:url(#id102)}
+ .fil14 {fill:url(#id103)}
+ .fil93 {fill:url(#id104)}
+ .fil52 {fill:url(#id105)}
+ .fil87 {fill:url(#id106)}
+ .fil25 {fill:url(#id107)}
+ .fil38 {fill:url(#id108)}
+ .fil26 {fill:url(#id109)}
+ .fil86 {fill:url(#id110)}
+ .fil24 {fill:url(#id111)}
+ .fil37 {fill:url(#id112)}
+ .fil88 {fill:url(#id113)}
+ .fil28 {fill:url(#id114)}
+ .fil29 {fill:url(#id115)}
+ .fil89 {fill:url(#id116)}
+ .fil90 {fill:url(#id117)}
+ .fil27 {fill:url(#id118)}
+ .fil58 {fill:url(#id119)}
+ .fil64 {fill:url(#id120)}
+ .fil57 {fill:url(#id121)}
+ .fil41 {fill:url(#id122)}
+ .fil39 {fill:url(#id123)}
+ .fil40 {fill:url(#id124)}
+ .fil73 {fill:url(#id125)}
+ .fil61 {fill:url(#id126)}
+ .fil44 {fill:url(#id127)}
+ .fil63 {fill:url(#id128)}
+ .fil56 {fill:url(#id129)}
+ .fil42 {fill:url(#id130)}
+ .fil75 {fill:url(#id131)}
+ .fil66 {fill:url(#id132)}
+ .fil72 {fill:url(#id133)}
+ .fil67 {fill:url(#id134)}
+ .fil62 {fill:url(#id135)}
+ .fil74 {fill:url(#id136)}
+ .fil71 {fill:url(#id137)}
+ .fil11 {fill:url(#id138)}
+ .fil70 {fill:url(#id139)}
+ .fil45 {fill:url(#id140)}
+ .fil43 {fill:url(#id141)}
+ .fil49 {fill:url(#id142)}
+ .fil50 {fill:url(#id143)}
+ .fil47 {fill:url(#id144)}
+
+ </style>
+ <inkscape:perspective
+ id="perspective806"
+ inkscape:persp3d-origin="382.5 : 330 : 1"
+ inkscape:vp_z="765 : 495 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 495 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ gradientTransform="matrix(0.2149522,0,0,0.2369714,30.871779,266.01932)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989"
+ id="linearGradient9477"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ gradientTransform="matrix(0.3505596,0,0,0.3701598,22.984703,226.27699)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190"
+ id="linearGradient9474"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ gradientTransform="matrix(1.630872,0,0,1.562044,-66.69223,-169.1379)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190"
+ id="linearGradient9453"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ gradientTransform="translate(-30,-1.428557)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989"
+ id="linearGradient9451"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ gradientTransform="matrix(1.630872,0,0,1.562044,-66.69223,-169.1379)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190"
+ id="linearGradient5850"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ gradientTransform="translate(-30,-1.428557)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989"
+ id="linearGradient5848"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ gradientTransform="matrix(1.630872,0,0,1.562044,-66.69223,-169.1379)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190"
+ id="linearGradient5824"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ gradientTransform="translate(-30,-1.428557)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989"
+ id="linearGradient5822"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ gradientTransform="translate(-30,-1.428557)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989"
+ id="linearGradient3995"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ gradientTransform="matrix(1.630872,0,0,1.562044,-66.69223,-169.1379)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190"
+ id="linearGradient2196"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ id="linearGradient2190">
+ <stop
+ offset="0"
+ style="stop-color:#acbbff;stop-opacity:1"
+ id="stop2192" />
+ <stop
+ offset="1"
+ style="stop-color:#acbbff;stop-opacity:0"
+ id="stop2194" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3989">
+ <stop
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1"
+ id="stop3991" />
+ <stop
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0"
+ id="stop3993" />
+ </linearGradient>
+ <radialGradient
+ cx="62.625"
+ cy="4.625"
+ r="10.625"
+ fx="62.625"
+ fy="4.625"
+ id="radialGradient5323"
+ xlink:href="#linearGradient8838"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.341176,0,3.047059)" />
+ <linearGradient
+ id="linearGradient8838">
+ <stop
+ id="stop8840"
+ style="stop-color:black;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop8842"
+ style="stop-color:black;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="28.771276"
+ y1="12.91806"
+ x2="28.771276"
+ y2="45.347591"
+ id="linearGradient5128"
+ xlink:href="#linearGradient37935"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
+ <linearGradient
+ id="linearGradient37935">
+ <stop
+ id="stop37937"
+ style="stop-color:#929292;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop37939"
+ style="stop-color:#4a4a4a;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="19.176617"
+ y1="13.479795"
+ x2="19.176617"
+ y2="45.358662"
+ id="linearGradient5130"
+ xlink:href="#linearGradient5354"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
+ <linearGradient
+ id="linearGradient5354">
+ <stop
+ id="stop5356"
+ style="stop-color:#3f3f3f;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop5358"
+ style="stop-color:black;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="11.901996"
+ cy="10.045444"
+ r="29.292715"
+ fx="11.901996"
+ fy="10.045444"
+ id="radialGradient5350"
+ xlink:href="#linearGradient2145"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2145">
+ <stop
+ id="stop2147"
+ style="stop-color:#fffffd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2149"
+ style="stop-color:#cbcbc9;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="62.625"
+ cy="4.625"
+ r="10.625"
+ fx="62.625"
+ fy="4.625"
+ id="radialGradient5323-9"
+ xlink:href="#linearGradient8838-0"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.341176,0,3.047059)" />
+ <linearGradient
+ id="linearGradient8838-0">
+ <stop
+ id="stop8840-7"
+ style="stop-color:black;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop8842-6"
+ style="stop-color:black;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="28.771276"
+ y1="12.91806"
+ x2="28.771276"
+ y2="45.347591"
+ id="linearGradient5128-5"
+ xlink:href="#linearGradient37935-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
+ <linearGradient
+ id="linearGradient37935-4">
+ <stop
+ id="stop37937-9"
+ style="stop-color:#929292;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop37939-7"
+ style="stop-color:#4a4a4a;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="19.176617"
+ y1="13.479795"
+ x2="19.176617"
+ y2="45.358662"
+ id="linearGradient5130-2"
+ xlink:href="#linearGradient5354-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
+ <linearGradient
+ id="linearGradient5354-4">
+ <stop
+ id="stop5356-0"
+ style="stop-color:#3f3f3f;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop5358-9"
+ style="stop-color:black;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ cx="11.901996"
+ cy="10.045444"
+ r="29.292715"
+ fx="11.901996"
+ fy="10.045444"
+ id="radialGradient5350-8"
+ xlink:href="#linearGradient2145-8"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2145-8">
+ <stop
+ id="stop2147-5"
+ style="stop-color:#fffffd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2149-8"
+ style="stop-color:#cbcbc9;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient10235"
+ y2="609.51001"
+ gradientUnits="userSpaceOnUse"
+ x2="302.85999"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+ y1="366.64999"
+ x1="302.85999">
+ <stop
+ id="stop5050-8"
+ style="stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056-5"
+ offset=".5" />
+ <stop
+ id="stop5052-57"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient10237"
+ xlink:href="#linearGradient5060-2"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5060-2">
+ <stop
+ id="stop5062-44"
+ offset="0" />
+ <stop
+ id="stop5064-09"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient10239"
+ xlink:href="#linearGradient5060-2"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient3046-9">
+ <stop
+ id="stop3048"
+ offset="0" />
+ <stop
+ id="stop3050"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient3797"
+ xlink:href="#linearGradient6945"
+ gradientUnits="userSpaceOnUse"
+ cy="15.928"
+ cx="12.5"
+ gradientTransform="matrix(1.0627,0,0,0.90795,-8.6172,-5.7767)"
+ r="18.5" />
+ <linearGradient
+ id="linearGradient6945">
+ <stop
+ id="stop6947"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop6949"
+ style="stop-color:#d3d7cf"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient3803"
+ xlink:href="#linearGradient6945"
+ gradientUnits="userSpaceOnUse"
+ cy="61.48"
+ cx="13.107"
+ gradientTransform="matrix(2.6083,0,0,2.2188,-74.941,-118.62)"
+ r="18.5" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#d3d7cf"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3785"
+ y2="75.499001"
+ gradientUnits="userSpaceOnUse"
+ x2="14.125"
+ gradientTransform="matrix(0.52778,0,0,0.52,-1.4028,-22.84)"
+ y1="79.813004"
+ x1="14.125">
+ <stop
+ id="stop6936"
+ style="stop-color:#babdb6"
+ offset="0" />
+ <stop
+ id="stop6938"
+ style="stop-color:#babdb6;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient3138"
+ gradientUnits="userSpaceOnUse"
+ cy="35.126999"
+ cx="23.070999"
+ gradientTransform="matrix(0.91481,0.01265,-0.008215,0.21356,2.2539,27.189)"
+ r="10.319">
+ <stop
+ id="stop2093"
+ offset="0" />
+ <stop
+ id="stop2095"
+ style="stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ id="linearGradient3809"
+ y2="90.696999"
+ gradientUnits="userSpaceOnUse"
+ x2="29.747"
+ gradientTransform="matrix(0.17299,0,0,0.17299,7.176,6.8944)"
+ y1="24.139999"
+ x1="29.747">
+ <stop
+ id="stop3682"
+ style="stop-color:#e86666"
+ offset="0" />
+ <stop
+ id="stop3684"
+ style="stop-color:#ab2a2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3811"
+ y2="90.678001"
+ gradientUnits="userSpaceOnUse"
+ x2="18.379"
+ gradientTransform="matrix(0.17299,0,0,0.17299,7.176,6.8944)"
+ y1="25.691"
+ x1="18.379">
+ <stop
+ id="stop3402"
+ style="stop-color:#b95858"
+ offset="0" />
+ <stop
+ id="stop3404"
+ style="stop-color:#763434"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3806"
+ y2="5.9661999"
+ gradientUnits="userSpaceOnUse"
+ x2="63.396999"
+ gradientTransform="matrix(0.68607,0,0,0.68605,-27.174,17.65)"
+ y1="-9.3832998"
+ x1="63.396999">
+ <stop
+ id="stop4875"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop4877"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="5.9661999"
+ x2="63.396999"
+ y1="-9.3832998"
+ x1="63.396999"
+ gradientTransform="matrix(0.68607,0,0,0.68605,-27.174,17.65)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3093"
+ xlink:href="#linearGradient3806"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4797"
+ y2="59.876999"
+ gradientUnits="userSpaceOnUse"
+ x2="61.338001"
+ gradientTransform="matrix(0.5625,0,0,0.47059,-52.681245,164.7167)"
+ y1="72.834"
+ x1="79.236">
+ <stop
+ id="stop3707"
+ style="stop-color:#85b6d1"
+ offset="0" />
+ <stop
+ id="stop3709"
+ style="stop-color:#7ab1d0"
+ offset=".1707" />
+ <stop
+ id="stop3711"
+ style="stop-color:#5ea5cd"
+ offset=".4684" />
+ <stop
+ id="stop3713-1"
+ style="stop-color:#2f92c9"
+ offset=".85530" />
+ <stop
+ id="stop3715-8"
+ style="stop-color:#1c8ac7"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4800"
+ y2="74.042"
+ gradientUnits="userSpaceOnUse"
+ x2="80.392998"
+ gradientTransform="matrix(0.5625,0,0,0.47059,-52.681245,164.7167)"
+ y1="74.042"
+ x1="72.138">
+ <stop
+ id="stop3689"
+ style="stop-color:#1773a6"
+ offset="0" />
+ <stop
+ id="stop3691"
+ style="stop-color:#1773a6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4803"
+ y2="67.132004"
+ gradientUnits="userSpaceOnUse"
+ x2="65.277"
+ gradientTransform="matrix(0.5625,0,0,0.47059,-52.681245,164.7167)"
+ y1="74.939003"
+ x1="65.277">
+ <stop
+ id="stop3695"
+ style="stop-color:#f5f2b0"
+ offset="0" />
+ <stop
+ id="stop3697"
+ style="stop-color:#f5efa5"
+ offset=".1374" />
+ <stop
+ id="stop3699"
+ style="stop-color:#f4e988"
+ offset=".377" />
+ <stop
+ id="stop3701"
+ style="stop-color:#f3de5a"
+ offset=".6886" />
+ <stop
+ id="stop3703"
+ style="stop-color:#f2d125"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4806"
+ y2="73.961998"
+ gradientUnits="userSpaceOnUse"
+ x2="73.086998"
+ gradientTransform="matrix(0.5625,0,0,0.47059,-52.681245,164.7167)"
+ y1="73.961998"
+ x1="57.16">
+ <stop
+ id="stop3679"
+ style="stop-color:#e1da1a"
+ offset="0" />
+ <stop
+ id="stop3681"
+ style="stop-color:#d6cf14"
+ offset=".31280" />
+ <stop
+ id="stop3683"
+ style="stop-color:#bab305"
+ offset=".85680" />
+ <stop
+ id="stop3685"
+ style="stop-color:#b2aa00"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4786"
+ y2="75.497002"
+ gradientUnits="userSpaceOnUse"
+ x2="66"
+ y1="61.889"
+ x1="66">
+ <stop
+ id="stop3660"
+ style="stop-color:#ef3b3b"
+ offset="0" />
+ <stop
+ id="stop3662"
+ style="stop-color:#f14646"
+ offset=".3827" />
+ <stop
+ id="stop3664-0"
+ style="stop-color:#f76060"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4810"
+ y2="73.065002"
+ gradientUnits="userSpaceOnUse"
+ x2="82"
+ gradientTransform="matrix(0.5625,0,0,0.47059,-52.681245,164.7167)"
+ y1="73.065002"
+ x1="50">
+ <stop
+ id="stop3669"
+ style="stop-color:#bb2f2f"
+ offset="0" />
+ <stop
+ id="stop3671"
+ style="stop-color:#d53c3c"
+ offset=".3087" />
+ <stop
+ id="stop3673"
+ style="stop-color:#e04242"
+ offset=".5" />
+ <stop
+ id="stop3675-0"
+ style="stop-color:#bb2f2f"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient3494"
+ gradientUnits="userSpaceOnUse"
+ cy="5.8834"
+ cx="16.961"
+ gradientTransform="matrix(0,2,-1.3459,0,28.919,-35.008)"
+ r="22.105">
+ <stop
+ id="stop3722-0"
+ style="stop-color:#8badea"
+ offset="0" />
+ <stop
+ id="stop3728-2"
+ style="stop-color:#6396cd"
+ offset=".5" />
+ <stop
+ id="stop3730-8"
+ style="stop-color:#3b7caf"
+ offset=".84091" />
+ <stop
+ id="stop3724-3"
+ style="stop-color:#194c70"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ id="linearGradient3496"
+ y2="-1.0863"
+ gradientUnits="userSpaceOnUse"
+ x2="10.55"
+ y1="43.124001"
+ x1="10.148">
+ <stop
+ id="stop3765-0"
+ style="stop-color:#1f4b6a"
+ offset="0" />
+ <stop
+ id="stop3767-4"
+ style="stop-color:#4083c2"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4198"
+ y2="7"
+ xlink:href="#linearGradient2264"
+ gradientUnits="userSpaceOnUse"
+ x2="12"
+ gradientTransform="matrix(0.66667,0,0,0.6,185.74627,11.9117)"
+ y1="2"
+ x1="12" />
+ <linearGradient
+ id="linearGradient2264">
+ <stop
+ id="stop2266-8"
+ style="stop-color:#d7e866"
+ offset="0" />
+ <stop
+ id="stop2268-5"
+ style="stop-color:#8cab2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4174"
+ y2="7"
+ xlink:href="#linearGradient2264"
+ gradientUnits="userSpaceOnUse"
+ x2="12"
+ gradientTransform="matrix(0.94444,0,0,0.8,-18.945029,181.14627)"
+ y1="2"
+ x1="12" />
+ <linearGradient
+ id="linearGradient3104">
+ <stop
+ id="stop3106-0"
+ style="stop-color:#d7e866"
+ offset="0" />
+ <stop
+ id="stop3108-1"
+ style="stop-color:#8cab2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3246"
+ y2="47.013"
+ gradientUnits="userSpaceOnUse"
+ x2="25.132"
+ gradientTransform="matrix(0.54288,0,0,0.46564,-20.640799,181.09459)"
+ y1="6.7287002"
+ x1="25.132">
+ <stop
+ id="stop3602-9"
+ style="stop-color:#f4f4f4"
+ offset="0" />
+ <stop
+ id="stop3604-1"
+ style="stop-color:#e6e6e6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3248"
+ y2="2.9061999"
+ gradientUnits="userSpaceOnUse"
+ x2="-51.785999"
+ gradientTransform="matrix(0.43837,0,0,0.43577,11.61231,180.57565)"
+ y1="50.785999"
+ x1="-51.785999">
+ <stop
+ id="stop3933-9"
+ style="stop-color:#797b75"
+ offset="0" />
+ <stop
+ id="stop3935-73"
+ style="stop-color:#bebebe"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient4208"
+ gradientUnits="userSpaceOnUse"
+ cy="25.749001"
+ cx="12"
+ gradientTransform="matrix(1,0,0,0.29557,0,18.138)"
+ r="11">
+ <stop
+ id="stop4204"
+ offset="0" />
+ <stop
+ id="stop4206"
+ style="stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ r="11"
+ cy="25.749001"
+ cx="12"
+ gradientTransform="matrix(1.0909,0,0,0.18181693,-20.702599,197.56468)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3137"
+ xlink:href="#radialGradient4208"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4532-5"
+ y2="609.51001"
+ gradientUnits="userSpaceOnUse"
+ x2="302.85999"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1892.2,-872.89)"
+ y1="366.64999"
+ x1="302.85999">
+ <stop
+ id="stop5050-9"
+ style="stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056-09"
+ offset=".5" />
+ <stop
+ id="stop5052-3"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient4534-1"
+ xlink:href="#linearGradient5060-8"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(2.7744,0,0,1.9697,-1891.6,-872.89)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5060-8">
+ <stop
+ id="stop5062-8"
+ offset="0" />
+ <stop
+ id="stop5064-3"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient4536-9"
+ xlink:href="#linearGradient5060-8"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(-2.7744,0,0,1.9697,112.76,-872.89)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient3052-4">
+ <stop
+ id="stop3054"
+ offset="0" />
+ <stop
+ id="stop3056"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4545-9"
+ y2="47.013"
+ gradientUnits="userSpaceOnUse"
+ x2="25.132"
+ gradientTransform="matrix(0.54288,0,0,0.48891,-1.0291,-0.23377)"
+ y1="6.7287002"
+ x1="25.132">
+ <stop
+ id="stop3602-41"
+ style="stop-color:#f4f4f4"
+ offset="0" />
+ <stop
+ id="stop3604-2"
+ style="stop-color:#dbdbdb"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4547-8"
+ y2="2.9061999"
+ gradientUnits="userSpaceOnUse"
+ x2="-51.785999"
+ gradientTransform="matrix(0.43837,0,0,0.45754,31.224,-0.77865)"
+ y1="50.785999"
+ x1="-51.785999">
+ <stop
+ id="stop3933-7"
+ style="stop-color:#8d8f8a"
+ offset="0" />
+ <stop
+ id="stop3935-4"
+ style="stop-color:#cbcbcb"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4543-7"
+ y2="5.4565001"
+ gradientUnits="userSpaceOnUse"
+ x2="36.358002"
+ gradientTransform="matrix(0.54163,0,0,0.5203,-0.94021,-0.30186)"
+ y1="8.059"
+ x1="32.891998">
+ <stop
+ id="stop8591-35"
+ style="stop-color:#fefefe"
+ offset="0" />
+ <stop
+ id="stop8593-3"
+ style="stop-color:#cbcbcb"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4565-6"
+ y2="5.8214998"
+ gradientUnits="userSpaceOnUse"
+ x2="15.18"
+ y1="2.1849"
+ x1="17.289">
+ <stop
+ id="stop4561-4"
+ style="stop-color:#cacaca"
+ offset="0" />
+ <stop
+ id="stop4563-8"
+ style="stop-color:#949492"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3773"
+ y2="15.5"
+ gradientUnits="userSpaceOnUse"
+ x2="-0.073089004"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ y1="15.5"
+ x1="23.997">
+ <stop
+ id="stop3700"
+ style="stop-color:#ce7ecc"
+ offset="0" />
+ <stop
+ id="stop3702"
+ style="stop-color:#c056bc;stop-opacity:.81569"
+ offset=".76279" />
+ <stop
+ id="stop3704"
+ style="stop-color:#f8c9f7;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3775"
+ y2="36"
+ gradientUnits="userSpaceOnUse"
+ x2="-0.097514004"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ y1="36"
+ x1="36.5">
+ <stop
+ id="stop3916"
+ style="stop-color:#c02cbb"
+ offset="0" />
+ <stop
+ id="stop3918"
+ style="stop-color:#b329ae;stop-opacity:.49804"
+ offset=".79722" />
+ <stop
+ id="stop3920"
+ style="stop-color:#982394;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3779"
+ y2="-0.43753999"
+ gradientUnits="userSpaceOnUse"
+ x2="20.483999"
+ gradientTransform="matrix(0,-0.43118,0.42426,0,8.8859,25.258)"
+ y1="12.82"
+ x1="20.483999">
+ <stop
+ id="stop2189"
+ style="stop-color:#fff;stop-opacity:.64341"
+ offset="0" />
+ <stop
+ id="stop2191"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="-0.43753999"
+ x2="20.483999"
+ y1="12.82"
+ x1="20.483999"
+ gradientTransform="matrix(0,-0.43118,0.42426,0,8.8859,25.258)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3099"
+ xlink:href="#linearGradient3779"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3773"
+ id="linearGradient5274"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ x1="23.997"
+ y1="15.5"
+ x2="-0.073089004"
+ y2="15.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3775"
+ id="linearGradient5276"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ x1="36.5"
+ y1="36"
+ x2="-0.097514004"
+ y2="36" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3779"
+ id="linearGradient5278"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-0.43118,0.42426,0,8.8859,25.258)"
+ x1="20.483999"
+ y1="12.82"
+ x2="20.483999"
+ y2="-0.43753999" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3773-1"
+ id="linearGradient5274-2"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ x1="23.997"
+ y1="15.5"
+ x2="-0.073089004"
+ y2="15.5" />
+ <linearGradient
+ id="linearGradient3773-1"
+ y2="15.5"
+ gradientUnits="userSpaceOnUse"
+ x2="-0.073089004"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ y1="15.5"
+ x1="23.997">
+ <stop
+ id="stop3700-7"
+ style="stop-color:#ce7ecc"
+ offset="0" />
+ <stop
+ id="stop3702-0"
+ style="stop-color:#c056bc;stop-opacity:.81569"
+ offset=".76279" />
+ <stop
+ id="stop3704-6"
+ style="stop-color:#f8c9f7;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3775-2"
+ id="linearGradient5276-8"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ x1="36.5"
+ y1="36"
+ x2="-0.097514004"
+ y2="36" />
+ <linearGradient
+ id="linearGradient3775-2"
+ y2="36"
+ gradientUnits="userSpaceOnUse"
+ x2="-0.097514004"
+ gradientTransform="matrix(0.36202,0,0,0.36792,8.2405,6.3535)"
+ y1="36"
+ x1="36.5">
+ <stop
+ id="stop3916-3"
+ style="stop-color:#c02cbb"
+ offset="0" />
+ <stop
+ id="stop3918-9"
+ style="stop-color:#b329ae;stop-opacity:.49804"
+ offset=".79722" />
+ <stop
+ id="stop3920-4"
+ style="stop-color:#982394;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3779-5"
+ id="linearGradient5278-9"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-0.43118,0.42426,0,8.8859,25.258)"
+ x1="20.483999"
+ y1="12.82"
+ x2="20.483999"
+ y2="-0.43753999" />
+ <linearGradient
+ id="linearGradient3779-5"
+ y2="-0.43753999"
+ gradientUnits="userSpaceOnUse"
+ x2="20.483999"
+ gradientTransform="matrix(0,-0.43118,0.42426,0,8.8859,25.258)"
+ y1="12.82"
+ x1="20.483999">
+ <stop
+ id="stop2189-3"
+ style="stop-color:#fff;stop-opacity:.64341"
+ offset="0" />
+ <stop
+ id="stop2191-7"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4198-3"
+ y2="7"
+ xlink:href="#linearGradient2264-3"
+ gradientUnits="userSpaceOnUse"
+ x2="12"
+ gradientTransform="matrix(0.66667,0,0,0.6,186.09982,67.32592)"
+ y1="2"
+ x1="12" />
+ <linearGradient
+ id="linearGradient2264-3">
+ <stop
+ id="stop2266-8-4"
+ style="stop-color:#d7e866"
+ offset="0" />
+ <stop
+ id="stop2268-5-8"
+ style="stop-color:#8cab2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4174-7"
+ y2="7"
+ xlink:href="#linearGradient2264-3"
+ gradientUnits="userSpaceOnUse"
+ x2="12"
+ gradientTransform="matrix(0.94444,0,0,0.8,-74.35925,181.49982)"
+ y1="2"
+ x1="12" />
+ <linearGradient
+ id="linearGradient5348">
+ <stop
+ id="stop5350"
+ style="stop-color:#d7e866"
+ offset="0" />
+ <stop
+ id="stop5352"
+ style="stop-color:#8cab2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3246-7"
+ y2="47.013"
+ gradientUnits="userSpaceOnUse"
+ x2="25.132"
+ gradientTransform="matrix(0.54288,0,0,0.46564,-76.05502,181.44814)"
+ y1="6.7287002"
+ x1="25.132">
+ <stop
+ id="stop3602-9-0"
+ style="stop-color:#f4f4f4"
+ offset="0" />
+ <stop
+ id="stop3604-1-5"
+ style="stop-color:#e6e6e6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3248-7"
+ y2="2.9061999"
+ gradientUnits="userSpaceOnUse"
+ x2="-51.785999"
+ gradientTransform="matrix(0.43837,0,0,0.43577,-43.80192,180.9292)"
+ y1="50.785999"
+ x1="-51.785999">
+ <stop
+ id="stop3933-9-6"
+ style="stop-color:#797b75"
+ offset="0" />
+ <stop
+ id="stop3935-73-6"
+ style="stop-color:#bebebe"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ r="11"
+ cy="25.749001"
+ cx="12"
+ gradientTransform="matrix(1.0909,0,0,0.18181693,-125.64194,197.91823)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3137-6"
+ xlink:href="#radialGradient4208-0"
+ inkscape:collect="always" />
+ <radialGradient
+ id="radialGradient4208-0"
+ gradientUnits="userSpaceOnUse"
+ cy="25.749001"
+ cx="12"
+ gradientTransform="matrix(1,0,0,0.29557,0,18.138)"
+ r="11">
+ <stop
+ id="stop4204-5"
+ offset="0" />
+ <stop
+ id="stop4206-1"
+ style="stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ r="11"
+ cy="25.749001"
+ cx="12"
+ gradientTransform="matrix(1.0909,0,0,0.18181693,-76.11682,197.91823)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5378"
+ xlink:href="#radialGradient4208-0"
+ inkscape:collect="always" />
+ <linearGradient
+ x1="108.97875"
+ y1="185.10141"
+ x2="111.16116"
+ y2="193.85185"
+ id="linearGradient5584"
+ xlink:href="#linearGradient5578"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.64228382,0,0,0.64226979,-130.93386,77.96672)" />
+ <linearGradient
+ id="linearGradient5578">
+ <stop
+ id="stop5580"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop5582"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="113.66741"
+ y1="189.97391"
+ x2="112.51836"
+ y2="187"
+ id="linearGradient5554"
+ xlink:href="#linearGradient5548"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.64228382,0,0,0.64226979,-130.93386,77.96672)" />
+ <linearGradient
+ id="linearGradient5548">
+ <stop
+ id="stop5550"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop5552"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="109.00001"
+ y1="177"
+ x2="113.00001"
+ y2="196"
+ id="linearGradient3817"
+ xlink:href="#linearGradient3533"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3533">
+ <stop
+ id="stop3535"
+ style="stop-color:#93b9dd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3545"
+ style="stop-color:#6396cd;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="109.00001"
+ y1="177"
+ x2="112.11741"
+ y2="188.79379"
+ id="linearGradient3883"
+ xlink:href="#linearGradient3533"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3044-7">
+ <stop
+ id="stop3046"
+ style="stop-color:#93b9dd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3048-3"
+ style="stop-color:#6396cd;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="109.00001"
+ y1="177"
+ x2="113.00001"
+ y2="196"
+ id="linearGradient3885"
+ xlink:href="#linearGradient3533"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3051">
+ <stop
+ id="stop3053"
+ style="stop-color:#93b9dd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3055"
+ style="stop-color:#6396cd;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="113.93192"
+ y1="192.53191"
+ x2="120.46526"
+ y2="189.73192"
+ id="linearGradient3833"
+ xlink:href="#linearGradient3827"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3827">
+ <stop
+ id="stop3835"
+ style="stop-color:#6396cd;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3831"
+ style="stop-color:#7ba7d5;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="116.4927"
+ y1="198.96754"
+ x2="110.65003"
+ y2="177.1624"
+ id="linearGradient5374"
+ xlink:href="#linearGradient4022-6-5-6"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4022-6-5-6">
+ <stop
+ id="stop4024-1-5-3"
+ style="stop-color:#555753;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop4026-2-3-1"
+ style="stop-color:#babdb6;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="114.86526"
+ y1="192.53191"
+ x2="119.53192"
+ y2="187.86525"
+ id="linearGradient3809-0"
+ xlink:href="#linearGradient3803"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3803">
+ <stop
+ id="stop3805"
+ style="stop-color:#538ec6;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3807"
+ style="stop-color:#538ec6;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="187.86525"
+ x2="119.53192"
+ y1="192.53191"
+ x1="114.86526"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3081"
+ xlink:href="#linearGradient3803"
+ inkscape:collect="always" />
+ <linearGradient
+ x1="-31.523001"
+ y1="190.50999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-55.188999"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3390-178-986-453-4-5-0-7"
+ y2="182.48"
+ id="linearGradient3044-1" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-0-7">
+ <stop
+ offset="0"
+ style="stop-color:#bb2b12"
+ id="stop3624-8-6-0-3" />
+ <stop
+ offset="1"
+ style="stop-color:#cd7233"
+ id="stop3626-1-1-2-5" />
+ </linearGradient>
+ <linearGradient
+ x1="-31.523001"
+ y1="191.52"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-55.188999"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3390-178-986-453-4-5-0-7"
+ y2="182.48"
+ id="linearGradient3046-2" />
+ <linearGradient
+ id="linearGradient4942">
+ <stop
+ offset="0"
+ style="stop-color:#bb2b12"
+ id="stop4944" />
+ <stop
+ offset="1"
+ style="stop-color:#cd7233"
+ id="stop4946" />
+ </linearGradient>
+ <linearGradient
+ x1="-55.188999"
+ y1="182.48"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-31.523001"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8-1"
+ y2="191.52"
+ id="linearGradient3048-0" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8-1">
+ <stop
+ offset="0"
+ style="stop-color:#f0c178"
+ id="stop3618-1-9-14-1" />
+ <stop
+ offset=".5"
+ style="stop-color:#e18941"
+ id="stop3270-5-6-8-1" />
+ <stop
+ offset="1"
+ style="stop-color:#ec4f18"
+ id="stop3620-9-3-4-1" />
+ </linearGradient>
+ <linearGradient
+ x1="-55.188999"
+ y1="183.48"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ x2="-31.523001"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-8-1"
+ y2="191.52"
+ id="linearGradient3050-0" />
+ <linearGradient
+ id="linearGradient4954">
+ <stop
+ offset="0"
+ style="stop-color:#f0c178"
+ id="stop4956" />
+ <stop
+ offset=".5"
+ style="stop-color:#e18941"
+ id="stop4958" />
+ <stop
+ offset="1"
+ style="stop-color:#ec4f18"
+ id="stop4960" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4456-4-4">
+ <stop
+ offset="0"
+ style="stop-color:#f6daae"
+ id="stop4458-9-5" />
+ <stop
+ offset="1"
+ style="stop-color:#f0c178;stop-opacity:0"
+ id="stop4460-7-5" />
+ </linearGradient>
+ <linearGradient
+ y1="-8.8818001e-16"
+ x2="22"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient4456-4-4"
+ y2="9"
+ id="linearGradient3052-42" />
+ <linearGradient
+ id="linearGradient4967">
+ <stop
+ offset="0"
+ style="stop-color:#f6daae"
+ id="stop4969" />
+ <stop
+ offset="1"
+ style="stop-color:#f0c178;stop-opacity:0"
+ id="stop4971" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3732-7"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-8"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="191.52"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-8">
+ <stop
+ id="stop3624-8-6-6"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-0"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3017"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-8"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="191.52"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient3019-9">
+ <stop
+ id="stop3021"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3023"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-57"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="182.48"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1">
+ <stop
+ id="stop3618-1-9-9"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-0"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-50"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3030"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="182.48"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3032">
+ <stop
+ id="stop3034"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3036"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3038"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-7"
+ y2="9"
+ xlink:href="#linearGradient4456-0"
+ gradientUnits="userSpaceOnUse"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22" />
+ <linearGradient
+ id="linearGradient4456-0">
+ <stop
+ id="stop4458-4"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-0"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3044-10"
+ y2="9"
+ xlink:href="#linearGradient4456-0"
+ gradientUnits="userSpaceOnUse"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22" />
+ <linearGradient
+ id="linearGradient3046-97">
+ <stop
+ id="stop3048-9"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop3050-1"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="182.48"
+ x2="-31.523001"
+ y1="191.52"
+ x1="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3060-7"
+ xlink:href="#linearGradient3390-178-986-453-4-5-8-9"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-8-9">
+ <stop
+ id="stop3624-8-6-6-1"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-0-2"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3732-7-1"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-8-9"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="191.52"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient5305">
+ <stop
+ id="stop5307"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5309"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="191.52"
+ x2="-55.188999"
+ y1="182.48"
+ x1="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3062-1"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1-2">
+ <stop
+ id="stop3618-1-9-9-3"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-0-5"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-50-2"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-57-2"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-1-2"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="182.48"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient5317">
+ <stop
+ id="stop5319"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop5321"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop5323"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="9"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3064-0"
+ xlink:href="#linearGradient4456-0-0"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4456-0-0">
+ <stop
+ id="stop4458-4-9"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-0-2"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-7-4"
+ y2="9"
+ xlink:href="#linearGradient4456-0-0"
+ gradientUnits="userSpaceOnUse"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22" />
+ <linearGradient
+ id="linearGradient5330">
+ <stop
+ id="stop5332"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop5334"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3732-6"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="190.50999"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-7">
+ <stop
+ id="stop3624-8-6-3"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-8"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4452"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="191.52"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3034">
+ <stop
+ id="stop3036-6"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3038-0"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3795"
+ y2="167"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577" />
+ <linearGradient
+ id="linearGradient3041">
+ <stop
+ id="stop3043"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3045"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3047"
+ y2="167"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577" />
+ <linearGradient
+ id="linearGradient3049">
+ <stop
+ id="stop3051"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3053-1"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040-8-9-7"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272,102)"
+ y1="185.44"
+ x1="-86.552002">
+ <stop
+ id="stop4036-9-1-5"
+ style="stop-color:#eeeeec"
+ offset="0" />
+ <stop
+ id="stop4038-0-5-5"
+ style="stop-color:#babdb6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3965"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272.58,80)"
+ y1="178"
+ x1="-89">
+ <stop
+ id="stop3618-1-9-8"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-3-3"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-0"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-9"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="182.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12">
+ <stop
+ id="stop3618-1-9-89"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-6"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-1"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4324-6"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="183.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient3068">
+ <stop
+ id="stop3070"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3072"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3074"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-4"
+ y2="9"
+ xlink:href="#linearGradient4456-1"
+ gradientUnits="userSpaceOnUse"
+ x2="22"
+ gradientTransform="translate(1,0)"
+ y1="-8.8818001e-16" />
+ <linearGradient
+ id="linearGradient4456-1">
+ <stop
+ id="stop4458-5"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-2"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3080"
+ y2="9"
+ xlink:href="#linearGradient4456-1"
+ gradientUnits="userSpaceOnUse"
+ x2="22"
+ gradientTransform="translate(1,0)"
+ y1="-8.8818001e-16" />
+ <linearGradient
+ id="linearGradient3082">
+ <stop
+ id="stop3084"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop3086"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="167"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3102"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="9"
+ x2="22"
+ y1="-8.8818001e-16"
+ gradientTransform="translate(1,0)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3104-9"
+ xlink:href="#linearGradient4456-1"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3732-6-6"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-6"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="190.50999"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-7-6">
+ <stop
+ id="stop3624-8-6-3-0"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-8-5"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4452-2"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-6"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="191.52"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient5604">
+ <stop
+ id="stop5606"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5608"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3795-3"
+ y2="167"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-6"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577" />
+ <linearGradient
+ id="linearGradient5611">
+ <stop
+ id="stop5613"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5615"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="167"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3102-2"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5618">
+ <stop
+ id="stop5620"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5622"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040-8-9-7-6"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272,102)"
+ y1="185.44"
+ x1="-86.552002">
+ <stop
+ id="stop4036-9-1-5-8"
+ style="stop-color:#eeeeec"
+ offset="0" />
+ <stop
+ id="stop4038-0-5-5-8"
+ style="stop-color:#babdb6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3965-3"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272.58,80)"
+ y1="178"
+ x1="-89">
+ <stop
+ id="stop3618-1-9-8-7"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-3-3-6"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-0-8"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-9-9"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="182.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-3">
+ <stop
+ id="stop3618-1-9-89-9"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-6-5"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-1-2"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4324-6-7"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="183.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient5637">
+ <stop
+ id="stop5639"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop5641"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop5643"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="9"
+ x2="22"
+ y1="-8.8818001e-16"
+ gradientTransform="translate(1,0)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3104-9-4"
+ xlink:href="#linearGradient4456-1-5"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4456-1-5">
+ <stop
+ id="stop4458-5-7"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-2-6"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-4-9"
+ y2="9"
+ xlink:href="#linearGradient4456-1-5"
+ gradientUnits="userSpaceOnUse"
+ x2="22"
+ gradientTransform="translate(1,0)"
+ y1="-8.8818001e-16" />
+ <linearGradient
+ id="linearGradient5650">
+ <stop
+ id="stop5652"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop5654"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3732-6-7"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="190.50999"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-7-3">
+ <stop
+ id="stop3624-8-6-3-7"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-8-2"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4452-23"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="191.52"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient5604-7">
+ <stop
+ id="stop5606-9"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5608-9"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3795-7"
+ y2="167"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577" />
+ <linearGradient
+ id="linearGradient5611-3">
+ <stop
+ id="stop5613-0"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5615-2"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="167"
+ x2="-55.577"
+ y1="162"
+ x1="-33.577"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3102-5"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-3"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5618-3">
+ <stop
+ id="stop5620-9"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop5622-3"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4040-8-9-7-2"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272,102)"
+ y1="185.44"
+ x1="-86.552002">
+ <stop
+ id="stop4036-9-1-5-9"
+ style="stop-color:#eeeeec"
+ offset="0" />
+ <stop
+ id="stop4038-0-5-5-6"
+ style="stop-color:#babdb6"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3965-34"
+ y2="197.31"
+ gradientUnits="userSpaceOnUse"
+ x2="-83.371002"
+ gradientTransform="matrix(0,-1,1,0,-272.58,80)"
+ y1="178"
+ x1="-89">
+ <stop
+ id="stop3618-1-9-8-1"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-3-3-3"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-0-7"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-9-8"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-6"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="182.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-6">
+ <stop
+ id="stop3618-1-9-89-7"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-6-0"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-1-0"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4324-6-1"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-12-6"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,52.302,-181.74)"
+ y1="183.48"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient5637-0">
+ <stop
+ id="stop5639-5"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop5641-8"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop5643-2"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="9"
+ x2="22"
+ y1="-8.8818001e-16"
+ gradientTransform="translate(1,0)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3104-9-7"
+ xlink:href="#linearGradient4456-1-3"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4456-1-3">
+ <stop
+ id="stop4458-5-1"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-2-8"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-4-2"
+ y2="9"
+ xlink:href="#linearGradient4456-1-3"
+ gradientUnits="userSpaceOnUse"
+ x2="22"
+ gradientTransform="translate(1,0)"
+ y1="-8.8818001e-16" />
+ <linearGradient
+ id="linearGradient5650-8">
+ <stop
+ id="stop5652-3"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop5654-4"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3390-178-986-453-4-5-7-3"
+ id="linearGradient6036"
+ gradientUnits="userSpaceOnUse"
+ x1="-33.577"
+ y1="162"
+ x2="-55.577"
+ y2="167" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4040-8-9-7-2"
+ id="linearGradient6038"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-1,1,0,-272,102)"
+ x1="-86.552002"
+ y1="185.44"
+ x2="-83.371002"
+ y2="197.31" />
+ <radialGradient
+ id="radialGradient2403"
+ gradientUnits="userSpaceOnUse"
+ cy="19.031"
+ cx="11.25"
+ gradientTransform="matrix(1.4062,0,0,0.3867,-3.8197,13.523)"
+ r="8.0625">
+ <stop
+ id="stop2487"
+ style="stop-color:#0d0d0d"
+ offset="0" />
+ <stop
+ id="stop2489"
+ style="stop-color:#0d0d0d;stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ id="linearGradient2400"
+ y2="34.224998"
+ gradientUnits="userSpaceOnUse"
+ x2="24.104"
+ gradientTransform="matrix(0.89889,0,0,0.89347,-9.4637,-8.566)"
+ y1="15.181"
+ x1="24.104">
+ <stop
+ id="stop2266-3"
+ style="stop-color:#d7e866"
+ offset="0" />
+ <stop
+ id="stop2268-0"
+ style="stop-color:#8cab2a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2397"
+ y2="45.689999"
+ gradientUnits="userSpaceOnUse"
+ x2="24.139"
+ gradientTransform="matrix(0.53994,0,0,0.53668,-0.84892,-0.5062)"
+ y1="6.5317001"
+ x1="24.139">
+ <stop
+ id="stop4224-5"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop4226-6"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="45.689999"
+ x2="24.139"
+ y1="6.5317001"
+ x1="24.139"
+ gradientTransform="matrix(0.53994,0,0,0.53668,-0.84892,-0.5062)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3019-5"
+ xlink:href="#linearGradient2397"
+ inkscape:collect="always" />
+ <radialGradient
+ id="radialGradient2478"
+ gradientUnits="userSpaceOnUse"
+ cy="4.625"
+ cx="62.625"
+ gradientTransform="matrix(1.1294,0,0,0.28235,-58.729,19.694)"
+ r="10.625">
+ <stop
+ id="stop8840-3"
+ offset="0" />
+ <stop
+ id="stop8842-0"
+ style="stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <radialGradient
+ id="radialGradient2500"
+ gradientUnits="userSpaceOnUse"
+ cy="3.99"
+ cx="23.896"
+ gradientTransform="matrix(0,1.2316,-1.6257,0,18.487,-28.721)"
+ r="20.396999">
+ <stop
+ id="stop3244"
+ style="stop-color:#f8b17e"
+ offset="0" />
+ <stop
+ id="stop3246"
+ style="stop-color:#e35d4f"
+ offset=".26238" />
+ <stop
+ id="stop3248"
+ style="stop-color:#c6262e"
+ offset=".66094" />
+ <stop
+ id="stop3250"
+ style="stop-color:#690b54"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ id="linearGradient2502"
+ y2="3.0816"
+ gradientUnits="userSpaceOnUse"
+ x2="18.379"
+ gradientTransform="matrix(0.51604,0,0,0.51604,-0.38499,-0.38499)"
+ y1="44.98"
+ x1="18.379">
+ <stop
+ id="stop2492"
+ style="stop-color:#791235"
+ offset="0" />
+ <stop
+ id="stop2494"
+ style="stop-color:#dd3b27"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2497"
+ y2="5.4675999"
+ gradientUnits="userSpaceOnUse"
+ x2="63.396999"
+ gradientTransform="matrix(1.0863,0,0,1.0862,-55.567,15.814)"
+ y1="-12.489"
+ x1="63.396999">
+ <stop
+ id="stop4875-9"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop4877-2"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2494"
+ y2="45.074001"
+ gradientUnits="userSpaceOnUse"
+ x2="24.481001"
+ gradientTransform="matrix(0.53842,0,0,0.53842,-0.92208,-1.4605)"
+ y1="5.0809002"
+ x1="24.481001">
+ <stop
+ id="stop3783"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3785"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="45.074001"
+ x2="24.481001"
+ y1="5.0809002"
+ x1="24.481001"
+ gradientTransform="matrix(0.53842,0,0,0.53842,-0.92208,-1.4605)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3037"
+ xlink:href="#linearGradient2494"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3732-2"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-74"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="191.52"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient3390-178-986-453-4-5-74">
+ <stop
+ id="stop3624-8-6-33"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3626-1-1-23"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3017-1"
+ y2="182.48"
+ xlink:href="#linearGradient3390-178-986-453-4-5-74"
+ gradientUnits="userSpaceOnUse"
+ x2="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="191.52"
+ x1="-55.188999" />
+ <linearGradient
+ id="linearGradient3019-2">
+ <stop
+ id="stop3021-4"
+ style="stop-color:#bb2b12"
+ offset="0" />
+ <stop
+ id="stop3023-3"
+ style="stop-color:#cd7233"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4322-2"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="182.48"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-3">
+ <stop
+ id="stop3618-1-9-6"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3270-5-6-4"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3620-9-3-8"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3030-7"
+ y2="191.52"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-3"
+ gradientUnits="userSpaceOnUse"
+ x2="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ y1="182.48"
+ x1="-31.523001" />
+ <linearGradient
+ id="linearGradient3032-2">
+ <stop
+ id="stop3034-8"
+ style="stop-color:#f0c178"
+ offset="0" />
+ <stop
+ id="stop3036-1"
+ style="stop-color:#e18941"
+ offset=".5" />
+ <stop
+ id="stop3038-4"
+ style="stop-color:#ec4f18"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4462-5"
+ y2="9"
+ xlink:href="#linearGradient4456-16"
+ gradientUnits="userSpaceOnUse"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22" />
+ <linearGradient
+ id="linearGradient4456-16">
+ <stop
+ id="stop4458-8"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop4460-52"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3044-5"
+ y2="9"
+ xlink:href="#linearGradient4456-16"
+ gradientUnits="userSpaceOnUse"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22" />
+ <linearGradient
+ id="linearGradient3046-8">
+ <stop
+ id="stop3048-0"
+ style="stop-color:#f6daae"
+ offset="0" />
+ <stop
+ id="stop3050-12"
+ style="stop-color:#f0c178;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="182.48"
+ x2="-31.523001"
+ y1="191.52"
+ x1="-55.188999"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3060"
+ xlink:href="#linearGradient3390-178-986-453-4-5-74"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="191.52"
+ x2="-55.188999"
+ y1="182.48"
+ x1="-31.523001"
+ gradientTransform="matrix(0.92957,0,0,0.99594,51.302,-181.74)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3062"
+ xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94-1-0-3"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="9"
+ x2="0"
+ y1="-8.8818001e-16"
+ x1="22"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3064"
+ xlink:href="#linearGradient4456-16"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.2149522,0,0,0.2369714,30.871779,266.01932)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989-9"
+ id="linearGradient9477-4"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ id="linearGradient3989-9">
+ <stop
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1"
+ id="stop3991-3" />
+ <stop
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0"
+ id="stop3993-0" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.3505596,0,0,0.3701598,22.984703,226.27699)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190-2"
+ id="linearGradient9474-9"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ id="linearGradient2190-2">
+ <stop
+ offset="0"
+ style="stop-color:#acbbff;stop-opacity:1"
+ id="stop2192-2" />
+ <stop
+ offset="1"
+ style="stop-color:#acbbff;stop-opacity:0"
+ id="stop2194-7" />
+ </linearGradient>
+ <linearGradient
+ x1="23.878078"
+ y1="18.541262"
+ x2="23.878078"
+ y2="27.495163"
+ id="linearGradient2836"
+ xlink:href="#linearGradient3725-8"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8988874,0,0,0.8934652,-9.4637044,-8.565972)" />
+ <linearGradient
+ id="linearGradient3725-8">
+ <stop
+ id="stop3729-1"
+ style="stop-color:#e35d4f;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3731"
+ style="stop-color:#c6262e;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="27.495163"
+ x2="23.878078"
+ y1="18.541262"
+ x1="23.878078"
+ gradientTransform="matrix(0.8988874,0,0,0.8934652,-241.4637,171.43403)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3013"
+ xlink:href="#linearGradient3725-8"
+ inkscape:collect="always" />
+ <linearGradient
+ x1="24.138529"
+ y1="6.5316639"
+ x2="24.138529"
+ y2="45.690399"
+ id="linearGradient2833"
+ xlink:href="#linearGradient4222"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.5399382,0,0,0.5366811,-214.55602,180.4938)" />
+ <linearGradient
+ id="linearGradient4222">
+ <stop
+ id="stop4224-0"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop4226-3"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="23.878078"
+ y1="18.541262"
+ x2="23.878078"
+ y2="27.495163"
+ id="linearGradient2836-7"
+ xlink:href="#linearGradient3725-5"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8988874,0,0,0.8934652,-9.4637044,-8.565972)" />
+ <linearGradient
+ id="linearGradient3725-5">
+ <stop
+ id="stop3729-2"
+ style="stop-color:#e35d4f;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3731-0"
+ style="stop-color:#c6262e;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="27.495163"
+ x2="23.878078"
+ y1="18.541262"
+ x1="23.878078"
+ gradientTransform="matrix(0.8988874,0,0,0.8934652,-11.463704,-16.560791)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3062-6"
+ xlink:href="#linearGradient3725-5"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3725-5"
+ id="linearGradient7189"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8988874,0,0,0.8934652,-223.1708,172.43403)"
+ x1="23.878078"
+ y1="18.541262"
+ x2="23.878078"
+ y2="27.495163" />
+ <radialGradient
+ id="radialGradient2541"
+ fx="28.603001"
+ gradientUnits="userSpaceOnUse"
+ cy="69"
+ cx="38"
+ gradientTransform="matrix(1,0,0,0.45,0,37.95)"
+ r="20">
+ <stop
+ id="stop6021"
+ offset="0" />
+ <stop
+ id="stop6023"
+ style="stop-opacity:0"
+ offset="1" />
+ </radialGradient>
+ <linearGradient
+ id="linearGradient2543"
+ y2="8"
+ gradientUnits="userSpaceOnUse"
+ x2="26"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="16"
+ x1="28">
+ <stop
+ id="stop5960"
+ style="stop-color:#c17d11"
+ offset="0" />
+ <stop
+ id="stop5962"
+ style="stop-color:#e9b96e"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2545"
+ y2="18"
+ gradientUnits="userSpaceOnUse"
+ x2="34"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="9.2407999"
+ x1="30.325001">
+ <stop
+ id="stop5998"
+ style="stop-color:#8f5902"
+ offset="0" />
+ <stop
+ id="stop6000"
+ style="stop-color:#73521e"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2547"
+ y2="16.686001"
+ gradientUnits="userSpaceOnUse"
+ x2="33.446999"
+ y1="8"
+ x1="28">
+ <stop
+ id="stop5986"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop5988"
+ style="stop-color:#fff;stop-opacity:.13439"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2549"
+ y2="30.743"
+ gradientUnits="userSpaceOnUse"
+ x2="30.208"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="25.061001"
+ x1="20.934">
+ <stop
+ id="stop5968"
+ style="stop-color:#fdef72"
+ offset="0" />
+ <stop
+ id="stop5970"
+ style="stop-color:#e2cb0b"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2551"
+ y2="37.846001"
+ gradientUnits="userSpaceOnUse"
+ x2="29.493999"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="27.447001"
+ x1="17.032">
+ <stop
+ id="stop5989"
+ style="stop-color:#b3a10b"
+ offset="0" />
+ <stop
+ id="stop5991"
+ style="stop-color:#91780a"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2553"
+ y2="37.028999"
+ gradientUnits="userSpaceOnUse"
+ x2="18.986"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="41.956001"
+ x1="22.32">
+ <stop
+ id="stop5983"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop5985"
+ style="stop-color:#fff;stop-opacity:.69412"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2555"
+ y2="34.728001"
+ gradientUnits="userSpaceOnUse"
+ x2="23.489"
+ gradientTransform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)"
+ y1="36.217999"
+ x1="27.355">
+ <stop
+ id="stop6001"
+ style="stop-color:#c4a000"
+ offset="0" />
+ <stop
+ id="stop6003"
+ style="stop-color:#c4a000;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2557"
+ y2="20.618999"
+ gradientUnits="userSpaceOnUse"
+ x2="21.591"
+ gradientTransform="matrix(0.51282,0,0,0.5277,-0.84657,-0.77947)"
+ y1="23.146"
+ x1="27.652">
+ <stop
+ id="stop2473"
+ style="stop-color:#919191"
+ offset="0" />
+ <stop
+ id="stop2475"
+ style="stop-color:#cecece"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3328"
+ y2="11.543"
+ gradientUnits="userSpaceOnUse"
+ x2="15.289"
+ gradientTransform="matrix(0.51378,0,0,-0.52177,-1.8456,25.023)"
+ y1="36.458"
+ x1="15.289">
+ <stop
+ id="stop3924"
+ style="stop-color:#eeeeec"
+ offset="0" />
+ <stop
+ id="stop3926"
+ style="stop-color:#babdb6"
+ offset=".69692" />
+ <stop
+ id="stop3928"
+ style="stop-color:#a1a59b"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3203"
+ y2="44.056"
+ xlink:href="#linearGradient4222-5"
+ gradientUnits="userSpaceOnUse"
+ x2="20.622"
+ gradientTransform="matrix(-0.51406,0,0,0.52227,24.325,-0.034536)"
+ y1="5.2263999"
+ x1="20.622" />
+ <linearGradient
+ id="linearGradient4222-5">
+ <stop
+ id="stop4224-45"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop4226-8"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3199"
+ y2="34.889"
+ xlink:href="#linearGradient4222-5"
+ gradientUnits="userSpaceOnUse"
+ x2="20.622"
+ gradientTransform="matrix(-0.51406,0,0,0.52227,24.325,-0.034536)"
+ y1="15.425"
+ x1="20.622" />
+ <linearGradient
+ id="linearGradient3017-2">
+ <stop
+ id="stop3019"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3021-3"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3201"
+ y2="44.056"
+ xlink:href="#linearGradient4222-5"
+ gradientUnits="userSpaceOnUse"
+ x2="20.622"
+ gradientTransform="matrix(-0.47993,0,0,0.52395,24.153,-0.074909)"
+ y1="5.2263999"
+ x1="20.622" />
+ <linearGradient
+ id="linearGradient3024">
+ <stop
+ id="stop3026"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3028"
+ style="stop-color:#fff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="44.056"
+ x2="20.622"
+ y1="5.2263999"
+ x1="20.622"
+ gradientTransform="matrix(-0.47993,0,0,0.52395,24.153,-0.074909)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3035"
+ xlink:href="#linearGradient4222-5"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.2149522,0,0,0.2369714,30.871779,266.01932)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3989-8"
+ id="linearGradient9477-2"
+ y2="332.36218"
+ x2="152.87143"
+ y1="585.21936"
+ x1="460.01428" />
+ <linearGradient
+ id="linearGradient3989-8">
+ <stop
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1"
+ id="stop3991-9" />
+ <stop
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0"
+ id="stop3993-6" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.3505596,0,0,0.3701598,22.984703,226.27699)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient2190-1"
+ id="linearGradient9474-98"
+ y2="322.8829"
+ x2="131.99297"
+ y1="451.83481"
+ x1="225.2822" />
+ <linearGradient
+ id="linearGradient2190-1">
+ <stop
+ offset="0"
+ style="stop-color:#acbbff;stop-opacity:1"
+ id="stop2192-9" />
+ <stop
+ offset="1"
+ style="stop-color:#acbbff;stop-opacity:0"
+ id="stop2194-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5533"
+ y2="609.51001"
+ gradientUnits="userSpaceOnUse"
+ x2="302.85999"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.77854,15.669)"
+ y1="366.64999"
+ x1="302.85999">
+ <stop
+ id="stop5050-7"
+ style="stop-opacity:0"
+ offset="0" />
+ <stop
+ id="stop5056-19"
+ offset=".5" />
+ <stop
+ id="stop5052-4"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5535"
+ xlink:href="#linearGradient5060-1"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(0.031048,0,0,0.013668,0.78465,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient5060-1">
+ <stop
+ id="stop5062-7"
+ offset="0" />
+ <stop
+ id="stop5064-9"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="radialGradient5537"
+ xlink:href="#linearGradient5060-1"
+ gradientUnits="userSpaceOnUse"
+ cy="486.64999"
+ cx="605.71002"
+ gradientTransform="matrix(-0.031048,0,0,0.013668,23.215,15.669)"
+ r="117.14" />
+ <linearGradient
+ id="linearGradient3043">
+ <stop
+ id="stop3045-0"
+ offset="0" />
+ <stop
+ id="stop3047"
+ style="stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5565"
+ y2="39.924"
+ gradientUnits="userSpaceOnUse"
+ x2="21.780001"
+ gradientTransform="matrix(0.63636,0,0,0.62295,-3.9091,-3.1066)"
+ y1="8.5762997"
+ x1="21.865999">
+ <stop
+ id="stop2783"
+ style="stop-color:#505050"
+ offset="0" />
+ <stop
+ id="stop6301"
+ style="stop-color:#6e6e6e"
+ offset=".13216" />
+ <stop
+ id="stop2785"
+ style="stop-color:#8c8c8c"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5562-5"
+ y2="15.044"
+ gradientUnits="userSpaceOnUse"
+ x2="16.075001"
+ gradientTransform="matrix(0.61291,0,0,0.58621,-3.3226,-2.069)"
+ y1="9.0734997"
+ x1="16.034">
+ <stop
+ id="stop3692"
+ style="stop-color:#fff"
+ offset="0" />
+ <stop
+ id="stop3694"
+ style="stop-color:#fff;stop-opacity:.46875"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5559"
+ y2="40"
+ gradientUnits="userSpaceOnUse"
+ x2="24"
+ gradientTransform="matrix(0.52632,0,0,0.48148,-0.63158,1.7407)"
+ y1="13"
+ x1="24">
+ <stop
+ id="stop6459"
+ style="stop-color:#fff;stop-opacity:.94118"
+ offset="0" />
+ <stop
+ id="stop6461"
+ style="stop-color:#fff;stop-opacity:.70588"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6639"
+ y2="22.839001"
+ xlink:href="#linearGradient6388"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,-1,0,25.121,-26.636)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient6388">
+ <stop
+ id="stop6390"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop6392"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6643"
+ y2="22.839001"
+ xlink:href="#linearGradient6388"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,-1,-1,0,25.121,55.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient3064-1">
+ <stop
+ id="stop3066"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop3068"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6647"
+ y2="22.839001"
+ xlink:href="#linearGradient6388"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,1,1,0,-1.1213,-26.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient3071">
+ <stop
+ id="stop3073"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop3075"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6651"
+ y2="22.839001"
+ xlink:href="#linearGradient6388"
+ gradientUnits="userSpaceOnUse"
+ x2="33.25"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ y1="16.121"
+ x1="39.879002" />
+ <linearGradient
+ id="linearGradient3078">
+ <stop
+ id="stop3080"
+ style="stop-color:#73a300"
+ offset="0" />
+ <stop
+ id="stop3082"
+ style="stop-color:#428300;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ y2="22.839001"
+ x2="33.25"
+ y1="16.121"
+ x1="39.879002"
+ gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3099-5"
+ xlink:href="#linearGradient6388"
+ inkscape:collect="always" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.9999996"
+ inkscape:cx="-9.9597451"
+ inkscape:cy="-165.16747"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ width="16px"
+ height="16px"
+ showgrid="true"
+ inkscape:grid-points="true"
+ gridtolerance="10"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1056"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2410"
+ visible="true"
+ enabled="true"
+ empspacing="5"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="-62"
+ height="24"
+ width="24"
+ id="add_action"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_connection"
+ width="24"
+ height="24"
+ x="-22"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="18"
+ height="24"
+ width="24"
+ id="add_block"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_initial_step"
+ width="24"
+ height="24"
+ x="58"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="98"
+ height="24"
+ width="24"
+ id="add_jump"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_variable"
+ width="24"
+ height="24"
+ x="138"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="178"
+ height="24"
+ width="24"
+ id="add_contact"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_branch"
+ width="24"
+ height="24"
+ x="218"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="258"
+ height="24"
+ width="24"
+ id="add_transition"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_rung"
+ width="24"
+ height="24"
+ x="298"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="338"
+ height="24"
+ width="24"
+ id="select"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_comment"
+ width="24"
+ height="24"
+ x="378"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_step"
+ width="24"
+ height="24"
+ x="-182"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="-142"
+ height="24"
+ width="24"
+ id="add_wire"
+ style="fill:#ff0000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_divergence"
+ width="24"
+ height="24"
+ x="-102"
+ y="91"
+ inkscape:label="#rect3636" />
+ <rect
+ inkscape:label="#rect3636"
+ y="91"
+ x="-222"
+ height="24"
+ width="24"
+ id="add_coil"
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient5203);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect5195"
+ width="565.5"
+ height="25"
+ x="-5"
+ y="-4" />
+ <path
+ style="opacity:1;fill:#09ff00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 124,30 L 124,33 L 127,33 L 127,30 L 124,30 z M 125,31 L 126,31 L 126,32 L 125,32 L 125,31 z"
+ id="rect2530" />
+ <g
+ style="fill:#000000"
+ transform="matrix(-8.1928955e-2,0.9966381,-0.9966381,-8.1928955e-2,146.38388,-97.87885)"
+ id="g5155">
+ <path
+ id="path5157"
+ d="M 128.4375,4.1875 C 127.98404,4.1898596 127.55714,4.3491119 127.21875,4.6875 L 127.90625,5.40625 C 128.22947,5.0830262 128.65236,5.0593917 129.125,5.4375 C 129.59764,5.8156083 130.0625,6.6423611 130.0625,8.03125 L 131.0625,8.03125 C 131.0625,6.4201389 130.52736,5.2781417 129.75,4.65625 C 129.36132,4.3453041 128.89096,4.1851404 128.4375,4.1875 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path5159"
+ d="M 130.5581,9.1981942 L 129.5581,7.4681942 L 131.5581,7.4681942 L 130.5581,9.1981942 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.2pt;marker-start:none;stroke-opacity:1" />
+ </g>
+ <path
+ style="opacity:1;fill:#00b0ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 130.5083,27 C 129.6803,27 129.0083,27.672 129.0083,28.5 C 129.0083,29.328 129.6803,30 130.5083,30 C 131.3363,30 132.0083,29.328 132.0083,28.5 C 132.0083,27.672 131.3363,27 130.5083,27 z M 130.5083,28 C 130.7843,28 131.0083,28.224 131.0083,28.5 C 131.0083,28.776 130.7843,29 130.5083,29 C 130.2323,29 130.0083,28.776 130.0083,28.5 C 130.0083,28.224 130.2323,28 130.5083,28 z"
+ id="path2525" />
+ <g
+ id="g5151"
+ transform="matrix(0.9414634,-0.3371153,0.3371153,0.9414634,5.1193823,62.570324)"
+ style="fill:#000000">
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 128.4375,4.1875 C 127.98404,4.1898596 127.55714,4.3491119 127.21875,4.6875 L 127.90625,5.40625 C 128.22947,5.0830262 128.65236,5.0593917 129.125,5.4375 C 129.59764,5.8156083 130.0625,6.6423611 130.0625,8.03125 L 131.0625,8.03125 C 131.0625,6.4201389 130.52736,5.2781417 129.75,4.65625 C 129.36132,4.3453041 128.89096,4.1851404 128.4375,4.1875 z"
+ id="path2537" />
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.2pt;marker-start:none;stroke-opacity:1"
+ d="M 130.5581,9.1981942 L 129.5581,7.4681942 L 131.5581,7.4681942 L 130.5581,9.1981942 z"
+ id="path5145" />
+ </g>
+ <rect
+ style="opacity:1;fill:#00ffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect2496"
+ width="3"
+ height="3"
+ x="90"
+ y="8" />
+ <path
+ style="opacity:1;fill:#00ffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 69 3 L 69 8 L 73 8 L 73 3 L 69 3 z M 70 4 L 72 4 L 72 7 L 70 7 L 70 4 z "
+ id="rect2491" />
+ <path
+ style="opacity:1;fill:#00ffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 42 8 L 42 14 L 48 14 L 48 8 L 42 8 z M 43 9 L 47 9 L 47 13 L 43 13 L 43 9 z "
+ id="rect2486" />
+ <text
+ xml:space="preserve"
+ style="font-size:5px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ x="8"
+ y="-14"
+ id="text2432"><tspan
+ sodipodi:role="line"
+ id="tspan2434"
+ x="8"
+ y="-14">%% ST IL FBD LD SFC FUNCTION FUNCTIONBLOCK PROJECT TRANSITION ACTION CONFIGURATION RESOURCE DATATYPE DATATYPES PROGRAM TRANSITIONS ACTIONS CONFIGURATIONS RESOURCES GRAPH%%</tspan></text>
+ <text
+ sodipodi:linespacing="100%"
+ id="text2443"
+ y="24"
+ x="102"
+ style="font-size:6.9483304px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ xml:space="preserve"><tspan
+ dx="0 0 -0.72712123 -0.55745977"
+ y="24"
+ x="102"
+ id="tspan2445"
+ sodipodi:role="line">f(x)</tspan></text>
+ <path
+ id="path2456"
+ style="display:inline;font-size:6.94833039999999970px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 106.38947,9.8791853 C 106.02456,10.289712 105.66315,10.606378 105.30527,10.829185 C 104.94736,11.051993 104.62808,11.163396 104.34737,11.163396 C 104.21403,11.163396 104.03158,11.112518 103.80001,11.010765 C 103.65263,11.782694 103.5228,12.616027 103.41052,13.510767 C 103.04561,13.559887 102.60702,13.635325 102.09474,13.737083 C 102.20702,12.775676 102.40176,11.528309 102.67894,9.9949745 C 102.64035,9.9283088 102.57193,9.7598871 102.47369,9.4897109 L 102.12105,9.8791853 L 102,9.8791853 L 102,9.4055001 L 103,8.3686566 C 103.19298,7.4248004 103.34912,6.7423444 103.46842,6.3212872 C 103.58773,5.9002394 103.75438,5.43445 103.96842,4.9239177 C 104.18245,4.4133976 104.31754,4.1204152 104.37369,4.0449697 C 104.42982,3.9695381 104.56579,3.8976083 104.78158,3.82918 C 104.99736,3.7607663 105.22807,3.6958539 105.47369,3.634443 C 105.71929,3.573047 105.85087,3.5423452 105.86842,3.5423377 C 106.00175,3.5423452 106.11053,3.6291873 106.19473,3.8028642 C 106.27894,3.9765556 106.32106,4.205503 106.32106,4.4897069 C 106.32106,4.9809414 106.19123,5.4546254 105.93159,5.9107605 C 105.67193,6.366906 105.1807,6.9283093 104.4579,7.5949725 L 104.01579,9.7897109 C 104.18772,10.158133 104.44561,10.342343 104.78947,10.342343 C 105.28071,10.342343 105.81403,9.9862034 106.38947,9.2739218 L 106.38947,9.8791853 z M 104.62105,6.8791824 C 105.03158,6.5212918 105.35351,6.1362042 105.58684,5.7239183 C 105.82017,5.3116431 105.93685,4.9318185 105.93685,4.5844438 C 105.93685,4.4370818 105.90965,4.3160292 105.85526,4.2212856 C 105.80087,4.1265556 105.73333,4.0791872 105.65263,4.0791802 C 105.49825,4.0791872 105.34122,4.2809416 105.18158,4.6844438 C 105.02194,5.0879589 104.83508,5.8195376 104.62105,6.8791824 L 104.62105,6.8791824 z" />
+ <path
+ style="display:inline;font-size:6.94833039999999970px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 109.08648,6.51458 C 108.47828,7.078571 108.05839,7.6792438 107.82678,8.3166003 C 107.59518,8.9539606 107.47938,9.5502136 107.47938,10.105361 C 107.47938,10.397078 107.51251,10.660065 107.57882,10.894323 C 107.64512,11.12858 107.75164,11.370353 107.89838,11.61964 L 107.62523,11.61964 C 107.34058,11.244825 107.14125,10.900953 107.0272,10.588019 C 106.91317,10.275087 106.85615,9.9533146 106.85615,9.6227006 C 106.85615,9.0268896 107.0113,8.4620192 107.32159,7.9280853 C 107.63187,7.3941562 108.12292,6.9229883 108.79476,6.51458 L 109.08648,6.51458 z"
+ id="path2452" />
+ <path
+ style="display:inline;font-size:6.94833039999999970px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 112.91682,10.078181 C 112.28227,10.705079 111.72799,11.018529 111.25401,11.018529 C 110.80548,11.018529 110.51115,10.759869 110.37099,10.242549 C 110.11615,10.759869 109.77212,11.018529 109.3389,11.018529 C 109.16306,11.018529 109.00251,10.970111 108.85725,10.873272 C 108.712,10.776434 108.63936,10.673224 108.63936,10.563644 C 108.63936,10.512678 108.71327,10.424122 108.86107,10.297977 C 109.00888,10.171833 109.0968,10.108761 109.12483,10.10876 C 109.16561,10.108761 109.20511,10.163551 109.24334,10.27313 C 109.32743,10.502484 109.4472,10.61716 109.60265,10.61716 C 109.77084,10.61716 109.92057,10.489105 110.0518,10.232994 C 110.18304,9.9768826 110.28498,9.6589726 110.35761,9.2792626 C 110.43024,8.8995566 110.46655,8.6651062 110.46655,8.5759107 C 110.46655,8.3032368 110.36717,8.1668987 110.16839,8.166896 C 109.91865,8.1668987 109.58736,8.5173004 109.17453,9.2181016 C 108.95537,9.5901666 108.73366,9.8768586 108.5094,10.078181 L 108.42148,10.078181 L 108.42148,9.7341486 C 108.55654,9.6016336 108.70562,9.4054096 108.86872,9.1454726 C 109.18472,8.6357998 109.47077,8.2643741 109.72689,8.0311948 C 109.983,7.7980214 110.23975,7.6814331 110.49714,7.6814299 C 110.92271,7.6814331 111.17755,7.9298998 111.26165,8.4268305 C 111.38906,8.1694471 111.52221,7.9808672 111.6611,7.8610906 C 111.79999,7.74132 111.96755,7.6814331 112.16377,7.6814299 C 112.35745,7.6814331 112.50398,7.7406828 112.60336,7.8591793 C 112.70275,7.9776819 112.75245,8.0904475 112.75245,8.1974765 C 112.75245,8.2331565 112.68109,8.3191642 112.53839,8.4554998 C 112.39567,8.5918404 112.29629,8.6600095 112.24022,8.6600071 C 112.20455,8.6600095 112.18543,8.6135016 112.18288,8.5204835 C 112.18034,8.4274701 112.15421,8.3376399 112.10452,8.2509924 C 112.05482,8.1643504 111.99048,8.121028 111.91149,8.1210252 C 111.7127,8.121028 111.53942,8.3841477 111.39162,8.9103856 C 111.24381,9.4366266 111.16991,9.8360846 111.16991,10.10876 C 111.16991,10.254018 111.20685,10.371243 111.28077,10.460436 C 111.35466,10.549629 111.45023,10.594225 111.56745,10.594224 C 111.90638,10.594225 112.35617,10.275679 112.91682,9.6385846 L 112.91682,10.078181 z"
+ id="path2450" />
+ <path
+ style="display:inline;font-size:6.94833039999999970px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 113.85069,6.51458 C 114.11766,6.8558049 114.31389,7.1846501 114.43943,7.5011164 C 114.56496,7.8175886 114.62772,8.1570418 114.62772,8.5194768 C 114.62772,9.0905376 114.47656,9.6452426 114.17423,10.183594 C 113.87191,10.721945 113.3751,11.200626 112.68382,11.61964 L 112.38414,11.61964 C 112.68648,11.327921 112.91498,11.081729 113.06969,10.881064 C 113.22439,10.680397 113.37643,10.420946 113.52583,10.10271 C 113.67522,9.7844726 113.79014,9.4481136 113.87058,9.0936306 C 113.95102,8.7391516 113.99124,8.3886477 113.99124,8.0421204 C 113.99124,7.7238855 113.95943,7.4520579 113.89577,7.2266366 C 113.83213,7.0012217 113.7287,6.7638698 113.58549,6.51458 L 113.85069,6.51458 z"
+ id="text2438" />
+ <text
+ xml:space="preserve"
+ style="display:inline;font-size:10.35983276000000000px;font-style:normal;font-weight:normal;fill:#d19f34;fill-opacity:1;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;opacity:1;color:#000000;fill-rule:nonzero;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;visibility:visible;overflow:visible;enable-background:accumulate"
+ x="1.3590535"
+ y="11.606757"
+ id="text3137"
+ transform="scale(0.9794545,1.0209765)"><tspan
+ sodipodi:role="line"
+ id="tspan3139"
+ x="1.3590535"
+ y="11.606757">ST</tspan></text>
+ <path
+ style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible"
+ d="M 43,3 L 43,4 L 42,4 L 42,5 L 43,5 L 43,7 L 47,7 L 47,5 L 48,5 L 48,10 L 47,10 L 47,9 L 43,9 L 43,10 L 42,10 L 42,11 L 43,11 L 43,13 L 47,13 L 47,11 L 49,11 L 49,8 L 50,8 L 50,10 L 54,10 L 54,6 L 50,6 L 50,7 L 49,7 L 49,4 L 47,4 L 47,3 L 43,3 z M 44,4 L 46,4 L 46,6 L 44,6 L 44,4 z M 51,7 L 53,7 L 53,9 L 51,9 L 51,7 z M 44,10 L 46,10 L 46,12 L 44,12 L 44,10 z"
+ id="rect4119" />
+ <path
+ style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible"
+ d="M 65,4 L 65,7 L 64,7 L 64,6 L 63,6 L 63,10 L 64,10 L 64,9 L 65,9 L 65,12 L 64,12 L 64,11 L 63,11 L 63,13 L 62,13 L 62,3 L 63,3 L 63,5 L 64,5 L 64,4 L 65,4 z"
+ id="path4154"
+ sodipodi:nodetypes="ccccccccccccccccccccc" />
+ <path
+ style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible"
+ d="M 66,4 L 66,7 L 67,7 L 67,6 L 68,6 L 68,10 L 67,10 L 67,9 L 66,9 L 66,12 L 67,12 L 67,11 L 69,11 L 69,6 L 69.5625,6 C 69.633907,6.3274464 69.782413,6.649006 70,7 L 71,7 C 70.189404,5.9684327 70.157836,5.0315673 71,4 L 70.125,4 C 69.854381,4.3520909 69.684547,4.6715671 69.59375,5 L 67,5 L 67,4 L 66,4 z"
+ id="path4156" />
+ <path
+ style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible"
+ d="M 74,3 L 74,8 L 73,8 L 73,6 L 72.4375,6 C 72.366093,6.3274464 72.217587,6.649006 72,7 L 71,7 C 71.810596,5.9684327 71.842164,5.0315673 71,4 L 71.875,4 C 72.145619,4.3520909 72.315453,4.6715671 72.40625,5 L 73,5 L 73,3 L 74,3 z"
+ id="path4167"
+ sodipodi:nodetypes="ccccccccccccc" />
+ <path
+ style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;overflow:visible"
+ d="M 82,14 L 87,14 L 87,11 L 85,11 L 85,10 L 86,10 L 86,9 L 85,9 L 85,8 L 91,8 L 91,9 L 90,9 L 90,10 L 91,10 L 91,11 L 89,11 L 89,14 L 94,14 L 94,11 L 92,11 L 92,10 L 93,10 L 93,9 L 92,9 L 92,7 L 88,7 L 88,5 L 90,5 L 90,2 L 85,2 L 85,5 L 87,5 L 87,7 L 84,7 L 84,9 L 83,9 L 83,10 L 84,10 L 84,11 L 82,11 L 82,14 z M 83,13 L 83,12 L 86,12 L 86,13 L 83,13 z M 86,4 L 86,3 L 89,3 L 89,4 L 86,4 z M 90,13 L 90,12 L 93,12 L 93,13 L 90,13 z"
+ id="path4188"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <text
+ xml:space="preserve"
+ style="display:inline;font-size:10.82976246000000000px;font-style:normal;font-weight:normal;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="23.256508"
+ y="11.842409"
+ id="text4114"
+ transform="scale(0.9868675,1.0133073)"><tspan
+ sodipodi:role="line"
+ id="tspan4116"
+ x="23.256508"
+ y="11.842409">IL</tspan></text>
+ <path
+ style="opacity:1;fill:#ff8700;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 126,23 C 124.896,23 124,23.896 124,25 C 124,26.104 124.896,27 126,27 C 127.104,27 128,26.104 128,25 C 128,23.896 127.104,23 126,23 z M 126,24 C 126.552,24 127,24.448 127,25 C 127,25.552 126.552,26 126,26 C 125.448,26 125,25.552 125,25 C 125,24.448 125.448,24 126,24 z"
+ id="path2513" />
+ <g
+ id="g5161"
+ transform="matrix(-0.9663035,-0.2574047,0.2574047,-0.9663035,249.5574,68.565406)"
+ style="fill:#000000">
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 129.125,5.4375 C 129.59764,5.8156083 130.0625,6.6423611 130.0625,8.03125 L 131.0625,8.03125 C 131.0625,6.4201389 130.52736,5.2781417 129.75,4.65625 L 129.125,5.4375 z"
+ id="path5163"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.2pt;marker-start:none;stroke-opacity:1"
+ d="M 130.5581,9.1981942 L 129.5581,7.4681942 L 131.5581,7.4681942 L 130.5581,9.1981942 z"
+ id="path5165" />
+ </g>
+ <g
+ id="g4204">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc"
+ id="rect2479"
+ d="M 123,2 L 123,3 L 122,3 L 122,4 L 123,4 L 123,6 L 122,6 L 122,7 L 123,7 L 123,9 L 122,9 L 122,10 L 123,10 L 123,12 L 122,12 L 122,13 L 123,13 L 123,14 L 133,14 L 133,11 L 134,11 L 134,10 L 133,10 L 133,6 L 134,6 L 134,5 L 133,5 L 133,2 L 123,2 z M 124,3 L 132,3 L 132,13 L 124,13 L 124,3 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726068999999997px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="cccccccccsssssssssccccssssscc"
+ id="path5167"
+ style="font-size:6.9483304px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 125.94198,9.8755094 C 125.80966,10.568221 125.69325,11.316015 125.59253,12.118906 C 125.265,12.162987 124.87137,12.230693 124.41164,12.322 C 124.51258,11.459273 124.68723,10.339933 124.93593,8.9639793 C 124.90136,8.9041561 124.84001,8.7530205 124.75179,8.5105761 L 124.43531,8.8600748 L 124.32675,8.8600748 L 124.32675,8.4350072 L 125.22402,7.5045847 C 125.39724,6.6576045 125.53729,6.0451959 125.64437,5.6673547 C 125.75147,5.2895232 125.90106,4.8715427 126.0932,4.4134113 C 126.28523,3.9552907 126.40646,3.6923784 126.45671,3.624678 C 126.50718,3.5569877 126.62921,3.4924413 126.82278,3.4310365 C 127.01645,3.3696443 127.22348,3.3113945 127.44389,3.2562857 C 127.66419,3.2011916 127.78241,3.1736421 127.7981,3.1736348 C 127.91771,3.1736421 128.01537,3.2515712 128.09094,3.407421 C 128.16643,3.5632857 128.20419,3.7687337 128.20419,4.0237666 C 128.20419,4.464582 128.08777,4.8896471 127.85476,5.2989647 C 127.62168,5.708293 127.18103,6.212073 126.53243,6.8103117 L 126.1357,8.7797841 L 125.94198,9.8755094 z M 126.67884,6.1679889 C 127.04714,5.8468321 127.33614,5.5012691 127.54551,5.1313007 C 127.75482,4.761341 127.85951,4.4204999 127.85951,4.1087806 C 127.85951,3.9765439 127.83516,3.8679156 127.7863,3.7828955 C 127.7374,3.6978895 127.67694,3.655383 127.60442,3.6553757 C 127.46595,3.655383 127.32496,3.836429 127.18177,4.1985165 C 127.03853,4.5606151 126.87073,5.2171058 126.67884,6.1679889 L 126.67884,6.1679889 z" />
+ <path
+ style="font-size:6.9483304px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 128,7 C 127.58288,7.386804 127.2949,7.798766 127.13605,8.235886 C 126.97722,8.673009 126.8978,9.081939 126.8978,9.462678 C 126.8978,9.662747 126.92052,9.843114 126.96601,10.003774 C 127.01147,10.164436 127.08453,10.330252 127.18516,10.501222 L 126.99783,10.501222 C 126.80261,10.244161 126.6659,10.008322 126.58768,9.793702 C 126.50947,9.579081 126.47036,9.3584 126.47036,9.131653 C 126.47036,8.723026 126.57677,8.335619 126.78959,7.969429 C 127.00238,7.603243 127.33915,7.2801 127.79993,7 L 128,7 z"
+ id="path5169" />
+ <path
+ style="font-size:6.9483304px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 130.62698,9.444037 C 130.19178,9.873984 129.81163,10.08896 129.48656,10.08896 C 129.17894,10.08896 128.97709,9.911562 128.88097,9.556767 C 128.70618,9.911562 128.47024,10.08896 128.17311,10.08896 C 128.05251,10.08896 127.94241,10.055753 127.8428,9.989338 C 127.74316,9.922923 127.69336,9.852138 127.69336,9.776984 C 127.69336,9.74203 127.74404,9.681295 127.8454,9.594782 C 127.94677,9.508267 128.00709,9.465011 128.02631,9.465009 C 128.05428,9.465011 128.08135,9.502587 128.10759,9.577739 C 128.16526,9.735039 128.2474,9.813687 128.35401,9.813687 C 128.46935,9.813687 128.57205,9.725863 128.66204,9.550214 C 128.75206,9.374564 128.82197,9.15653 128.8718,8.896112 C 128.92159,8.635697 128.94649,8.474903 128.94649,8.41373 C 128.94649,8.226722 128.87833,8.133216 128.74201,8.133214 C 128.57073,8.133216 128.34352,8.373533 128.06039,8.854166 C 127.91008,9.109341 127.75802,9.305963 127.60422,9.444037 L 127.54392,9.444037 L 127.54392,9.208088 C 127.63655,9.117205 127.73879,8.982628 127.85065,8.804355 C 128.06737,8.454804 128.26357,8.200067 128.43921,8.040146 C 128.61487,7.880227 128.79095,7.800267 128.96749,7.800265 C 129.25935,7.800267 129.43413,7.970674 129.4918,8.311486 C 129.57918,8.134963 129.67051,8.005629 129.76577,7.923482 C 129.86101,7.841339 129.97593,7.800267 130.11051,7.800265 C 130.24335,7.800267 130.34383,7.840902 130.41199,7.922172 C 130.48016,8.003444 130.51425,8.080783 130.51425,8.154188 C 130.51425,8.178657 130.4653,8.237645 130.36743,8.331148 C 130.26956,8.424655 130.2014,8.471407 130.16294,8.471406 C 130.13848,8.471407 130.12536,8.439511 130.12362,8.375716 C 130.12187,8.311925 130.10396,8.250316 130.06988,8.19089 C 130.03579,8.131468 129.99166,8.101756 129.93748,8.101754 C 129.80115,8.101756 129.68231,8.282213 129.58095,8.643124 C 129.47957,9.004038 129.42889,9.278 129.42889,9.465009 C 129.42889,9.564632 129.45421,9.64503 129.50492,9.706201 C 129.55558,9.767372 129.62114,9.797958 129.70153,9.797957 C 129.93398,9.797958 130.24246,9.579488 130.62698,9.142547 L 130.62698,9.444037 z"
+ id="path5171" />
+ <path
+ style="font-size:6.9483304px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;font-family:Script MT Bold;-inkscape-font-specification:'Script MT Bold, Bold'"
+ d="M 131.26747,7 C 131.45055,7.234023 131.58513,7.459556 131.67123,7.6766 C 131.75733,7.893648 131.80037,8.126455 131.80037,8.375026 C 131.80037,8.766678 131.6967,9.147114 131.48935,9.516333 C 131.28201,9.885552 130.94127,10.213849 130.46719,10.501222 L 130.26164,10.501222 C 130.46901,10.301152 130.62573,10.132305 130.73182,9.994681 C 130.83791,9.857057 130.94219,9.679117 131.04466,9.460861 C 131.14712,9.242602 131.22594,9.011917 131.28111,8.768799 C 131.33628,8.525685 131.36386,8.285299 131.36386,8.047638 C 131.36386,7.829382 131.34203,7.642954 131.29838,7.488352 C 131.25474,7.333755 131.1838,7.170971 131.08557,7 L 131.26747,7 z"
+ id="path5173" />
+ </g>
+ <g
+ id="g3262"
+ inkscape:label="Calque 1"
+ transform="translate(18,91)">
+ <rect
+ y="1"
+ x="4"
+ height="23"
+ width="17"
+ id="rect6341"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccc"
+ id="rect4559"
+ d="M 3,1 L 3,8 L 0,8 L 0,9 L 3,9 L 3,14 L 0,14 L 0,15 L 3,15 L 3,20 L 0,20 L 0,21 L 3,21 L 3,24 L 21,24 L 21,15 L 24,15 L 24,14 L 21,14 L 21,9 L 24,9 L 24,8 L 21,8 L 21,1 L 3,1 z M 4,2 L 20,2 L 20,23 L 4,23 L 4,2 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ transform="scale(1.0096698,0.9904228)"
+ sodipodi:linespacing="125%"
+ id="text6441"
+ y="8.0773582"
+ x="6.9329596"
+ style="font-size:6.94388676px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="8.0773582"
+ x="6.9329596"
+ id="tspan6443"
+ sodipodi:role="line">FB</tspan></text>
+ </g>
+ <g
+ id="g3283"
+ inkscape:label="Calque 1"
+ transform="translate(218,92)">
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3285"
+ d="M 15,4 L 15,13 L 16,13 L 16,9 L 18,9 L 18,8 L 16,8 L 16,4 L 15,4 z"
+ style="opacity:1;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path1964"
+ d="M 9,4 L 9,13 L 8,13 L 8,9 L 6,9 L 6,8 L 8,8 L 8,4 L 9,4 z"
+ style="opacity:1;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ id="rect3364"
+ d="M 0,8 L 2,8 L 2,9 L 0,9 L 0,8 z"
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ id="path4252"
+ d="M 3,8 L 5,8 L 5,9 L 3,9 L 3,8 z"
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ id="path4254"
+ d="M 19,8 L 21,8 L 21,9 L 19,9 L 19,8 z"
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ id="path4256"
+ d="M 22,8 L 24,8 L 24,9 L 22,9 L 22,8 z"
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ sodipodi:nodetypes="ccccccccc"
+ id="path4258"
+ d="M 3,9 L 4,9 L 4,20 L 20,20 L 20,9 L 21,9 L 21,21 L 3,21 L 3,9 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -222,106 L -222,107 L -216,107 L -216,106 L -222,106 z"
+ id="path3309"
+ sodipodi:nodetypes="ccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <text
+ xml:space="preserve"
+ style="font-size:6.87738276px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-217.91504"
+ y="98.013641"
+ id="text3311"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan7478"
+ x="-217.91504"
+ y="98.013641">VAR</tspan></text>
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -198,106 L -198,107 L -204,107 L -204,106 L -198,106 z"
+ id="path3314"
+ sodipodi:nodetypes="ccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path1984"
+ sodipodi:cx="7.25"
+ sodipodi:cy="16.5"
+ sodipodi:rx="2.25"
+ sodipodi:ry="4.5"
+ d="M 5.5467128,19.440281 A 2.25,4.5 0 0 1 5.5410496,13.572893"
+ transform="matrix(3.333333,0,0,1.666667,-232.63542,79)"
+ sodipodi:start="2.4295312"
+ sodipodi:end="3.8497933"
+ sodipodi:open="true" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path2879"
+ sodipodi:cx="7.25"
+ sodipodi:cy="16.5"
+ sodipodi:rx="2.25"
+ sodipodi:ry="4.5"
+ d="M 5.5467128,19.440281 A 2.25,4.5 0 0 1 5.5410496,13.572893"
+ transform="matrix(-3.333333,0,0,1.666667,-187.36459,79)"
+ sodipodi:start="2.4295312"
+ sodipodi:end="3.8497933"
+ sodipodi:open="true" />
+ <g
+ id="g3333"
+ inkscape:label="Calque 1"
+ transform="translate(378,91)">
+ <path
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ d="M 0,4 L 18.65625,4 L 24,8.1562504 L 24,21 L 0,21 L 0,4 z"
+ id="rect3335"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 378 95 L 378 112 L 402 112 L 402 98.46875 L 397.40625 95 L 378 95 z M 379 96 L 396 96 L 396 100 L 401 100 L 401 111 L 379 111 L 379 96 z M 397 96 L 401 99 L 397 99 L 397 96 z "
+ transform="translate(-378,-91)"
+ id="path3337" />
+ <text
+ transform="scale(1.0345959,0.9665609)"
+ sodipodi:linespacing="125%"
+ id="text3339"
+ y="18.622726"
+ x="1.425997"
+ style="font-size:6.97013092px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan1988"
+ y="18.622726"
+ x="1.425997"
+ sodipodi:role="line">CMT</tspan></text>
+ </g>
+ <g
+ id="g3359"
+ inkscape:label="Calque 1"
+ transform="translate(-22,90)">
+ <rect
+ y="6"
+ x="8.8817842e-16"
+ height="13"
+ width="21"
+ id="rect3361"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccc"
+ id="path3363"
+ d="M 0,6 L 0,19 L 21,18.955806 L 21,12.955806 L 24,13 L 24,12 L 21,11.955806 L 21,5.955806 L 0,6 z M 1,7 L 20,6.955806 L 20,17.955806 L 1,18 L 1,7 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3365"
+ y="15.104307"
+ x="8.3926907"
+ style="font-size:6.87738276px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="15.104307"
+ x="8.3926907"
+ id="tspan3367"
+ sodipodi:role="line">C</tspan></text>
+ <path
+ sodipodi:nodetypes="ccc"
+ id="path1949"
+ d="M 0.51386409,6.5268353 L 6.4523494,12.574524 L 0.48613591,18.513864"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="ccc"
+ id="path1951"
+ d="M 14.527728,6.5129709 L 20.466214,12.56066 L 14.5,18.5"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g3383"
+ inkscape:label="Calque 1"
+ transform="translate(178,91)">
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3385"
+ d="M 17,9 L 17,22 L 19,22 L 19,16 L 24,16 L 24,15 L 19,15 L 19,9 L 17,9 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3387"
+ y="7.013639"
+ x="3.8336635"
+ style="font-size:6.87738276px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="7.013639"
+ x="3.8336635"
+ id="tspan3389"
+ sodipodi:role="line">VAR</tspan></text>
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3391"
+ d="M 7,9 L 7,22 L 5,22 L 5,16 L 0,16 L 0,15 L 5,15 L 5,9 L 7,9 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -80,95 L -90,95 L -90,91 L -91,91 L -91,95 L -101,95 L -101,101 L -100,101 L -100,97 L -95,97 L -95,101 L -94,101 L -94,97 L -87,97 L -87,101 L -86,101 L -86,97 L -81,97 L -81,101 L -80,101 L -80,95 z"
+ id="path3406"
+ sodipodi:nodetypes="ccccccccccccccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -79,109 L -102,109 L -102,110 L -101,110 L -101,114 L -100,114 L -100,110 L -95,110 L -95,114 L -94,114 L -94,110 L -87,110 L -87,114 L -86,114 L -86,110 L -81,110 L -81,114 L -80,114 L -80,110 L -79,110 L -79,109 z"
+ id="path1921"
+ sodipodi:nodetypes="ccccccccccccccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -79,107 L -90,107 L -90,103 L -91,103 L -91,107 L -102,107 L -102,108 L -79,108 L -79,107 z"
+ id="path1923"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <g
+ id="g3423"
+ inkscape:label="Calque 1"
+ transform="translate(58,90)">
+ <rect
+ y="2"
+ x="1"
+ height="18"
+ width="23"
+ id="rect3425"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 58,91 L 58,111 L 70,111 L 70,115 L 71,115 L 71,111 L 82,111 L 82,91 L 58,91 z M 59,92 L 81,92 L 81,110 L 59,110 L 59,92 z M 60,93 L 60,109 L 80,109 L 80,93 L 60,93 z M 61,94 L 79,94 L 79,108 L 61,108 L 61,94 z"
+ transform="translate(-58,-90)"
+ id="path3427"
+ sodipodi:nodetypes="cccccccccccccccccccccccc" />
+ <text
+ transform="scale(0.9459506,1.0571376)"
+ sodipodi:linespacing="125%"
+ id="text3429"
+ y="12.225685"
+ x="3.270576"
+ style="font-size:5.06194162px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="12.225685"
+ x="3.270576"
+ id="tspan3431"
+ sodipodi:role="line">START</tspan></text>
+ </g>
+ <g
+ id="g3445"
+ inkscape:label="Calque 1"
+ transform="translate(97,92)">
+ <text
+ transform="scale(1.00967,0.990423)"
+ sodipodi:linespacing="125%"
+ id="text3447"
+ y="15.189672"
+ x="14.255835"
+ style="font-size:6.94388819px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="15.189672"
+ x="14.255835"
+ id="tspan3449"
+ sodipodi:role="line">JP</tspan></text>
+ <path
+ sodipodi:nodetypes="cccccccc"
+ id="path1941"
+ d="M 8,4 L 8,11 L 3,8 L 8.5606601,20 L 14,8 L 9,11 L 9,4 L 8,4 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -260,91 L -260,115 L -258,115 L -258,110 L -254,110 L -254,109 L -258,109 L -258,98 L -254,98 L -254,97 L -258,97 L -258,91 L -260,91 z"
+ id="path3469"
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -247,95 L -247,100 L -248,100 L -248,98 L -250,98 L -250,97 L -248,97 L -248,95 L -247,95 z"
+ id="path3471"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -247,107 L -247,112 L -248,112 L -248,110 L -250,110 L -250,109 L -248,109 L -248,107 L -247,107 z"
+ id="path2391"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -243,95 L -243,100 L -242,100 L -242,98 L -240,98 L -240,97 L -242,97 L -242,95 L -243,95 z"
+ id="path2397"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -243,107 L -243,112 L -242,112 L -242,110 L -240,110 L -240,109 L -242,109 L -242,107 L -243,107 z"
+ id="path2403"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -253,97 L -251,97 L -251,98 L -253,98 L -253,97 z"
+ id="path3476" />
+ <path
+ style="fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -253,109 L -251,109 L -251,110 L -253,110 L -253,109 z"
+ id="path2410" />
+ <g
+ id="g3493"
+ inkscape:label="Calque 1"
+ transform="translate(298,91)">
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3495"
+ d="M 0,9 L 0,22 L 2,22 L 2,16 L 6,16 L 6,15 L 2,15 L 2,9 L 0,9 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3497"
+ y="7.013639"
+ x="4.143023"
+ style="font-size:6.87738276px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="7.013639"
+ x="4.143023"
+ id="tspan3499"
+ sodipodi:role="line">VAR</tspan></text>
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3501"
+ d="M 24,9 L 24,22 L 22,22 L 22,16 L 18,16 L 18,15 L 22,15 L 22,9 L 24,9 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ sodipodi:open="true"
+ sodipodi:end="3.8497933"
+ sodipodi:start="2.4295312"
+ transform="matrix(3.333333,0,0,1.666667,-10.63542,-12)"
+ d="M 5.5467128,19.440281 C 4.8199153,17.756163 4.8175085,15.262611 5.5410496,13.572893"
+ sodipodi:ry="4.5"
+ sodipodi:rx="2.25"
+ sodipodi:cy="16.5"
+ sodipodi:cx="7.25"
+ id="path3503"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:open="true"
+ sodipodi:end="3.8497933"
+ sodipodi:start="2.4295312"
+ transform="matrix(-3.333333,0,0,1.666667,34.63541,-12)"
+ d="M 5.5467128,19.440281 C 4.8199153,17.756163 4.8175085,15.262611 5.5410496,13.572893"
+ sodipodi:ry="4.5"
+ sodipodi:rx="2.25"
+ sodipodi:cy="16.5"
+ sodipodi:cx="7.25"
+ id="path3505"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g3524"
+ inkscape:label="Calque 1"
+ transform="matrix(0.9051989,0.2388625,-0.2388625,0.9051989,342.2072,89.218641)"
+ style="opacity:1">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.64947593px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ d="M 2.3984129,1.596563 L 7.6030213,21.307013 L 11.088758,15.523361 L 16.41681,22.265231 C 17.161169,24.105656 21.046941,21.975555 20.144957,21.151993 L 13.836144,13.398555 L 20.067914,11.764736 L 2.3984129,1.596563 z"
+ id="path3771"
+ sodipodi:nodetypes="cccccccc" />
+ </g>
+ <g
+ id="g3539"
+ inkscape:label="Calque 1"
+ transform="translate(-183,90)">
+ <rect
+ y="5"
+ x="3"
+ height="15"
+ width="17"
+ id="rect3541"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccccccccccc"
+ id="path3543"
+ d="M 21,4 L 12,4 L 12,1 L 11,1 L 11,4 L 2,4 L 2,21 L 11,21 L 11,25 L 12,25 L 12,21 L 21,21 L 21,13 L 25,13 L 25,12 L 21,12 L 21,4 z M 20,5 L 20,20 L 3,20 L 3,5 L 20,5 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ transform="scale(0.942584,1.060914)"
+ sodipodi:linespacing="125%"
+ id="text3545"
+ y="13.696054"
+ x="4.9849925"
+ style="font-size:5.08002424px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan1952"
+ y="13.696054"
+ x="4.9849925"
+ sodipodi:role="line">STEP</tspan></text>
+ </g>
+ <g
+ id="g3559"
+ inkscape:label="Calque 1"
+ transform="translate(258,89)">
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccccccccccc"
+ id="path3561"
+ d="M 13,11 L 7,11 L 7,7 L 6,7 L 6,11 L 0,11 L 0,14 L 6,14 L 6,18 L 7,18 L 7,14 L 13,14 L 13,13 L 17,13 L 17,12 L 13,12 L 13,11 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ transform="scale(0.8804132,1.1358303)"
+ sodipodi:linespacing="125%"
+ id="text3563"
+ y="14.086612"
+ x="20.444946"
+ style="font-size:8.45267773px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan3565"
+ y="14.086612"
+ x="20.444946"
+ sodipodi:role="line">T</tspan></text>
+ </g>
+ <g
+ id="g3579"
+ inkscape:label="Calque 1"
+ transform="translate(138,90.044194)">
+ <rect
+ y="6"
+ x="8.8817842e-16"
+ height="13"
+ width="21"
+ id="rect3581"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccc"
+ id="path3583"
+ d="M 0,6 L 0,19 L 21,18.955806 L 21,12.955806 L 24,12.955806 L 24,11.955806 L 21,11.955806 L 21,5.955806 L 0,6 z M 1,7 L 20,6.955806 L 20,17.955806 L 1,18 L 1,7 z"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3585"
+ y="15.013638"
+ x="2.2035112"
+ style="font-size:6.87738276px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="15.013638"
+ x="2.2035112"
+ id="tspan3587"
+ sodipodi:role="line">VAR</tspan></text>
+ </g>
+ <g
+ id="g3600"
+ inkscape:label="Calque 1"
+ transform="translate(-142,91)">
+ <path
+ transform="matrix(1.25,0,0,1.265625,0,9.9375)"
+ d="M 4,2 C 4,3.1045695 3.1045695,4 2,4 0.8954305,4 0,3.1045695 0,2 0,0.8954305 0.8954305,0 2,0 3.1045695,0 4,0.8954305 4,2 z"
+ sodipodi:ry="2"
+ sodipodi:rx="2"
+ sodipodi:cy="2"
+ sodipodi:cx="2"
+ id="path7255"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ sodipodi:type="arc" />
+ <path
+ transform="matrix(1.25,0,0,1.25,19,18.996094)"
+ d="M 4,2 C 4,3.1045695 3.1045695,4 2,4 0.8954305,4 0,3.1045695 0,2 0,0.8954305 0.8954305,0 2,0 3.1045695,0 4,0.8954305 4,2 z"
+ sodipodi:ry="2"
+ sodipodi:rx="2"
+ sodipodi:cy="2"
+ sodipodi:cx="2"
+ id="path8146"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="ccccccccccc"
+ id="path8148"
+ d="M 2,11 L 3,11 L 3,2 L 12,2 L 12,22 L 21,22.03125 L 21,21.03125 L 13,21 L 13,1 L 2,1 L 2,11 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="add_powerrail"
+ width="24"
+ height="24"
+ x="-262"
+ y="91"
+ inkscape:label="#rect3636" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-275"
+ y="81"
+ id="text3638"><tspan
+ sodipodi:role="line"
+ id="tspan3640"
+ x="-275"
+ y="81">%%add_powerrail%%</tspan></text>
+ <text
+ id="text3659"
+ y="88"
+ x="-227"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="88"
+ x="-227"
+ id="tspan3661"
+ sodipodi:role="line">%%add_coil%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-190"
+ y="81"
+ id="text3663"><tspan
+ sodipodi:role="line"
+ id="tspan3665"
+ x="-190"
+ y="81">%%add_step%%</tspan></text>
+ <text
+ id="text3669"
+ y="87"
+ x="-150"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="-150"
+ id="tspan3671"
+ sodipodi:role="line">%%add_wire%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-110"
+ y="81"
+ id="text3675"><tspan
+ sodipodi:role="line"
+ id="tspan3677"
+ x="-110"
+ y="81">%%add_divergence%%</tspan></text>
+ <text
+ id="text3681"
+ y="87"
+ x="-70"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="-70"
+ id="tspan3683"
+ sodipodi:role="line">%%add_action%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="-30"
+ y="81"
+ id="text3687"><tspan
+ sodipodi:role="line"
+ id="tspan3689"
+ x="-30"
+ y="81">%%add_connection%%</tspan></text>
+ <text
+ id="text3693"
+ y="87"
+ x="10"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="10"
+ id="tspan3695"
+ sodipodi:role="line">%%add_block%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="50"
+ y="81"
+ id="text3699"><tspan
+ sodipodi:role="line"
+ id="tspan3701"
+ x="50"
+ y="81">%%add_initial_step%%</tspan></text>
+ <text
+ id="text3705"
+ y="87"
+ x="90"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="90"
+ id="tspan3707"
+ sodipodi:role="line">%%add_jump%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="130"
+ y="81"
+ id="text3711"><tspan
+ sodipodi:role="line"
+ id="tspan3713"
+ x="130"
+ y="81">%%add_variable%%</tspan></text>
+ <text
+ id="text3717"
+ y="87"
+ x="170"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="170"
+ id="tspan3719"
+ sodipodi:role="line">%%add_contact%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="210"
+ y="81"
+ id="text3723"><tspan
+ sodipodi:role="line"
+ id="tspan3725"
+ x="210"
+ y="81">%%add_branch%%</tspan></text>
+ <text
+ id="text3729"
+ y="87"
+ x="250"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="250"
+ id="tspan3731"
+ sodipodi:role="line">%%add_transition%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="290"
+ y="81"
+ id="text3735"><tspan
+ sodipodi:role="line"
+ id="tspan3737"
+ x="290"
+ y="81">%%add_rung%%</tspan></text>
+ <text
+ id="text3741"
+ y="87"
+ x="330"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="330"
+ id="tspan3743"
+ sodipodi:role="line">%%select%%</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="370"
+ y="81"
+ id="text3747"><tspan
+ sodipodi:role="line"
+ id="tspan3749"
+ x="370"
+ y="81">%%add_comment%%</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 22.097087,-12.461048 L 8.8388348,-1.3241161"
+ id="path3792" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 29.168155,-12.637825 L 27.753941,-3.4454365"
+ id="path3794" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 39.244426,-13.344931 L 48.260038,-1.8544462"
+ id="path3796" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 57.629203,-13.875262 L 81.670833,-2.7383297"
+ id="path3798"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 77.958523,-12.991378 L 102.00015,-2.561553"
+ id="path3800"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 115.2584,-12.637825 L 126.04179,-2.3847763"
+ id="path3802"
+ sodipodi:nodetypes="cc" />
+ <g
+ id="g3824">
+ <path
+ style="opacity:1;fill:url(#linearGradient3808);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M -40,0 L -40,16 L -24,16 L -24,0 L -40,0 z M -39,1 L -25,1 L -25,15 L -39,15 L -39,1 z"
+ id="path3806"
+ sodipodi:nodetypes="cccccccccc"
+ inkscape:label="#rect2160" />
+ </g>
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="ST"
+ transform="translate(40,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3827" />
+ <use
+ height="16"
+ width="16"
+ transform="translate(60,0)"
+ id="IL"
+ xlink:href="#g3824"
+ y="0"
+ x="0"
+ inkscape:label="#use3829" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="FBD"
+ transform="translate(80,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3831" />
+ <use
+ height="16"
+ width="16"
+ transform="translate(100,0)"
+ id="LD"
+ xlink:href="#g3824"
+ y="0"
+ x="0"
+ inkscape:label="#use3833" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="SFC"
+ transform="translate(120,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3835" />
+ <use
+ height="16"
+ width="16"
+ transform="translate(140,0)"
+ id="FUNCTION"
+ xlink:href="#g3824"
+ y="0"
+ x="0"
+ inkscape:label="#use3837" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="FUNCTIONBLOCK"
+ transform="translate(160,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path3841"
+ d="M 48.967145,-12.991379 L 67.351921,-2.0312229"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(180,0)"
+ id="PROJECT"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="TRANSITION"
+ transform="translate(208,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(234,0)"
+ id="ACTION"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(266,0)"
+ id="CONFIGURATION"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="RESOURCE"
+ transform="translate(302,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(328,0)"
+ id="DATATYPE"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="DATATYPES"
+ transform="translate(358,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(388,0)"
+ id="PROGRAM"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="TRANSITIONS"
+ transform="translate(420,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(450,0)"
+ id="ACTIONS"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="CONFIGURATIONS"
+ transform="translate(480,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(520,0)"
+ id="RESOURCES"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="GRAPH"
+ transform="translate(544.5,0)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ id="g4352">
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 179,6 L 175,6 L 175,2 L 174,2 L 174,6 L 170,6 L 170,9 L 174,9 L 174,13 L 175,13 L 175,9 L 179,9 L 179,8 L 182,8 L 182,7 L 179,7 L 179,6 z"
+ id="path3891"
+ sodipodi:nodetypes="ccccccccccccccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ id="path3897"
+ d="M 179,2 L 179,3 L 180,3 L 180,5 L 181,5 L 181,3 L 182,3 L 182,2 L 179,2 z"
+ style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:7.47265005px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial Bold"
+ x="91.967514"
+ y="112.92294"
+ id="textg"
+ sodipodi:linespacing="125%"
+ transform="matrix(0.7071068,-0.7071068,0.7071068,0.7071068,0,0)"
+ inkscape:label="#text3904"><tspan
+ sodipodi:role="line"
+ id="tspan3906"
+ x="91.967514"
+ y="112.92294">PLC</tspan></text>
+ <g
+ id="g4346">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 198,3 L 198,7 L 195,7 L 195,8 L 198,8 L 198,12 L 209,12 L 209,11 L 205,11 L 205,4 L 209,4 L 209,3 L 198,3 z M 199,4 L 204,4 L 204,11 L 199,11 L 199,4 z"
+ id="path3910" />
+ <path
+ id="text3912"
+ d="M 207.375,5.0625 L 206.03125,9 L 206.96875,9 L 207.21875,8.28125 L 208.6875,8.28125 L 208.90625,9 L 209,9 L 209,6.5 L 208.5,5.0625 L 207.375,5.0625 z M 207.9375,6 L 208.4375,7.5625 L 207.4375,7.5625 L 207.9375,6 z"
+ style="font-size:5.20968056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" />
+ <text
+ transform="scale(0.9148123,1.0931204)"
+ sodipodi:linespacing="125%"
+ id="text3916"
+ y="8.2332983"
+ x="218.16463"
+ style="font-size:5.01952219px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan3918"
+ y="8.2332983"
+ x="218.16463"
+ sodipodi:role="line">N</tspan></text>
+ </g>
+ <g
+ id="g4360">
+ <path
+ id="rect3960"
+ d="M 227 1 L 227 11 L 241 11 L 241 1 L 227 1 z M 228 2 L 240 2 L 240 10 L 228 10 L 228 2 z "
+ style="opacity:1;fill:#00ffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ id="rect3951"
+ d="M 228 2 L 228 10 L 233 10 L 233 13 L 227 13 L 227 14 L 241 14 L 241 13 L 234 13 L 234 10 L 240 10 L 240 2 L 228 2 z M 229 3 L 239 3 L 239 9 L 229 9 L 229 3 z M 230 4 L 230 8 L 234 8 L 234 4 L 230 4 z M 235 4 L 235 8 L 238 8 L 238 4 L 235 4 z M 231 5 L 233 5 L 233 7 L 231 7 L 231 5 z M 236 5 L 237 5 L 237 7 L 236 7 L 236 5 z "
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <g
+ id="g4356">
+ <rect
+ y="3"
+ x="265"
+ height="6"
+ width="10"
+ id="rect3979"
+ style="opacity:1;fill:#00ffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 264,2 L 264,10 L 269,10 L 269,13 L 263,13 L 263,14 L 277,14 L 277,13 L 270,13 L 270,10 L 276,10 L 276,2 L 264,2 z M 265,3 L 275,3 L 275,9 L 265,9 L 265,3 z M 266,4 L 266,8 L 270,8 L 270,4 L 266,4 z M 271,4 L 271,8 L 274,8 L 274,4 L 271,4 z M 267,5 L 269,5 L 269,7 L 267,7 L 267,5 z M 272,5 L 273,5 L 273,7 L 272,7 L 272,5 z"
+ id="path3977" />
+ </g>
+ <g
+ id="g4152"
+ transform="translate(-256.5,-44)">
+ <path
+ style="opacity:1;fill:url(#linearGradient4156);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ id="path20463" />
+ <path
+ id="path27225"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ style="opacity:1;fill:url(#linearGradient4158);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4194"
+ id="use4202"
+ transform="translate(30,0)"
+ width="16"
+ height="16" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4152"
+ id="use4160"
+ transform="translate(358,0)"
+ width="16"
+ height="16" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4352"
+ id="use4364"
+ transform="translate(212,0)"
+ width="16"
+ height="16" />
+ <use
+ height="16"
+ width="16"
+ transform="translate(420,0)"
+ id="use4162"
+ xlink:href="#g4152"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4346"
+ id="use4366"
+ transform="translate(216,0)"
+ width="16"
+ height="16" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4152"
+ id="use4164"
+ transform="translate(450,0)"
+ width="16"
+ height="16" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4360"
+ id="use4368"
+ transform="translate(214,0)"
+ width="16"
+ height="16" />
+ <use
+ height="16"
+ width="16"
+ transform="translate(480,0)"
+ id="use4166"
+ xlink:href="#g4152"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4356"
+ id="use4370"
+ transform="translate(218,0)"
+ width="16"
+ height="16" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4152"
+ id="use4168"
+ transform="translate(520,0)"
+ width="16"
+ height="16" />
+ <g
+ id="g4194">
+ <text
+ id="text4170"
+ y="12"
+ x="295"
+ style="font-size:13.0971384px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="12"
+ x="295"
+ id="tspan4172"
+ sodipodi:role="line">}</tspan></text>
+ <rect
+ style="opacity:1;fill:#44aa00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect4176"
+ width="6"
+ height="1"
+ x="290"
+ y="3" />
+ <rect
+ y="5"
+ x="290"
+ height="1"
+ width="6"
+ id="rect4178"
+ style="opacity:1;fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <rect
+ style="opacity:1;fill:#5599ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect4180"
+ width="6"
+ height="1"
+ x="290"
+ y="7" />
+ <rect
+ y="9"
+ x="290"
+ height="4"
+ width="6"
+ id="rect4182"
+ style="opacity:1;fill:url(#linearGradient4190);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <g
+ id="g4335">
+ <use
+ height="16"
+ width="16"
+ transform="translate(228,0)"
+ id="use4211"
+ xlink:href="#g4204"
+ y="0"
+ x="0" />
+ <g
+ style="opacity:0.89263802"
+ transform="matrix(0,0.75,1,0,305,-157.375)"
+ id="g4317">
+ <path
+ id="path4319"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ style="opacity:1;fill:url(#linearGradient4323);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="opacity:1;fill:url(#linearGradient4325);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ id="path4321" />
+ </g>
+ <g
+ style="opacity:0.89263802"
+ id="g4299"
+ transform="matrix(0,0.75,1,0,299,-163.375)">
+ <path
+ style="opacity:1;fill:url(#linearGradient4305);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ id="path4301" />
+ <path
+ id="path4303"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ style="opacity:1;fill:url(#linearGradient4307);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ id="g4430">
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4419"
+ width="19"
+ height="15"
+ x="-58"
+ y="95" />
+ <path
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccc"
+ id="path3624"
+ d="M -59,94 L -59,98 L -62,98 L -62,99 L -59,99 L -59,111 L -38,111 L -38,94 L -59,94 z M -58,95 L -53,95 L -53,102 L -58,102 L -58,95 z M -52,95 L -39,95 L -39,102 L -52,102 L -52,95 z M -58,103 L -53,103 L -53,110 L -58,110 L -58,103 z M -52,103 L -39,103 L -39,110 L -52,110 L -52,103 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;marker:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <text
+ transform="scale(0.9666368,1.0345148)"
+ sodipodi:linespacing="125%"
+ id="text3626"
+ y="96.663689"
+ x="-52.761501"
+ style="font-size:5.20968056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan3628"
+ y="96.663689"
+ x="-52.761501"
+ sodipodi:role="line">ACT</tspan></text>
+ <text
+ transform="scale(0.9148123,1.0931204)"
+ sodipodi:linespacing="125%"
+ id="text2015"
+ y="91.481232"
+ x="-62.768635"
+ style="font-size:5.01952219px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan2017"
+ y="91.481232"
+ x="-62.768635"
+ sodipodi:role="line">N</tspan></text>
+ <text
+ transform="scale(0.9930776,1.0069706)"
+ sodipodi:linespacing="125%"
+ id="text2019"
+ y="107.17803"
+ x="-57.774296"
+ style="font-size:5.25197458px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan2023"
+ y="107.17803"
+ x="-57.774296"
+ sodipodi:role="line">S</tspan></text>
+ <text
+ transform="scale(0.934197,1.070438)"
+ sodipodi:linespacing="125%"
+ id="text2027"
+ y="100.89328"
+ x="-54.591038"
+ style="font-size:5.12588978px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan2029"
+ y="100.89328"
+ x="-54.591038"
+ sodipodi:role="line">VAR</tspan></text>
+ </g>
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="VAR_INPUT"
+ transform="translate(88,-50)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ id="g3105"
+ transform="translate(-300,-50)">
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4204"
+ id="use3107"
+ transform="translate(228,0)"
+ width="16"
+ height="16"
+ style="opacity:0.25" />
+ <g
+ id="g3109"
+ transform="matrix(0,0.75,1,0,305,-157.375)"
+ style="opacity:0.89263802" />
+ <g
+ transform="matrix(0,0.75,1,0,299,-163.375)"
+ id="g3115"
+ style="opacity:0.89263802">
+ <path
+ id="path3117"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ style="opacity:1;fill:url(#linearGradient3125);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="opacity:1;fill:url(#linearGradient3127);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ id="path3119" />
+ </g>
+ </g>
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(128,-50)"
+ id="VAR_OUTPUT"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <g
+ id="g3131"
+ transform="translate(-260,-50)">
+ <use
+ height="16"
+ width="16"
+ transform="translate(228,0)"
+ id="use3133"
+ xlink:href="#g4204"
+ y="0"
+ x="0"
+ style="opacity:0.25" />
+ <g
+ style="opacity:0.89263802"
+ transform="matrix(0,0.75,1,0,305,-157.375)"
+ id="g3135">
+ <path
+ id="path3137"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ style="opacity:1;fill:url(#linearGradient3147);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="opacity:1;fill:url(#linearGradient3149);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ id="path3139" />
+ </g>
+ <g
+ style="opacity:0.89263802"
+ id="g3141"
+ transform="matrix(0,0.75,1,0,299,-163.375)" />
+ </g>
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="VAR_INOUT"
+ transform="translate(168,-50)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ id="g3157"
+ transform="translate(-220,-50)">
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g4204"
+ id="use3159"
+ transform="translate(228,0)"
+ width="16"
+ height="16"
+ style="opacity:0.25" />
+ <g
+ id="g3161"
+ transform="matrix(0,0.75,1,0,305,-157.375)"
+ style="opacity:0.89263802">
+ <path
+ style="opacity:1;fill:url(#linearGradient3173);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ id="path3163" />
+ <path
+ id="path3165"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ style="opacity:1;fill:url(#linearGradient3175);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <g
+ transform="matrix(0,0.75,1,0,299,-163.375)"
+ id="g3167"
+ style="opacity:0.89263802">
+ <path
+ id="path3169"
+ d="M 227.5,60 L 232.5,55 L 229.5,55 L 229.5,48 L 225.5,48 L 225.5,55 L 222.5,55 L 227.5,60 z"
+ style="opacity:1;fill:url(#linearGradient3177);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="opacity:1;fill:url(#linearGradient3179);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 226.5,59 L 231.5,54 L 228.5,54 L 228.5,47 L 224.5,47 L 224.5,54 L 221.5,54 L 226.5,59 z"
+ id="path3171" />
+ </g>
+ </g>
+ <use
+ inkscape:label="#use3839"
+ height="16"
+ width="16"
+ transform="translate(48,-50)"
+ id="VAR_LOCAL"
+ xlink:href="#g3824"
+ y="0"
+ x="0" />
+ <g
+ transform="translate(-340,-50)"
+ id="g3971">
+ <use
+ style="opacity:0.25"
+ height="16"
+ width="16"
+ transform="translate(228,0)"
+ id="use3973"
+ xlink:href="#g4204"
+ y="0"
+ x="0" />
+ <g
+ style="opacity:1"
+ id="g3982"
+ transform="matrix(0,0.75,1,0,299,-163.375)">
+ <path
+ id="path3986"
+ d="M 234.5,58 L 229.5,54 L 224.5,58 L 229.5,62 L 234.5,58 z"
+ style="opacity:1;fill:#7f7f7f;fill-opacity:0.56725147;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ style="opacity:1;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 233.5,57 L 228.5,53 L 223.5,57 L 228.5,61 L 233.5,57 z"
+ id="path3996" />
+ </g>
+ </g>
+ <text
+ id="text4026"
+ y="-54"
+ x="8"
+ style="font-size:5px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="-54"
+ x="8"
+ id="tspan4028"
+ sodipodi:role="line">%% VAR_LOCAL VAR_INPUT VAR_OUTPUT VAR_INOUT %%</tspan></text>
+ <text
+ id="text4026-5"
+ y="-54"
+ x="210"
+ style="font-size:5px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="-54"
+ x="210"
+ id="tspan4028-7"
+ sodipodi:role="line">%% COMMENT BLOCK IO_VARIABLE CONNECTOR CONTACT COIL STEP JUMP %%</tspan></text>
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="COMMENT"
+ transform="translate(265,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="BLOCK"
+ transform="translate(289,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ style="display:inline"
+ id="g3333-5"
+ inkscape:label="Calque 1"
+ transform="translate(224,-54)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+ d="m 3,8 7.65625,0 4.28309,4.15625 0,7.816022 L 3,20 z"
+ id="rect3335-3"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible"
+ d="m 381,99 0,12 12,0 0,-8.44286 L 389.40625,99 z m 1,1 6,0 0,4 4,0 0,6 -10,0 z m 7,0 3,3 -3,0 z"
+ transform="translate(-378,-91)"
+ id="path3337-9"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+ <text
+ transform="scale(1.0345959,0.96656095)"
+ sodipodi:linespacing="125%"
+ id="text3339-4"
+ y="17.998188"
+ x="4.4307899"
+ style="font-size:3.46388555px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan1988-1"
+ y="17.998188"
+ x="4.4307899"
+ sodipodi:role="line">CMT</tspan></text>
+ </g>
+ <g
+ transform="translate(129,-48)"
+ style="display:inline"
+ id="g4204-0">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc"
+ id="rect2479-6"
+ d="m 123,2 0,1 -1,0 0,1 1,0 0,2 -1,0 0,1 1,0 0,2 -1,0 0,1 1,0 0,2 -1,0 0,1 1,0 0,1 10,0 0,-3 1,0 0,-1 -1,0 0,-4 1,0 0,-1 -1,0 0,-3 -10,0 z m 1,1 8,0 0,10 -8,0 0,-10 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.33726069px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ transform="scale(1.0096698,0.9904228)"
+ sodipodi:linespacing="125%"
+ id="text6441-8"
+ y="8.1013546"
+ x="123.80813"
+ style="font-size:4.17388439px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="8.1013546"
+ x="123.80813"
+ id="tspan6443-7"
+ sodipodi:role="line">FB</tspan></text>
+ </g>
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="IO_VARIABLE"
+ transform="translate(316,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ style="display:inline"
+ id="g3579-7"
+ inkscape:label="Calque 1"
+ transform="translate(269,-52.95581)">
+ <rect
+ y="7.9558101"
+ x="9"
+ height="10"
+ width="10"
+ id="rect3581-3"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+ ry="0" />
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccc"
+ id="path3583-9"
+ d="m 9,8.95581 0,8 10,0 0,-4 2,0 0,-1 -2,0 0,-3 z m 1,1 8,-4e-6 0,6 -8,4e-6 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3585-0"
+ y="13.95581"
+ x="11"
+ style="font-size:2.67459083px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="13.95581"
+ x="11"
+ id="tspan3587-7"
+ sodipodi:role="line">VAR</tspan></text>
+ </g>
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="CONNECTOR"
+ transform="translate(348,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <g
+ style="display:inline"
+ id="g3579-7-2"
+ inkscape:label="Calque 1"
+ transform="translate(301,-52.95581)">
+ <rect
+ y="7.9558101"
+ x="9"
+ height="10"
+ width="10"
+ id="rect3581-3-2"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+ ry="0" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc"
+ id="path1951-4"
+ d="M 15,9.3879711 18.35476,12.95581 15,16.5"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc"
+ id="path1949-6"
+ d="M 9.4649098,9.5580851 12.55965,12.95581 9.5309398,16.357614"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3365-3"
+ y="13.95581"
+ x="13.46875"
+ style="font-size:2.69470572px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="13.95581"
+ x="13.46875"
+ id="tspan3367-9"
+ sodipodi:role="line">C</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccc"
+ id="path3583-9-4"
+ d="m 9,8.95581 0,8 10,0 0,-4 2,0 0,-1 -2,0 0,-3 z m 1,1 8,-4e-6 0,6 -8,4e-6 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <g
+ style="display:inline"
+ id="g3383-5"
+ inkscape:label="Calque 1"
+ transform="translate(335,-49)">
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3385-2"
+ d="m 10,6 0,9 2,0 0,-4 2,0 0,-1 -2,0 0,-4 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3387-1"
+ y="5"
+ x="5"
+ style="font-size:2.80351019px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="5"
+ x="5"
+ id="tspan3389-3"
+ sodipodi:role="line">VAR</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="ccccccccc"
+ id="path3391-2"
+ d="m 6,6 0,9 -2,0 0,-4 -2,0 0,-1 2,0 0,-4 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="CONTACT"
+ transform="translate(375,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="COIL"
+ transform="translate(393,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="STEP"
+ transform="translate(411,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#g3824"
+ id="JUMP"
+ transform="translate(429,-48)"
+ width="16"
+ height="16"
+ inkscape:label="#use3839" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible"
+ d="m 354.98855,-38.999999 0,1 3,-10e-7 0,-1 z"
+ id="path3309-2"
+ sodipodi:nodetypes="ccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible"
+ d="m 366.98855,-39 0,1 -3,-10e-7 0,-1 z"
+ id="path3314-4"
+ sodipodi:nodetypes="ccccc"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+ id="path1984-0"
+ sodipodi:cx="7.25"
+ sodipodi:cy="16.5"
+ sodipodi:rx="2.25"
+ sodipodi:ry="3.6750026"
+ d="m 5.3391583,18.440291 a 2.25,3.6750026 0 0 1 -0.00478,-3.867976"
+ transform="matrix(3.333333,0,0,1.666667,341.35313,-65.999999)"
+ sodipodi:start="2.5853841"
+ sodipodi:end="3.6937671"
+ sodipodi:open="true" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3387-1-9"
+ y="-44"
+ x="357.98856"
+ style="font-size:2.80351019px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="-44"
+ x="357.98856"
+ id="tspan3389-3-6"
+ sodipodi:role="line">VAR</tspan></text>
+ <path
+ sodipodi:open="true"
+ sodipodi:end="3.6937671"
+ sodipodi:start="2.5853841"
+ transform="matrix(-3.333333,0,0,1.666667,380.75449,-65.999999)"
+ d="m 5.3391583,18.440291 a 2.25,3.6750026 0 0 1 -0.00478,-3.867976"
+ sodipodi:ry="3.6750026"
+ sodipodi:rx="2.25"
+ sodipodi:cy="16.5"
+ sodipodi:cx="7.25"
+ id="path3855"
+ style="fill:none;stroke:#000000;stroke-width:0.84852809;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+ sodipodi:type="arc" />
+ <g
+ style="display:inline"
+ id="g3539-4"
+ inkscape:label="Calque 1"
+ transform="translate(364,-53)">
+ <rect
+ y="9"
+ x="10"
+ height="8"
+ width="9"
+ id="rect3541-3"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="90"
+ inkscape:export-xdpi="90"
+ sodipodi:nodetypes="cccccccccccccccccccccc"
+ id="path3543-6"
+ d="m 19,9 -4,0 0,-2 -1,0 0,2 -4,0 0,8 4,0 0,2 1,0 0,-2 4,0 0,-4 2,0 0,-1 -2,0 z m -1,1 0,6 -7,0 0,-6 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible" />
+ <text
+ transform="scale(0.94258374,1.0609137)"
+ sodipodi:linespacing="125%"
+ id="text3545-3"
+ y="13.081616"
+ x="12.051801"
+ style="font-size:2.41527915px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ id="tspan1952-7"
+ y="13.081616"
+ x="12.051801"
+ sodipodi:role="line">STEP</tspan></text>
+ </g>
+ <g
+ style="display:inline"
+ id="g3445-5"
+ inkscape:label="Calque 1"
+ transform="translate(383,-51)">
+ <text
+ transform="scale(1.0096698,0.99042281)"
+ sodipodi:linespacing="125%"
+ id="text3447-6"
+ y="11.106368"
+ x="15.846765"
+ style="font-size:4.09374714px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="11.106368"
+ x="15.846765"
+ id="tspan3449-5"
+ sodipodi:role="line">JP</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc"
+ id="path1941-7"
+ d="M 11,7 11,11 8,9 11.516466,17 15,9 12,11 12,7 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ </g>
+ <rect
+ style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="move"
+ width="24"
+ height="24"
+ x="420"
+ y="92"
+ inkscape:label="#rect3636" />
+ <text
+ id="text3741-2"
+ y="87"
+ x="416"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="87"
+ x="416"
+ id="tspan3743-5"
+ sodipodi:role="line">%%move%%</tspan></text>
+ <path
+ id="rect4019-0"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.04667795;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ d="m 422.51945,108.09999 0,-3.69999 c 0,-1.10799 0.58266,-2 1.69064,-2 1.108,0 1.30076,0.58603 1.30076,0.95332 l 0,4.94668 c 0,-4.53334 0,-5.76667 0,-10.3 0,-1.108 1.00248,-1.999999 2.11049,-1.999999 1.108,0 1.90765,0.892006 1.9116,1.999999 l 0,6.19293 0,-7.79293 c 0,-1.107998 0.9804,-1.999999 2.0884,-1.999999 1.108,0 1.9116,0.957418 1.9116,2.065417 0,0 0,8.704682 0,7.596682 l 0,-6.0621 c 0,-1.108 0.9804,-1.999999 2.0884,-1.999999 1.108,0 1.91162,0.891999 1.91162,1.999999 0,0 0,8.14719 0,7.03918 l 0,-3.72334 c 0,-1.10799 0.98038,-1.999996 2.08838,-1.999996 1.10802,0 1.91161,0.892006 1.91161,1.999996 l 0,4.68416 c 0,0 0,3.59999 -0.79999,6 -4.82135,2 -11.28619,2 -16.12285,0 -1.02363,-0.38443 -2.09065,-1.59999 -2.09066,-3.90001 z"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csssccsscsscssscsssscccc" />
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-265"
+ height="24"
+ width="24"
+ id="new"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;overflow:visible;enable-background:accumulate;opacity:0" />
+ <g
+ id="g4362"
+ transform="translate(-265.97486,138.99141)">
+ <rect
+ x="3.5"
+ y="22"
+ width="17"
+ height="2"
+ style="opacity:0.15;fill:url(#linearGradient2447)"
+ id="rect2879" />
+ <path
+ d="m 3.5,22 v 1.9999 c -0.6205,0.004 -1.5,-0.448 -1.5,-1 0,-0.552 0.6924,-1 1.5,-1 z"
+ style="opacity:0.15;fill:url(#radialGradient2444)"
+ id="path2881"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 20.5,22 v 1.9999 c 0.62047,0.0038 1.5,-0.44807 1.5,-1.0001 0,-0.552 -0.6924,-0.99982 -1.5,-0.99982 z"
+ style="opacity:0.15;fill:url(#radialGradient2441)"
+ id="path2883"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 3.5,0.49996 H 15 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 3.9e-5,11.271 3.9e-5,17 h -17 v -22 z"
+ style="fill:url(#linearGradient2435);stroke:url(#linearGradient2438);stroke-width:0.99992001;stroke-linejoin:round"
+ id="path4160"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 4.1702,22 C 4.0764,22 4,21.914 4,21.809 V 1.211 C 4,1.106 4.0764,1.0205 4.1702,1.0205 7.6917,1.0732 11.594,0.94167 15.1112,1.0336 l 4.839,4.3272 0.05,16.448 c 0,0.105 -0.076,0.191 -0.17,0.191 h -15.66 z"
+ style="fill:url(#radialGradient2432)"
+ id="path4191"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19.5,5.677 V 21.5 h -15 v -20 h 10.394"
+ style="opacity:0.6;fill:none;stroke:url(#linearGradient2429)"
+ id="path2435"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 14.075,1 c 1.1563,0.32877 0.33906,4.6144 0.33906,4.6144 0,0 4.5154,-0.42774 5.6077,1.195 1.489,2.2122 -0.068,-0.6352 -0.173,-0.8217 -0.756,-1.3401 -3.867,-4.5471 -5.046,-4.9412 C 14.71476,1.017 14.51976,1 14.07476,1 z"
+ style="opacity:0.2;fill-rule:evenodd"
+ id="path3370"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 14,1 c 1.5262,0 1,4 1,4 0,0 4.9921,-0.45326 4.9921,2 0,-0.59774 0.05575,-1.4784 -0.06407,-1.6559 -0.839,-1.243 -3.744,-3.8619 -4.798,-4.2976 C 15.04403,1.0109 14.44403,1 14.00003,1 z"
+ style="fill:url(#linearGradient2425);fill-rule:evenodd"
+ id="path4474"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 16.489,16.507 v -3 h 4 v 3 h 3 v 4 h -3 v 3 h -4 v -3 h -2.9895 v -4 h 2.9895 z"
+ style="color:#000000;fill:url(#radialGradient3121);stroke:url(#linearGradient3123);stroke-width:0.98543;stroke-linecap:round;stroke-linejoin:round"
+ id="path2262"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 17.489,17.507 v -3 h 2 v 3 h 3 v 2 h -3 v 3 h -2 v -3 h -3 v -2 h 3 z"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient5530)"
+ id="path2272"
+ inkscape:connector-curvature="0" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-266"
+ y="133"
+ id="text3638-3"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1"
+ x="-266"
+ y="133">%%new%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-231"
+ height="24"
+ width="24"
+ id="open"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <g
+ id="layer1-3"
+ transform="translate(-231.01164,138.33944)">
+ <g
+ id="g3350">
+ <g
+ transform="matrix(0.54593,0,0,0.51685,-0.96573,-0.57818)"
+ style="stroke-width:1.88259995"
+ id="g3490">
+ <g
+ transform="matrix(0.021652,0,0,0.014857,43.008,42.685)"
+ style="stroke-width:104.95999908"
+ id="g5022">
+ <rect
+ x="-1559.3"
+ y="-150.7"
+ width="1339.6"
+ height="478.35999"
+ style="opacity:0.40206;fill:url(#linearGradient2617)"
+ id="rect2527" />
+ <path
+ d="m -219.62,-150.68 v 478.33 c 142.88,0.9 345.4,-107.17 345.4,-239.2 0,-132.02 -159.44,-239.13 -345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient2619)"
+ id="path2529"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -1559.3,-150.68 v 478.33 c -142.8,0.9 -345.4,-107.17 -345.4,-239.2 0,-132.02 159.5,-239.13 345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient2621)"
+ id="path2531"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <path
+ d="m 0.71358,2.5695 c -0.08553,0 -0.15886,0.0927 -0.15886,0.1885 0,5.8692 -0.04308,12.244 -0.04915,18.225 0.02909,0.895 0.53723,1.505 0.88963,1.508 1.0128,0.009 0.5393,-0.004 1.0486,0 6.4703,-0.016 13.579,-0.078 20.049,-0.094 0.054,0.007 -1.477,-0.108 -1.462,-1.446 0,-4.673 -0.502,-11.187 -0.502,-15.86 0,-0.1865 -0.015,-0.2905 -0.031,-0.3767 -0.013,-0.0666 -0.029,-0.0989 -0.064,-0.1257 -0.028,-0.0244 -0.054,-0.057 -0.095,-0.0628 h -8.82 c -0.816,0 -1.002,-1.992 -2.2141,-1.992 L 0.7134,2.5695 h -2e-5 z"
+ style="fill:url(#linearGradient3019);stroke:url(#linearGradient3021);stroke-width:1.01110005;stroke-linecap:round;stroke-linejoin:round"
+ id="path3496"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="2"
+ y="4"
+ width="7"
+ height="1"
+ ry="0.5"
+ rx="0.53846002"
+ style="fill:#edbd74;display:block"
+ id="rect2545" />
+ <g
+ transform="translate(4,1)"
+ id="layer1-9">
+ <path
+ d="m 1.4976,0.49763 h 7.9177 c 0.5228,0.16184 4.3537,2.6136 5.0867,3.8903 v 11.114 H 1.498 c -4e-4,-5.001 -4e-4,-10.003 -4e-4,-15.004 v -3.7e-4 z"
+ style="fill:url(#linearGradient3253);stroke:url(#linearGradient3255);stroke-width:0.99524999;stroke-linejoin:round"
+ id="rect2594"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.5,0.49792 v 4.0021 h 5.002 c 0,-1.9018 -3.006,-4.0021 -5.002,-4.0021 v 2e-5 z"
+ style="fill:url(#linearGradient3257);fill-rule:evenodd;stroke:url(#linearGradient3259);stroke-width:0.99524999;stroke-linejoin:round"
+ id="path12038"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ d="m 3.5357,10.582 c 10.57,0 13.03,0 19.993,-0.021 0,1.175 0.258,12 -0.484,12 -0.714,0 -14.046,-0.07 -21.009,-0.05 1.472,0 1.4997,-0.465 1.4997,-11.929 z"
+ style="fill:url(#radialGradient3293);stroke:url(#linearGradient3295);stroke-linecap:round;stroke-linejoin:round"
+ id="path3498"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 22.975,11.376 c 0,0 -16.832,0.07 -18.397,-0.069 C 4.4954,21.653 4.0774,22.109 4.0774,22.109"
+ style="opacity:0.4;fill:none;stroke:url(#radialGradient3004-5-2);stroke-width:0.98119003"
+ id="path3211"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 4.288,10.172 c -0.6849,0 -1.2517,0.384 -1.2517,0.847 v 4.816 c 0.0026,0.086 0.0661,0.185 0.1669,0.245 0.1008,0.06 0.2297,0.085 0.3755,0.075 h 0.0139 l 20.026,-2.145 c 0.232,-0.024 0.414,-0.16 0.417,-0.319 v -2.672 c 0,-0.463 -0.566,-0.847 -1.251,-0.847 H 4.2876 z"
+ style="fill:url(#linearGradient3001-8-3);fill-rule:evenodd"
+ id="path2608"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-233"
+ y="133"
+ id="text3638-3-1"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2"
+ x="-233"
+ y="133">%%open%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-197"
+ height="24"
+ width="24"
+ id="save"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-199"
+ y="133"
+ id="text3638-3-1-0"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4"
+ x="-199"
+ y="133">%%save%%</tspan></text>
+ <g
+ id="layer1-97"
+ transform="translate(-197.02443,138.98432)">
+ <rect
+ x="2.8817999"
+ y="21.363001"
+ width="18.236"
+ height="2.6373999"
+ style="opacity:0.40206;fill:url(#linearGradient2883)"
+ id="rect2723" />
+ <path
+ d="m 21.109,21.363 v 2.6373 c 1.1423,0.005 2.7616,-0.59088 2.7616,-1.3188 0,-0.72793 -1.2748,-1.3185 -2.7616,-1.3185 z"
+ style="opacity:0.40206;fill:url(#radialGradient2880)"
+ id="path2725"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 2.8907,21.363 v 2.6373 c -1.1424,0.005 -2.7616,-0.59088 -2.7616,-1.3188 0,-0.72793 1.2748,-1.3185 2.7616,-1.3185 z"
+ style="opacity:0.40206;fill:url(#radialGradient2877)"
+ id="path2727"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 23.5,18.5 c -0.46639,3.38 -0.35138,3.9815 -1.1959,3.9815 -0.33079,0.04823 -13.337,0 -20.654,0 -0.91714,0 -0.68541,0.06576 -1.1498,-3.9786 l 23,-0.0029 v -4.9e-5 z"
+ style="fill:url(#linearGradient2872);stroke:#353537;stroke-linejoin:round"
+ id="rect2992"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 2.7,19 H 16 v 3 H 3 L 2.7,19 z"
+ style="fill:url(#linearGradient8490);fill-rule:evenodd"
+ id="rect9146"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 3,22 C 3,22 2.8,19.8365 2.8,19.8365 4.1539,21.5511 9.1194,22 12.5426,22 H 3 z"
+ style="opacity:0.81142997;fill:url(#linearGradient8487);fill-rule:evenodd"
+ id="path9148"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 2.5,2 0.5226,18.483 c -0.014949,0 -0.022616,0.0044 -0.022616,0.01667 h 23 c 0,-0.01232 -0.0077,-0.01667 -0.02261,-0.01667 L 21.500374,2 c -0.099,-0.8274 -1.167,-1.5 -2,-1.5 h -15 c -0.8333,0 -1.9007,0.6726 -2,1.5 z"
+ style="fill:url(#radialGradient4072);stroke:url(#linearGradient2869);stroke-linecap:round;stroke-linejoin:round"
+ id="rect2990"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 1.4991,18.5 h 21.002"
+ style="opacity:0.6;fill:none;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round"
+ id="path2215"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="1"
+ y="19"
+ width="22"
+ height="3"
+ style="opacity:0.2;fill:url(#radialGradient3248)"
+ id="rect6300-3" />
+ <path
+ d="m 14.531,1.8125 c -0.27749,0 -0.52468,0.080669 -0.71875,0.21875 -0.19407,0.138081 -0.33544,0.35016 -0.34375,0.59375 -0.02564,0.74969 -0.40816,1.2893 -1.125,1.7812 -0.71102,0.48797 -1.7547,0.89986 -3,1.2812 -0.010197,0.00312 -0.021024,-0.00312 -0.03125,0 -0.471,0.098904 -1.245,0.37608 -2,0.65625 -0.7654,0.2842 -1.4645,0.5582 -1.781,0.7813 -1.5978,1.0054 -2.5829,2.3417 -2.7187,3.813 -0.12364,1.3391 0.47939,2.6794 1.6562,3.75 1.7217,1.5662 4.5353,2.5 7.5312,2.5 2.9959,0 5.8094,-0.93383 7.5312,-2.5 1.075,-0.97766 1.6562,-2.1551 1.6562,-3.375 0,-0.09948 0.0114,-0.24227 0,-0.375 -0.12,-1.2916 -0.916,-2.4839 -2.188,-3.438 -0.036,-0.5044 -0.344,-4.875 -0.344,-4.875 -0.017,-0.2437 -0.176,-0.4572 -0.375,-0.5938 -0.199,-0.1365 -0.442,-0.2187 -0.719,-0.2187 h -3.0312 z m 0,0.53125 h 3.0312 c 0.06724,0 0.12597,0.018412 0.15625,0.03125 l 0.375,5.125 c -0.0035,0.020687 -0.0035,0.041813 0,0.0625 0.0091,0.011612 0.01964,0.022101 0.03125,0.03125 0.01556,0.025516 0.03698,0.046944 0.0625,0.0625 1.232,0.84283 1.959,2.1272 2.0625,3.25 0.12887,1.3951 -0.69926,2.5486 -2.1875,3.4062 -1.4882,0.85766 -3.6257,1.375 -6.0625,1.375 -2.4368,0 -4.5743,-0.51734 -6.0625,-1.375 -1.4877,-0.857 -2.3158,-2.011 -2.187,-3.406 0.1109,-1.2003 0.9556,-2.5362 2.3438,-3.406 0.0116,-0.0091 0.0221,-0.0196 0.0312,-0.0312 0.0046,-0.0041 0.0687,-0.0477 0.1562,-0.0938 0.0876,-0.0461 0.2082,-0.0951 0.3438,-0.1562 0.2712,-0.1224 0.601,-0.2644 0.9688,-0.4063 0.7355,-0.2837 1.5683,-0.5498 1.9687,-0.625 0.010399,8.695e-4 0.020851,8.695e-4 0.03125,0 1.2093,-0.36821 2.3581,-0.8066 3.25,-1.4062 0.87385,-0.5875 1.5033,-1.369 1.5625,-2.4062 0.02028,-0.00976 0.06108,-0.03125 0.125,-0.03125 z"
+ style="fill:url(#radialGradient8471)"
+ id="path8469"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 12,13 C 10.3175,13 9.0277,12.15526 9.0005,11.0348 9.0002,11.0218 9,11.0108 9,10.9988 9,10.5928 9.1691,10.2078 9.4903,9.8811 10.034,9.3292 10.972,8.9998 12,8.9998 c 1.028,0 1.9662,0.32942 2.5096,0.8813 0.33072,0.33567 0.50019,0.73466 0.48986,1.1533 -0.028,1.12 -1.318,1.965 -3,1.965 v -6.3e-5 3e-6 z m 0.05026,-2.746 c -1.5809,0 -2.4365,0.21014 -2.4175,0.97447 0.01833,0.75242 1.0782,1.3536 2.3673,1.3536 1.2892,0 2.349,-0.60139 2.3673,-1.3536 0.01876,-0.76453 -0.73621,-0.97447 -2.3171,-0.97447 z"
+ style="fill:url(#radialGradient8464)"
+ id="path9156"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 4.9688,1.75 C 4.901587,1.7681 4.84496,1.8134 4.81255,1.875 4.799259,1.894268 4.78874,1.915307 4.7813,1.9375 L 4.7188,2.1562 3.875,5.4062 3.75,5.9375 C 3.74409,5.96847 3.74409,6.00028 3.75,6.03125 3.75408,6.064268 3.7647,6.09614 3.78125,6.125 3.813663,6.1866 3.870287,6.2319 3.9375,6.25 c 0.040995,0.010584 0.084005,0.010584 0.125,0 l 0.40625,-0.125 6.875,-2.0312 H 11.375 c 0.01041,6.516e-4 0.02084,6.516e-4 0.03125,0 0.01041,6.516e-4 0.02084,6.516e-4 0.03125,0 C 11.45969,4.08636 11.48073,4.075841 11.5,4.06255 11.96004,3.80879 12.19521,3.31995 12.25,2.87505 12.27806,2.64681 12.2584,2.41855 12.1875,2.2188 12.1465,2.1052 12.0605,1.9975 11.9685,1.9062 11.8755,1.815 11.7565,1.7452 11.5935,1.75 H 11.56225 5.18725 5.031 C 5.010249,1.74739 4.989251,1.74739 4.9685,1.75 z m 0.5,0.6875 H 11.375 C 11.36,2.5153 11.34,2.5681 11.312,2.6562 11.263,2.8181 11.167,2.9496 11.188,2.9375 L 4.9375,4.75 5.4688,2.4375 z"
+ style="fill:url(#radialGradient8475)"
+ id="path8473"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(-0.097811,0,0,0.10767,8.1622,-3.8183)"
+ id="g9158">
+ <path
+ d="m 48.3,190.28 c 0.31446,4.2476 5.0238,7.7116 10.514,7.7116 5.4879,0 9.6432,-3.464 9.2763,-7.7116 -0.36437,-4.2251 -5.0712,-7.6367 -10.507,-7.6367 -5.438,0.002 -9.5933,3.4116 -9.2838,7.6367 z"
+ style="fill:#e6e6e6"
+ id="path9160"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 51.851,187.29 c -0.50163,0.53906 -1.0756,1.4026 -1.0756,2.5506 0,0.0824 0.005,0.16471 0.01,0.25206 0.21713,2.9274 3.8932,5.4006 8.026,5.4006 2.3534,0 4.5046,-0.81109 5.7525,-2.1737 0.53656,-0.58149 1.148,-1.5448 1.0382,-2.8276 -0.25206,-2.9024 -3.9232,-5.3557 -8.021,-5.3557 -2.3359,0.002 -4.4797,0.8061 -5.73,2.1538 h -2e-5 z"
+ style="fill:url(#radialGradient4747)"
+ id="path9162"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="matrix(0.097811,0,0,0.10767,15.777,-3.8183)"
+ id="g9190">
+ <path
+ d="m 48.3,190.28 c 0.31446,4.2476 5.0238,7.7116 10.514,7.7116 5.4879,0 9.6432,-3.464 9.2763,-7.7116 -0.36437,-4.2251 -5.0712,-7.6367 -10.507,-7.6367 -5.438,0.002 -9.5933,3.4116 -9.2838,7.6367 z"
+ style="fill:#e6e6e6"
+ id="path9192"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 51.851,187.29 c -0.50163,0.53906 -1.0756,1.4026 -1.0756,2.5506 0,0.0824 0.005,0.16471 0.01,0.25206 0.21713,2.9274 3.8932,5.4006 8.026,5.4006 2.3534,0 4.5046,-0.81109 5.7525,-2.1737 0.53656,-0.58149 1.148,-1.5448 1.0382,-2.8276 -0.25206,-2.9024 -3.9232,-5.3557 -8.021,-5.3557 -2.3359,0.002 -4.4797,0.8061 -5.73,2.1538 h -2e-5 z"
+ style="fill:url(#radialGradient8498)"
+ id="path9194"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ d="m 18.611,7.5098 -6.6112,7 -6.6112,-7 h 3.1112 v -6 h 7 v 6 h 3.1112 z"
+ style="fill:url(#linearGradient2834);stroke:#548820;stroke-width:0.98045999;stroke-linecap:round;stroke-linejoin:round"
+ id="path4348"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 16.394,8.5098 -4.3945,4.5863 -4.348,-4.5863 h 1.848 v -6 h 5 v 6 h 1.8945 z"
+ style="opacity:0.35400008;fill:none;stroke:url(#linearGradient2831);stroke-width:0.98045999"
+ id="path4360"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-162"
+ height="24"
+ width="24"
+ id="saveas"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-166"
+ y="133"
+ id="text3638-3-1-0-5"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7"
+ x="-166"
+ y="133">%%saveas%%</tspan></text>
+ <g
+ id="g4931"
+ transform="translate(-161.99995,138.98881)">
+ <g
+ id="layer1-39">
+ <rect
+ x="2.8817999"
+ y="21.363001"
+ width="18.236"
+ height="2.6373999"
+ style="opacity:0.40206;fill:url(#linearGradient2883-8)"
+ id="rect2723-8" />
+ <path
+ d="m 21.109,21.363 v 2.6373 c 1.1423,0.005 2.7616,-0.59088 2.7616,-1.3188 0,-0.72793 -1.2748,-1.3185 -2.7616,-1.3185 z"
+ style="opacity:0.40206;fill:url(#radialGradient2880-4)"
+ id="path2725-3"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 2.8907,21.363 v 2.6373 c -1.1424,0.005 -2.7616,-0.59088 -2.7616,-1.3188 0,-0.72793 1.2748,-1.3185 2.7616,-1.3185 z"
+ style="opacity:0.40206;fill:url(#radialGradient2877-6)"
+ id="path2727-4"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 23.5,18.5 c -0.46639,3.38 -0.35138,3.9815 -1.1959,3.9815 -0.33079,0.04823 -13.337,0 -20.654,0 -0.91714,0 -0.68541,0.06576 -1.1498,-3.9786 l 23,-0.0029 v -4.9e-5 z"
+ style="fill:url(#linearGradient2872-0);stroke:#353537;stroke-linejoin:round"
+ id="rect2992-1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 2.7,19 H 16 v 3 H 3 L 2.7,19 z"
+ style="fill:url(#linearGradient8490-6);fill-rule:evenodd"
+ id="rect9146-2"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 3,22 C 3,22 2.8,19.8365 2.8,19.8365 4.1539,21.5511 9.1194,22 12.5426,22 H 3 z"
+ style="opacity:0.81142997;fill:url(#linearGradient8487-5);fill-rule:evenodd"
+ id="path9148-8"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 2.5,2 0.5226,18.483 c -0.014949,0 -0.022616,0.0044 -0.022616,0.01667 h 23 c 0,-0.01232 -0.0077,-0.01667 -0.02261,-0.01667 L 21.500374,2 c -0.099,-0.8274 -1.167,-1.5 -2,-1.5 h -15 c -0.8333,0 -1.9007,0.6726 -2,1.5 z"
+ style="fill:url(#radialGradient4072-4);stroke:url(#linearGradient2869-6);stroke-linecap:round;stroke-linejoin:round"
+ id="rect2990-4"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 1.4991,18.5 h 21.002"
+ style="opacity:0.6;fill:none;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round"
+ id="path2215-6"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="1"
+ y="19"
+ width="22"
+ height="3"
+ style="opacity:0.2;fill:url(#radialGradient3248-5)"
+ id="rect6300-3-0" />
+ <path
+ d="m 14.531,1.8125 c -0.27749,0 -0.52468,0.080669 -0.71875,0.21875 -0.19407,0.138081 -0.33544,0.35016 -0.34375,0.59375 -0.02564,0.74969 -0.40816,1.2893 -1.125,1.7812 -0.71102,0.48797 -1.7547,0.89986 -3,1.2812 -0.010197,0.00312 -0.021024,-0.00312 -0.03125,0 -0.471,0.098904 -1.245,0.37608 -2,0.65625 -0.7654,0.2842 -1.4645,0.5582 -1.781,0.7813 -1.5978,1.0054 -2.5829,2.3417 -2.7187,3.813 -0.12364,1.3391 0.47939,2.6794 1.6562,3.75 1.7217,1.5662 4.5353,2.5 7.5312,2.5 2.9959,0 5.8094,-0.93383 7.5312,-2.5 1.075,-0.97766 1.6562,-2.1551 1.6562,-3.375 0,-0.09948 0.0114,-0.24227 0,-0.375 -0.12,-1.2916 -0.916,-2.4839 -2.188,-3.438 -0.036,-0.5044 -0.344,-4.875 -0.344,-4.875 -0.017,-0.2437 -0.176,-0.4572 -0.375,-0.5938 -0.199,-0.1365 -0.442,-0.2187 -0.719,-0.2187 h -3.0312 z m 0,0.53125 h 3.0312 c 0.06724,0 0.12597,0.018412 0.15625,0.03125 l 0.375,5.125 c -0.0035,0.020687 -0.0035,0.041813 0,0.0625 0.0091,0.011612 0.01964,0.022101 0.03125,0.03125 0.01556,0.025516 0.03698,0.046944 0.0625,0.0625 1.232,0.84283 1.959,2.1272 2.0625,3.25 0.12887,1.3951 -0.69926,2.5486 -2.1875,3.4062 -1.4882,0.85766 -3.6257,1.375 -6.0625,1.375 -2.4368,0 -4.5743,-0.51734 -6.0625,-1.375 -1.4877,-0.857 -2.3158,-2.011 -2.187,-3.406 0.1109,-1.2003 0.9556,-2.5362 2.3438,-3.406 0.0116,-0.0091 0.0221,-0.0196 0.0312,-0.0312 0.0046,-0.0041 0.0687,-0.0477 0.1562,-0.0938 0.0876,-0.0461 0.2082,-0.0951 0.3438,-0.1562 0.2712,-0.1224 0.601,-0.2644 0.9688,-0.4063 0.7355,-0.2837 1.5683,-0.5498 1.9687,-0.625 0.010399,8.695e-4 0.020851,8.695e-4 0.03125,0 1.2093,-0.36821 2.3581,-0.8066 3.25,-1.4062 0.87385,-0.5875 1.5033,-1.369 1.5625,-2.4062 0.02028,-0.00976 0.06108,-0.03125 0.125,-0.03125 z"
+ style="fill:url(#radialGradient8471-3)"
+ id="path8469-9"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 12,13 C 10.3175,13 9.0277,12.15526 9.0005,11.0348 9.0002,11.0218 9,11.0108 9,10.9988 9,10.5928 9.1691,10.2078 9.4903,9.8811 10.034,9.3292 10.972,8.9998 12,8.9998 c 1.028,0 1.9662,0.32942 2.5096,0.8813 0.33072,0.33567 0.50019,0.73466 0.48986,1.1533 -0.028,1.12 -1.318,1.965 -3,1.965 v -6.3e-5 3e-6 z m 0.05026,-2.746 c -1.5809,0 -2.4365,0.21014 -2.4175,0.97447 0.01833,0.75242 1.0782,1.3536 2.3673,1.3536 1.2892,0 2.349,-0.60139 2.3673,-1.3536 0.01876,-0.76453 -0.73621,-0.97447 -2.3171,-0.97447 z"
+ style="fill:url(#radialGradient8464-7)"
+ id="path9156-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 4.9688,1.75 C 4.9015,1.7681 4.8449,1.8134 4.8125,1.875 4.7992,1.8943 4.7887,1.9153 4.7812,1.9375 L 4.7188,2.1562 3.875,5.4062 3.75,5.9375 c -0.0059,0.031 -0.0059,0.0628 0,0.0937 0.0041,0.0331 0.0147,0.0649 0.0312,0.0938 0.0325,0.0616 0.0891,0.1069 0.1563,0.125 0.040995,0.010584 0.084005,0.010584 0.125,0 l 0.40625,-0.125 6.875,-2.0312 H 11.375 c 0.01041,6.516e-4 0.02084,6.516e-4 0.03125,0 0.01041,6.516e-4 0.02084,6.516e-4 0.03125,0 0.022,-0.0075 0.043,-0.018 0.062,-0.0313 0.46,-0.2538 0.695,-0.7426 0.75,-1.1875 0.028,-0.2282 0.008,-0.4565 -0.062,-0.6562 C 12.1465,2.1052 12.0605,1.9975 11.9685,1.9062 11.8755,1.815 11.7565,1.7452 11.5935,1.75 H 11.56225 5.18725 5.031 C 5.0099,1.7474 4.9889,1.7474 4.9682,1.75 z m 0.5,0.6875 H 11.375 C 11.36,2.5153 11.34,2.5681 11.312,2.6562 11.263,2.8181 11.167,2.9496 11.188,2.9375 L 4.9375,4.75 5.4688,2.4375 z"
+ style="fill:url(#radialGradient8475-7)"
+ id="path8473-8"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(-0.097811,0,0,0.10767,8.1622,-3.8183)"
+ id="g9158-3">
+ <path
+ d="m 48.3,190.28 c 0.31446,4.2476 5.0238,7.7116 10.514,7.7116 5.4879,0 9.6432,-3.464 9.2763,-7.7116 -0.36437,-4.2251 -5.0712,-7.6367 -10.507,-7.6367 -5.438,0.002 -9.5933,3.4116 -9.2838,7.6367 z"
+ style="fill:#e6e6e6"
+ id="path9160-0"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 51.851,187.29 c -0.50163,0.53906 -1.0756,1.4026 -1.0756,2.5506 0,0.0824 0.005,0.16471 0.01,0.25206 0.21713,2.9274 3.8932,5.4006 8.026,5.4006 2.3534,0 4.5046,-0.81109 5.7525,-2.1737 0.53656,-0.58149 1.148,-1.5448 1.0382,-2.8276 -0.25206,-2.9024 -3.9232,-5.3557 -8.021,-5.3557 -2.3359,0.002 -4.4797,0.8061 -5.73,2.1538 h -2e-5 z"
+ style="fill:url(#radialGradient4958)"
+ id="path9162-0"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="matrix(0.097811,0,0,0.10767,15.777,-3.8183)"
+ id="g9190-7">
+ <path
+ d="m 48.3,190.28 c 0.31446,4.2476 5.0238,7.7116 10.514,7.7116 5.4879,0 9.6432,-3.464 9.2763,-7.7116 -0.36437,-4.2251 -5.0712,-7.6367 -10.507,-7.6367 -5.438,0.002 -9.5933,3.4116 -9.2838,7.6367 z"
+ style="fill:#e6e6e6"
+ id="path9192-7"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 51.851,187.29 c -0.50163,0.53906 -1.0756,1.4026 -1.0756,2.5506 0,0.0824 0.005,0.16471 0.01,0.25206 0.21713,2.9274 3.8932,5.4006 8.026,5.4006 2.3534,0 4.5046,-0.81109 5.7525,-2.1737 0.53656,-0.58149 1.148,-1.5448 1.0382,-2.8276 -0.25206,-2.9024 -3.9232,-5.3557 -8.021,-5.3557 -2.3359,0.002 -4.4797,0.8061 -5.73,2.1538 h -2e-5 z"
+ style="fill:url(#radialGradient8498-1)"
+ id="path9194-2"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ d="m 18.611,7.5098 -6.6112,7 -6.6112,-7 h 3.1112 v -6 h 7 v 6 h 3.1112 z"
+ style="fill:url(#linearGradient2834-9);stroke:#548820;stroke-width:0.98045999;stroke-linecap:round;stroke-linejoin:round"
+ id="path4348-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 16.394,8.5098 -4.3945,4.5863 -4.348,-4.5863 h 1.848 v -6 h 5 v 6 h 1.8945 z"
+ style="opacity:0.35400008;fill:none;stroke:url(#linearGradient2831-9);stroke-width:0.98045999"
+ id="path4360-3"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="translate(36.952,1.0738)"
+ id="g5600">
+ <rect
+ x="-34.480999"
+ y="-0.60262001"
+ width="19.058001"
+ height="4.0577002"
+ ry="0.86373001"
+ rx="1.0142"
+ style="fill:url(#linearGradient5605);stroke:#548820;stroke-width:0.94229001"
+ id="rect5480" />
+ <rect
+ x="-32.952"
+ y="0.92623001"
+ width="6"
+ height="1"
+ style="fill:#c8cdc3"
+ id="rect6467" />
+ <rect
+ x="-25.952"
+ y="0.42623001"
+ width="1"
+ height="2"
+ style="fill:#969696"
+ id="rect6469" />
+ </g>
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-127"
+ height="24"
+ width="24"
+ id="print"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-129"
+ y="133"
+ id="text3638-3-1-0-5-2"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9"
+ x="-129"
+ y="133">%%print%%</tspan></text>
+ <g
+ id="g5067"
+ transform="translate(-127.01381,138.27905)">
+ <g
+ id="g5529">
+ <path
+ d="M 24,20.75 C 24,22.545 18.627,24 12,24 5.3726,24 0,22.545 0,20.75 5e-8,18.955 5.3726,17.5 12,17.5 c 6.6274,0 12,1.4551 12,3.25 z"
+ style="opacity:0.7;fill:url(#radialGradient5083)"
+ id="path3087"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="4.5"
+ y="5.5"
+ width="15"
+ height="11"
+ ry="0.36184999"
+ rx="0.29157001"
+ style="fill:#505050;stroke:#3c3d3a;stroke-width:1.00010002;stroke-linecap:round;stroke-linejoin:round"
+ id="rect2315" />
+ <g
+ transform="translate(4,1)"
+ id="layer1-6">
+ <path
+ d="m 1.4976,0.49763 13.005,0.002374 v 15.002 H 1.4976 v -15.005 z"
+ style="fill:#f4f4f4;stroke:#b5b6b2;stroke-width:0.99524999;stroke-linejoin:round"
+ id="rect2594-0"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ d="m 2.5074,19.5 c 0.037084,0.51806 -0.096022,1.0755 0.11744,1.5555 0.40044,0.60919 1.2842,0.40888 1.8752,0.44374 0.010664,0.35961 0.043357,0.91477 0.5,0.99127 4.8239,0.01951 9.1756,0.0028 14,0.0084 0.49786,0.01588 0.5161,-0.58079 0.5,-0.95942 0.1626,-0.09327 0.62471,-0.01381 0.84925,-0.0402 0.5556,0.08151 1.2349,-0.32979 1.1424,-1.0137 v -0.98556 h -18.984 z"
+ style="fill:#505050;stroke:#3c3d3a;stroke-width:0.99971002;stroke-linecap:square"
+ id="rect6333"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 2.5153,11.5 h 18.969 c 0.56248,0 1.0153,0.46361 1.0153,1.0395 v 6.9606 h -21 V 12.5395 C 1.4996,11.96362 1.95243,11.5 2.5149,11.5 z"
+ style="fill:url(#linearGradient5085);stroke:url(#linearGradient5087);stroke-width:0.99993002;stroke-linecap:round;stroke-linejoin:round"
+ id="rect2313"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 2.9634,12.5 h 18.073 c 0.24043,0 0.46342,0.20622 0.46342,0.42858 v 5.5715 h -19 v -5.5715 C 2.49982,12.70622 2.72281,12.5 2.96324,12.5 z"
+ style="opacity:0.9;fill:none;stroke:url(#radialGradient5089);stroke-width:0.99991;stroke-linecap:round"
+ id="rect2374"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="5.4714999"
+ y="11.472"
+ width="13.057"
+ height="5.0570002"
+ style="fill:url(#linearGradient5091);stroke:url(#linearGradient5093);stroke-width:0.94301999;stroke-linecap:round"
+ id="rect2319" />
+ <rect
+ x="6.4998999"
+ y="12.5"
+ width="11"
+ height="3.0002"
+ style="opacity:0.3;fill:none;stroke:url(#radialGradient5095);stroke-width:0.99981999;stroke-linecap:round"
+ id="rect2459" />
+ <path
+ d="m 21,17.5 c 2.42e-4,0.27607 -0.22369,0.5 -0.5,0.5 -0.27631,0 -0.50024,-0.22393 -0.5,-0.5 -2.42e-4,-0.27607 0.22369,-0.5 0.5,-0.5 0.27631,0 0.50024,0.22393 0.5,0.5 z"
+ style="fill:#6efb27"
+ id="path2764"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 19,20 v 1.75 C 19,21.888 18.902,22 18.781,22 H 5.219 C 5.0976,22 5,21.888 5,21.75 V 20 h 14 z"
+ style="fill:#e6e6e6"
+ id="rect6331"
+ inkscape:connector-curvature="0" />
+ <rect
+ x="6"
+ y="20"
+ width="12"
+ height="1"
+ style="fill:#323232"
+ id="rect6329" />
+ </g>
+ <path
+ d="M 16,6 11.99,10 8,6 h 2 V 3 h 4 v 3 h 2 z"
+ style="fill:url(#linearGradient5097)"
+ id="path4348-54"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-92"
+ height="24"
+ width="24"
+ id="undo"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-93.51992"
+ y="133"
+ id="text3638-3-1-0-5-2-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4"
+ x="-93.51992"
+ y="133">%%undo%%</tspan></text>
+ <g
+ transform="translate(-90.992695,145.90231)"
+ id="layer1-62">
+ <path
+ d="M 7.7501,-0.62517 2.5,4.99993 l 5.2502,5.6251"
+ style="fill:none;stroke:url(#linearGradient5188);stroke-width:5;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3169-2-3"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19.025,10.281 16.5,6.625 C 15.914,5.5475 14.287,5.0058 13,5 l -6.5,-7e-5"
+ style="fill:none;stroke:url(#linearGradient3732);stroke-width:6;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3765"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 7.7501,-0.62517 2.5,4.99993 l 5.2502,5.6251"
+ style="fill:none;stroke:url(#linearGradient5190);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4277"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19.025,10.281 16.5,6.625 C 15.873,5.7124 14.41,5.0058 13,5 l -6.5,-7e-5"
+ style="fill:none;stroke:url(#linearGradient4322);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4279"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 8.2275,9.6039 6.5,8 C 5.5,7 6.5,6.5 8,6.5 l 3.8324,-0.04122 c 1.2454,0.017784 3.1612,0.051221 3.6967,1.6076 l 1.9816,2.5556 c 0.65045,2.061 3.819,1.2457 2.8854,-1.31 l -2.227,-2.7379 c -0.845,-1.3426 -1.894,-2.6289 -4.169,-3.0741 h -5.5 c -1.5,0 -3,0 -2,-1.5 l 1.6451,-1.5626 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 l -4.6451,5.0626 c -0.5,0.5 -0.5276,1.3333 0,2 l 4.7275,5.1039 c 1,1 3,0 1.5,-1.5 z"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient2839);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4454"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 6.5,2 8.1451,0.4786 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 4.7688,5.2275 c 1,1 3,0 1.5,-1.5 L 6.5,7.9995"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient4462);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4464"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 7.1397,-1.5161 2,4"
+ style="opacity:0.6;fill:none;stroke:#f6daae;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4466"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-57"
+ height="24"
+ width="24"
+ id="redo"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-59"
+ y="133"
+ id="text3638-3-1-0-5-2-8-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9"
+ x="-59"
+ y="133">%%redo%%</tspan></text>
+ <g
+ transform="matrix(-1,0,0,1,-34.0625,145.9375)"
+ id="layer1-7">
+ <path
+ d="M 7.7501,-0.62517 2.5,4.99993 l 5.2502,5.6251"
+ style="fill:none;stroke:#688c35;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3169-2-3-7"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19.025,10.281 16.5,6.625 C 15.914,5.5475 14.287,5.0058 13,5 l -6.5,-7e-5"
+ style="fill:none;stroke:#688c35;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3765-6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 7.7501,-0.62517 2.5,4.99993 l 5.2502,5.6251"
+ style="fill:none;stroke:url(#linearGradient4322-5);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4277-6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19.025,10.281 16.5,6.625 C 15.873,5.7124 14.41,5.0058 13,5 l -6.5,-7e-5"
+ style="fill:none;stroke:url(#linearGradient4324);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4279-1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 8.2275,9.6039 6.5,8 C 5.5,7 6.5,6.5 8,6.5 l 3.8324,-0.04122 c 1.2454,0.017784 3.1612,0.051221 3.6967,1.6076 l 1.9816,2.5556 c 0.65045,2.061 3.819,1.2457 2.8854,-1.31 l -2.227,-2.7379 c -0.845,-1.3426 -1.894,-2.6289 -4.169,-3.0741 h -5.5 c -1.5,0 -3,0 -2,-1.5 l 1.6451,-1.5626 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 l -4.6451,5.0626 c -0.5,0.5 -0.5276,1.3333 0,2 l 4.7275,5.1039 c 1,1 3,0 1.5,-1.5 z"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient2839-6);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4454-6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 6.5,2 8.1451,0.4786 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 4.7688,5.2275 c 1,1 3,0 1.5,-1.5 L 6.5,7.9995"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient4462-6);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4464-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 7.1397,-1.5161 2,4"
+ style="opacity:0.5;fill:none;stroke:#e5f09a;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4466-6"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="-22"
+ height="24"
+ width="24"
+ id="cut"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <g
+ id="layer1-96"
+ transform="translate(-22.438221,131.01798)">
+ <g
+ id="g6545"
+ transform="matrix(0.8930733,0.44991119,-0.44991119,0.8930733,-40.088761,-46.216687)">
+ <path
+ style="fill:url(#linearGradient6541);stroke:#888a85;stroke-width:1.04343462;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path5602"
+ d="m 76.142125,23.986492 c 0.09772,-0.0027 2.66917,1.381244 2.682584,2.528199 -0.147983,3.419111 -0.267287,6.871404 -0.393387,10.308303 -0.261504,0.181262 -0.539755,0.333954 -0.831783,0.459341 -0.614034,0.265138 -1.302926,0.415227 -2.008258,0.41128 0.168253,-4.570471 0.333331,-9.149623 0.550844,-13.707123 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:url(#linearGradient6538);fill-opacity:1;stroke:none"
+ id="path5604"
+ d="m 76.69305,36.496033 c 0.02208,0.121701 0.561324,0.736373 0.847087,0.430344 0.13909,-3.350207 0.161691,-6.756579 0.29782,-10.10639 0.04116,-0.225898 -0.524997,-0.756728 -0.812014,-1.01595 0,0 -0.183607,7.068972 -0.332893,10.691996 z"
+ inkscape:connector-curvature="0" />
+ <polygon
+ style="fill:#d1524c;fill-opacity:1;stroke:#973137;stroke-width:0.64594996;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="polygon5606"
+ transform="matrix(1.3838457,-0.5995106,0.7577226,1.5096437,-342.70934,195.72431)"
+ points="294.73242,13.672852 295.74658,11.960449 297.04443,12.300293 297.04443,12.300293 296.39941,13.384766 295.13281,14.71875 " />
+ <path
+ style="fill:url(#linearGradient6534);stroke:#888a85;stroke-width:0.98886794;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path5608"
+ d="m 67.639697,28.266191 c -0.05269,0.06962 -0.296019,2.772516 0.59766,3.468394 2.761317,1.93736 5.533849,3.915495 8.297817,5.879457 0.277594,-0.08225 0.541414,-0.193806 0.79092,-0.331734 0.525799,-0.289138 0.999978,-0.701673 1.361708,-1.218336 -3.675876,-2.611319 -7.356932,-5.230144 -11.048105,-7.797781 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:url(#linearGradient6531);fill-opacity:1;stroke:none"
+ id="path5610"
+ d="m 68.970807,31.046041 c 2.685267,1.87904 5.347419,3.832107 8.030235,5.708814 0.448513,-0.123082 0.941956,-0.157475 0.685348,-0.384296 -2.78652,-1.914994 -6.157886,-4.301651 -8.935065,-6.23079 -0.03991,-0.05846 0.02717,0.371107 0.219482,0.906272 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:url(#linearGradient6543);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.31858397;marker:none;visibility:visible;display:inline;overflow:visible"
+ id="path5612"
+ d="m 76.496347,35.488424 c 0.123486,0.230684 0.07861,0.498967 -0.100205,0.599078 -0.178816,0.100111 -0.423792,-0.0059 -0.547032,-0.236718 -0.123485,-0.230684 -0.07861,-0.498966 0.100205,-0.599078 0.178816,-0.100112 0.423792,0.0059 0.547031,0.236718 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#d1524c;fill-opacity:1;stroke:#973137;stroke-width:0.97633934;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path5614"
+ d="m 74.811562,39.139626 c 1.667108,-0.01317 3.313785,2.564256 3.427111,4.773436 0.11403,2.210116 -0.82558,3.652505 -2.491127,3.668822 -1.667657,0.01347 -3.110985,-1.765159 -3.224307,-3.974334 -0.113885,-2.208867 0.624172,-4.449706 2.288323,-4.467924 z m 0.0741,1.407319 c -0.739442,0.007 -0.989158,1.561088 -0.911477,3.046366 0.0762,1.486096 0.961232,2.590125 1.699529,2.582531 0.740197,-0.005 1.191745,-0.763213 1.114065,-2.248495 -0.0757,-1.48637 -1.162699,-3.387404 -1.902117,-3.380402 z"
+ inkscape:connector-curvature="0" />
+ <polygon
+ style="fill:#d1524c;fill-opacity:1;stroke:#973137;stroke-width:0.6332444;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="polygon5616"
+ transform="matrix(1.1476031,-0.79527758,0.96691724,1.4548542,-275.37886,255.51909)"
+ points="298.25391,11.960449 296.95605,12.300293 296.95605,12.300293 297.6001,13.384766 298.86719,14.71875 299.26807,13.672852 " />
+ <path
+ style="fill:#d1524c;fill-opacity:1;stroke:#973137;stroke-width:0.99573755;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path5618"
+ d="m 80.776122,36.5167 c -0.795059,1.33926 0.03789,3.562041 1.862156,4.96416 1.824731,1.403245 3.948421,1.456187 4.745424,0.120053 0.79558,-1.339521 -0.03741,-3.560912 -1.861673,-4.963023 -1.823738,-1.402384 -3.947941,-1.455065 -4.745907,-0.12119 z m 1.161227,0.894731 c 0.35364,-0.593323 1.664559,-0.55346 2.890349,0.390418 1.227185,0.943163 1.749342,2.313239 1.395742,2.905303 -0.352296,0.595128 -1.665019,0.553695 -2.89081,-0.390181 -1.227653,-0.942927 -1.748909,-2.312216 -1.395281,-2.90554 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#d1524c;fill-opacity:1;stroke:none"
+ id="path3611"
+ d="m 74.811562,39.139626 c 1.667108,-0.01317 3.313785,2.564256 3.427111,4.773436 0.11403,2.210116 -0.82558,3.652505 -2.491127,3.668822 -1.667657,0.01347 -3.110985,-1.765159 -3.224307,-3.974334 -0.113885,-2.208867 0.624172,-4.449706 2.288323,-4.467924 z m 0.0741,1.407319 c -0.739442,0.007 -0.989158,1.561088 -0.911477,3.046366 0.0762,1.486096 0.961232,2.590125 1.699529,2.582531 0.740197,-0.005 1.191745,-0.763213 1.114065,-2.248495 -0.0757,-1.48637 -1.162699,-3.387404 -1.902117,-3.380402 z"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#d1524c;fill-opacity:1;stroke:none"
+ id="path3613"
+ d="m 80.776122,36.5167 c -0.795059,1.33926 0.03789,3.562041 1.862156,4.96416 1.824731,1.403245 3.948421,1.456187 4.745424,0.120053 0.79558,-1.339521 -0.03741,-3.560912 -1.861673,-4.963023 -1.823738,-1.402384 -3.947941,-1.455065 -4.745907,-0.12119 z m 1.161227,0.894731 c 0.35364,-0.593323 1.664559,-0.55346 2.890349,0.390418 1.227185,0.943163 1.749342,2.313239 1.395742,2.905303 -0.352296,0.595128 -1.665019,0.553695 -2.89081,-0.390181 -1.227653,-0.942927 -1.748909,-2.312216 -1.395281,-2.90554 z"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-22"
+ y="133"
+ id="text3638-3-1-0-5-2-8-8-2"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9-0"
+ x="-22"
+ y="133">%%cut%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="13"
+ height="24"
+ width="24"
+ id="copy"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="11"
+ y="133"
+ id="text3638-3-1-0-5-2-8-8-2-5"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9-0-2"
+ x="11"
+ y="133">%%copy%%</tspan></text>
+ <g
+ id="g5446"
+ transform="translate(13,139)">
+ <path
+ d="m 2.4996,1.5004 h 13 v 15 h -13 v -15 z"
+ style="fill:url(#linearGradient2906);stroke:url(#linearGradient2908);stroke-width:0.99914002;stroke-linejoin:round"
+ id="path2904"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 8.4996,7.5004 h 13 v 15 h -13 v -15 z"
+ style="fill:url(#linearGradient2990);stroke:url(#linearGradient2992);stroke-width:0.99914002;stroke-linejoin:round"
+ id="rect2594-6"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 10.5,18.5 h 7 m -7,-3 h 8 m -8,-3 h 5 m -5,-3 h 9 m -15,3 h 3 m -3,-3 h 3 m -3,-3 h 5 m -5,-3 h 9"
+ style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:square"
+ id="path3696"
+ inkscape:connector-curvature="0" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="45"
+ y="133"
+ id="text3638-3-1-0-5-2-8-8-2-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9-0-9"
+ x="45"
+ y="133">%%paste%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="48"
+ height="24"
+ width="24"
+ id="paste"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <g
+ id="g5533"
+ transform="translate(47.971897,139.01041)">
+ <path
+ d="m 2.4996,3.5004 h 13 v 15 h -13 v -15 z"
+ style="fill:url(#linearGradient2906-5);stroke:url(#linearGradient3621);stroke-width:0.99914002;stroke-linejoin:round"
+ id="path2904-1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 3.4996,4.5004 h 11 v 13 h -11 v -13 z"
+ style="opacity:0.2;fill:none;stroke:#ffffff;stroke-width:0.99914002;stroke-linejoin:round"
+ id="path3624-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 8.4996,7.5004 h 13 v 15 h -13 v -15 z"
+ style="fill:url(#linearGradient2990-5);stroke:url(#linearGradient2992-9);stroke-width:0.99914002;stroke-linejoin:round"
+ id="rect2594-8"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 10.5,9.5004 h 9 m -9,3 h 5 m -5,3 h 8 m -8,3 h 7"
+ style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:square"
+ id="path2835"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 7.4996,1.5004 c 0.052927,1.8402 -0.049158,2.9464 -1,2.9773 v 1.0258 h 5 V 4.4777 c -1.078,-0.023242 -0.97602,-1.1425 -1,-2.9773 h -3 z"
+ style="fill:url(#linearGradient2933);stroke:url(#linearGradient2935);stroke-width:0.99826998;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.5;enable-background:new"
+ id="path4675"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="139"
+ x="83"
+ height="24"
+ width="24"
+ id="find"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="82"
+ y="133"
+ id="text3638-3-1-0-5-2-8-8-2-8-6"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9-0-9-4"
+ x="82"
+ y="133">%%find%%</tspan></text>
+ <g
+ id="layer1-35"
+ transform="translate(83.013866,139.01391)">
+ <g
+ transform="matrix(0.54593,0,0,0.51685,-0.96573,-0.57818)"
+ style="stroke-width:1.88259995"
+ id="g3490-2">
+ <g
+ transform="matrix(0.021652,0,0,0.014857,43.008,42.685)"
+ style="stroke-width:104.95999908"
+ id="g5022-2">
+ <rect
+ x="-1559.3"
+ y="-150.7"
+ width="1339.6"
+ height="478.35999"
+ style="opacity:0.40206;fill:url(#linearGradient4532)"
+ id="rect4173" />
+ <path
+ d="m -219.62,-150.68 v 478.33 c 142.87,0.90045 345.4,-107.17 345.4,-239.2 0,-132.03 -159.44,-239.13 -345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient4534)"
+ id="path5058"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -1559.3,-150.68 v 478.33 c -142.87,0.90045 -345.4,-107.17 -345.4,-239.2 0,-132.03 159.44,-239.13 345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient4536)"
+ id="path5018"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <path
+ d="m 2.4996,0.49957 h 13.062 c 0.7638,0.23731 4.8672,2.9408 5.9387,4.8128 v 17.188 H 2.4993 v -22.001 z"
+ style="fill:url(#linearGradient4545);stroke:url(#linearGradient4547);stroke-width:0.99914002;stroke-linejoin:round"
+ id="rect2594-64"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 15.5,0.5 v 5 h 6 c 0,-0.83659 -4.8205,-4.9857 -6,-5 z"
+ style="fill:url(#linearGradient4543);fill-rule:evenodd;stroke:url(#linearGradient4565);stroke-linejoin:round"
+ id="path12038-2"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 17.461,16.55 6.1067,5.8764 -0.84647,1.1878 -6.0846,-6.3567 0.82435,-0.70748 z"
+ style="fill:url(#linearGradient2710);fill-rule:evenodd;stroke:#333333;stroke-width:0.77201003;stroke-linecap:round;stroke-linejoin:round"
+ id="path11112"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(0.1354,-0.15647,0.15133,0.14,-26.423,45.631)"
+ d="m 248.35,110.29 a 5.3125,2.65625 0 1 1 -10.625,0 5.3125,2.65625 0 1 1 10.625,0 z"
+ style="fill:#3e3e3e"
+ id="path13082"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(0.50364,0,0,0.50807,5.2767,4.2233)"
+ d="m 25.898,18.478 a 8.3085,8.3085 0 1 1 -16.617,0 8.3085,8.3085 0 1 1 16.617,0 z"
+ style="fill:url(#radialGradient4193);fill-rule:evenodd"
+ id="path4452"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 13.963,9.9776 c -2.0862,0 -3.7756,1.5628 -3.7756,3.4927 0,0.55737 0.16838,1.0702 0.41951,1.538 0.50172,0.17107 1.0347,0.28747 1.6003,0.28747 2.4722,0 4.4465,-1.8017 4.5991,-4.0533 -0.693,-0.758 -1.686,-1.2644 -2.843,-1.2644 z"
+ style="opacity:0.83422002;fill:url(#radialGradient2705);fill-rule:evenodd"
+ id="path4462"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 14.043,9.3962 c -2.5699,0 -4.6564,1.9893 -4.6564,4.4393 0,2.4501 2.0865,4.4393 4.6564,4.4393 2.5699,0 4.6564,-1.9893 4.6564,-4.4393 0,-2.4501 -2.0865,-4.4393 -4.6564,-4.4393 z m 0.05548,0.29125 c 2.3001,0 4.1698,1.7788 4.1698,3.9716 0,2.1928 -1.8698,3.9716 -4.1698,3.9716 -2.3001,0 -4.1656,-1.7788 -4.1656,-3.9716 0,-2.1928 1.8655,-3.9716 4.1656,-3.9716 z"
+ style="fill:url(#linearGradient2700);stroke:url(#linearGradient2702);stroke-width:0.77201003;stroke-linecap:round;stroke-linejoin:round"
+ id="path2298"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="color:#000000;fill:#969696;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.72142136px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 245,136 c -38.68657,0.0916 -70,31.40484 -70,70 l 70.15625,0 L 245,136 z m -35,25 10,0 0,10 10,0 0,10 -10,0 0,10 -10,0 0,-10 -10,0 0,-10 10,0 0,-10 z"
+ id="custom_tree_background"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="175"
+ y="131"
+ id="text3638-3-1-0-5-2-8-8-2-8-6-1"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-2-4-7-9-4-9-0-9-4-8"
+ x="175"
+ y="131">%%custom_tree_background%%</tspan></text>
+ <g
+ transform="matrix(0,1,1,0,-125.9999,182.5)"
+ id="layer1-5">
+ <path
+ d="M 8.475,-1.4875 2.5,4.9999 8.475,11.488"
+ style="fill:none;stroke:url(#linearGradient3044);stroke-width:5;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3169-2-3-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19,5 6.5,4.9999"
+ style="fill:none;stroke:url(#linearGradient3046);stroke-width:6;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path3765-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 8.4376,-1.4502 2.5,4.9999 8.4377,11.45"
+ style="fill:none;stroke:url(#linearGradient3048);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4277-64"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 19,5 6.5,4.9999"
+ style="fill:none;stroke:url(#linearGradient3050);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4279-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 8.9,10.469 6.5,8 C 5.5142,6.986 6.5,6.5 8,6.5 h 10.5 c 3,0 2.5,-3 0,-3 h -10 c -1.5,0 -3,0 -2,-1.5 l 2.3312,-2.1937 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 5.4,5.9688 c 1,1 2.9787,0.02103 1.5,-1.5 z"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient4005);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4454-7"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 6.5,2 2.4,-2.3312 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 5.4,5.9 c 1,1 2.7957,0.13265 1.3625,-1.4312 L 6.5,7.9998"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3052);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4464-3"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 7.2625,-1.65 2,4"
+ style="opacity:0.51000001;fill:none;stroke:#f6daae;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ id="path4466-7"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-133"
+ height="24"
+ width="24"
+ id="up"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-133"
+ y="177"
+ id="text3638-3-3"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8"
+ x="-133"
+ y="177">%%up%%</tspan></text>
+ <g
+ id="layer1-57"
+ transform="translate(-233.09946,219.05807)">
+ <g
+ transform="matrix(0.50043,0,0,0.51685,0.11475,-0.57818)"
+ style="stroke-width:1.88259995"
+ id="g3490-7">
+ <g
+ transform="matrix(0.021652,0,0,0.014857,43.008,42.685)"
+ style="stroke-width:104.95999908"
+ id="g5022-0">
+ <rect
+ x="-1559.3"
+ y="-150.7"
+ width="1339.6"
+ height="478.35999"
+ style="opacity:0.40206;fill:url(#linearGradient4532-7)"
+ id="rect4173-3" />
+ <path
+ d="m -219.62,-150.68 v 478.33 c 142.87,0.90045 345.4,-107.17 345.4,-239.2 0,-132.03 -159.44,-239.13 -345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient4534-7)"
+ id="path5058-5"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -1559.3,-150.68 v 478.33 c -142.87,0.90045 -345.4,-107.17 -345.4,-239.2 0,-132.03 159.44,-239.13 345.4,-239.13 z"
+ style="opacity:0.40206;fill:url(#radialGradient4536-0)"
+ id="path5018-8"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <path
+ d="m 3.5134,0.5 c 4.1393,0.002518 12.829,0.007809 16.969,0.010328 -0.0037,6.0486 0.02118,15.962 0.01751,21.99 -5.6666,-0.0034 -11.333,-0.0069 -17,-0.01035 0.00447,-7.3299 0.00893,-14.66 0.013397,-21.99 z"
+ style="fill:url(#linearGradient4194);stroke:url(#linearGradient4196);stroke-linecap:round;stroke-linejoin:round"
+ id="path2855"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 19.028,12.429 c -0.08415,-0.04997 -0.14927,-0.03697 -0.19229,-0.0092 l -5.7296,3.6801 -1.0545,0.67922 -0.03247,0.01374 -1.2485,2.8377 3.0941,0.094 0.02569,-0.01734 1.0613,-0.67562 5.7281,-3.7118 C 20.8519,15.20859 20.57976,14.47153 20.06935,13.6635 19.68654,13.05751 19.2804,12.5789 19.02795,12.429 z"
+ style="opacity:0.15;fill:#0c0c0c;fill-rule:evenodd"
+ id="path2422"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(1.0195,0,0,1,32.167,7.7733)"
+ style="fill:#999999"
+ id="g4198">
+ <path
+ d="m -25.666,-2.7733 h 1.1241 v 1 h -1.1241 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6035"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -24.388,-2.7733 h 1.0606 v 1 h -1.0606 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6033"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.175,-2.7733 h 0.93368 v 1 H -23.175 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6031"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -22.088,-2.7733 h 0.41011 v 1 H -22.088 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6029"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.525,-2.7733 h 0.91782 v 1 H -21.525 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6027"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.454,-2.7733 h 2.3775 v 1 h -2.3775 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6025"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -17.923,-2.7733 h 1.8063 v 1 h -1.8063 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6023"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,3.2267 h 1.1241 v 1 h -1.1241 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6017"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -24.388,3.2267 h 1.0606 v 1 h -1.0606 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6015"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.175,3.2267 h 0.93368 v 1 H -23.175 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6013"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -22.088,3.2267 h 0.41011 v 1 H -22.088 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6011"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.525,3.2267 h 0.91781 v 1 H -21.525 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6009"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.454,3.2267 h 2.3775 v 1 h -2.3775 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6007"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -17.923,3.2267 h 1.8063 v 1 h -1.8063 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6005"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.964,3.2267 h 0.5529 v 1 h -0.5529 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6003"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.258,3.2267 h 1.0923 v 1 h -1.0923 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6001"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,-0.77334 h 1.7428 v 1 h -1.7428 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5999"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.764,-0.77334 h 0.83849 v 1 H -23.764 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5997"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -22.766,-0.77334 h 0.39424 v 1 H -22.766 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5995"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -22.213,-0.77334 h 0.75916 v 1 H -22.213 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5993"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.295,-0.77334 h 0.75916 v 1 H -21.295 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5991"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.376,-0.77334 h 1.2193 v 1 h -1.2193 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5989"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.998,-0.77334 h 1.6 v 1 h -1.6 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5987"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -17.239,-0.77334 h 1.0765 v 1 h -1.0765 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5985"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -16.003,-0.77334 h 0.25145 v 1 H -16.003 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5983"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,1.2267 h 1.9967 v 1 h -1.9967 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5981"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.512,1.2267 h 2.1395 v 1 h -2.1395 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5979"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.216,1.2267 h 0.85436 v 1 H -21.216 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5977"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.205,1.2267 h 2.0125 v 1 h -2.0125 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5975"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -16.502,1.2267 h 0.53704 v 1 H -16.502 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5973"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.036,1.2267 h 1.3779 v 1 h -1.3779 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5971"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.808,1.2267 h 0.31492 v 1 H -15.808 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5969"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.337,1.2267 h 0.85436 v 1 H -15.337 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5967"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,9.2267 h 1.9967 v 1 h -1.9967 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5965"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.512,9.2267 h 2.1395 v 1 h -2.1395 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5963"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.216,9.2267 h 0.85436 v 1 H -21.216 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5961"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.205,9.2267 h 2.0125 v 1 h -2.0125 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5959"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -16.502,9.2267 h 0.53704 v 1 H -16.502 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5957"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.036,9.2267 h 1.3779 v 1 h -1.3779 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5955"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.808,9.2267 h 0.31492 v 1 H -15.808 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5953"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.337,9.2267 h 0.85436 v 1 H -15.337 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5951"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,5.2267 h 1.4414 v 1 h -1.4414 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5949"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -24.071,5.2267 h 2.3933 v 1 h -2.3933 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5947"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.525,5.2267 h 1.1558 v 1 h -1.1558 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5945"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.986,5.2267 h 0.91782 v 1 H -18.986 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5943"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -17.915,5.2267 h 1.4731 v 1 h -1.4731 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5941"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -16.289,5.2267 h 2.3933 v 1 h -2.3933 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5939"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.216,5.2267 h 1.0765 v 1 h -1.0765 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5937"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,7.2267 h 1.8063 v 1 h -1.8063 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5935"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -23.71,7.2267 h 0.56877 v 1 H -23.71 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5933"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -22.992,7.2267 h 1.2986 v 1 h -1.2986 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5931"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -21.544,7.2267 h 0.88609 v 1 H -21.544 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5929"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -20.509,7.2267 h 0.88608 v 1 H -20.509 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5927"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -19.474,7.2267 h 1.2827 v 1 h -1.2827 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5925"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.042,7.2267 h 0.26733 v 1 H -18.042 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5923"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -17.626,7.2267 h 1.6635 v 1 h -1.6635 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5921"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -15.813,7.2267 h 0.88609 v 1 H -15.813 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5919"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -25.666,11.227 h 0.72743 v 1 H -25.666 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5917"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -24.75,11.227 h 2.6472 v 1 H -24.75 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path5915"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -18.374,11.227 h -0.72743 v 1 h 0.72743 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6838"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m -19.29,11.227 h -2.6472 v 1 h 2.6472 v -1 z"
+ style="opacity:0.7;fill:#999999"
+ id="path6840"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="matrix(0.8214,0,0,0.82339,10.976,6.2715)"
+ id="g8626">
+ <path
+ d="m 2.0488,11.037 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81045,0.80363 1.2602,1.6413 1.0576,1.9306 -7.708e-4,0.0011 0.01977,0.01774 0.01898,0.01882 l 10.138,-10.18 c 0.258,-0.2581 -0.213,-1.1432 -1.051,-1.9744 -0.838,-0.8311 -1.728,-1.295 -1.986,-1.0366 l -10.138,10.18 z"
+ style="fill:url(#linearGradient3841);stroke:#0c0c0c;stroke-width:0.60798001;stroke-linejoin:round"
+ id="path3041"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 10.565,2.4841 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81044,0.80363 1.2602,1.6413 1.0576,1.9306 -7.69e-4,0.0011 0.01977,0.017738 0.01898,0.018821 l 1.552,-1.5571 c 0.409,-0.4086 -0.029,-1.0928 -0.981,-2.0447 -0.813,-0.8055 -1.674,-1.2696 -1.96,-1.0619 l -0.02525,0.025356 -1.6218,1.6271 z"
+ style="opacity:0.8;fill:#ffb6ed;stroke:#e28ccd;stroke-width:0.60798001;stroke-linejoin:round"
+ id="path3043"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 2.0488,11.037 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81044,0.80363 1.2602,1.6413 1.0576,1.9306 -7.714e-4,0.0011 0.01977,0.01774 0.01898,0.01882 l 6.982,-7.0109 0.02525,-0.025356 c 7.9e-4,-0.00108 -0.01975,-0.01772 -0.01898,-0.01882 0.203,-0.2885 -0.247,-1.1262 -1.058,-1.9298 -0.812,-0.8056 -1.6734,-1.2697 -1.9599,-1.062 l -0.025251,0.025355 -6.982,7.0109 z"
+ style="opacity:0.6;fill:#0c0c0c"
+ id="path3045"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.1785,3.8767 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81044,0.80363 1.2602,1.6413 1.0576,1.9306 -7.71e-4,0.0011 0.01977,0.017736 0.01898,0.01882 l 0.12626,-0.12678 c 7.9e-4,-0.00108 -0.01975,-0.01772 -0.01898,-0.01882 0.203,-0.2892 -0.246,-1.1269 -1.057,-1.9305 -0.812,-0.8056 -1.6737,-1.2697 -1.9602,-1.062 L 9.1792,3.8767 z"
+ style="fill:url(#linearGradient3843)"
+ id="path3047"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.0628,3.9929 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81044,0.80363 1.2602,1.6413 1.0576,1.9306 -7.71e-4,0.0011 0.01977,0.017738 0.01898,0.018822 l 0.126,-0.1268 c 7.9e-4,-0.00108 -0.01975,-0.017721 -0.01898,-0.018821 0.203,-0.2892 -0.247,-1.1269 -1.058,-1.9305 -0.812,-0.8056 -1.6734,-1.2697 -1.9599,-1.062 l -0.1263,0.1268 z"
+ style="fill:url(#linearGradient3845)"
+ id="path3049"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.5806,3.473 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81045,0.80363 1.2602,1.6413 1.0576,1.9306 -7.73e-4,0.0011 0.01977,0.017737 0.01898,0.01882 l 0.12626,-0.12678 c 7.91e-4,-0.00108 -0.01975,-0.017721 -0.01898,-0.01882 0.202,-0.2892 -0.247,-1.1269 -1.058,-1.9306 -0.812,-0.8055 -1.6736,-1.2696 -1.9601,-1.0619 l -0.1263,0.1268 z"
+ style="fill:url(#linearGradient3847)"
+ id="path3051"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.4649,3.5891 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81045,0.80363 1.2602,1.6413 1.0576,1.9306 -7.69e-4,0.0011 0.01977,0.017737 0.01898,0.018821 l 0.12626,-0.12678 c 7.9e-4,-0.00108 -0.01975,-0.01772 -0.01898,-0.01882 0.203,-0.2892 -0.247,-1.1269 -1.057,-1.9305 -0.813,-0.8055 -1.6743,-1.2696 -1.9608,-1.0619 l -0.1263,0.1267 z"
+ style="fill:url(#linearGradient3849)"
+ id="path3053"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.9846,3.0673 c 0.2864,-0.2077 1.1484,0.2564 1.9604,1.0619 0.81045,0.80363 1.2602,1.6413 1.0576,1.9306 -7.7e-4,0.0011 0.01977,0.017738 0.01898,0.018821 l 0.126,-0.1268 C 13.148372,5.950741 13.12783,5.9341 13.1286,5.933 13.3306,5.6438 12.8816,4.8061 12.0706,4.0024 11.2586,3.1969 10.3966,2.7328 10.1106,2.9405 L 9.9842,3.0673 z"
+ style="fill:url(#linearGradient3851)"
+ id="path3055"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 9.8689,3.1835 c 0.28654,-0.20771 1.148,0.25639 1.9603,1.0619 0.81044,0.80363 1.2602,1.6413 1.0576,1.9306 -7.71e-4,0.0011 0.01977,0.017737 0.01898,0.018821 l 0.12626,-0.12678 c 7.89e-4,-0.00108 -0.01975,-0.017722 -0.01898,-0.018821 0.203,-0.2892 -0.247,-1.1269 -1.057,-1.9306 -0.813,-0.8055 -1.674,-1.2696 -1.9608,-1.0619 l -0.1263,0.1268 z"
+ style="fill:url(#linearGradient3853)"
+ id="path3057"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 0.25981,15.794 4.7719,-1.7255 0.039308,-0.03926 c 0.2026,-0.289 -0.2524,-1.127 -1.0629,-1.93 -0.8124,-0.806 -1.6727,-1.268 -1.9592,-1.06 l -1.7891,4.755 z"
+ style="fill:url(#linearGradient3855);fill-rule:evenodd;stroke:url(#linearGradient3857);stroke-width:0.60798001"
+ id="path3059"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 0.74443,14.506 -0.48521,1.283 1.3014,-0.473 C 1.44692,15.182 1.34132,15.048 1.20622,14.914 1.05072,14.76 0.89917,14.633 0.74445,14.506 z"
+ style="fill:#0c0c0c;fill-rule:evenodd;stroke:#0c0c0c;stroke-width:0.60798001"
+ id="path3061"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="219"
+ x="-233"
+ height="24"
+ width="24"
+ id="edit"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-233.67409"
+ y="215.06073"
+ id="text3638-3-3-2"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0"
+ x="-233.67409"
+ y="215.06073">%%edit%%</tspan></text>
+ <g
+ style="fill-rule:evenodd"
+ id="g652"
+ transform="matrix(0.36763942,0,0,0.36763942,-262.23385,204.36126)">
+ <path
+ id="_70839568"
+ class="fil5"
+ d="m 245.14,76.1907 13.9222,-14.7283 c 0.0809,0.6801 0.1351,1.356 0.1601,2.0273 L 245.14,78.3993 c -0.4437,2.4162 -1.5951,4.4076 -3.4451,5.9744 -1.8593,1.5668 -3.9829,2.3501 -6.3802,2.3501 -2.8032,0 -5.04,-0.8777 -6.7295,-2.6238 -1.6896,-1.7555 -2.7372,-4.2945 -3.1431,-7.6356 0.3305,-0.4248 0.5003,-0.8306 0.5003,-1.2176 0,-0.5568 -0.2077,-0.9721 -0.6324,-1.2459 -0.4247,-0.2736 -1.0665,-0.4152 -1.9159,-0.4152 -1.7838,0 -2.6805,0.5758 -2.6805,1.7177 0,0.3871 0.1888,0.774 0.5568,1.161 -0.5757,3.5205 -1.68,6.116 -3.3034,7.7677 -1.6329,1.6611 -3.8792,2.4917 -6.739,2.4917 -2.7937,0 -5.1627,-1.0193 -7.0976,-3.058 -1.9349,-2.0292 -2.9069,-4.5303 -2.9069,-7.4751 l 17.95388,-14.916911 c 0.1275,0.7035 -0.14566,1.3868 -0.0287,1.9271 L 203.1865,76.1907 l 13.7895,0 c 1.6422,0 2.4634,-0.302 2.4634,-0.9156 l 0,-0.5285 c 0,-1.6611 1.3686,-2.4917 4.0963,-2.4917 1.0193,0 1.8781,0.236 2.605,0.7173 0.7172,0.5003 1.0759,1.0948 1.0759,1.7744 l 0,0.3586 c 0,0.7269 0.84,1.0855 2.5201,1.0855 z m -4.9834,1.3779 -8.2963,0 c -1.7556,0 -2.8788,0.1133 -3.379,0.3305 -0.5002,0.2265 -0.7457,0.7267 -0.7457,1.5195 0,1.444 0.7929,2.756 2.3691,3.9546 1.5762,1.1987 3.3129,1.8028 5.21,1.8028 1.8877,0 3.7281,-0.6985 5.5215,-2.1048 1.8026,-1.3969 2.6992,-2.8409 2.6992,-4.3133 0,-0.7929 -1.1231,-1.1893 -3.3788,-1.1893 z m -24.8039,0 -8.3813,0 c -1.7084,0 -2.7844,0.0661 -3.2186,0.1983 -0.434,0.1227 -0.6512,0.4529 -0.6512,0.991 0,1.4913 0.8967,2.9353 2.6806,4.3322 1.7932,1.3874 3.6526,2.0859 5.5874,2.0859 1.8971,0 3.6243,-0.6041 5.1817,-1.8028 1.5572,-1.1986 2.3407,-2.5389 2.3407,-4.0301 0,-0.7457 -0.2171,-1.2175 -0.6512,-1.444 -0.4343,-0.2172 -1.397,-0.3305 -2.8881,-0.3305 z"
+ style="fill:#1f1a17"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccsccscssccscccccsssscssscsscscscsssscscscscs" />
+ <path
+ id="_69925256"
+ class="fil16"
+ style="fill:#0093dd"
+ d="m 205.782,83.0901 c 1.7932,1.3873 3.6526,2.0858 5.5874,2.0858 1.8971,0 3.6243,-0.604 5.1817,-1.8027 1.5572,-1.1986 2.3407,-2.5388 2.3407,-4.0301 0,-0.7457 -0.2171,-1.2175 -0.6512,-1.4441 -0.4342,-0.2171 -1.3969,-0.3304 -2.8881,-0.3304 l -8.3813,0 c -1.7084,0 -2.7844,0.0661 -3.2186,0.1983 -0.434,0.1226 -0.6512,0.4529 -0.6512,0.991 0,1.4913 0.8967,2.9354 2.6806,4.3322 z"
+ mask="url(#id22)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_102160928"
+ class="fil16"
+ style="fill:#0093dd"
+ d="m 227.736,79.4186 c 0,1.444 0.7929,2.756 2.3691,3.9546 1.5762,1.1987 3.3129,1.8027 5.2099,1.8027 1.8877,0 3.7282,-0.6985 5.5215,-2.1047 1.8027,-1.3969 2.6993,-2.8409 2.6993,-4.3133 0,-0.7929 -1.1231,-1.1893 -3.3788,-1.1893 l -8.2963,0 c -1.7556,0 -2.8788,0.1133 -3.379,0.3304 -0.5002,0.2266 -0.7457,0.7268 -0.7457,1.5196 z"
+ mask="url(#id24)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_102006768"
+ class="fil1"
+ style="fill:#ffffff"
+ d="m 203.559,78.8618 c 0.0792,0.2221 0.6379,1.9862 2.9444,3.7595 2.3066,1.7733 4.404,1.9137 4.404,1.9137 l 0,0 c 0,0 -3.0482,-1.7023 -4.1102,-2.9098 -1.062,-1.2074 -1.5803,-2.0926 -2.0079,-2.8379 -0.4277,-0.7454 -1.3432,-0.7296 -1.2303,0.0745 z"
+ mask="url(#id26)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_49335328"
+ class="fil1"
+ style="fill:#ffffff"
+ d="m 206.61,78.6036 c 0.0229,0.0846 0.2318,0.7226 0.9997,1.4637 0.768,0.7412 1.3655,1.0035 1.3655,1.0035 l -0.7343,-0.6097 0.9289,0.4597 c 0,0 -1.0843,-0.8877 -1.4958,-1.3468 -0.4116,-0.459 -0.5193,-0.7935 -0.6056,-0.9623 -0.0864,-0.1688 -0.4042,-0.2187 -0.4584,-0.0081 z"
+ mask="url(#id28)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_102104840"
+ class="fil1"
+ style="fill:#ffffff"
+ d="m 242.831,79.168 c -0.0957,0.2155 -0.7183,2.0591 -3.0646,3.7111 -2.3464,1.652 -4.2378,1.8675 -4.2378,1.8675 l -0.4549,-0.4788 c 0,0 3.711,-1.8436 5.0518,-3.352 1.3408,-1.5084 1.4845,-1.9154 1.4845,-1.9154 0.6599,-1.0187 1.3934,-0.5479 1.221,0.1676 z"
+ mask="url(#id30)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_102575136"
+ class="fil1"
+ style="fill:#ffffff"
+ d="m 240.841,79.0105 c -0.0569,0.128 -0.4266,1.2229 -1.82,2.204 -1.3935,0.9811 -2.5168,1.1091 -2.5168,1.1091 l -0.2702,-0.2844 c 0,0 2.204,-1.0949 3.0002,-1.9907 0.7963,-0.8958 0.8816,-1.1375 0.8816,-1.1375 0.4924,-0.7043 0.9573,-0.5546 0.7252,0.0995 z"
+ mask="url(#id32)"
+ inkscape:connector-curvature="0" />
+ <path
+ id="_102768136"
+ class="fil1"
+ style="fill:#ffffff"
+ d="m 228.303,78.2913 c -0.0134,0.1394 -0.2298,1.2745 0.5013,2.8139 0.7311,1.5395 1.6463,2.2032 1.6463,2.2032 l 0.3751,-0.1148 c 0,0 -1.3809,-2.037 -1.6339,-3.2085 -0.253,-1.1716 -0.0323,-2.0424 -0.0323,-2.0424 -0.315,-0.0102 -0.5765,0.122 -0.8565,0.3486 z"
+ mask="url(#id34)"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="219"
+ x="-190"
+ height="24"
+ width="24"
+ id="debug_instance"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-204"
+ y="215"
+ id="text3638-3-3-2-0"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6"
+ x="-204"
+ y="215">%%debug_instance%%</tspan></text>
+ <g
+ style="display:inline"
+ id="layer1-8"
+ transform="matrix(0.23162724,0,0,0.23162724,-154.66454,145.99446)">
+ <rect
+ style="opacity:0.74621211;fill:url(#linearGradient9477);fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect3108"
+ transform="matrix(0.9981479,-0.0608342,0.1048727,0.9944857,0,0)"
+ y="340.71729"
+ x="63.882298"
+ height="72.445557"
+ width="74.619125" />
+ <rect
+ style="fill:url(#linearGradient9474);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.31597084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect1307"
+ transform="matrix(0.9981479,-0.0608342,0.1048727,0.9944857,0,0)"
+ y="337.67053"
+ x="60.043865"
+ height="72.445557"
+ width="74.619125" />
+ <g
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="g4884"
+ transform="matrix(0.2219064,-0.01352455,0.02485183,0.2356647,62.010554,262.81217)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13866174px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.59230783"
+ id="path4870"
+ d="m 186.47628,304.64652 c 0,294.06348 0,298.06436 0,298.06436"
+ inkscape:connector-curvature="0" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4872"
+ transform="translate(-48.94082,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4874"
+ transform="translate(146.8225,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4876"
+ transform="translate(48.94083,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4878"
+ transform="translate(97.88164,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4880"
+ transform="translate(195.7633,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4882"
+ transform="translate(244.7041,-4.83334e-5)" />
+ </g>
+ <g
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="g4893"
+ transform="matrix(0.02570346,0.2437405,-0.2145541,0.01307645,227.24128,291.2289)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13866174px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.59230783"
+ id="path4895"
+ d="m 186.47628,304.64652 c 0,294.06348 0,298.06436 0,298.06436"
+ inkscape:connector-curvature="0" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4897"
+ transform="translate(-48.94082,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4899"
+ transform="translate(146.8225,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4901"
+ transform="translate(48.94083,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4903"
+ transform="translate(97.88164,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4905"
+ transform="translate(195.7633,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4907"
+ transform="translate(244.7041,-4.83334e-5)" />
+ </g>
+ <path
+ style="fill:none;stroke:#f90000;stroke-width:1.96257424;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3079"
+ d="m 101.87045,376.48331 c 15.88269,-21.97532 15.54068,-22.2933 15.54068,-22.2933 l 11.1987,-2.03784 15.62825,-9.76202 8.0733,23.90355 11.92414,-15.63516 6.6307,13.14898"
+ inkscape:connector-curvature="0" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="219"
+ x="-134.625"
+ height="24"
+ width="24"
+ id="instance_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-148"
+ y="215"
+ id="text3638-3-3-2-0-9"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8"
+ x="-148"
+ y="215">%%instance_graph%%</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4200"
+ style="opacity:0.5;fill:url(#radialGradient3137)"
+ d="m 4.38811,202.24651 a 11.999905,2.0000054 0 0 1 -23.999809,0 11.999905,2.0000054 0 1 1 23.999809,0 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect2594-05"
+ style="fill:url(#linearGradient3246);stroke:url(#linearGradient3248);stroke-width:0.99914002;stroke-linejoin:round"
+ d="M -16.111699,182.74627 H 0.88831 v 17 h -17.000009 v -17 z" />
+ <rect
+ id="rect2875"
+ style="fill:url(#linearGradient4174);stroke:#699536;stroke-linejoin:round"
+ height="4"
+ width="17"
+ y="182.74625"
+ x="-16.111702" />
+ <rect
+ id="rect4194"
+ style="fill:#bebebe"
+ height="1"
+ width="16"
+ y="189.24625"
+ x="-15.611702" />
+ <rect
+ id="rect4192"
+ style="fill:#bebebe"
+ height="1"
+ width="16"
+ y="192.24625"
+ x="-15.611702" />
+ <rect
+ id="rect4196"
+ style="fill:url(#linearGradient4198);stroke:#699536;stroke-linejoin:round"
+ transform="matrix(0,1,-1,0,0,0)"
+ height="3"
+ width="12"
+ y="13.111702"
+ x="187.74625" />
+ <g
+ transform="translate(-25.892279,180.35357)"
+ id="layer1-0">
+ <g
+ id="g3490-1"
+ style="stroke-width:1.88259995"
+ transform="matrix(0.54593,0,0,0.51685,-0.96573,-0.57818)">
+ <g
+ id="g5022-9"
+ style="stroke-width:104.95999908"
+ transform="matrix(0.021652,0,0,0.014857,43.008,42.685)" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path1432"
+ style="fill:url(#linearGradient5274);stroke:url(#linearGradient5276);stroke-linecap:round;stroke-linejoin:round;display:block"
+ d="m 17.5,7.4997 6,5.3693 -6,5.631 V 15.4994 H 7.5004 v -4.999 H 17.5 V 7.4998 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3777"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient5278);display:block"
+ d="M 18.531,8.7812 V 10 A 0.51754,0.51754 0 0 1 18,10.531 H 9.4375 l 0.03125,2.9375 h 8.5312 a 0.51754,0.51754 0 0 1 0.531,0.532 v 1.1562 l 3.469,-3.281 -3.469,-3.0938 z"
+ transform="translate(0,0.99987)" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="180.64645"
+ x="-19.585785"
+ height="24"
+ width="24"
+ id="export_graph"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-30.037788"
+ y="177.11613"
+ id="text3638-3-3-2-0-9-8-5-5"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9"
+ x="-30.037788"
+ y="177.11613">%%export_graph%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-165"
+ height="24"
+ width="24"
+ id="down"
+ style="opacity:0;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans"
+ x="-167"
+ y="177"
+ id="text3638-3-3-29"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-3"
+ x="-167"
+ y="177">%%down%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="219"
+ x="-265.58582"
+ height="24"
+ width="24"
+ id="top"
+ style="opacity:0;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans"
+ x="-265.58582"
+ y="215"
+ id="text3638-3-3-29-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-3-4"
+ x="-265.58582"
+ y="215">%%top%%</tspan></text>
+ <g
+ id="layer1-06"
+ transform="matrix(0,1,1,0,-258.5827,219.99205)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3169-2-3-9"
+ style="fill:none;stroke:url(#linearGradient3732-6);stroke-width:5;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.65,-0.525 3.5,4.9999 8.65,10.525" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3765-1"
+ style="fill:none;stroke:url(#linearGradient4452);stroke-width:6;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 19,5 7.5,4.9999" />
+ <g
+ id="g6115-7"
+ style="enable-background:new"
+ transform="matrix(1,0,0,-1,56.577,170)">
+ <g
+ id="g4018-8"
+ style="stroke:url(#linearGradient3795)"
+ transform="translate(-1,0)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3395-1"
+ style="fill:none;stroke:url(#linearGradient3102);stroke-width:4;stroke-linecap:round;enable-background:new"
+ d="m -53.577,158 v 14" />
+ </g>
+ <g
+ id="g4030-5"
+ style="stroke:url(#linearGradient4040-8-9-7);enable-background:new"
+ transform="translate(39,0)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3397-3"
+ style="fill:none;stroke:url(#linearGradient3965);stroke-width:2;stroke-linecap:round;enable-background:new"
+ d="m -93.577,158 v 14" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path3397-6-7"
+ style="text-indent:0;text-transform:none;block-progression:tb;opacity:0.6;color:#000000;fill:none;stroke:#f6daae;stroke-linecap:round;enable-background:new"
+ d="m -54.671,172.5 c -0.2292,-0.0437 -0.41011,-0.2667 -0.406,-0.5 v -14" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4277-4"
+ style="fill:none;stroke:url(#linearGradient4322-9);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.7501,-0.62517 3.5,4.99993 l 5.2502,5.6251" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4279-2"
+ style="fill:none;stroke:url(#linearGradient4324-6);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 19,5 7.5,4.9999" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4454-69"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3104-9);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="m 3.6544,6.704 4.1268,4.44 c 1,1 3.059,-0.06138 1.5,-1.5 L 7.5,8 C 6.4607,7.0409 7.5,6.5 9,6.5 h 9.5 c 3,0 2.5,-3 0,-3 h -9 C 8,3.5 6.5,3.5 7.5,2 L 9.1438,0.7688 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 3.6752,3.3121" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4464-9"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient4462-4);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 7.5,2 9.35,0.4938 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 3.6043,3.3762 m 0.0076,3.3096 4.1006,4.5952 c 1,1 2.9702,0.02922 1.5,-1.5 L 7.5,8" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4466-57"
+ style="opacity:0.51000001;fill:none;stroke:#f6daae;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.2625,-1.35 3.8123,3.1742" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-265.58582"
+ height="24"
+ width="24"
+ id="add_element"
+ style="opacity:0;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans"
+ x="-276.58582"
+ y="177"
+ id="text3638-3-3-29-8-1"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-3-4-3"
+ x="-276.58582"
+ y="177">%%add_element%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-213.58582"
+ height="24"
+ width="24"
+ id="remove_element"
+ style="opacity:0;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans"
+ x="-227.08582"
+ y="177"
+ id="text3638-3-3-29-8-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-3-4-7"
+ x="-227.08582"
+ y="177">%%remove_element%%</tspan></text>
+ <g
+ transform="translate(-265.9142,180.9194)"
+ id="layer1-2">
+ <path
+ inkscape:connector-curvature="0"
+ id="path2262-4"
+ style="fill:url(#linearGradient2400);fill-rule:evenodd;stroke:#699536;stroke-linejoin:round"
+ d="M 8.5664,8.4948 V 2.5 H 15.5 v 5.9948 h 6 V 15.5 h -6 v 6 H 8.5603 l -0.027869,-6 h -6.0324 V 8.4948 h 6.0664 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path2272-4"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3019-5)"
+ d="M 9.5,9.4948 V 3.5 h 5 v 5.9948 h 6 V 14.5 h -6 v 6 h -5 v -6 h -6 V 9.4948 h 6 z" />
+ </g>
+ <g
+ id="layer1-07"
+ transform="matrix(0,-1,1,0,-158.12521,204.00001)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3169-2-3-8"
+ style="fill:none;stroke:url(#linearGradient3060);stroke-width:5;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.4802,-1.4936 2.5,4.9999 8.4802,11.494" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3765-51"
+ style="fill:none;stroke:url(#linearGradient3732-2);stroke-width:6;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 19,5 6.5,4.9999" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4277-1"
+ style="fill:none;stroke:url(#linearGradient3062);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.5111,-1.4554 2.5,4.9999 8.5112,11.455" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4279-6"
+ style="fill:none;stroke:url(#linearGradient4322-2);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 19,5 6.5,4.9999" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4454-5"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3064);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 8.9731,10.405 6.5,8 C 5.4861,7.0141 6.5,6.5 8,6.5 h 10.5 c 3,0 2.5,-3 0,-3 h -10 c -1.5,0 -3,0 -2,-1.5 l 2.4048,-2.3382 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 5.4731,5.9048 c 1,1 3.0209,-0.02115 1.5,-1.5 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4464-7"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient4462-5);stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="m 6.5,2 2.3343,-2.3364 c 1.5,-2 -0.5,-2.5 -1.5,-1.5 L 2,4 C 1.5,4.5 1.4724,5.3333 2,6 l 5.4735,5.9731 c 1,1 3.0001,-1.32e-4 1.5,-1.5 L 6.5,8.0001" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4466-9"
+ style="opacity:0.51000001;fill:none;stroke:#f6daae;stroke-linecap:round;stroke-linejoin:round;enable-background:new"
+ d="M 9.1415,-0.64683 6.3064,2.36687" />
+ </g>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-100"
+ height="24"
+ width="24"
+ id="reset"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-102"
+ y="177"
+ id="text3638-3-3-2-0-9-8"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4"
+ x="-102"
+ y="177">%%reset%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="181"
+ x="-62.818016"
+ height="24"
+ width="24"
+ id="current"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="-67.292892"
+ y="177"
+ id="text3638-3-3-2-0-9-8-0"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-7"
+ x="-67.292892"
+ y="177">%%current%%</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ d="m -192.2071,189.49482 0,7.00518 -19,0 0,-7.00518 19,0 z"
+ id="path2262-0"
+ style="fill:url(#linearGradient7189);fill-opacity:1;fill-rule:evenodd;stroke:#a53738;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m -193.2071,190.49482 0,5.00518 -17,0 0,-5.00518 17,0 z"
+ id="path2272-3"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient2833);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+ <g
+ transform="translate(-99.945143,180.87525)"
+ id="layer1-33">
+ <g
+ id="g2524"
+ transform="translate(-0.47496,0)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path6017-8"
+ style="opacity:0.25;fill:url(#radialGradient2541)"
+ d="m 58,69 a 20,9 0 1 1 -40,0 20,9 0 1 1 40,0 z"
+ transform="matrix(0.52499,0,0,0.3518,-7.4748,-3.418)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5371"
+ style="fill:url(#linearGradient2543);fill-rule:evenodd;stroke:url(#linearGradient2545);stroke-width:1px"
+ d="m 18.037,0.52328 c -4.307,0.05575 -4.934,8.5737 -6.169,10.109 l 2.1,0.75856 C 15.35,9.08294 22.24,1.41264 18.463,0.53984 18.316,0.52327 18.176,0.52132 18.037,0.52312 z m -0.542,1.0884 c 0.10162,-0.00989 0.19319,0.00302 0.2789,0.049472 0.34285,0.18581 0.37991,0.78884 0.08203,1.3357 -0.29788,0.54689 -0.82198,0.84543 -1.1648,0.65962 -0.343,-0.1858 -0.38,-0.7889 -0.082,-1.3358 0.224,-0.4101 0.581,-0.6794 0.886,-0.709 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5992"
+ style="opacity:0.26667002;fill:none;stroke:url(#linearGradient2547);stroke-width:1.89989996px"
+ d="m 36.625,4.4375 c -1.4036,0.018077 -2.4077,0.62407 -3.4688,1.9062 -1.0611,1.2822 -2.0112,3.2438 -2.7812,5.3438 -0.77003,2.0999 -1.388,4.3243 -1.9688,6.2812 -0.27707,0.9336 -0.50201,1.7858 -0.78125,2.5625 l 0.46875,0.1875 c 1.8051,-2.666 4.7391,-6.2904 7.0312,-10 1.297,-2.1008 2.232,-4.1053 2.406,-5.22 0.087,-0.5574 -0.01,-0.8073 -0.031,-0.8438 -0.018,-0.032 -0.113,-0.0896 -0.469,-0.1874 -0.125,-0.0079 -0.24,-0.0334 -0.406,-0.0313 z"
+ transform="matrix(0.52499,0,0,0.5277,-1.1749,-0.77947)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5367"
+ style="fill:url(#linearGradient2549);fill-rule:evenodd;stroke:url(#linearGradient2551);stroke-miterlimit:20"
+ d="m 10.637,7.9275 c 0.37249,1.057 -0.28422,1.9724 -0.52499,2.6385 -2.7255,0.81565 -4.613,5.1206 -7.4195,7.3632 0.35058,0.36801 0.76241,0.76209 1.1196,1.0799 l 2.1328,-1.8634 -1.6104,2.3876 c 1.1598,0.9451 2.3651,1.5866 3.1526,1.8504 l 1.4437,-1.9294 -0.91874,2.1932 c 0.76229,0.31075 2.6172,0.78206 3.675,0.79154 l 1.052,-1.7955 -0.26941,1.8285 c 0.43551,0.08357 1.28,0.19747 1.8423,0.21438 1.7651,-2.9023 2.1,-7.6351 1.05,-9.7459 -0.2625,-1.0554 0.78749,-2.3746 1.575,-2.9023 -1.312,-1.0561 -4.307,-2.2718 -6.3,-2.1115 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5975-1"
+ style="opacity:0.26667002;fill:#c4a000;fill-rule:evenodd"
+ d="M 4.6001,19.537 C 7.1287,16.893 6.9442,15.735 9.911,13.454 8.3452,16.002 8.0914,17.327 6.175,20.592 L 4.6,19.5366 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5979-4"
+ style="opacity:0.41569004;fill:#c4a000;fill-rule:evenodd"
+ d="m 8.3537,21.491 2.2816,-5.1926 c 0.87386,-1.5745 1.5013,-3.1504 2.3777,-4.4049 -0.84102,2.8591 -2.5676,6.3966 -3.9539,9.8283 l -0.7051,-0.23 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6014"
+ style="opacity:0.47843;fill:none;stroke:url(#linearGradient2553);stroke-width:1px"
+ d="m 11.547,8.8512 c 0.05079,1.0106 -0.80302,1.4666 -0.95832,1.8962 -0.05465,0.15024 0.27818,0.66925 -0.01074,0.58778 -2.5453,1.3384 -5.9207,5.8907 -6.7749,6.754 l 6.357,-6.167 -4.279,7.238 c 0.66676,0.54227 0.39408,0.55888 1.1839,0.87793 l 5.6511,-7.211 -3.3911,8.245 c 0.83766,0.27739 0.75453,0.22673 1.724,0.3645 l 3.6086,-5.9583 -1.1073,6.1533 0.14835,0.04006 c 0.75243,-1.338 0.72682,-2.2928 1.0979,-3.9036 0.36799,-1.5972 -0.29214,-3.5975 -0.26605,-4.6086 -0.1821,-0.73214 0.4285,-1.5354 0.79908,-2.1313 0.22412,-0.36043 -0.23142,-0.50359 0.04923,-0.77325 -0.615,-0.4125 -0.936,-0.413 -1.872,-0.753 -0.925,-0.3358 -1.221,-0.6399 -1.96,-0.6498 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5977-4"
+ style="opacity:0.24706002;fill:url(#linearGradient2555);fill-rule:evenodd"
+ d="m 9.8336,21.911 c 1.1432,-3.0352 3.3578,-5.3133 4.2088,-8.4091 0.02706,1.9325 0.1311,5.3845 -0.99324,8.8213 -0.16788,9.37e-4 -0.09131,-0.05847 -0.24668,-0.06256 l 0.42041,-3.0208 -1.7228,2.931 c -1.1502,-0.08249 -0.67966,0.0172 -1.6666,-0.25992 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5995-8"
+ style="opacity:0.48235001;fill:#c4a000;fill-rule:evenodd"
+ d="m 10.902,11.152 c -0.65729,0.32526 -1.0826,0.80594 -1.5661,1.2477 0.6439,-0.37395 1.249,-0.76463 2.0997,-1.0494 l -0.53364,-0.19823 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5997-3"
+ style="opacity:0.48235001;fill:#c4a000;fill-rule:evenodd"
+ d="m 11.955,11.39 -0.53364,1.0494 1.5649,-0.83811 -1.0312,-0.21134 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6007-5"
+ style="opacity:0.48235001;fill:#c4a000;fill-rule:evenodd"
+ d="M 11.918,8.6987 11.448,9.5995 12.27,9.8644 11.918,8.6987 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6009-9"
+ style="opacity:0.48235001;fill:#c4a000;fill-rule:evenodd"
+ d="m 15.585,9.714 -1.2916,0.96856 0.822,0.26495 0.46962,-1.2335 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6011-2"
+ style="opacity:0.48235001;fill:#c4a000;fill-rule:evenodd"
+ d="m 13.532,8.6866 -0.79479,1.3322 0.822,0.26495 -0.0272,-1.5972 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6013-6"
+ style="opacity:0.2;fill-rule:evenodd"
+ d="m 9.8331,10.843 c -0.29756,-1.5649 4.8997,1.1361 5.7943,1.5801 -0.0034,0.56942 0,1.0554 -0.51282,1.0554 -1.3479,-0.73507 -3.431,-1.9024 -5.2814,-2.6355 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5373"
+ style="fill:url(#linearGradient2557);fill-rule:evenodd;stroke:#464646;stroke-width:1px;stroke-linejoin:round"
+ d="m 9.6663,10.566 c -0.25641,-0.5277 0,-1.0554 0.51282,-1.0554 2.0955,0.51472 3.8455,1.1642 5.6411,2.1108 0.25641,0.5277 0,1.0554 -0.51282,1.0554 -1.8553,-0.98385 -3.605,-1.6226 -5.6411,-2.1108 z" />
+ </g>
+ </g>
+ <g
+ transform="translate(-62.939338,180.80705)"
+ id="layer1-63">
+ <path
+ inkscape:connector-curvature="0"
+ id="path2339"
+ style="fill:url(#linearGradient3328);fill-rule:evenodd;stroke:#888a86;stroke-width:0.99900001;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:0.7"
+ d="m 22.494,6.4996 c 0.01344,3.8458 6.88e-4,8.0483 0,12.001 h -2.9942 v -5.8047 l -9,5.5438 V 12.6633 L 1.4937,18.2397 V 6.7607 l 9.006,5.576 V 6.7603 l 9,5.5438 c -8.42e-4,-1.8634 0,-4.0628 0,-5.8047 h 2.9942 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3192"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3203);stroke-linecap:square;stroke-dashoffset:0.7"
+ d="M 11.5,16.472 V 8.5271 l 6.5,3.9727 -6.5,3.9722 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3190"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3199);stroke-linecap:square;stroke-dashoffset:0.7"
+ d="M 2.4912,16.474 2.4956,8.5217 9,12.5 2.4912,16.474 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path2343"
+ style="opacity:0.4;fill:none;stroke:url(#linearGradient3035);stroke-width:0.96779001;stroke-linecap:square;stroke-dashoffset:0.7"
+ d="m 20.5,7.4839 h 1.0104 v 10.032 H 20.5 V 7.4839 z" />
+ </g>
+ <g
+ style="display:inline"
+ id="layer1-8-6"
+ transform="matrix(0.13864219,0,0,0.13864219,493.32194,-42.754308)">
+ <rect
+ style="opacity:0.74621211;fill:url(#linearGradient9477-2);fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="rect3108-4"
+ transform="matrix(0.9981479,-0.0608342,0.1048727,0.9944857,0,0)"
+ y="340.71729"
+ x="63.882298"
+ height="72.445557"
+ width="74.619125" />
+ <rect
+ style="fill:url(#linearGradient9474-98);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.31597084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect1307-1"
+ transform="matrix(0.9981479,-0.0608342,0.1048727,0.9944857,0,0)"
+ y="337.67053"
+ x="60.043865"
+ height="72.445557"
+ width="74.619125" />
+ <g
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="g4884-7"
+ transform="matrix(0.2219064,-0.01352455,0.02485183,0.2356647,62.010554,262.81217)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13866174px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.59230783"
+ id="path4870-1"
+ d="m 186.47628,304.64652 c 0,294.06348 0,298.06436 0,298.06436"
+ inkscape:connector-curvature="0" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4872-4"
+ transform="translate(-48.94082,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4874-2"
+ transform="translate(146.8225,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4876-1"
+ transform="translate(48.94083,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4878-1"
+ transform="translate(97.88164,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4880-4"
+ transform="translate(195.7633,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4882-4"
+ transform="translate(244.7041,-4.83334e-5)" />
+ </g>
+ <g
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="g4893-8"
+ transform="matrix(0.02570346,0.2437405,-0.2145541,0.01307645,227.24128,291.2289)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13866174px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.59230783"
+ id="path4895-7"
+ d="m 186.47628,304.64652 c 0,294.06348 0,298.06436 0,298.06436"
+ inkscape:connector-curvature="0" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4897-5"
+ transform="translate(-48.94082,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4899-8"
+ transform="translate(146.8225,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4901-6"
+ transform="translate(48.94083,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4903-4"
+ transform="translate(97.88164,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4905-9"
+ transform="translate(195.7633,-4.83334e-5)" />
+ <use
+ xlink:href="#path4870-1"
+ height="1052.3622"
+ width="744.09448"
+ y="0"
+ x="0"
+ style="stroke:#000000;stroke-opacity:0.59230783"
+ id="use4907-6"
+ transform="translate(244.7041,-4.83334e-5)" />
+ </g>
+ <path
+ style="fill:none;stroke:#f90000;stroke-width:1.96257424;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3079-5"
+ d="m 101.87045,376.48331 c 15.88269,-21.97532 15.54068,-22.2933 15.54068,-22.2933 l 11.1987,-2.03784 15.62825,-9.76202 8.0733,23.90355 11.92414,-15.63516 6.6307,13.14898"
+ inkscape:connector-curvature="0" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+ x="20.5"
+ y="177.11613"
+ id="text3638-3-3-2-0-9-8-5-5-9"><tspan
+ sodipodi:role="line"
+ id="tspan3640-1-8-0-6-8-4-3-9-0"
+ x="20.5"
+ y="177.11613">%%fit%%</tspan></text>
+ <rect
+ inkscape:label="#rect3636"
+ y="180.75"
+ x="19"
+ height="24"
+ width="24"
+ id="fit"
+ style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <g
+ transform="translate(18.875,180.375)"
+ id="g6918">
+ <g
+ id="g3217"
+ transform="matrix(1.0562,0,0,1.205,-0.67489,-5.921)">
+ <rect
+ id="rect4173-9"
+ style="opacity:0.23613;fill:url(#linearGradient5533)"
+ height="3.3194001"
+ width="14.992"
+ y="20.681"
+ x="4.5042" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5058-55"
+ style="opacity:0.23613;fill:url(#radialGradient5535)"
+ d="m 19.496,20.681 v 3.3192 c 1.5989,0.0062 3.8653,-0.74366 3.8653,-1.6598 0,-0.91615 -1.7842,-1.6594 -3.8653,-1.6594 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5018-3"
+ style="opacity:0.23613;fill:url(#radialGradient5537)"
+ d="m 4.5042,20.681 v 3.3192 c -1.5989,0.0062 -3.8653,-0.74366 -3.8653,-1.6598 0,-0.91615 1.7842,-1.6594 3.8653,-1.6594 z" />
+ </g>
+ <rect
+ id="rect1887"
+ style="fill:url(#linearGradient5565);stroke:#565853;stroke-width:0.99994999"
+ rx="1"
+ ry="1"
+ height="19"
+ width="21"
+ y="2.5"
+ x="1.5" />
+ <rect
+ id="rect2779"
+ style="opacity:0.2;fill:none;stroke:url(#linearGradient5562-5);stroke-width:0.99993998"
+ rx="0.5"
+ ry="0.5"
+ height="17"
+ width="19"
+ y="3.5"
+ x="2.5" />
+ <rect
+ id="rect6287"
+ style="fill:url(#linearGradient5559)"
+ rx="0.50016999"
+ ry="0.5"
+ height="13"
+ width="20"
+ y="8"
+ x="2" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6293"
+ style="fill:#ffc24c"
+ d="m 21,5 c 0,0.5522 -0.448,1 -1,1 -0.552,0 -1,-0.4478 -1,-1 0,-0.5522 0.448,-1 1,-1 0.55245,0 1.0002,0.44779 1,1 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6622"
+ style="fill:#ffc24c"
+ d="m 18,5 c 2.38e-4,0.55221 -0.44755,1 -1,1 -0.55246,0 -1.0002,-0.44779 -1,-1 -2.41e-4,-0.55221 0.44754,-1 1,-1 0.55245,0 1.0002,0.44779 1,1 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6630"
+ style="fill:#ffc24c"
+ d="m 15,5 c 2.38e-4,0.55221 -0.44755,1 -1,1 -0.55246,0 -1.0002,-0.44779 -1,-1 -2.41e-4,-0.55221 0.44754,-1 1,-1 0.55245,0 1.0002,0.44779 1,1 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect5590"
+ style="fill:url(#linearGradient6639)"
+ d="M 9,9 7.5858,10.414 6.1716,9 4.7574,10.414 6.1716,11.828 4.7574,13.243 H 9 V 9 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6641"
+ style="fill:url(#linearGradient6643)"
+ d="M 9,20.243 7.5858,18.828 6.1716,20.243 4.7574,18.828 6.1716,17.414 4.7574,16 H 9 v 4.243 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6645"
+ style="fill:url(#linearGradient6647)"
+ d="m 15,8.7574 1.4142,1.4142 1.4142,-1.4142 1.4142,1.4142 -1.4142,1.4142 1.415,1.414 h -4.243 V 8.7572 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6649"
+ style="fill:url(#linearGradient3099-5)"
+ d="m 15,20.243 1.4142,-1.4142 1.4142,1.4142 1.4142,-1.4142 -1.414,-1.415 1.415,-1.414 h -4.243 v 4.2426 z" />
+ </g>
+ </g>
+</svg>
Binary file images/poe.ico has changed
Binary file images/poe.png has changed
Binary file images/print.png has changed
Binary file images/redo.png has changed
Binary file images/remove_element.png has changed
Binary file images/reset.png has changed
Binary file images/save.png has changed
Binary file images/saveas.png has changed
Binary file images/select.png has changed
Binary file images/top.png has changed
Binary file images/undo.png has changed
Binary file images/up.png has changed
Binary file locale/fr_FR/LC_MESSAGES/Beremiz.mo has changed
Binary file locale/ko_KR/LC_MESSAGES/Beremiz.mo has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/.cvsignore Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1 @@
+*.pyc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/TC6_XML_V10.xsd Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1361 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://www.plcopen.org/xml/tc6.xsd" xmlns:ppx="http://www.plcopen.org/xml/tc6.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xhtml="http://www.w3.org/1999/xhtml" elementFormDefault="qualified" attributeFormDefault="unqualified">
+ <xsd:element name="project">
+ <xsd:annotation>
+ <xsd:documentation>The complete project</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="fileHeader">
+ <xsd:complexType>
+ <xsd:attribute name="companyName" type="xsd:string" use="required"/>
+ <xsd:attribute name="companyURL" type="xsd:anyURI" use="optional"/>
+ <xsd:attribute name="productName" type="xsd:string" use="required"/>
+ <xsd:attribute name="productVersion" type="xsd:string" use="required"/>
+ <xsd:attribute name="productRelease" type="xsd:string" use="optional"/>
+ <xsd:attribute name="creationDateTime" type="xsd:dateTime" use="required"/>
+ <xsd:attribute name="contentDescription" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contentHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Comment" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="coordinateInfo">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pageSize" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="fbd">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ld">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="sfc">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="version" type="xsd:string" use="optional"/>
+ <xsd:attribute name="modificationDateTime" type="xsd:dateTime" use="optional"/>
+ <xsd:attribute name="organization" type="xsd:string" use="optional"/>
+ <xsd:attribute name="author" type="xsd:string" use="optional"/>
+ <xsd:attribute name="language" type="xsd:language" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Documentation language of the project e.g. "en-US"</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="types">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataTypes">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="pous">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pou" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="interface" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="returnType" type="ppx:dataType" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="localVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="tempVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="externalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="accessVars" type="ppx:varList"/>
+ </xsd:choice>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transitions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="transition" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="body" type="ppx:body" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="pouType" type="ppx:pouType" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="instances">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configurations">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configuration" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of resources and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="resource" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of programs and tasks and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="task" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a periodic or triggered task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="single" type="xsd:string" use="optional"/>
+ <xsd:attribute name="interval" type="xsd:time" use="optional"/>
+ <xsd:attribute name="priority" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="dataType">
+ <xsd:annotation>
+ <xsd:documentation>A generic data type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:group ref="ppx:elementaryTypes"/>
+ <xsd:group ref="ppx:derivedTypes"/>
+ <xsd:group ref="ppx:extended"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="rangeSigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with signed bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:long" use="required"/>
+ <xsd:attribute name="upper" type="xsd:long" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="rangeUnsigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with unsigned bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="upper" type="xsd:unsignedLong" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="value">
+ <xsd:annotation>
+ <xsd:documentation>A generic value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="simpleValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Value that can be represented as a single token string </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="arrayValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Array value consisting of a list of occurrances - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="repetitionValue" type="xsd:unsignedLong" use="optional" default="1"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="structValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Struct value consisting of a list of member - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="member" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="body">
+ <xsd:annotation>
+ <xsd:documentation>Implementation part of a POU, action or transistion</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice>
+ <xsd:element name="IL" type="ppx:formattedText"/>
+ <xsd:element name="ST" type="ppx:formattedText"/>
+ <xsd:element name="FBD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="LD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="SFC">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ <xsd:group ref="ppx:sfcObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="varList">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations that share the same memory attributes (CONSTANT, RETAIN, NON_RETAIN, PERSISTENT)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varListPlain">
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="constant" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="retain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonretain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="persistent" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonpersistent" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="varListPlain">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations without attributes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Declaration of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="type" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="address" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="position">
+ <xsd:annotation>
+ <xsd:documentation>Defines a graphical position in X, Y coordinates</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="connection">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0">
+ <xsd:element name="position" type="ppx:position" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation>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).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="refLocalId" type="xsd:unsignedLong" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Identifies the element the connection starts from.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>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. </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointIn">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the consumer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:choice minOccurs="0">
+ <xsd:element name="connection" type="ppx:connection" maxOccurs="unbounded"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointOut">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the producer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="expression" type="xsd:string" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="pouInstance">
+ <xsd:annotation>
+ <xsd:documentation>Represents a program or function block instance either running with or without a task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="type" type="ppx:pouType" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="formattedText">
+ <xsd:annotation>
+ <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:group name="elementaryTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of elementary IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="BOOL"/>
+ <xsd:element name="BYTE"/>
+ <xsd:element name="WORD"/>
+ <xsd:element name="DWORD"/>
+ <xsd:element name="LWORD"/>
+ <xsd:element name="SINT"/>
+ <xsd:element name="INT"/>
+ <xsd:element name="DINT"/>
+ <xsd:element name="LINT"/>
+ <xsd:element name="USINT"/>
+ <xsd:element name="UINT"/>
+ <xsd:element name="UDINT"/>
+ <xsd:element name="ULINT"/>
+ <xsd:element name="REAL"/>
+ <xsd:element name="LREAL"/>
+ <xsd:element name="TIME"/>
+ <xsd:element name="DATE"/>
+ <xsd:element name="DT"/>
+ <xsd:element name="TOD"/>
+ <xsd:element name="string">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The single byte character string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="wstring">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The wide character (WORD) string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="derivedTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of derived IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="array">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dimension" type="ppx:rangeSigned" maxOccurs="unbounded"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="derived">
+ <xsd:annotation>
+ <xsd:documentation>Reference to a user defined datatype or POU. Variable declarations use this type to declare e.g. function block instances.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The user defined alias type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="enum">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="values">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>An enumeration value used to build up enumeration types</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="baseType" type="ppx:dataType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="struct" type="ppx:varListPlain"/>
+ <xsd:element name="subrangeSigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeSigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="subrangeUnsigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeUnsigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="extended">
+ <xsd:annotation>
+ <xsd:documentation>Collection of datatypes not defined in IEC 61131-3</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="pointer">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="commonObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which have no direct iec scope and can be used in any graphical body.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="comment">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="error">
+ <xsd:complexType mixed="false">
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a conversion error. Used to keep information which can not be interpreted by the importing system</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connector">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="continuation">
+ <xsd:annotation>
+ <xsd:documentation>Counterpart of the connector element</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actionBlock">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Association of an action with qualifier</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="reference" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Name of an action or boolean variable.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inline" type="ppx:body" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Inline implementation of an action body.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="qualifier" use="optional" default="N">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="P1"/>
+ <xsd:enumeration value="N"/>
+ <xsd:enumeration value="P0"/>
+ <xsd:enumeration value="R"/>
+ <xsd:enumeration value="S"/>
+ <xsd:enumeration value="L"/>
+ <xsd:enumeration value="D"/>
+ <xsd:enumeration value="P"/>
+ <xsd:enumeration value="DS"/>
+ <xsd:enumeration value="DL"/>
+ <xsd:enumeration value="SD"/>
+ <xsd:enumeration value="SL"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="duration" type="xsd:string" use="optional"/>
+ <xsd:attribute name="indicator" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="fbdObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in fbd. They can be used in all graphical bodies.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="block">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a call statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position">
+ <xsd:annotation>
+ <xsd:documentation>Anchor position of the box. Top left corner excluding the instance name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="inputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used input variables (consumers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes an inputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used inOut variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a inOutVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used output variables (producers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a outputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="typeName" type="xsd:string" use="required"/>
+ <xsd:attribute name="instanceName" type="xsd:string" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable or expression used as l-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer and consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negatedIn" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeIn" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageIn" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="negatedOut" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeOut" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageOut" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="label">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump label</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jump">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="return">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing areturn statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="ldObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in ld and are an extension to fbd. They can be used in ld and sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="leftPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a left powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="rightPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a right powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="coil">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a boolean variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contact">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="sfcObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in sfc. They can only be used in sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="step">
+ <xsd:annotation>
+ <xsd:documentation>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</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Contains actions</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOutAction" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="initialStep" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="macroStep">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="body" type="ppx:body" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jumpStep">
+ <xsd:annotation>
+ <xsd:documentation>Jump to a step, macro step or simultaneous divergence. Acts like a step. Predecessor should be a transition.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="targetName" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transition">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="condition" minOccurs="0">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="reference">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connection" type="ppx:connection" maxOccurs="unbounded"/>
+ <xsd:element name="inline">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:body">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="priority" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>The priority of a transition is evaluated, if the transition is connected to a selectionDivergence element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointIn"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:simpleType name="edgeModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the edge detection behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="falling"/>
+ <xsd:enumeration value="rising"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="storageModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the storage mode (S/R) behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="set"/>
+ <xsd:enumeration value="reset"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="pouType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the different types of a POU</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="function"/>
+ <xsd:enumeration value="functionBlock"/>
+ <xsd:enumeration value="program"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+</xsd:schema>
Binary file plcopen/TC6_XML_V101.pdf has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/TC6_XML_V10_B.xsd Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1364 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://www.plcopen.org/xml/tc6.xsd" xmlns:ppx="http://www.plcopen.org/xml/tc6.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xhtml="http://www.w3.org/1999/xhtml" elementFormDefault="qualified" attributeFormDefault="unqualified">
+ <xsd:element name="project">
+ <xsd:annotation>
+ <xsd:documentation>The complete project</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="fileHeader">
+ <xsd:complexType>
+ <xsd:attribute name="companyName" type="xsd:string" use="required"/>
+ <xsd:attribute name="companyURL" type="xsd:anyURI" use="optional"/>
+ <xsd:attribute name="productName" type="xsd:string" use="required"/>
+ <xsd:attribute name="productVersion" type="xsd:string" use="required"/>
+ <xsd:attribute name="productRelease" type="xsd:string" use="optional"/>
+ <xsd:attribute name="creationDateTime" type="xsd:dateTime" use="required"/>
+ <xsd:attribute name="contentDescription" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contentHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Comment" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="coordinateInfo">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pageSize" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="fbd">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ld">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="sfc">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="version" type="xsd:string" use="optional"/>
+ <xsd:attribute name="modificationDateTime" type="xsd:dateTime" use="optional"/>
+ <xsd:attribute name="organization" type="xsd:string" use="optional"/>
+ <xsd:attribute name="author" type="xsd:string" use="optional"/>
+ <xsd:attribute name="language" type="xsd:language" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Documentation language of the project e.g. "en-US"</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="types">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataTypes">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="pous">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pou" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="interface" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="returnType" type="ppx:dataType" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="localVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="tempVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="externalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="accessVars" type="ppx:varList"/>
+ </xsd:choice>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transitions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="transition" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="body" type="ppx:body" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="pouType" type="ppx:pouType" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="instances">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configurations">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configuration" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of resources and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="resource" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of programs and tasks and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="task" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a periodic or triggered task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="single" type="xsd:string" use="optional"/>
+ <xsd:attribute name="interval" type="xsd:time" use="optional"/>
+ <xsd:attribute name="priority" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="dataType">
+ <xsd:annotation>
+ <xsd:documentation>A generic data type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:group ref="ppx:elementaryTypes"/>
+ <xsd:group ref="ppx:derivedTypes"/>
+ <xsd:group ref="ppx:extended"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="rangeSigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with signed bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:long" use="required"/>
+ <xsd:attribute name="upper" type="xsd:long" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="rangeUnsigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with unsigned bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="upper" type="xsd:unsignedLong" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="value">
+ <xsd:annotation>
+ <xsd:documentation>A generic value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="simpleValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Value that can be represented as a single token string </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="arrayValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Array value consisting of a list of occurrances - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="repetitionValue" type="xsd:unsignedLong" use="optional" default="1"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="structValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Struct value consisting of a list of member - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="member" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="body">
+ <xsd:annotation>
+ <xsd:documentation>Implementation part of a POU, action or transistion</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice>
+ <xsd:element name="IL" type="ppx:formattedText"/>
+ <xsd:element name="ST" type="ppx:formattedText"/>
+ <xsd:element name="FBD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="LD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="SFC">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ <xsd:group ref="ppx:sfcObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="varList">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations that share the same memory attributes (CONSTANT, RETAIN, NON_RETAIN, PERSISTENT)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varListPlain">
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="constant" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="retain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonretain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="persistent" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonpersistent" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="varListPlain">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations without attributes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Declaration of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="type" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="address" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="position">
+ <xsd:annotation>
+ <xsd:documentation>Defines a graphical position in X, Y coordinates</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="connection">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0">
+ <xsd:element name="position" type="ppx:position" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation>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).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="refLocalId" type="xsd:unsignedLong" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Identifies the element the connection starts from.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>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. </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointIn">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the consumer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:choice minOccurs="0">
+ <xsd:element name="connection" type="ppx:connection" maxOccurs="unbounded"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointOut">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the producer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="expression" type="xsd:string" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="pouInstance">
+ <xsd:annotation>
+ <xsd:documentation>Represents a program or function block instance either running with or without a task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="formattedText">
+ <xsd:annotation>
+ <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:group name="elementaryTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of elementary IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="BOOL"/>
+ <xsd:element name="BYTE"/>
+ <xsd:element name="WORD"/>
+ <xsd:element name="DWORD"/>
+ <xsd:element name="LWORD"/>
+ <xsd:element name="SINT"/>
+ <xsd:element name="INT"/>
+ <xsd:element name="DINT"/>
+ <xsd:element name="LINT"/>
+ <xsd:element name="USINT"/>
+ <xsd:element name="UINT"/>
+ <xsd:element name="UDINT"/>
+ <xsd:element name="ULINT"/>
+ <xsd:element name="REAL"/>
+ <xsd:element name="LREAL"/>
+ <xsd:element name="TIME"/>
+ <xsd:element name="DATE"/>
+ <xsd:element name="DT"/>
+ <xsd:element name="TOD"/>
+ <xsd:element name="string">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The single byte character string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="wstring">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The wide character (WORD) string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="derivedTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of derived IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="array">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dimension" type="ppx:rangeSigned" maxOccurs="unbounded"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="derived">
+ <xsd:annotation>
+ <xsd:documentation>Reference to a user defined datatype or POU. Variable declarations use this type to declare e.g. function block instances.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The user defined alias type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="enum">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="values">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>An enumeration value used to build up enumeration types</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="baseType" type="ppx:dataType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="struct" type="ppx:varListPlain"/>
+ <xsd:element name="subrangeSigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeSigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="subrangeUnsigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeUnsigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="extended">
+ <xsd:annotation>
+ <xsd:documentation>Collection of datatypes not defined in IEC 61131-3</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="pointer">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="commonObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which have no direct iec scope and can be used in any graphical body.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="comment">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="error">
+ <xsd:complexType mixed="false">
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a conversion error. Used to keep information which can not be interpreted by the importing system</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connector">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="continuation">
+ <xsd:annotation>
+ <xsd:documentation>Counterpart of the connector element</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actionBlock">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Association of an action with qualifier</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="reference" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Name of an action or boolean variable.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inline" type="ppx:body" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Inline implementation of an action body.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="qualifier" use="optional" default="N">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="P1"/>
+ <xsd:enumeration value="N"/>
+ <xsd:enumeration value="P0"/>
+ <xsd:enumeration value="R"/>
+ <xsd:enumeration value="S"/>
+ <xsd:enumeration value="L"/>
+ <xsd:enumeration value="D"/>
+ <xsd:enumeration value="P"/>
+ <xsd:enumeration value="DS"/>
+ <xsd:enumeration value="DL"/>
+ <xsd:enumeration value="SD"/>
+ <xsd:enumeration value="SL"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="duration" type="xsd:string" use="optional"/>
+ <xsd:attribute name="indicator" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="fbdObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in fbd. They can be used in all graphical bodies.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="block">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a call statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position">
+ <xsd:annotation>
+ <xsd:documentation>Anchor position of the box. Top left corner excluding the instance name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="inputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used input variables (consumers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes an inputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used inOut variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a inOutVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used output variables (producers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a outputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="typeName" type="xsd:string" use="required"/>
+ <xsd:attribute name="instanceName" type="xsd:string" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable or expression used as l-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer and consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negatedIn" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeIn" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageIn" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="negatedOut" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeOut" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageOut" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="label">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump label</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jump">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="return">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing areturn statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="ldObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in ld and are an extension to fbd. They can be used in ld and sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="leftPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a left powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="rightPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a right powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="coil">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a boolean variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contact">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="sfcObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in sfc. They can only be used in sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="step">
+ <xsd:annotation>
+ <xsd:documentation>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</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Contains actions</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOutAction" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="initialStep" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="macroStep">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="body" type="ppx:body" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jumpStep">
+ <xsd:annotation>
+ <xsd:documentation>Jump to a step, macro step or simultaneous divergence. Acts like a step. Predecessor should be a transition.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="targetName" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transition">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="condition" minOccurs="0">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="reference">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connection" type="ppx:connection" maxOccurs="unbounded"/>
+ <xsd:element name="inline">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:body">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="priority" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>The priority of a transition is evaluated, if the transition is connected to a selectionDivergence element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointIn"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:simpleType name="edgeModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the edge detection behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="falling"/>
+ <xsd:enumeration value="rising"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="storageModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the storage mode (S/R) behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="set"/>
+ <xsd:enumeration value="reset"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="pouType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the different types of a POU</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="function"/>
+ <xsd:enumeration value="functionBlock"/>
+ <xsd:enumeration value="program"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+</xsd:schema>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialisation
+
+# plcopen module dynamically creates its classes
+import plcopen
+for classname, cls in plcopen.PLCOpenClasses.items():
+ plcopen.__dict__[classname] = cls
+from plcopen import VarOrder, ElementNameToClass, rect
+
+from structures import *
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/iec_std.csv Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,90 @@
+Standard_functions_variables_types;name;type;comment;;;;;
+;N;ANY_INT;Number of bits to be shifted;;;;;
+;L;ANY_INT;Left position within character string;;;;;
+;P;ANY_INT;Position within character string;;;;;
+;G;BOOL;Selection out of 2 inputs (gate);;;;;
+;K;ANY_INT;Selection out of n inputs;;;;;
+;MN;ANY;Minimum value for limitation;;;;;
+;MX;ANY;Maximum value for limitation;;;;;
+;;;;;;;;
+;;;;;;;;
+;;;;;;;;
+;;;;;;;;
+;;;;;;;;
+;;;;;;;;
+Standard_functions_type;name;baseinputnumber;inputs;outputs;comment;extensible;python_eval_c_code_format;return_type_rule
+_("Type conversion");*_TO_**;1;(ANY);ANY;_("Data type conversion");no;ANY_TO_ANY_FORMAT_GEN(ANY_TO_ANY_LIST,fdecl);defined
+;TRUNC;1;(ANY_REAL);ANY_INT;_("Rounding up/down");no;("int", "__move_", None);&search_constant_type_c::integer
+;BCD_TO_**;1;(ANY_BIT);ANY_INT;_("Conversion from BCD");no;ANY_TO_ANY_FORMAT_GEN(BCD_TO_ANY_LIST,fdecl);defined
+;*_TO_BCD;1;(ANY_INT);ANY_BIT;_("Conversion to BCD");no;ANY_TO_ANY_FORMAT_GEN(ANY_TO_BCD_LIST,fdecl);&search_constant_type_c::integer
+;DATE_AND_TIME_TO_TIME_OF_DAY;1;(DT);TOD;_("Conversion to time-of-day");no;(None, "__date_and_time_to_time_of_day", None);defined
+;DATE_AND_TIME_TO_DATE;1;(DT);DATE;_("Conversion to date");no;(None, "__date_and_time_to_date", None);defined
+_("Numerical");ABS;1;(ANY_NUM);ANY_NUM;_("Absolute number");no;(None, "__abs_", "IN_type");IN_type_symbol
+;SQRT;1;(ANY_REAL);ANY_REAL;_("Square root (base 2)");no;(None, "__sqrt_", "IN_type");IN_type_symbol
+;LN;1;(ANY_REAL);ANY_REAL;_("Natural logarithm");no;(None, "__ln_", "IN_type");IN_type_symbol
+;LOG;1;(ANY_REAL);ANY_REAL;_("Logarithm to base 10");no;(None, "__log_", "IN_type");IN_type_symbol
+;EXP;1;(ANY_REAL);ANY_REAL;_("Exponentiation");no;(None, "__exp_", "IN_type");IN_type_symbol
+;SIN;1;(ANY_REAL);ANY_REAL;_("Sine");no;(None, "__sin_", "IN_type");IN_type_symbol
+;COS;1;(ANY_REAL);ANY_REAL;_("Cosine");no;(None, "__cos_", "IN_type");IN_type_symbol
+;TAN;1;(ANY_REAL);ANY_REAL;_("Tangent");no;(None, "__tan_", "IN_type");IN_type_symbol
+;ASIN;1;(ANY_REAL);ANY_REAL;_("Arc sine");no;(None, "__asin_", "IN_type");IN_type_symbol
+;ACOS;1;(ANY_REAL);ANY_REAL;_("Arc cosine");no;(None, "__acos_", "IN_type");IN_type_symbol
+;ATAN;1;(ANY_REAL);ANY_REAL;_("Arc tangent");no;(None, "__atan_", "IN_type");IN_type_symbol
+_("Arithmetic");ADD;1;(ANY_NUM, ANY_NUM);ANY_NUM;_("Addition");yes;(None, "__add_", "return_type");copy_input
+;MUL;1;(ANY_NUM, ANY_NUM);ANY_NUM;_("Multiplication");yes;(None, "__mul_", "return_type");copy_input
+;SUB;1;(ANY_NUM, ANY_NUM);ANY_NUM;_("Subtraction");no;(None, "__sub_", "return_type");copy_input
+;DIV;1;(ANY_NUM, ANY_NUM);ANY_NUM;_("Division");no;(None, "__div_", "return_type");copy_input
+;MOD;1;(ANY_INT, ANY_INT);ANY_INT;_("Remainder (modulo)");no;(None, "__mod_", "return_type");copy_input
+;EXPT;1;(ANY_REAL, ANY_NUM);ANY_REAL;_("Exponent");no;(None, "__expt_", "IN1_type");IN1_type_symbol
+;MOVE;1;(ANY);ANY;_("Assignment");no;(None, "__move_", "return_type");copy_input
+_("Time");ADD;1;(TIME, TIME);TIME;_("Time addition");no;(None, "__time_add", None);defined
+;ADD_TIME;1;(TIME, TIME);TIME;_("Time addition");no;(None, "__time_add", None);defined
+;ADD;1;(TOD, TIME);TOD;_("Time-of-day addition")+" "+_("DEPRECATED");no;(None, "__time_add", None);defined
+;ADD_TOD_TIME;1;(TOD, TIME);TOD;_("Time-of-day addition");no;(None, "__time_add", None);defined
+;ADD;1;(DT, TIME);DT;_("Date addition")+" "+_("DEPRECATED");no;(None, "__time_add", None);defined
+;ADD_DT_TIME;1;(DT, TIME);DT;_("Date addition");no;(None, "__time_add", None);defined
+;MUL;1;(TIME, ANY_NUM);TIME;_("Time multiplication")+" "+_("DEPRECATED");no;(None, "__time_mul", None);defined
+;MULTIME;1;(TIME, ANY_NUM);TIME;_("Time multiplication");no;(None, "__time_mul", None);defined
+;SUB_TIME;1;(TIME, TIME);TIME;_("Time subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(TIME, TIME);TIME;_("Time subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(DATE, DATE);TIME;_("Date subtraction")+" "+_("DEPRECATED");no;(None, "__time_sub", None);defined
+;SUB_DATE_DATE;1;(DATE, DATE);TIME;_("Date subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(TOD, TIME);TOD;_("Time-of-day subtraction")+" "+_("DEPRECATED");no;(None, "__time_sub", None);defined
+;SUB_TOD_TIME;1;(TOD, TIME);TOD;_("Time-of-day subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(TOD, TOD);TIME;_("Time-of-day subtraction")+" "+_("DEPRECATED");no;(None, "__time_sub", None);defined
+;SUB_TOD_TOD;1;(TOD, TOD);TIME;_("Time-of-day subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(DT, TIME);DT;_("Date and time subtraction")+" "+_("DEPRECATED");no;(None, "__time_sub", None);defined
+;SUB_DT_TIME;1;(DT, TIME);DT;_("Date and time subtraction");no;(None, "__time_sub", None);defined
+;SUB;1;(DT, DT);TIME;_("Date and time subtraction")+" "+_("DEPRECATED");no;(None, "__time_sub", None);defined
+;SUB_DT_TIME;1;(DT, DT);TIME;_("Date and time subtraction");no;(None, "__time_sub", None);defined
+;DIV;1;(TIME, ANY_NUM);TIME;_("Time division")+" "+_("DEPRECATED");no;(None, "__time_div", None);defined
+;DIVTIME;1;(TIME, ANY_NUM);TIME;_("Time division");no;(None, "__time_div", None);defined
+_("Bit-shift");SHL;1;(ANY_BIT, N);ANY_BIT;_("Shift left");no;(None, "__shl_", "IN_type");IN_type_symbol
+;SHR;1;(ANY_BIT, N);ANY_BIT;_("Shift right");no;(None, "__shr_", "IN_type");IN_type_symbol
+;ROR;1;(ANY_NBIT, N);ANY_NBIT;_("Rotate right");no;(None, "__ror_", "IN_type");IN_type_symbol
+;ROL;1;(ANY_NBIT, N);ANY_NBIT;_("Rotate left");no;(None, "__rol_", "IN_type");IN_type_symbol
+_("Bitwise");AND;1;(ANY_BIT, ANY_BIT);ANY_BIT;_("Bitwise AND");yes;(None, "__and_", "return_type");copy_input
+;OR;1;(ANY_BIT, ANY_BIT);ANY_BIT;_("Bitwise OR");yes;(None, "__or_", "return_type");copy_input
+;XOR;1;(ANY_BIT, ANY_BIT);ANY_BIT;_("Bitwise XOR");yes;(None, "__xor_", "return_type");copy_input
+;NOT;1;(ANY_BIT);ANY_BIT;_("Bitwise inverting");no;(None, "__not_", "return_type");IN_type_symbol
+_("Selection");SEL;0;(G, ANY, ANY);ANY;_("Binary selection (1 of 2)");no;(None, "__sel_", "IN0_type");copy_input
+;MAX;1;(ANY, ANY);ANY;_("Maximum");yes;(None, "__max_", "return_type");copy_input
+;MIN;1;(ANY, ANY);ANY;_("Minimum");yes;(None, "__min_", "return_type");copy_input
+;LIMIT;1;(MN, ANY, MX);ANY;_("Limitation");no;(None, "__limit_", "IN_type");IN_type_symbol
+;MUX;0;(K, ANY, ANY);ANY;_("Multiplexer (select 1 of N)");yes;(None, "__mux_", "return_type");copy_input
+_("Comparison");GT;1;(ANY, ANY);BOOL;_("Greater than");yes;(None, "__gt_", "common_type");defined
+;GE;1;(ANY, ANY);BOOL;_("Greater than or equal to");yes;(None, "__ge_", "common_type");defined
+;EQ;1;(ANY, ANY);BOOL;_("Equal to");yes;(None, "__eq_", "common_type");defined
+;LT;1;(ANY, ANY);BOOL;_("Less than");yes;(None, "__lt_", "common_type");defined
+;LE;1;(ANY, ANY);BOOL;_("Less than or equal to");yes;(None, "__le_", "common_type");defined
+;NE;1;(ANY, ANY);BOOL;_("Not equal to");no;(None, "__ne_", "common_type");defined
+_("Character string");LEN;1;(STRING);INT;_("Length of string");no;(None, "__len", None);defined
+;LEFT;1;(STRING, L);STRING;_("string left of");no;(None, "__left", None);defined
+;RIGHT;1;(STRING, L);STRING;_("string right of");no;(None, "__right", None);defined
+;MID;1;(STRING, L, P);STRING;_("string from the middle");no;(None, "__mid", None);defined
+;CONCAT;1;(STRING, STRING);STRING;_("Concatenation");yes;(None, "__concat", None);defined
+;CONCAT_DAT_TOD;1;(DATE, TOD);DT;_("Time concatenation");no;(None, "__time_add", None);defined
+;INSERT;1;(STRING, STRING, P);STRING;_("Insertion (into)");no;(None, "__insert", None);defined
+;DELETE;1;(STRING, L, P);STRING;_("Deletion (within)");no;(None, "__delete", None);defined
+;REPLACE;1;(STRING, STRING, L, P);STRING;_("Replacement (within)");no;(None, "__replace", None);defined
+;FIND;1;(STRING, STRING);INT;_("Find position");no;(None, "__find", None);defined
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/plcopen.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,2843 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from xmlclass import *
+from structures import *
+from types import *
+import os, re
+
+"""
+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"}
+
+searchResultVarTypes = {
+ "inputVars": "var_input",
+ "outputVars": "var_output",
+ "inOutVars": "var_inout"
+}
+
+"""
+Define in which order var types must be displayed
+"""
+VarOrder = ["Local","Temp","Input","Output","InOut","External","Global","Access"]
+
+"""
+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}
+
+
+FILTER_ADDRESS_MODEL = "(%%[IQM](?:[XBWDL])?)(%s)((?:\.[0-9]+)*)"
+
+def update_address(address, address_model, new_leading):
+ result = address_model.match(address)
+ if result is None:
+ return address
+ groups = result.groups()
+ return groups[0] + new_leading + groups[2]
+
+def _init_and_compare(function, v1, v2):
+ if v1 is None:
+ return v2
+ if v2 is not None:
+ return function(v1, v2)
+ return v1
+
+"""
+Helper class for bounding_box calculation
+"""
+class rect:
+
+ def __init__(self, x=None, y=None, width=None, height=None):
+ self.x_min = x
+ self.x_max = None
+ self.y_min = y
+ self.y_max = None
+ if width is not None and x is not None:
+ self.x_max = x + width
+ if height is not None and y is not None:
+ self.y_max = y + height
+
+ def update(self, x, y):
+ self.x_min = _init_and_compare(min, self.x_min, x)
+ self.x_max = _init_and_compare(max, self.x_max, x)
+ self.y_min = _init_and_compare(min, self.y_min, y)
+ self.y_max = _init_and_compare(max, self.y_max, y)
+
+ def union(self, rect):
+ self.x_min = _init_and_compare(min, self.x_min, rect.x_min)
+ self.x_max = _init_and_compare(max, self.x_max, rect.x_max)
+ self.y_min = _init_and_compare(min, self.y_min, rect.y_min)
+ self.y_max = _init_and_compare(max, self.y_max, rect.y_max)
+
+ def bounding_box(self):
+ width = height = None
+ if self.x_min is not None and self.x_max is not None:
+ width = self.x_max - self.x_min
+ if self.y_min is not None and self.y_max is not None:
+ height = self.y_max - self.y_min
+ return self.x_min, self.y_min, width, height
+
+def TextLenInRowColumn(text):
+ if text == "":
+ return (0, 0)
+ lines = text.split("\n")
+ return len(lines) - 1, len(lines[-1])
+
+def TestTextElement(text, criteria):
+ lines = text.splitlines()
+ if not criteria["case_sensitive"]:
+ text = text.upper()
+ test_result = []
+ result = criteria["pattern"].search(text)
+ while result is not None:
+ start = TextLenInRowColumn(text[:result.start()])
+ end = TextLenInRowColumn(text[:result.end() - 1])
+ test_result.append((start, end, "\n".join(lines[start[0]:end[0] + 1])))
+ result = criteria["pattern"].search(text, result.end())
+ return test_result
+
+PLCOpenClasses = GenerateClassesFromXSD(os.path.join(os.path.split(__file__)[0], "tc6_xml_v201.xsd"))
+
+ElementNameToClass = {}
+
+cls = PLCOpenClasses.get("formattedText", None)
+if cls:
+ def updateElementName(self, old_name, new_name):
+ text = self.text.decode("utf-8")
+ index = text.find(old_name)
+ while index != -1:
+ if index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_"):
+ index = text.find(old_name, index + len(old_name))
+ elif index < len(text) - len(old_name) and (text[index + len(old_name)].isalnum() or text[index + len(old_name)] == "_"):
+ index = text.find(old_name, index + len(old_name))
+ else:
+ text = text[:index] + new_name + text[index + len(old_name):]
+ index = text.find(old_name, index + len(new_name))
+ self.text = text.encode("utf-8")
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ text = self.text.decode("utf-8")
+ startpos = 0
+ result = address_model.search(text, startpos)
+ while result is not None:
+ groups = result.groups()
+ new_address = groups[0] + new_leading + groups[2]
+ text = text[:result.start()] + new_address + text[result.end():]
+ startpos = result.start() + len(new_address)
+ result = address_model.search(self.text, startpos)
+ self.text = text.encode("utf-8")
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos):
+ return [(tuple(parent_infos),) + result for result in TestTextElement(self.gettext(), criteria)]
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("project", None)
+if cls:
+ cls.singleLineAttributes = False
+ cls.EnumeratedDataTypeValues = {}
+ cls.CustomDataTypeRange = {}
+ cls.CustomTypeHierarchy = {}
+ cls.ElementUsingTree = {}
+ cls.CustomBlockTypes = []
+
+ def setname(self, name):
+ self.contentHeader.setname(name)
+ setattr(cls, "setname", setname)
+
+ def getname(self):
+ return self.contentHeader.getname()
+ setattr(cls, "getname", getname)
+
+ def getfileHeader(self):
+ fileheader = {}
+ for name, value in [("companyName", self.fileHeader.getcompanyName()),
+ ("companyURL", self.fileHeader.getcompanyURL()),
+ ("productName", self.fileHeader.getproductName()),
+ ("productVersion", self.fileHeader.getproductVersion()),
+ ("productRelease", self.fileHeader.getproductRelease()),
+ ("creationDateTime", self.fileHeader.getcreationDateTime()),
+ ("contentDescription", self.fileHeader.getcontentDescription())]:
+ if value is not None:
+ fileheader[name] = value
+ else:
+ fileheader[name] = ""
+ return fileheader
+ setattr(cls, "getfileHeader", getfileHeader)
+
+ def setfileHeader(self, fileheader):
+ if fileheader.has_key("companyName"):
+ self.fileHeader.setcompanyName(fileheader["companyName"])
+ if fileheader.has_key("companyURL"):
+ self.fileHeader.setcompanyURL(fileheader["companyURL"])
+ if fileheader.has_key("productName"):
+ self.fileHeader.setproductName(fileheader["productName"])
+ if fileheader.has_key("productVersion"):
+ self.fileHeader.setproductVersion(fileheader["productVersion"])
+ if fileheader.has_key("productRelease"):
+ self.fileHeader.setproductRelease(fileheader["productRelease"])
+ if fileheader.has_key("creationDateTime"):
+ self.fileHeader.setcreationDateTime(fileheader["creationDateTime"])
+ if fileheader.has_key("contentDescription"):
+ self.fileHeader.setcontentDescription(fileheader["contentDescription"])
+ setattr(cls, "setfileHeader", setfileHeader)
+
+ def getcontentHeader(self):
+ contentheader = {}
+ for name, value in [("projectName", self.contentHeader.getname()),
+ ("projectVersion", self.contentHeader.getversion()),
+ ("modificationDateTime", self.contentHeader.getmodificationDateTime()),
+ ("organization", self.contentHeader.getorganization()),
+ ("authorName", self.contentHeader.getauthor()),
+ ("language", self.contentHeader.getlanguage())]:
+ if value is not None:
+ contentheader[name] = value
+ else:
+ contentheader[name] = ""
+ contentheader["pageSize"] = self.contentHeader.getpageSize()
+ contentheader["scaling"] = self.contentHeader.getscaling()
+ return contentheader
+ setattr(cls, "getcontentHeader", getcontentHeader)
+
+ def setcontentHeader(self, contentheader):
+ if contentheader.has_key("projectName"):
+ self.contentHeader.setname(contentheader["projectName"])
+ if contentheader.has_key("projectVersion"):
+ self.contentHeader.setversion(contentheader["projectVersion"])
+ if contentheader.has_key("modificationDateTime"):
+ self.contentHeader.setmodificationDateTime(contentheader["modificationDateTime"])
+ if contentheader.has_key("organization"):
+ self.contentHeader.setorganization(contentheader["organization"])
+ if contentheader.has_key("authorName"):
+ self.contentHeader.setauthor(contentheader["authorName"])
+ if contentheader.has_key("language"):
+ self.contentHeader.setlanguage(contentheader["language"])
+ if contentheader.has_key("pageSize"):
+ self.contentHeader.setpageSize(*contentheader["pageSize"])
+ if contentheader.has_key("scaling"):
+ self.contentHeader.setscaling(contentheader["scaling"])
+ setattr(cls, "setcontentHeader", setcontentHeader)
+
+ def getdataTypes(self):
+ return self.types.getdataTypeElements()
+ setattr(cls, "getdataTypes", getdataTypes)
+
+ def getdataType(self, name):
+ return self.types.getdataTypeElement(name)
+ setattr(cls, "getdataType", getdataType)
+
+ def appenddataType(self, name):
+ if self.CustomTypeHierarchy.has_key(name):
+ raise ValueError, "\"%s\" Data Type already exists !!!"%name
+ self.types.appenddataTypeElement(name)
+ self.AddCustomDataType(self.getdataType(name))
+ setattr(cls, "appenddataType", appenddataType)
+
+ def insertdataType(self, index, datatype):
+ self.types.insertdataTypeElement(index, datatype)
+ self.AddCustomDataType(datatype)
+ setattr(cls, "insertdataType", insertdataType)
+
+ def removedataType(self, name):
+ self.types.removedataTypeElement(name)
+ self.RefreshDataTypeHierarchy()
+ self.RefreshElementUsingTree()
+ setattr(cls, "removedataType", removedataType)
+
+ 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)
+ self.AddCustomBlockType(self.getpou(name))
+ setattr(cls, "appendpou", appendpou)
+
+ def insertpou(self, index, pou):
+ self.types.insertpouElement(index, pou)
+ self.AddCustomBlockType(pou)
+ setattr(cls, "insertpou", insertpou)
+
+ def removepou(self, name):
+ self.types.removepouElement(name)
+ self.RefreshCustomBlockTypes()
+ self.RefreshElementUsingTree()
+ 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)
+
+ def updateElementName(self, old_name, new_name):
+ for datatype in self.types.getdataTypeElements():
+ datatype.updateElementName(old_name, new_name)
+ for pou in self.types.getpouElements():
+ pou.updateElementName(old_name, new_name)
+ for configuration in self.instances.configurations.getconfiguration():
+ configuration.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, old_leading, new_leading):
+ address_model = re.compile(FILTER_ADDRESS_MODEL % old_leading)
+ for pou in self.types.getpouElements():
+ pou.updateElementAddress(address_model, new_leading)
+ for configuration in self.instances.configurations.getconfiguration():
+ configuration.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def removeVariableByAddress(self, address):
+ for pou in self.types.getpouElements():
+ pou.removeVariableByAddress(address)
+ for configuration in self.instances.configurations.getconfiguration():
+ configuration.removeVariableByAddress(address)
+ setattr(cls, "removeVariableByAddress", removeVariableByAddress)
+
+ def removeVariableByFilter(self, leading):
+ address_model = re.compile(FILTER_ADDRESS_MODEL % leading)
+ for pou in self.types.getpouElements():
+ pou.removeVariableByFilter(address_model)
+ for configuration in self.instances.configurations.getconfiguration():
+ configuration.removeVariableByFilter(address_model)
+ setattr(cls, "removeVariableByFilter", removeVariableByFilter)
+
+ def RefreshDataTypeHierarchy(self):
+ self.EnumeratedDataTypeValues = {}
+ self.CustomDataTypeRange = {}
+ self.CustomTypeHierarchy = {}
+ for datatype in self.getdataTypes():
+ self.AddCustomDataType(datatype)
+ setattr(cls, "RefreshDataTypeHierarchy", RefreshDataTypeHierarchy)
+
+ def AddCustomDataType(self, datatype):
+ name = datatype.getname()
+ basetype_content = datatype.getbaseType().getcontent()
+ if basetype_content["value"] is None:
+ self.CustomTypeHierarchy[name] = basetype_content["name"]
+ elif basetype_content["name"] in ["string", "wstring"]:
+ self.CustomTypeHierarchy[name] = basetype_content["name"].upper()
+ elif basetype_content["name"] == "derived":
+ self.CustomTypeHierarchy[name] = basetype_content["value"].getname()
+ elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned"]:
+ range = (basetype_content["value"].range.getlower(),
+ basetype_content["value"].range.getupper())
+ self.CustomDataTypeRange[name] = range
+ base_type = basetype_content["value"].baseType.getcontent()
+ if base_type["value"] is None:
+ self.CustomTypeHierarchy[name] = base_type["name"]
+ else:
+ self.CustomTypeHierarchy[name] = base_type["value"].getname()
+ else:
+ if basetype_content["name"] == "enum":
+ values = []
+ for value in basetype_content["value"].values.getvalue():
+ values.append(value.getname())
+ self.EnumeratedDataTypeValues[name] = values
+ self.CustomTypeHierarchy[name] = "ANY_DERIVED"
+ setattr(cls, "AddCustomDataType", AddCustomDataType)
+
+ # Update Block types with user-defined pou added
+ def RefreshCustomBlockTypes(self):
+ # Reset the tree of user-defined pou cross-use
+ self.CustomBlockTypes = []
+ for pou in self.getpous():
+ self.AddCustomBlockType(pou)
+ setattr(cls, "RefreshCustomBlockTypes", RefreshCustomBlockTypes)
+
+ def AddCustomBlockType(self, pou):
+ pou_name = pou.getname()
+ pou_type = pou.getpouType()
+ block_infos = {"name" : pou_name, "type" : pou_type, "extensible" : False,
+ "inputs" : [], "outputs" : [], "comment" : pou.getdescription(),
+ "generate" : generate_block, "initialise" : initialise_block}
+ if pou.getinterface():
+ return_type = pou.interface.getreturnType()
+ if return_type:
+ var_type = return_type.getcontent()
+ if var_type["name"] == "derived":
+ block_infos["outputs"].append(("", var_type["value"].getname(), "none"))
+ elif var_type["name"] in ["string", "wstring"]:
+ block_infos["outputs"].append(("", var_type["name"].upper(), "none"))
+ else:
+ block_infos["outputs"].append(("", var_type["name"], "none"))
+ for type, varlist in pou.getvars():
+ if type == "InOut":
+ for var in varlist.getvariable():
+ var_type = var.type.getcontent()
+ if var_type["name"] == "derived":
+ block_infos["inputs"].append((var.getname(), var_type["value"].getname(), "none"))
+ block_infos["outputs"].append((var.getname(), var_type["value"].getname(), "none"))
+ elif var_type["name"] in ["string", "wstring"]:
+ block_infos["inputs"].append((var.getname(), var_type["name"].upper(), "none"))
+ block_infos["outputs"].append((var.getname(), var_type["name"].upper(), "none"))
+ else:
+ block_infos["inputs"].append((var.getname(), var_type["name"], "none"))
+ block_infos["outputs"].append((var.getname(), var_type["name"], "none"))
+ elif type == "Input":
+ for var in varlist.getvariable():
+ var_type = var.type.getcontent()
+ if var_type["name"] == "derived":
+ block_infos["inputs"].append((var.getname(), var_type["value"].getname(), "none"))
+ elif var_type["name"] in ["string", "wstring"]:
+ block_infos["inputs"].append((var.getname(), var_type["name"].upper(), "none"))
+ else:
+ block_infos["inputs"].append((var.getname(), var_type["name"], "none"))
+ elif type == "Output":
+ for var in varlist.getvariable():
+ var_type = var.type.getcontent()
+ if var_type["name"] == "derived":
+ block_infos["outputs"].append((var.getname(), var_type["value"].getname(), "none"))
+ elif var_type["name"] in ["string", "wstring"]:
+ block_infos["outputs"].append((var.getname(), var_type["name"].upper(), "none"))
+ else:
+ block_infos["outputs"].append((var.getname(), var_type["name"], "none"))
+ block_infos["usage"] = "\n (%s) => (%s)" % (", ".join(["%s:%s" % (input[1], input[0]) for input in block_infos["inputs"]]),
+ ", ".join(["%s:%s" % (output[1], output[0]) for output in block_infos["outputs"]]))
+ self.CustomBlockTypes.append(block_infos)
+ setattr(cls, "AddCustomBlockType", AddCustomBlockType)
+
+ def RefreshElementUsingTree(self):
+ # Reset the tree of user-defined element cross-use
+ self.ElementUsingTree = {}
+ pous = self.getpous()
+ datatypes = self.getdataTypes()
+ # Reference all the user-defined elementu names and initialize the tree of
+ # user-defined elemnt cross-use
+ elementnames = [datatype.getname() for datatype in datatypes] + \
+ [pou.getname() for pou in pous]
+ for name in elementnames:
+ self.ElementUsingTree[name] = []
+ # Analyze each datatype
+ for datatype in datatypes:
+ name = datatype.getname()
+ basetype_content = datatype.baseType.getcontent()
+ if basetype_content["name"] == "derived":
+ typename = basetype_content["value"].getname()
+ if name in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+ elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned", "array"]:
+ base_type = basetype_content["value"].baseType.getcontent()
+ if base_type["name"] == "derived":
+ typename = base_type["value"].getname()
+ if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+ elif basetype_content["name"] == "struct":
+ for element in basetype_content["value"].getvariable():
+ type_content = element.type.getcontent()
+ if type_content["name"] == "derived":
+ typename = type_content["value"].getname()
+ if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+ # Analyze each pou
+ for pou in pous:
+ name = pou.getname()
+ if pou.interface:
+ # Extract variables from every varLists
+ for type, varlist in pou.getvars():
+ for var in varlist.getvariable():
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived":
+ typename = vartype_content["value"].getname()
+ if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
+ self.ElementUsingTree[typename].append(name)
+ setattr(cls, "RefreshElementUsingTree", RefreshElementUsingTree)
+
+ def GetParentType(self, type):
+ if self.CustomTypeHierarchy.has_key(type):
+ return self.CustomTypeHierarchy[type]
+ elif TypeHierarchy.has_key(type):
+ return TypeHierarchy[type]
+ return None
+ setattr(cls, "GetParentType", GetParentType)
+
+ def GetBaseType(self, type):
+ parent_type = self.GetParentType(type)
+ if parent_type is not None:
+ if parent_type.startswith("ANY"):
+ return type
+ else:
+ return self.GetBaseType(parent_type)
+ return None
+ setattr(cls, "GetBaseType", GetBaseType)
+
+ def GetSubrangeBaseTypes(self, exclude):
+ derived = []
+ for type in self.CustomTypeHierarchy.keys():
+ for base_type in DataTypeRange.keys():
+ if self.IsOfType(type, base_type) and not self.IsOfType(type, exclude):
+ derived.append(type)
+ break
+ return derived
+ setattr(cls, "GetSubrangeBaseTypes", GetSubrangeBaseTypes)
+
+ """
+ returns true if the given data type is the same that "reference" meta-type or one of its types.
+ """
+ def IsOfType(self, type, reference):
+ if reference is None:
+ return True
+ elif type == reference:
+ return True
+ else:
+ parent_type = self.GetParentType(type)
+ if parent_type is not None:
+ return self.IsOfType(parent_type, reference)
+ return False
+ setattr(cls, "IsOfType", IsOfType)
+
+ # Return if pou given by name is used by another pou
+ def ElementIsUsed(self, name):
+ if self.ElementUsingTree.has_key(name):
+ return len(self.ElementUsingTree[name]) > 0
+ return False
+ setattr(cls, "ElementIsUsed", ElementIsUsed)
+
+ def DataTypeIsDerived(self, name):
+ return name in self.CustomTypeHierarchy.values()
+ setattr(cls, "DataTypeIsDerived", DataTypeIsDerived)
+
+ # Return if pou given by name is directly or undirectly used by the reference pou
+ def ElementIsUsedBy(self, name, reference):
+ if self.ElementUsingTree.has_key(name):
+ list = self.ElementUsingTree[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.ElementIsUsedBy(element, reference)
+ return used
+ return False
+ setattr(cls, "ElementIsUsedBy", ElementIsUsedBy)
+
+ def GetDataTypeRange(self, type):
+ if self.CustomDataTypeRange.has_key(type):
+ return self.CustomDataTypeRange[type]
+ elif DataTypeRange.has_key(type):
+ return DataTypeRange[type]
+ else:
+ parent_type = self.GetParentType(type)
+ if parent_type is not None:
+ return self.GetDataTypeRange(parent_type)
+ return None
+ setattr(cls, "GetDataTypeRange", GetDataTypeRange)
+
+ def GetEnumeratedDataTypeValues(self, type = None):
+ if type is None:
+ all_values = []
+ for values in self.EnumeratedDataTypeValues.values():
+ all_values.extend(values)
+ return all_values
+ elif self.EnumeratedDataTypeValues.has_key(type):
+ return self.EnumeratedDataTypeValues[type]
+ return []
+ setattr(cls, "GetEnumeratedDataTypeValues", GetEnumeratedDataTypeValues)
+
+ # Function that returns the block definition associated to the block type given
+ def GetCustomBlockType(self, type, inputs = None):
+ for customblocktype in self.CustomBlockTypes:
+ if inputs is not None and inputs != "undefined":
+ customblock_inputs = tuple([var_type for name, var_type, modifier in customblocktype["inputs"]])
+ same_inputs = inputs == customblock_inputs
+ else:
+ same_inputs = True
+ if customblocktype["name"] == type and same_inputs:
+ return customblocktype
+ return None
+ setattr(cls, "GetCustomBlockType", GetCustomBlockType)
+
+ # Return Block types checking for recursion
+ def GetCustomBlockTypes(self, exclude = "", onlyfunctions = False):
+ type = None
+ if exclude != "":
+ pou = self.getpou(exclude)
+ if pou is not None:
+ type = pou.getpouType()
+ customblocktypes = []
+ for customblocktype in self.CustomBlockTypes:
+ if customblocktype["type"] != "program" and customblocktype["name"] != exclude and not self.ElementIsUsedBy(exclude, customblocktype["name"]) and not (onlyfunctions and customblocktype["type"] != "function"):
+ customblocktypes.append(customblocktype)
+ return customblocktypes
+ setattr(cls, "GetCustomBlockTypes", GetCustomBlockTypes)
+
+ # Return Function Block types checking for recursion
+ def GetCustomFunctionBlockTypes(self, exclude = ""):
+ customblocktypes = []
+ for customblocktype in self.CustomBlockTypes:
+ if customblocktype["type"] == "functionBlock" and customblocktype["name"] != exclude and not self.ElementIsUsedBy(exclude, customblocktype["name"]):
+ customblocktypes.append(customblocktype["name"])
+ return customblocktypes
+ setattr(cls, "GetCustomFunctionBlockTypes", GetCustomFunctionBlockTypes)
+
+ # Return Block types checking for recursion
+ def GetCustomBlockResource(self):
+ customblocktypes = []
+ for customblocktype in self.CustomBlockTypes:
+ if customblocktype["type"] == "program":
+ customblocktypes.append(customblocktype["name"])
+ return customblocktypes
+ setattr(cls, "GetCustomBlockResource", GetCustomBlockResource)
+
+ # Return Data Types checking for recursion
+ def GetCustomDataTypes(self, exclude = "", only_locatable = False):
+ customdatatypes = []
+ for customdatatype in self.getdataTypes():
+ if not only_locatable or self.IsLocatableType(customdatatype):
+ customdatatype_name = customdatatype.getname()
+ if customdatatype_name != exclude and not self.ElementIsUsedBy(exclude, customdatatype_name):
+ customdatatypes.append({"name": customdatatype_name, "infos": customdatatype})
+ return customdatatypes
+ setattr(cls, "GetCustomDataTypes", GetCustomDataTypes)
+
+ # Return if Data Type can be used for located variables
+ def IsLocatableType(self, datatype):
+ basetype_content = datatype.baseType.getcontent()
+ if basetype_content["name"] in ["enum", "struct"]:
+ return False
+ elif basetype_content["name"] == "derived":
+ base_type = self.getdataType(basetype_content["value"].getname())
+ if base_type is not None:
+ return self.IsLocatableType(base_type)
+ elif basetype_content["name"] == "array":
+ array_base_type = basetype_content["value"].baseType.getcontent()
+ if array_base_type["value"] is not None and array_base_type["name"] not in ["string", "wstring"]:
+ base_type = self.getdataType(array_base_type["value"].getname())
+ if base_type is not None:
+ return self.IsLocatableType(base_type)
+ return True
+ setattr(cls, "IsLocatableType", IsLocatableType)
+
+ def Search(self, criteria, parent_infos=[]):
+ result = self.types.Search(criteria, parent_infos)
+ for configuration in self.instances.configurations.getconfiguration():
+ result.extend(configuration.Search(criteria, parent_infos))
+ return result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("project_fileHeader", None)
+if cls:
+ cls.singleLineAttributes = False
+
+cls = PLCOpenClasses.get("project_contentHeader", None)
+if cls:
+ cls.singleLineAttributes = False
+
+ def setpageSize(self, width, height):
+ self.coordinateInfo.setpageSize(width, height)
+ setattr(cls, "setpageSize", setpageSize)
+
+ def getpageSize(self):
+ return self.coordinateInfo.getpageSize()
+ setattr(cls, "getpageSize", getpageSize)
+
+ def setscaling(self, scaling):
+ for language, (x, y) in scaling.items():
+ self.coordinateInfo.setscaling(language, x, y)
+ setattr(cls, "setscaling", setscaling)
+
+ def getscaling(self):
+ scaling = {}
+ scaling["FBD"] = self.coordinateInfo.getscaling("FBD")
+ scaling["LD"] = self.coordinateInfo.getscaling("LD")
+ scaling["SFC"] = self.coordinateInfo.getscaling("SFC")
+ return scaling
+ setattr(cls, "getscaling", getscaling)
+
+cls = PLCOpenClasses.get("contentHeader_coordinateInfo", None)
+if cls:
+ def setpageSize(self, width, height):
+ if width == 0 and height == 0:
+ self.deletepageSize()
+ else:
+ if self.pageSize is None:
+ self.addpageSize()
+ self.pageSize.setx(width)
+ self.pageSize.sety(height)
+ setattr(cls, "setpageSize", setpageSize)
+
+ def getpageSize(self):
+ if self.pageSize is not None:
+ return self.pageSize.getx(), self.pageSize.gety()
+ return 0, 0
+ setattr(cls, "getpageSize", getpageSize)
+
+ def setscaling(self, language, x, y):
+ if language == "FBD":
+ self.fbd.scaling.setx(x)
+ self.fbd.scaling.sety(y)
+ elif language == "LD":
+ self.ld.scaling.setx(x)
+ self.ld.scaling.sety(y)
+ elif language == "SFC":
+ self.sfc.scaling.setx(x)
+ self.sfc.scaling.sety(y)
+ setattr(cls, "setscaling", setscaling)
+
+ def getscaling(self, language):
+ if language == "FBD":
+ return self.fbd.scaling.getx(), self.fbd.scaling.gety()
+ elif language == "LD":
+ return self.ld.scaling.getx(), self.ld.scaling.gety()
+ elif language == "SFC":
+ return self.sfc.scaling.getx(), self.sfc.scaling.gety()
+ return 0, 0
+ setattr(cls, "getscaling", getscaling)
+
+def _Search(attributes, criteria, parent_infos):
+ search_result = []
+ for attr, value in attributes:
+ if value is not None:
+ search_result.extend([(tuple(parent_infos + [attr]),) + result for result in TestTextElement(value, criteria)])
+ return search_result
+
+def _updateConfigurationResourceElementName(self, old_name, new_name):
+ for varlist in self.getglobalVars():
+ for var in varlist.getvariable():
+ var_address = var.getaddress()
+ if var_address is not None:
+ if var_address == old_name:
+ var.setaddress(new_name)
+ if var.getname() == old_name:
+ var.setname(new_name)
+
+def _updateConfigurationResourceElementAddress(self, address_model, new_leading):
+ for varlist in self.getglobalVars():
+ for var in varlist.getvariable():
+ var_address = var.getaddress()
+ if var_address is not None:
+ var.setaddress(update_address(var_address, address_model, new_leading))
+
+def _removeConfigurationResourceVariableByAddress(self, address):
+ for varlist in self.getglobalVars():
+ variables = varlist.getvariable()
+ for i in xrange(len(variables)-1, -1, -1):
+ if variables[i].getaddress() == address:
+ variables.pop(i)
+
+def _removeConfigurationResourceVariableByFilter(self, address_model):
+ for varlist in self.getglobalVars():
+ variables = varlist.getvariable()
+ for i in xrange(len(variables)-1, -1, -1):
+ var_address = variables[i].getaddress()
+ if var_address is not None:
+ result = address_model.match(var_address)
+ if result is not None:
+ variables.pop(i)
+
+def _SearchInConfigurationResource(self, criteria, parent_infos=[]):
+ search_result = _Search([("name", self.getname())], criteria, parent_infos)
+ var_number = 0
+ for varlist in self.getglobalVars():
+ variable_type = searchResultVarTypes.get("globalVars", "var_local")
+ variables = varlist.getvariable()
+ for modifier, has_modifier in [("constant", varlist.getconstant()),
+ ("retain", varlist.getretain()),
+ ("non_retain", varlist.getnonretain())]:
+ if has_modifier:
+ for result in TestTextElement(modifier, criteria):
+ search_result.append((tuple(parent_infos + [variable_type, (var_number, var_number + len(variables)), modifier]),) + result)
+ break
+ for variable in variables:
+ search_result.extend(variable.Search(criteria, parent_infos + [variable_type, var_number]))
+ var_number += 1
+ return search_result
+
+cls = PLCOpenClasses.get("configurations_configuration", None)
+if cls:
+ def updateElementName(self, old_name, new_name):
+ _updateConfigurationResourceElementName(self, old_name, new_name)
+ for resource in self.getresource():
+ resource.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ _updateConfigurationResourceElementAddress(self, address_model, new_leading)
+ for resource in self.getresource():
+ resource.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ setattr(cls, "removeVariableByAddress", _removeConfigurationResourceVariableByAddress)
+ setattr(cls, "removeVariableByFilter", _removeConfigurationResourceVariableByFilter)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = []
+ parent_infos = parent_infos + ["C::%s" % self.getname()]
+ filter = criteria["filter"]
+ if filter == "all" or "configuration" in filter:
+ search_result = _SearchInConfigurationResource(self, criteria, parent_infos)
+ for resource in self.getresource():
+ search_result.extend(resource.Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("configuration_resource", None)
+if cls:
+ def updateElementName(self, old_name, new_name):
+ _updateConfigurationResourceElementName(self, old_name, new_name)
+ for instance in self.getpouInstance():
+ instance.updateElementName(old_name, new_name)
+ for task in self.gettask():
+ task.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ _updateConfigurationResourceElementAddress(self, address_model, new_leading)
+ for task in self.gettask():
+ task.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ setattr(cls, "removeVariableByAddress", _removeConfigurationResourceVariableByAddress)
+ setattr(cls, "removeVariableByFilter", _removeConfigurationResourceVariableByFilter)
+
+ def Search(self, criteria, parent_infos=[]):
+ parent_infos = parent_infos[:-1] + ["R::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
+ search_result = _SearchInConfigurationResource(self, criteria, parent_infos)
+ task_number = 0
+ instance_number = 0
+ for task in self.gettask():
+ results = TestTextElement(task.getname(), criteria)
+ for result in results:
+ search_result.append((tuple(parent_infos + ["task", task_number, "name"]),) + result)
+ search_result.extend(task.Search(criteria, parent_infos + ["task", task_number]))
+ task_number += 1
+ for instance in task.getpouInstance():
+ search_result.extend(task.Search(criteria, parent_infos + ["instance", instance_number]))
+ for result in results:
+ search_result.append((tuple(parent_infos + ["instance", instance_number, "task"]),) + result)
+ instance_number += 1
+ for instance in self.getpouInstance():
+ search_result.extend(instance.Search(criteria, parent_infos + ["instance", instance_number]))
+ instance_number += 1
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("resource_task", None)
+if cls:
+ def compatibility(self, tree):
+ if tree.hasAttribute("interval"):
+ interval = GetAttributeValue(tree._attrs["interval"])
+ result = time_model.match(interval)
+ if result is not None:
+ values = result.groups()
+ time_values = [int(v) for v in values[:2]]
+ seconds = float(values[2])
+ time_values.extend([int(seconds), int((seconds % 1) * 1000000)])
+ text = "t#"
+ if time_values[0] != 0:
+ text += "%dh"%time_values[0]
+ if time_values[1] != 0:
+ text += "%dm"%time_values[1]
+ if time_values[2] != 0:
+ text += "%ds"%time_values[2]
+ if time_values[3] != 0:
+ if time_values[3] % 1000 != 0:
+ text += "%.3fms"%(float(time_values[3]) / 1000)
+ else:
+ text += "%dms"%(time_values[3] / 1000)
+ NodeSetAttr(tree, "interval", text)
+ setattr(cls, "compatibility", compatibility)
+
+ def updateElementName(self, old_name, new_name):
+ if self.single == old_name:
+ self.single = new_name
+ if self.interval == old_name:
+ self.interval = new_name
+ for instance in self.getpouInstance():
+ instance.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ if self.single is not None:
+ self.single = update_address(self.single, address_model, new_leading)
+ if self.interval is not None:
+ self.interval = update_address(self.interval, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ return _Search([("single", self.getsingle()),
+ ("interval", self.getinterval()),
+ ("priority", str(self.getpriority()))],
+ criteria, parent_infos)
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("pouInstance", None)
+if cls:
+ def compatibility(self, tree):
+ if tree.hasAttribute("type"):
+ NodeRenameAttr(tree, "type", "typeName")
+ setattr(cls, "compatibility", compatibility)
+
+ def updateElementName(self, old_name, new_name):
+ if self.typeName == old_name:
+ self.typeName = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def Search(self, criteria, parent_infos=[]):
+ return _Search([("name", self.getname()),
+ ("type", self.gettypeName())],
+ criteria, parent_infos)
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("varListPlain_variable", None)
+if cls:
+ def gettypeAsText(self):
+ vartype_content = self.gettype().getcontent()
+ # Variable type is a user data type
+ if vartype_content["name"] == "derived":
+ return vartype_content["value"].getname()
+ # Variable type is a string type
+ elif vartype_content["name"] in ["string", "wstring"]:
+ return vartype_content["name"].upper()
+ # Variable type is an array
+ elif vartype_content["name"] == "array":
+ base_type = vartype_content["value"].baseType.getcontent()
+ # Array derived directly from a user defined type
+ if base_type["name"] == "derived":
+ basetype_name = base_type["value"].getname()
+ # Array derived directly from a string type
+ elif base_type["name"] in ["string", "wstring"]:
+ basetype_name = base_type["name"].upper()
+ # Array derived directly from an elementary type
+ else:
+ basetype_name = base_type["name"]
+ return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "%s..%s" % (x.getlower(), x.getupper()), vartype_content["value"].getdimension())), basetype_name)
+ # Variable type is an elementary type
+ return vartype_content["name"]
+ setattr(cls, "gettypeAsText", gettypeAsText)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = _Search([("name", self.getname()),
+ ("type", self.gettypeAsText()),
+ ("location", self.getaddress())],
+ criteria, parent_infos)
+ initial = self.getinitialValue()
+ if initial is not None:
+ search_result.extend(_Search([("initial value", initial.getvalue())], criteria, parent_infos))
+ doc = self.getdocumentation()
+ if doc is not None:
+ search_result.extend(doc.Search(criteria, parent_infos + ["documentation"]))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("project_types", None)
+if cls:
+ def getdataTypeElements(self):
+ return self.dataTypes.getdataType()
+ setattr(cls, "getdataTypeElements", getdataTypeElements)
+
+ def getdataTypeElement(self, name):
+ elements = self.dataTypes.getdataType()
+ for element in elements:
+ if element.getname() == name:
+ return element
+ return None
+ setattr(cls, "getdataTypeElement", getdataTypeElement)
+
+ def appenddataTypeElement(self, name):
+ new_datatype = PLCOpenClasses["dataTypes_dataType"]()
+ new_datatype.setname(name)
+ new_datatype.baseType.setcontent({"name" : "BOOL", "value" : None})
+ self.dataTypes.appenddataType(new_datatype)
+ setattr(cls, "appenddataTypeElement", appenddataTypeElement)
+
+ def insertdataTypeElement(self, index, dataType):
+ self.dataTypes.insertdataType(index, dataType)
+ setattr(cls, "insertdataTypeElement", insertdataTypeElement)
+
+ def removedataTypeElement(self, name):
+ found = False
+ for idx, element in enumerate(self.dataTypes.getdataType()):
+ if element.getname() == name:
+ self.dataTypes.removedataType(idx)
+ found = True
+ break
+ if not found:
+ raise ValueError, _("\"%s\" Data Type doesn't exist !!!")%name
+ setattr(cls, "removedataTypeElement", removedataTypeElement)
+
+ 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.setpouType(pou_type)
+ new_pou.appendbody(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 Search(self, criteria, parent_infos=[]):
+ search_result = []
+ filter = criteria["filter"]
+ for datatype in self.dataTypes.getdataType():
+ search_result.extend(datatype.Search(criteria, parent_infos))
+ for pou in self.pous.getpou():
+ search_result.extend(pou.Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+def _updateBaseTypeElementName(self, old_name, new_name):
+ self.baseType.updateElementName(old_name, new_name)
+
+cls = PLCOpenClasses.get("dataTypes_dataType", None)
+if cls:
+ setattr(cls, "updateElementName", _updateBaseTypeElementName)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = []
+ filter = criteria["filter"]
+ if filter == "all" or "datatype" in filter:
+ parent_infos = parent_infos + ["D::%s" % self.getname()]
+ search_result.extend(_Search([("name", self.getname())], criteria, parent_infos))
+ search_result.extend(self.baseType.Search(criteria, parent_infos))
+ if self.initialValue is not None:
+ search_result.extend(_Search([("initial", self.initialValue.getvalue())], criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("dataType", None)
+if cls:
+
+ def updateElementName(self, old_name, new_name):
+ if self.content["name"] in ["derived", "array", "subrangeSigned", "subrangeUnsigned"]:
+ self.content["value"].updateElementName(old_name, new_name)
+ elif self.content["name"] == "struct":
+ for element in self.content["value"].getvariable():
+ element_type = element.type.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = []
+ if self.content["name"] in ["derived", "array", "enum", "subrangeSigned", "subrangeUnsigned"]:
+ search_result.extend(self.content["value"].Search(criteria, parent_infos))
+ elif self.content["name"] == "struct":
+ for i, element in enumerate(self.content["value"].getvariable()):
+ search_result.extend(element.Search(criteria, parent_infos + ["struct", i]))
+ else:
+ basetype = self.content["name"]
+ if basetype in ["string", "wstring"]:
+ basetype = basetype.upper()
+ search_result.extend(_Search([("base", basetype)], criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("derivedTypes_array", None)
+if cls:
+ setattr(cls, "updateElementName", _updateBaseTypeElementName)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = self.baseType.Search(criteria, parent_infos)
+ for i, dimension in enumerate(self.getdimension()):
+ search_result.extend(_Search([("lower", dimension.getlower()),
+ ("upper", dimension.getupper())],
+ criteria, parent_infos + ["range", i]))
+ return search_result
+ setattr(cls, "Search", Search)
+
+def _SearchInSubrange(self, criteria, parent_infos=[]):
+ search_result = self.baseType.Search(criteria, parent_infos)
+ search_result.extend(_Search([("lower", self.range.getlower()),
+ ("upper", self.range.getupper())],
+ criteria, parent_infos))
+ return search_result
+
+cls = PLCOpenClasses.get("derivedTypes_subrangeSigned", None)
+if cls:
+ setattr(cls, "updateElementName", _updateBaseTypeElementName)
+ setattr(cls, "Search", _SearchInSubrange)
+
+cls = PLCOpenClasses.get("derivedTypes_subrangeUnsigned", None)
+if cls:
+ setattr(cls, "updateElementName", _updateBaseTypeElementName)
+ setattr(cls, "Search", _SearchInSubrange)
+
+cls = PLCOpenClasses.get("derivedTypes_enum", None)
+if cls:
+
+ def updateElementName(self, old_name, new_name):
+ pass
+ setattr(cls, "updateElementName", updateElementName)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = []
+ for i, value in enumerate(self.values.getvalue()):
+ for result in TestTextElement(value.getname(), criteria):
+ search_result.append((tuple(parent_infos + ["value", i]),) + result)
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("pous_pou", None)
+if cls:
+
+ def setdescription(self, description):
+ doc = self.getdocumentation()
+ if doc is None:
+ doc = PLCOpenClasses["formattedText"]()
+ self.setdocumentation(doc)
+ doc.settext(description)
+ setattr(cls, "setdescription", setdescription)
+
+ def getdescription(self):
+ doc = self.getdocumentation()
+ if doc is not None:
+ return doc.gettext()
+ return ""
+ setattr(cls, "getdescription", getdescription)
+
+ def setbodyType(self, type):
+ if len(self.body) > 0:
+ if type == "IL":
+ self.body[0].setcontent({"name" : "IL", "value" : PLCOpenClasses["formattedText"]()})
+ elif type == "ST":
+ self.body[0].setcontent({"name" : "ST", "value" : PLCOpenClasses["formattedText"]()})
+ elif type == "LD":
+ self.body[0].setcontent({"name" : "LD", "value" : PLCOpenClasses["body_LD"]()})
+ elif type == "FBD":
+ self.body[0].setcontent({"name" : "FBD", "value" : PLCOpenClasses["body_FBD"]()})
+ elif type == "SFC":
+ self.body[0].setcontent({"name" : "SFC", "value" : PLCOpenClasses["body_SFC"]()})
+ else:
+ raise ValueError, "%s isn't a valid body type!"%type
+ setattr(cls, "setbodyType", setbodyType)
+
+ def getbodyType(self):
+ if len(self.body) > 0:
+ return self.body[0].getcontent()["name"]
+ setattr(cls, "getbodyType", getbodyType)
+
+ def resetexecutionOrder(self):
+ if len(self.body) > 0:
+ self.body[0].resetexecutionOrder()
+ setattr(cls, "resetexecutionOrder", resetexecutionOrder)
+
+ def compileexecutionOrder(self):
+ if len(self.body) > 0:
+ self.body[0].compileexecutionOrder()
+ setattr(cls, "compileexecutionOrder", compileexecutionOrder)
+
+ def setelementExecutionOrder(self, instance, new_executionOrder):
+ if len(self.body) > 0:
+ self.body[0].setelementExecutionOrder(instance, new_executionOrder)
+ setattr(cls, "setelementExecutionOrder", setelementExecutionOrder)
+
+ def addinstance(self, name, instance):
+ if len(self.body) > 0:
+ self.body[0].appendcontentInstance(name, instance)
+ setattr(cls, "addinstance", addinstance)
+
+ def getinstances(self):
+ if len(self.body) > 0:
+ return self.body[0].getcontentInstances()
+ return []
+ setattr(cls, "getinstances", getinstances)
+
+ def getinstance(self, id):
+ if len(self.body) > 0:
+ return self.body[0].getcontentInstance(id)
+ return None
+ setattr(cls, "getinstance", getinstance)
+
+ def getrandomInstance(self, exclude):
+ if len(self.body) > 0:
+ return self.body[0].getcontentRandomInstance(exclude)
+ return None
+ setattr(cls, "getrandomInstance", getrandomInstance)
+
+ def getinstanceByName(self, name):
+ if len(self.body) > 0:
+ return self.body[0].getcontentInstanceByName(name)
+ return None
+ setattr(cls, "getinstanceByName", getinstanceByName)
+
+ def removeinstance(self, id):
+ if len(self.body) > 0:
+ self.body[0].removecontentInstance(id)
+ setattr(cls, "removeinstance", removeinstance)
+
+ def settext(self, text):
+ if len(self.body) > 0:
+ self.body[0].settext(text)
+ setattr(cls, "settext", settext)
+
+ def gettext(self):
+ if len(self.body) > 0:
+ return self.body[0].gettext()
+ return ""
+ setattr(cls, "gettext", gettext)
+
+ def getvars(self):
+ vars = []
+ if self.interface is not None:
+ 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):
+ if self.interface is None:
+ self.interface = PLCOpenClasses["pou_interface"]()
+ self.interface.setcontent([])
+ for vartype, varlist in vars:
+ self.interface.appendcontent({"name" : VarTypes[vartype], "value" : varlist})
+ setattr(cls, "setvars", setvars)
+
+ def addpouLocalVar(self, type, name, location="", description=""):
+ self.addpouVar(type, name, location=location, description=description)
+ setattr(cls, "addpouLocalVar", addpouLocalVar)
+
+ def addpouExternalVar(self, type, name):
+ self.addpouVar(type, name, "externalVars")
+ setattr(cls, "addpouExternalVar", addpouExternalVar)
+
+ def addpouVar(self, type, name, var_class="localVars", location="", description=""):
+ if self.interface is None:
+ self.interface = PLCOpenClasses["pou_interface"]()
+ content = self.interface.getcontent()
+ if len(content) == 0 or content[-1]["name"] != var_class:
+ content.append({"name" : var_class, "value" : PLCOpenClasses["interface_%s" % var_class]()})
+ else:
+ varlist = content[-1]["value"]
+ variables = varlist.getvariable()
+ if varlist.getconstant() or varlist.getretain() or len(variables) > 0 and variables[0].getaddress():
+ content.append({"name" : var_class, "value" : PLCOpenClasses["interface_%s" % var_class]()})
+ var = PLCOpenClasses["varListPlain_variable"]()
+ var.setname(name)
+ var_type = PLCOpenClasses["dataType"]()
+ if type in [x for x,y in TypeHierarchy_list if not x.startswith("ANY")]:
+ if type == "STRING":
+ var_type.setcontent({"name" : "string", "value" : PLCOpenClasses["elementaryTypes_string"]()})
+ elif type == "WSTRING":
+ var_type.setcontent({"name" : "wstring", "value" : PLCOpenClasses["elementaryTypes_wstring"]()})
+ else:
+ var_type.setcontent({"name" : type, "value" : None})
+ else:
+ derived_type = PLCOpenClasses["derivedTypes_derived"]()
+ derived_type.setname(type)
+ var_type.setcontent({"name" : "derived", "value" : derived_type})
+ var.settype(var_type)
+ if location != "":
+ var.setaddress(location)
+ if description != "":
+ ft = PLCOpenClasses["formattedText"]()
+ ft.settext(description)
+ var.setdocumentation(ft)
+
+ content[-1]["value"].appendvariable(var)
+ setattr(cls, "addpouVar", addpouVar)
+
+ def changepouVar(self, old_type, old_name, new_type, new_name):
+ if self.interface is not None:
+ content = self.interface.getcontent()
+ for varlist in content:
+ variables = varlist["value"].getvariable()
+ for var in variables:
+ if var.getname() == old_name:
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived" and vartype_content["value"].getname() == old_type:
+ var.setname(new_name)
+ vartype_content["value"].setname(new_type)
+ return
+ setattr(cls, "changepouVar", changepouVar)
+
+ def removepouVar(self, type, name):
+ if self.interface is not None:
+ content = self.interface.getcontent()
+ for varlist in content:
+ variables = varlist["value"].getvariable()
+ for var in variables:
+ if var.getname() == name:
+ vartype_content = var.gettype().getcontent()
+ if vartype_content["name"] == "derived" and vartype_content["value"].getname() == type:
+ variables.remove(var)
+ break
+ if len(varlist["value"].getvariable()) == 0:
+ content.remove(varlist)
+ break
+ setattr(cls, "removepouVar", removepouVar)
+
+ def hasblock(self, name):
+ if self.getbodyType() in ["FBD", "LD", "SFC"]:
+ for instance in self.getinstances():
+ if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
+ return True
+ if self.transitions:
+ for transition in self.transitions.gettransition():
+ result = transition.hasblock(name)
+ if result:
+ return result
+ if self.actions:
+ for action in self.actions.getaction():
+ result = action.hasblock(name)
+ if result:
+ return result
+ return False
+ setattr(cls, "hasblock", hasblock)
+
+ def addtransition(self, name, type):
+ if not self.transitions:
+ self.addtransitions()
+ self.transitions.settransition([])
+ transition = PLCOpenClasses["transitions_transition"]()
+ transition.setname(name)
+ transition.setbodyType(type)
+ if type == "ST":
+ transition.settext(":= ;")
+ elif type == "IL":
+ transition.settext("\tST\t%s"%name)
+ 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.pop(i)
+ removed = True
+ i += 1
+ if not removed:
+ raise ValueError, _("Transition with name %s doesn't exist!")%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.pop(i)
+ removed = True
+ i += 1
+ if not removed:
+ raise ValueError, _("Action with name %s doesn't exist!")%name
+ setattr(cls, "removeaction", removeaction)
+
+ def updateElementName(self, old_name, new_name):
+ if self.interface:
+ for content in self.interface.getcontent():
+ for var in content["value"].getvariable():
+ var_address = var.getaddress()
+ if var_address is not None:
+ if var_address == old_name:
+ var.setaddress(new_name)
+ if var.getname() == old_name:
+ var.setname(new_name)
+ var_type_content = var.gettype().getcontent()
+ if var_type_content["name"] == "derived":
+ if var_type_content["value"].getname() == old_name:
+ var_type_content["value"].setname(new_name)
+ self.body[0].updateElementName(old_name, new_name)
+ for action in self.getactionList():
+ action.updateElementName(old_name, new_name)
+ for transition in self.gettransitionList():
+ transition.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ if self.interface:
+ for content in self.interface.getcontent():
+ for var in content["value"].getvariable():
+ var_address = var.getaddress()
+ if var_address is not None:
+ var.setaddress(update_address(var_address, address_model, new_leading))
+ self.body[0].updateElementAddress(address_model, new_leading)
+ for action in self.getactionList():
+ action.updateElementAddress(address_model, new_leading)
+ for transition in self.gettransitionList():
+ transition.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def removeVariableByAddress(self, address):
+ if self.interface:
+ for content in self.interface.getcontent():
+ variables = content["value"].getvariable()
+ for i in xrange(len(variables)-1, -1, -1):
+ if variables[i].getaddress() == address:
+ variables.pop(i)
+ setattr(cls, "removeVariableByAddress", removeVariableByAddress)
+
+ def removeVariableByFilter(self, address_model):
+ if self.interface:
+ for content in self.interface.getcontent():
+ variables = content["value"].getvariable()
+ for i in xrange(len(variables)-1, -1, -1):
+ var_address = variables[i].getaddress()
+ if var_address is not None:
+ result = address_model.match(var_address)
+ if result is not None:
+ variables.pop(i)
+ setattr(cls, "removeVariableByFilter", removeVariableByFilter)
+
+ def Search(self, criteria, parent_infos=[]):
+ search_result = []
+ filter = criteria["filter"]
+ if filter == "all" or self.getpouType() in filter:
+ parent_infos = parent_infos + ["P::%s" % self.getname()]
+ search_result.extend(_Search([("name", self.getname())], criteria, parent_infos))
+ if self.interface is not None:
+ var_number = 0
+ for content in self.interface.getcontent():
+ variable_type = searchResultVarTypes.get(content["value"], "var_local")
+ variables = content["value"].getvariable()
+ for modifier, has_modifier in [("constant", content["value"].getconstant()),
+ ("retain", content["value"].getretain()),
+ ("non_retain", content["value"].getnonretain())]:
+ if has_modifier:
+ for result in TestTextElement(modifier, criteria):
+ search_result.append((tuple(parent_infos + [variable_type, (var_number, var_number + len(variables)), modifier]),) + result)
+ break
+ for variable in variables:
+ search_result.extend(variable.Search(criteria, parent_infos + [variable_type, var_number]))
+ var_number += 1
+ if len(self.body) > 0:
+ search_result.extend(self.body[0].Search(criteria, parent_infos))
+ for action in self.getactionList():
+ search_result.extend(action.Search(criteria, parent_infos))
+ for transition in self.gettransitionList():
+ search_result.extend(transition.Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+def setbodyType(self, type):
+ if type == "IL":
+ self.body.setcontent({"name" : "IL", "value" : PLCOpenClasses["formattedText"]()})
+ elif type == "ST":
+ self.body.setcontent({"name" : "ST", "value" : PLCOpenClasses["formattedText"]()})
+ elif type == "LD":
+ self.body.setcontent({"name" : "LD", "value" : PLCOpenClasses["body_LD"]()})
+ elif type == "FBD":
+ self.body.setcontent({"name" : "FBD", "value" : PLCOpenClasses["body_FBD"]()})
+ elif type == "SFC":
+ self.body.setcontent({"name" : "SFC", "value" : PLCOpenClasses["body_SFC"]()})
+ else:
+ raise ValueError, "%s isn't a valid body type!"%type
+
+def getbodyType(self):
+ return self.body.getcontent()["name"]
+
+def resetexecutionOrder(self):
+ self.body.resetexecutionOrder()
+
+def compileexecutionOrder(self):
+ self.body.compileexecutionOrder()
+
+def setelementExecutionOrder(self, instance, new_executionOrder):
+ self.body.setelementExecutionOrder(instance, new_executionOrder)
+
+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()
+
+cls = PLCOpenClasses.get("transitions_transition", None)
+if cls:
+ setattr(cls, "setbodyType", setbodyType)
+ setattr(cls, "getbodyType", getbodyType)
+ setattr(cls, "resetexecutionOrder", resetexecutionOrder)
+ setattr(cls, "compileexecutionOrder", compileexecutionOrder)
+ setattr(cls, "setelementExecutionOrder", setelementExecutionOrder)
+ 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 updateElementName(self, old_name, new_name):
+ self.body.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.body.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def hasblock(self, name):
+ if self.getbodyType() in ["FBD", "LD", "SFC"]:
+ for instance in self.getinstances():
+ if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
+ return True
+ return False
+ setattr(cls, "hasblock", hasblock)
+
+ def Search(self, criteria, parent_infos):
+ search_result = []
+ parent_infos = parent_infos[:-1] + ["T::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
+ for result in TestTextElement(self.getname(), criteria):
+ search_result.append((tuple(parent_infos + ["name"]),) + result)
+ search_result.extend(self.body.Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("actions_action", None)
+if cls:
+ setattr(cls, "setbodyType", setbodyType)
+ setattr(cls, "getbodyType", getbodyType)
+ setattr(cls, "resetexecutionOrder", resetexecutionOrder)
+ setattr(cls, "compileexecutionOrder", compileexecutionOrder)
+ setattr(cls, "setelementExecutionOrder", setelementExecutionOrder)
+ 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 updateElementName(self, old_name, new_name):
+ self.body.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.body.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def hasblock(self, name):
+ if self.getbodyType() in ["FBD", "LD", "SFC"]:
+ for instance in self.getinstances():
+ if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
+ return True
+ return False
+ setattr(cls, "hasblock", hasblock)
+
+ def Search(self, criteria, parent_infos):
+ search_result = []
+ parent_infos = parent_infos[:-1] + ["A::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
+ for result in TestTextElement(self.getname(), criteria):
+ search_result.append((tuple(parent_infos + ["name"]),) + result)
+ search_result.extend(self.body.Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("body", None)
+if cls:
+ cls.currentExecutionOrderId = 0
+
+ def resetcurrentExecutionOrderId(self):
+ object.__setattr__(self, "currentExecutionOrderId", 0)
+ setattr(cls, "resetcurrentExecutionOrderId", resetcurrentExecutionOrderId)
+
+ def getnewExecutionOrderId(self):
+ object.__setattr__(self, "currentExecutionOrderId", self.currentExecutionOrderId + 1)
+ return self.currentExecutionOrderId
+ setattr(cls, "getnewExecutionOrderId", getnewExecutionOrderId)
+
+ def resetexecutionOrder(self):
+ if self.content["name"] == "FBD":
+ for element in self.content["value"].getcontent():
+ if not isinstance(element["value"], (PLCOpenClasses.get("commonObjects_comment", None),
+ PLCOpenClasses.get("commonObjects_connector", None),
+ PLCOpenClasses.get("commonObjects_continuation", None))):
+ element["value"].setexecutionOrderId(0)
+ else:
+ raise TypeError, _("Can only generate execution order on FBD networks!")
+ setattr(cls, "resetexecutionOrder", resetexecutionOrder)
+
+ def compileexecutionOrder(self):
+ if self.content["name"] == "FBD":
+ self.resetexecutionOrder()
+ self.resetcurrentExecutionOrderId()
+ for element in self.content["value"].getcontent():
+ if isinstance(element["value"], PLCOpenClasses.get("fbdObjects_outVariable", None)) and element["value"].getexecutionOrderId() == 0:
+ connections = element["value"].connectionPointIn.getconnections()
+ if connections and len(connections) == 1:
+ self.compileelementExecutionOrder(connections[0])
+ element["value"].setexecutionOrderId(self.getnewExecutionOrderId())
+ else:
+ raise TypeError, _("Can only generate execution order on FBD networks!")
+ setattr(cls, "compileexecutionOrder", compileexecutionOrder)
+
+ def compileelementExecutionOrder(self, link):
+ if self.content["name"] == "FBD":
+ localid = link.getrefLocalId()
+ instance = self.getcontentInstance(localid)
+ if isinstance(instance, PLCOpenClasses.get("fbdObjects_block", None)) and instance.getexecutionOrderId() == 0:
+ for variable in instance.inputVariables.getvariable():
+ connections = variable.connectionPointIn.getconnections()
+ if connections and len(connections) == 1:
+ self.compileelementExecutionOrder(connections[0])
+ instance.setexecutionOrderId(self.getnewExecutionOrderId())
+ elif isinstance(instance, PLCOpenClasses.get("commonObjects_continuation", None)) and instance.getexecutionOrderId() == 0:
+ name = instance.getname()
+ for tmp_instance in self.getcontentInstances():
+ if isinstance(tmp_instance, PLCOpenClasses.get("commonObjects_connector", None)) and tmp_instance.getname() == name and tmp_instance.getexecutionOrderId() == 0:
+ connections = tmp_instance.connectionPointIn.getconnections()
+ if connections and len(connections) == 1:
+ self.compileelementExecutionOrder(connections[0])
+ else:
+ raise TypeError, _("Can only generate execution order on FBD networks!")
+ setattr(cls, "compileelementExecutionOrder", compileelementExecutionOrder)
+
+ def setelementExecutionOrder(self, instance, new_executionOrder):
+ if self.content["name"] == "FBD":
+ old_executionOrder = instance.getexecutionOrderId()
+ if old_executionOrder is not None and old_executionOrder != 0 and new_executionOrder != 0:
+ for element in self.content["value"].getcontent():
+ if element["value"] != instance and not isinstance(element["value"], PLCOpenClasses.get("commonObjects_comment", None)):
+ element_executionOrder = element["value"].getexecutionOrderId()
+ if old_executionOrder <= element_executionOrder <= new_executionOrder:
+ element["value"].setexecutionOrderId(element_executionOrder - 1)
+ if new_executionOrder <= element_executionOrder <= old_executionOrder:
+ element["value"].setexecutionOrderId(element_executionOrder + 1)
+ instance.setexecutionOrderId(new_executionOrder)
+ else:
+ raise TypeError, _("Can only generate execution order on FBD networks!")
+ setattr(cls, "setelementExecutionOrder", setelementExecutionOrder)
+
+ def appendcontentInstance(self, name, instance):
+ if self.content["name"] in ["LD","FBD","SFC"]:
+ self.content["value"].appendcontent({"name" : name, "value" : 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 isinstance(element["value"], PLCOpenClasses.get("fbdObjects_block", None)) and element["value"].getinstanceName() == 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 exist!")%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 updateElementName(self, old_name, new_name):
+ if self.content["name"] in ["IL", "ST"]:
+ self.content["value"].updateElementName(old_name, new_name)
+ else:
+ for element in self.content["value"].getcontent():
+ element["value"].updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ if self.content["name"] in ["IL", "ST"]:
+ self.content["value"].updateElementAddress(address_model, new_leading)
+ else:
+ for element in self.content["value"].getcontent():
+ element["value"].updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ if self.content["name"] in ["IL", "ST"]:
+ search_result = self.content["value"].Search(criteria, parent_infos + ["body", 0])
+ else:
+ search_result = []
+ for element in self.content["value"].getcontent():
+ search_result.extend(element["value"].Search(criteria, parent_infos))
+ return search_result
+ setattr(cls, "Search", Search)
+
+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)
+
+def _getBoundingBox(self):
+ return rect(self.getx(), self.gety(), self.getwidth(), self.getheight())
+
+def _getConnectionsBoundingBox(connectionPointIn):
+ bbox = rect()
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ for x, y in connection.getpoints():
+ bbox.update(x, y)
+ return bbox
+
+def _getBoundingBoxSingle(self):
+ bbox = _getBoundingBox(self)
+ if self.connectionPointIn is not None:
+ bbox.union(_getConnectionsBoundingBox(self.connectionPointIn))
+ return bbox
+
+def _getBoundingBoxMultiple(self):
+ bbox = _getBoundingBox(self)
+ for connectionPointIn in self.getconnectionPointIn():
+ bbox.union(_getConnectionsBoundingBox(connectionPointIn))
+ return bbox
+
+def _filterConnections(connectionPointIn, localId, connections):
+ in_connections = connectionPointIn.getconnections()
+ if in_connections is not None:
+ to_delete = []
+ for i, connection in enumerate(in_connections):
+ connected = connection.getrefLocalId()
+ if not connections.has_key((localId, connected)) and \
+ not connections.has_key((connected, localId)):
+ to_delete.append(i)
+ to_delete.reverse()
+ for i in to_delete:
+ connectionPointIn.removeconnection(i)
+
+def _filterConnectionsSingle(self, connections):
+ if self.connectionPointIn is not None:
+ _filterConnections(self.connectionPointIn, self.localId, connections)
+
+def _filterConnectionsMultiple(self, connections):
+ for connectionPointIn in self.getconnectionPointIn():
+ _filterConnections(connectionPointIn, self.localId, connections)
+
+def _getconnectionsdefinition(instance, connections_end):
+ id = instance.getlocalId()
+ return dict([((id, end), True) for end in connections_end])
+
+def _updateConnectionsId(connectionPointIn, translation):
+ connections_end = []
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ refLocalId = connection.getrefLocalId()
+ new_reflocalId = translation.get(refLocalId, refLocalId)
+ connection.setrefLocalId(new_reflocalId)
+ connections_end.append(new_reflocalId)
+ return connections_end
+
+def _updateConnectionsIdSingle(self, translation):
+ connections_end = []
+ if self.connectionPointIn is not None:
+ connections_end = _updateConnectionsId(self.connectionPointIn, translation)
+ return _getconnectionsdefinition(self, connections_end)
+
+def _updateConnectionsIdMultiple(self, translation):
+ connections_end = []
+ for connectionPointIn in self.getconnectionPointIn():
+ connections_end.extend(_updateConnectionsId(connectionPointIn, translation))
+ return _getconnectionsdefinition(self, connections_end)
+
+def _translate(self, dx, dy):
+ self.setx(self.getx() + dx)
+ self.sety(self.gety() + dy)
+
+def _translateConnections(connectionPointIn, dx, dy):
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ for position in connection.getposition():
+ position.setx(position.getx() + dx)
+ position.sety(position.gety() + dy)
+
+def _translateSingle(self, dx, dy):
+ _translate(self, dx, dy)
+ if self.connectionPointIn is not None:
+ _translateConnections(self.connectionPointIn, dx, dy)
+
+def _translateMultiple(self, dx, dy):
+ _translate(self, dx, dy)
+ for connectionPointIn in self.getconnectionPointIn():
+ _translateConnections(connectionPointIn, dx, dy)
+
+def _updateElementName(self, old_name, new_name):
+ pass
+
+def _updateElementAddress(self, address_model, new_leading):
+ pass
+
+def _SearchInElement(self, criteria, parent_infos=[]):
+ return []
+
+_connectionsFunctions = {
+ "bbox": {"none": _getBoundingBox,
+ "single": _getBoundingBoxSingle,
+ "multiple": _getBoundingBoxMultiple},
+ "translate": {"none": _translate,
+ "single": _translateSingle,
+ "multiple": _translateMultiple},
+ "filter": {"none": lambda self, connections: None,
+ "single": _filterConnectionsSingle,
+ "multiple": _filterConnectionsMultiple},
+ "update": {"none": lambda self, translation: {},
+ "single": _updateConnectionsIdSingle,
+ "multiple": _updateConnectionsIdMultiple},
+}
+
+def _initElementClass(name, classname, connectionPointInType="none"):
+ ElementNameToClass[name] = classname
+ cls = PLCOpenClasses.get(classname, None)
+ if cls:
+ setattr(cls, "getx", getx)
+ setattr(cls, "gety", gety)
+ setattr(cls, "setx", setx)
+ setattr(cls, "sety", sety)
+ setattr(cls, "updateElementName", _updateElementName)
+ setattr(cls, "updateElementAddress", _updateElementAddress)
+ setattr(cls, "getBoundingBox", _connectionsFunctions["bbox"][connectionPointInType])
+ setattr(cls, "translate", _connectionsFunctions["translate"][connectionPointInType])
+ setattr(cls, "filterConnections", _connectionsFunctions["filter"][connectionPointInType])
+ setattr(cls, "updateConnectionsId", _connectionsFunctions["update"][connectionPointInType])
+ setattr(cls, "Search", _SearchInElement)
+ return cls
+
+def _getexecutionOrder(instance, specific_values):
+ executionOrder = instance.getexecutionOrderId()
+ if executionOrder is None:
+ executionOrder = 0
+ specific_values["executionOrder"] = executionOrder
+
+def _getdefaultmodifiers(instance, infos):
+ infos["negated"] = instance.getnegated()
+ infos["edge"] = instance.getedge()
+
+def _getinputmodifiers(instance, infos):
+ infos["negated"] = instance.getnegatedIn()
+ infos["edge"] = instance.getedgeIn()
+
+def _getoutputmodifiers(instance, infos):
+ infos["negated"] = instance.getnegatedOut()
+ infos["edge"] = instance.getedgeOut()
+
+MODIFIERS_FUNCTIONS = {"default": _getdefaultmodifiers,
+ "input": _getinputmodifiers,
+ "output": _getoutputmodifiers}
+
+def _getconnectioninfos(instance, connection, links=False, modifiers=None, parameter=False):
+ infos = {"position": connection.getrelPositionXY()}
+ if parameter:
+ infos["name"] = instance.getformalParameter()
+ MODIFIERS_FUNCTIONS.get(modifiers, lambda x, y: None)(instance, infos)
+ if links:
+ infos["links"] = []
+ connections = connection.getconnections()
+ if connections is not None:
+ for link in connections:
+ dic = {"refLocalId": link.getrefLocalId(),
+ "points": link.getpoints(),
+ "formalParameter": link.getformalParameter()}
+ infos["links"].append(dic)
+ return infos
+
+def _getelementinfos(instance):
+ return {"id": instance.getlocalId(),
+ "x": instance.getx(),
+ "y": instance.gety(),
+ "height": instance.getheight(),
+ "width": instance.getwidth(),
+ "specific_values": {},
+ "inputs": [],
+ "outputs": []}
+
+def _getvariableinfosFunction(type, input, output):
+ def getvariableinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = type
+ specific_values = infos["specific_values"]
+ specific_values["name"] = self.getexpression()
+ _getexecutionOrder(self, specific_values)
+ if input and output:
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True, "input"))
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut, False, "output"))
+ elif input:
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True, "default"))
+ elif output:
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut, False, "default"))
+ return infos
+ return getvariableinfos
+
+def _getconnectorinfosFunction(type):
+ def getvariableinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = type
+ infos["specific_values"]["name"] = self.getname()
+ if type == "connector":
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ elif type == "continuation":
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut))
+ return infos
+ return getvariableinfos
+
+def _getpowerrailinfosFunction(type):
+ def getpowerrailinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = type
+ if type == "rightPowerRail":
+ for connectionPointIn in self.getconnectionPointIn():
+ infos["inputs"].append(_getconnectioninfos(self, connectionPointIn, True))
+ infos["specific_values"]["connectors"] = len(infos["inputs"])
+ elif type == "leftPowerRail":
+ for connectionPointOut in self.getconnectionPointOut():
+ infos["outputs"].append(_getconnectioninfos(self, connectionPointOut))
+ infos["specific_values"]["connectors"] = len(infos["outputs"])
+ return infos
+ return getpowerrailinfos
+
+def _getldelementinfosFunction(type):
+ def getldelementinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = type
+ specific_values = infos["specific_values"]
+ specific_values["name"] = self.getvariable()
+ _getexecutionOrder(self, specific_values)
+ specific_values["negated"] = self.getnegated()
+ specific_values["edge"] = self.getedge()
+ if type == "coil":
+ specific_values["storage"] = self.getstorage()
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut))
+ return infos
+ return getldelementinfos
+
+DIVERGENCE_TYPES = {(True, True): "simultaneousDivergence",
+ (True, False): "selectionDivergence",
+ (False, True): "simultaneousConvergence",
+ (False, False): "selectionConvergence"}
+
+def _getdivergenceinfosFunction(divergence, simultaneous):
+ def getdivergenceinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = DIVERGENCE_TYPES[(divergence, simultaneous)]
+ if divergence:
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ for connectionPointOut in self.getconnectionPointOut():
+ infos["outputs"].append(_getconnectioninfos(self, connectionPointOut))
+ infos["specific_values"]["connectors"] = len(infos["outputs"])
+ else:
+ for connectionPointIn in self.getconnectionPointIn():
+ infos["inputs"].append(_getconnectioninfos(self, connectionPointIn, True))
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut))
+ infos["specific_values"]["connectors"] = len(infos["inputs"])
+ return infos
+ return getdivergenceinfos
+
+cls = _initElementClass("comment", "commonObjects_comment")
+if cls:
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = "comment"
+ infos["specific_values"]["content"] = self.getcontentText()
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ def setcontentText(self, text):
+ self.content.settext(text)
+ setattr(cls, "setcontentText", setcontentText)
+
+ def getcontentText(self):
+ return self.content.gettext()
+ setattr(cls, "getcontentText", getcontentText)
+
+ def updateElementName(self, old_name, new_name):
+ self.content.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.content.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ return self.content.Search(criteria, parent_infos + ["comment", self.getlocalId(), "content"])
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("block", "fbdObjects_block")
+if cls:
+ def getBoundingBox(self):
+ bbox = _getBoundingBox(self)
+ for input in self.inputVariables.getvariable():
+ bbox.union(_getConnectionsBoundingBox(input.connectionPointIn))
+ return bbox
+ setattr(cls, "getBoundingBox", getBoundingBox)
+
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = self.gettypeName()
+ specific_values = infos["specific_values"]
+ specific_values["name"] = self.getinstanceName()
+ _getexecutionOrder(self, specific_values)
+ for variable in self.inputVariables.getvariable():
+ infos["inputs"].append(_getconnectioninfos(variable, variable.connectionPointIn, True, "default", True))
+ for variable in self.outputVariables.getvariable():
+ infos["outputs"].append(_getconnectioninfos(variable, variable.connectionPointOut, False, "default", True))
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ def updateElementName(self, old_name, new_name):
+ if self.typeName == old_name:
+ self.typeName = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def filterConnections(self, connections):
+ for input in self.inputVariables.getvariable():
+ _filterConnections(input.connectionPointIn, self.localId, connections)
+ setattr(cls, "filterConnections", filterConnections)
+
+ def updateConnectionsId(self, translation):
+ connections_end = []
+ for input in self.inputVariables.getvariable():
+ connections_end.extend(_updateConnectionsId(input.connectionPointIn, translation))
+ return _getconnectionsdefinition(self, connections_end)
+ setattr(cls, "updateConnectionsId", updateConnectionsId)
+
+ def translate(self, dx, dy):
+ _translate(self, dx, dy)
+ for input in self.inputVariables.getvariable():
+ _translateConnections(input.connectionPointIn, dx, dy)
+ setattr(cls, "translate", translate)
+
+ def Search(self, criteria, parent_infos=[]):
+ parent_infos = parent_infos + ["block", self.getlocalId()]
+ search_result = _Search([("name", self.getinstanceName()),
+ ("type", self.gettypeName())],
+ criteria, parent_infos)
+ for i, variable in enumerate(self.inputVariables.getvariable()):
+ for result in TestTextElement(variable.getformalParameter(), criteria):
+ search_result.append((tuple(parent_infos + ["input", i]),) + result)
+ for i, variable in enumerate(self.outputVariables.getvariable()):
+ for result in TestTextElement(variable.getformalParameter(), criteria):
+ search_result.append((tuple(parent_infos + ["output", i]),) + result)
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("leftPowerRail", "ldObjects_leftPowerRail")
+if cls:
+ setattr(cls, "getinfos", _getpowerrailinfosFunction("leftPowerRail"))
+
+cls = _initElementClass("rightPowerRail", "ldObjects_rightPowerRail", "multiple")
+if cls:
+ setattr(cls, "getinfos", _getpowerrailinfosFunction("rightPowerRail"))
+
+cls = _initElementClass("contact", "ldObjects_contact", "single")
+if cls:
+ setattr(cls, "getinfos", _getldelementinfosFunction("contact"))
+
+ def updateElementName(self, old_name, new_name):
+ if self.variable == old_name:
+ self.variable = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.variable = update_address(self.variable, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ return _Search([("reference", self.getvariable())], criteria, parent_infos + ["contact", self.getlocalId()])
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("coil", "ldObjects_coil", "single")
+if cls:
+ setattr(cls, "getinfos", _getldelementinfosFunction("coil"))
+
+ def updateElementName(self, old_name, new_name):
+ if self.variable == old_name:
+ self.variable = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.variable = update_address(self.variable, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ return _Search([("reference", self.getvariable())], criteria, parent_infos + ["coil", self.getlocalId()])
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("step", "sfcObjects_step", "single")
+if cls:
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = "step"
+ specific_values = infos["specific_values"]
+ specific_values["name"] = self.getname()
+ specific_values["initial"] = self.getinitialStep()
+ if self.connectionPointIn:
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ if self.connectionPointOut:
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut))
+ if self.connectionPointOutAction:
+ specific_values["action"] = _getconnectioninfos(self, self.connectionPointOutAction)
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ def Search(self, criteria, parent_infos=[]):
+ return _Search([("name", self.getname())], criteria, parent_infos + ["step", self.getlocalId()])
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("transition_condition", None)
+if cls:
+ def compatibility(self, tree):
+ connections = []
+ for child in tree.childNodes:
+ if child.nodeName == "connection":
+ connections.append(child)
+ if len(connections) > 0:
+ node = CreateNode("connectionPointIn")
+ relPosition = CreateNode("relPosition")
+ NodeSetAttr(relPosition, "x", "0")
+ NodeSetAttr(relPosition, "y", "0")
+ node.childNodes.append(relPosition)
+ node.childNodes.extend(connections)
+ tree.childNodes = [node]
+ setattr(cls, "compatibility", compatibility)
+
+cls = _initElementClass("transition", "sfcObjects_transition", "single")
+if cls:
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = "transition"
+ specific_values = infos["specific_values"]
+ priority = self.getpriority()
+ if priority is None:
+ priority = 0
+ specific_values["priority"] = priority
+ condition = self.getconditionContent()
+ specific_values["condition_type"] = condition["type"]
+ if specific_values["condition_type"] == "connection":
+ specific_values["connection"] = _getconnectioninfos(self, condition["value"], True)
+ else:
+ specific_values["condition"] = condition["value"]
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ infos["outputs"].append(_getconnectioninfos(self, self.connectionPointOut))
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ 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({"name" : "ST", "value" : PLCOpenClasses["formattedText"]()})
+ condition.settext(value)
+ elif type == "connection":
+ type = "connectionPointIn"
+ condition = PLCOpenClasses["connectionPointIn"]()
+ self.condition.setcontent({"name" : type, "value" : 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()
+ elif values["type"] == "connectionPointIn":
+ values["type"] = "connection"
+ values["value"] = content["value"]
+ return values
+ return ""
+ setattr(cls, "getconditionContent", getconditionContent)
+
+ def updateElementName(self, old_name, new_name):
+ if self.condition:
+ content = self.condition.getcontent()
+ if content["name"] == "reference":
+ if content["value"].getname() == old_name:
+ content["value"].setname(new_name)
+ elif content["name"] == "inline":
+ content["value"].updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ if self.condition:
+ content = self.condition.getcontent()
+ if content["name"] == "reference":
+ content["value"].setname(update_address(content["value"].getname(), address_model, new_leading))
+ elif content["name"] == "inline":
+ content["value"].updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def getconnections(self):
+ if self.condition:
+ content = self.condition.getcontent()
+ if content["name"] == "connectionPointIn":
+ return content["value"].getconnections()
+ setattr(cls, "getconnections", getconnections)
+
+ def Search(self, criteria, parent_infos=[]):
+ parent_infos = parent_infos + ["transition", self.getlocalId()]
+ search_result = []
+ content = self.condition.getcontent()
+ if content["name"] == "reference":
+ search_result.extend(_Search([("reference", content["value"].getname())], criteria, parent_infos))
+ elif content["name"] == "inline":
+ search_result.extend(content["value"].Search(criteria, parent_infos + ["inline"]))
+ return search_result
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("selectionDivergence", "sfcObjects_selectionDivergence", "single")
+if cls:
+ setattr(cls, "getinfos", _getdivergenceinfosFunction(True, False))
+
+cls = _initElementClass("selectionConvergence", "sfcObjects_selectionConvergence", "multiple")
+if cls:
+ setattr(cls, "getinfos", _getdivergenceinfosFunction(False, False))
+
+cls = _initElementClass("simultaneousDivergence", "sfcObjects_simultaneousDivergence", "single")
+if cls:
+ setattr(cls, "getinfos", _getdivergenceinfosFunction(True, True))
+
+cls = _initElementClass("simultaneousConvergence", "sfcObjects_simultaneousConvergence", "multiple")
+if cls:
+ setattr(cls, "getinfos", _getdivergenceinfosFunction(False, True))
+
+cls = _initElementClass("jumpStep", "sfcObjects_jumpStep", "single")
+if cls:
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = "jump"
+ infos["specific_values"]["target"] = self.gettargetName()
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ def Search(self, criteria, parent_infos):
+ return _Search([("target", self.gettargetName())], criteria, parent_infos + ["jump", self.getlocalId()])
+ setattr(cls, "Search", Search)
+
+cls = PLCOpenClasses.get("actionBlock_action", None)
+if cls:
+ def compatibility(self, tree):
+ relPosition = reduce(lambda x, y: x | (y.nodeName == "relPosition"), tree.childNodes, False)
+ if not tree.hasAttribute("localId"):
+ NodeSetAttr(tree, "localId", "0")
+ if not relPosition:
+ node = CreateNode("relPosition")
+ NodeSetAttr(node, "x", "0")
+ NodeSetAttr(node, "y", "0")
+ tree.childNodes.insert(0, node)
+ setattr(cls, "compatibility", compatibility)
+
+ 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({"name" : "ST", "value" : 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)
+
+ def updateElementName(self, old_name, new_name):
+ if self.reference and self.reference.getname() == old_name:
+ self.reference.setname(new_name)
+ if self.inline:
+ self.inline.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ if self.reference:
+ self.reference.setname(update_address(self.reference.getname(), address_model, new_leading))
+ if self.inline:
+ self.inline.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ qualifier = self.getqualifier()
+ if qualifier is None:
+ qualifier = "N"
+ return _Search([("inline", self.getinlineContent()),
+ ("reference", self.getreferenceName()),
+ ("qualifier", qualifier),
+ ("duration", self.getduration()),
+ ("indicator", self.getindicator())],
+ criteria, parent_infos)
+ setattr(cls, "Search", Search)
+
+cls = _initElementClass("actionBlock", "commonObjects_actionBlock", "single")
+if cls:
+ def compatibility(self, tree):
+ for child in tree.childNodes[:]:
+ if child.nodeName == "connectionPointOut":
+ tree.childNodes.remove(child)
+ setattr(cls, "compatibility", compatibility)
+
+ def getinfos(self):
+ infos = _getelementinfos(self)
+ infos["type"] = "actionBlock"
+ infos["specific_values"]["actions"] = self.getactions()
+ infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True))
+ return infos
+ setattr(cls, "getinfos", getinfos)
+
+ def setactions(self, actions):
+ self.action = []
+ for params in actions:
+ action = PLCOpenClasses["actionBlock_action"]()
+ action.setqualifier(params["qualifier"])
+ if params["type"] == "reference":
+ action.addreference()
+ action.setreferenceName(params["value"])
+ else:
+ action.addinline()
+ action.setinlineContent(params["value"])
+ if params.has_key("duration"):
+ action.setduration(params["duration"])
+ if params.has_key("indicator"):
+ action.setindicator(params["indicator"])
+ self.action.append(action)
+ setattr(cls, "setactions", setactions)
+
+ def getactions(self):
+ actions = []
+ for action in self.action:
+ params = {}
+ params["qualifier"] = action.getqualifier()
+ if params["qualifier"] is None:
+ params["qualifier"] = "N"
+ 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
+ actions.append(params)
+ return actions
+ setattr(cls, "getactions", getactions)
+
+ def updateElementName(self, old_name, new_name):
+ for action in self.action:
+ action.updateElementName(old_name, new_name)
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ for action in self.action:
+ action.updateElementAddress(address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ def Search(self, criteria, parent_infos=[]):
+ parent_infos = parent_infos + ["action_block", self.getlocalId()]
+ search_result = []
+ for idx, action in enumerate(self.action):
+ search_result.extend(action.Search(criteria, parent_infos + ["action", idx]))
+ return search_result
+ setattr(cls, "Search", Search)
+
+def _SearchInIOVariable(self, criteria, parent_infos=[]):
+ return _Search([("expression", self.getexpression())], criteria, parent_infos + ["io_variable", self.getlocalId()])
+
+cls = _initElementClass("inVariable", "fbdObjects_inVariable")
+if cls:
+ setattr(cls, "getinfos", _getvariableinfosFunction("input", False, True))
+
+ def updateElementName(self, old_name, new_name):
+ if self.expression == old_name:
+ self.expression = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.expression = update_address(self.expression, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ setattr(cls, "Search", _SearchInIOVariable)
+
+cls = _initElementClass("outVariable", "fbdObjects_outVariable", "single")
+if cls:
+ setattr(cls, "getinfos", _getvariableinfosFunction("output", True, False))
+
+ def updateElementName(self, old_name, new_name):
+ if self.expression == old_name:
+ self.expression = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.expression = update_address(self.expression, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ setattr(cls, "Search", _SearchInIOVariable)
+
+cls = _initElementClass("inOutVariable", "fbdObjects_inOutVariable", "single")
+if cls:
+ setattr(cls, "getinfos", _getvariableinfosFunction("inout", True, True))
+
+ def updateElementName(self, old_name, new_name):
+ if self.expression == old_name:
+ self.expression = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+ def updateElementAddress(self, address_model, new_leading):
+ self.expression = update_address(self.expression, address_model, new_leading)
+ setattr(cls, "updateElementAddress", updateElementAddress)
+
+ setattr(cls, "Search", _SearchInIOVariable)
+
+
+def _SearchInConnector(self, criteria, parent_infos=[]):
+ return _Search([("name", self.getname())], criteria, parent_infos + ["connector", self.getlocalId()])
+
+cls = _initElementClass("continuation", "commonObjects_continuation")
+if cls:
+ setattr(cls, "getinfos", _getconnectorinfosFunction("continuation"))
+ setattr(cls, "Search", _SearchInConnector)
+
+ def updateElementName(self, old_name, new_name):
+ if self.name == old_name:
+ self.name = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+cls = _initElementClass("connector", "commonObjects_connector", "single")
+if cls:
+ setattr(cls, "getinfos", _getconnectorinfosFunction("connector"))
+ setattr(cls, "Search", _SearchInConnector)
+
+ def updateElementName(self, old_name, new_name):
+ if self.name == old_name:
+ self.name = new_name
+ setattr(cls, "updateElementName", updateElementName)
+
+cls = PLCOpenClasses.get("connection", None)
+if cls:
+ 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)
+
+cls = PLCOpenClasses.get("connectionPointIn", None)
+if cls:
+ def setrelPositionXY(self, x, y):
+ self.relPosition = PLCOpenClasses["position"]()
+ self.relPosition.setx(x)
+ self.relPosition.sety(y)
+ setattr(cls, "setrelPositionXY", setrelPositionXY)
+
+ def getrelPositionXY(self):
+ if self.relPosition:
+ return self.relPosition.getx(), self.relPosition.gety()
+ else:
+ return self.relPosition
+ setattr(cls, "getrelPositionXY", getrelPositionXY)
+
+ 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)
+
+ def setconnectionParameter(self, idx, parameter):
+ if self.content:
+ self.content["value"][idx].setformalParameter(parameter)
+ setattr(cls, "setconnectionParameter", setconnectionParameter)
+
+ def getconnectionParameter(self, idx):
+ if self.content:
+ return self.content["value"][idx].getformalParameter()
+ return None
+ setattr(cls, "getconnectionParameter", getconnectionParameter)
+
+cls = PLCOpenClasses.get("connectionPointOut", None)
+if cls:
+ def setrelPositionXY(self, x, y):
+ self.relPosition = PLCOpenClasses["position"]()
+ self.relPosition.setx(x)
+ self.relPosition.sety(y)
+ setattr(cls, "setrelPositionXY", setrelPositionXY)
+
+ def getrelPositionXY(self):
+ if self.relPosition:
+ return self.relPosition.getx(), self.relPosition.gety()
+ return self.relPosition
+ setattr(cls, "getrelPositionXY", getrelPositionXY)
+
+cls = PLCOpenClasses.get("value", None)
+if cls:
+ def setvalue(self, value):
+ value = value.strip()
+ if value.startswith("[") and value.endswith("]"):
+ arrayValue = PLCOpenClasses["value_arrayValue"]()
+ self.content = {"name" : "arrayValue", "value" : arrayValue}
+ elif value.startswith("(") and value.endswith(")"):
+ structValue = PLCOpenClasses["value_structValue"]()
+ self.content = {"name" : "structValue", "value" : structValue}
+ else:
+ simpleValue = PLCOpenClasses["value_simpleValue"]()
+ self.content = {"name" : "simpleValue", "value": simpleValue}
+ self.content["value"].setvalue(value)
+ setattr(cls, "setvalue", setvalue)
+
+ def getvalue(self):
+ return self.content["value"].getvalue()
+ setattr(cls, "getvalue", getvalue)
+
+def extractValues(values):
+ items = values.split(",")
+ i = 1
+ while i < len(items):
+ opened = items[i - 1].count("(") + items[i - 1].count("[")
+ closed = items[i - 1].count(")") + items[i - 1].count("]")
+ if opened > closed:
+ items[i - 1] = ','.join([items[i - 1], items.pop(i)])
+ elif opened == closed:
+ i += 1
+ else:
+ raise ValueError, _("\"%s\" is an invalid value!")%value
+ return items
+
+cls = PLCOpenClasses.get("value_arrayValue", None)
+if cls:
+ arrayValue_model = re.compile("([0-9]*)\((.*)\)$")
+
+ def setvalue(self, value):
+ self.value = []
+ for item in extractValues(value[1:-1]):
+ item = item.strip()
+ element = PLCOpenClasses["arrayValue_value"]()
+ result = arrayValue_model.match(item)
+ if result is not None:
+ groups = result.groups()
+ element.setrepetitionValue(groups[0])
+ element.setvalue(groups[1].strip())
+ else:
+ element.setvalue(item)
+ self.value.append(element)
+ setattr(cls, "setvalue", setvalue)
+
+ def getvalue(self):
+ values = []
+ for element in self.value:
+ repetition = element.getrepetitionValue()
+ if repetition is not None and int(repetition) > 1:
+ value = element.getvalue()
+ if value is None:
+ value = ""
+ values.append("%s(%s)"%(repetition, value))
+ else:
+ values.append(element.getvalue())
+ return "[%s]"%", ".join(values)
+ setattr(cls, "getvalue", getvalue)
+
+cls = PLCOpenClasses.get("value_structValue", None)
+if cls:
+ structValue_model = re.compile("(.*):=(.*)")
+
+ def setvalue(self, value):
+ self.value = []
+ for item in extractValues(value[1:-1]):
+ result = structValue_model.match(item)
+ if result is not None:
+ groups = result.groups()
+ element = PLCOpenClasses["structValue_value"]()
+ element.setmember(groups[0].strip())
+ element.setvalue(groups[1].strip())
+ self.value.append(element)
+ setattr(cls, "setvalue", setvalue)
+
+ def getvalue(self):
+ values = []
+ for element in self.value:
+ values.append("%s := %s"%(element.getmember(), element.getvalue()))
+ return "(%s)"%", ".join(values)
+ setattr(cls, "getvalue", getvalue)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/structures.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,693 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import string, os, sys
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+LANGUAGES = ["IL","ST","FBD","LD","SFC"]
+
+LOCATIONDATATYPES = {"X" : ["BOOL"],
+ "B" : ["SINT", "USINT", "BYTE", "STRING"],
+ "W" : ["INT", "UINT", "WORD", "WSTRING"],
+ "D" : ["DINT", "UDINT", "REAL", "DWORD"],
+ "L" : ["LINT", "ULINT", "LREAL", "LWORD"]}
+
+_ = lambda x:x
+
+# Helper for emulate join on element list
+def JoinList(separator, mylist):
+ if len(mylist) > 0 :
+ return reduce(lambda x, y: x + separator + y, mylist)
+ else :
+ return mylist
+
+def generate_block(generator, block, block_infos, body, link, order=False, to_inout=False):
+ body_type = body.getcontent()["name"]
+ name = block.getinstanceName()
+ type = block.gettypeName()
+ executionOrderId = block.getexecutionOrderId()
+ inout_variables = {}
+ for input_variable in block.inputVariables.getvariable():
+ for output_variable in block.outputVariables.getvariable():
+ if input_variable.getformalParameter() == output_variable.getformalParameter():
+ inout_variables[input_variable.getformalParameter()] = ""
+ if block_infos["type"] == "function":
+ output_variables = block.outputVariables.getvariable()
+ if not generator.ComputedBlocks.get(block, False) and not order:
+ generator.ComputedBlocks[block] = True
+ vars = []
+ one_input_connected = False
+ for i, variable in enumerate(block.inputVariables.getvariable()):
+ input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
+ connections = variable.connectionPointIn.getconnections()
+ if connections is not None:
+ parameter = variable.getformalParameter()
+ if parameter != "EN":
+ one_input_connected = True
+ if inout_variables.has_key(parameter):
+ value = generator.ComputeExpression(body, connections, executionOrderId > 0, True)
+ inout_variables[parameter] = value
+ else:
+ value = generator.ComputeExpression(body, connections, executionOrderId > 0)
+ if len(output_variables) > 1:
+ vars.append([(parameter, input_info),
+ (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
+ else:
+ vars.append(generator.ExtractModifier(variable, value, input_info))
+ if one_input_connected:
+ for i, variable in enumerate(output_variables):
+ parameter = variable.getformalParameter()
+ if not inout_variables.has_key(parameter):
+ if variable.getformalParameter() == "":
+ variable_name = "%s%d"%(type, block.getlocalId())
+ else:
+ variable_name = "%s%d_%s"%(type, block.getlocalId(), parameter)
+ if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
+ generator.Interface.append(("VAR", None, False, []))
+ if variable.connectionPointOut in generator.ConnectionTypes:
+ generator.Interface[-1][3].append((generator.ConnectionTypes[variable.connectionPointOut], variable_name, None, None))
+ else:
+ generator.Interface[-1][3].append(("ANY", variable_name, None, None))
+ if len(output_variables) > 1 and parameter not in ["", "OUT"]:
+ vars.append([(parameter, (generator.TagName, "block", block.getlocalId(), "output", i)),
+ (" => %s"%variable_name, ())])
+ else:
+ output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
+ output_name = variable_name
+ generator.Program += [(generator.CurrentIndent, ()),
+ (output_name, output_info),
+ (" := ", ()),
+ (type, (generator.TagName, "block", block.getlocalId(), "type")),
+ ("(", ())]
+ generator.Program += JoinList([(", ", ())], vars)
+ generator.Program += [(");\n", ())]
+ else:
+ generator.Warnings.append(_("\"%s\" function cancelled in \"%s\" POU: No input connected")%(type, generator.TagName.split("::")[-1]))
+ if link:
+ connectionPoint = link.getposition()[-1]
+ else:
+ connectionPoint = None
+ for i, variable in enumerate(output_variables):
+ blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
+ if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
+ output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
+ parameter = variable.getformalParameter()
+ if inout_variables.has_key(parameter):
+ output_value = inout_variables[parameter]
+ else:
+ if parameter == "":
+ output_name = "%s%d"%(type, block.getlocalId())
+ else:
+ output_name = "%s%d_%s"%(type, block.getlocalId(), parameter)
+ output_value = [(output_name, output_info)]
+ return generator.ExtractModifier(variable, output_value, output_info)
+ elif block_infos["type"] == "functionBlock":
+ if not generator.ComputedBlocks.get(block, False) and not order:
+ generator.ComputedBlocks[block] = True
+ vars = []
+ for i, variable in enumerate(block.inputVariables.getvariable()):
+ input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
+ connections = variable.connectionPointIn.getconnections()
+ if connections is not None:
+ parameter = variable.getformalParameter()
+ value = generator.ComputeExpression(body, connections, executionOrderId > 0, inout_variables.has_key(parameter))
+ vars.append([(parameter, input_info),
+ (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
+ generator.Program += [(generator.CurrentIndent, ()),
+ (name, (generator.TagName, "block", block.getlocalId(), "name")),
+ ("(", ())]
+ generator.Program += JoinList([(", ", ())], vars)
+ generator.Program += [(");\n", ())]
+ if link:
+ connectionPoint = link.getposition()[-1]
+ else:
+ connectionPoint = None
+ for i, variable in enumerate(block.outputVariables.getvariable()):
+ blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
+ if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
+ output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
+ output_name = generator.ExtractModifier(variable, [("%s.%s"%(name, variable.getformalParameter()), output_info)], output_info)
+ if to_inout:
+ variable_name = "%s_%s"%(name, variable.getformalParameter())
+ if not generator.IsAlreadyDefined(variable_name):
+ if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
+ generator.Interface.append(("VAR", None, False, []))
+ if variable.connectionPointOut in generator.ConnectionTypes:
+ generator.Interface[-1][3].append((generator.ConnectionTypes[variable.connectionPointOut], variable_name, None, None))
+ else:
+ generator.Interface[-1][3].append(("ANY", variable_name, None, None))
+ generator.Program += [(generator.CurrentIndent, ()),
+ ("%s := "%variable_name, ())]
+ generator.Program += output_name
+ generator.Program += [(";\n", ())]
+ return [(variable_name, ())]
+ return output_name
+ if link is not None:
+ raise ValueError, _("No output variable found")
+
+def initialise_block(type, name, block = None):
+ return [(type, name, None, None)]
+
+#-------------------------------------------------------------------------------
+# 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
+ - "generate" : Method that generator will call for generating ST block code
+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" : _("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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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 and down on the other."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"name" : "TON", "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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"name" : "TOF", "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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ ]},
+ {"name" : _("Additional function blocks"), "list":
+ [{"name" : "RTC", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("IN","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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"name" : "RAMP", "type" : "functionBlock", "extensible" : False,
+ "inputs" : [("RUN","BOOL","none"),("X0","REAL","none"),("X1","REAL","none"),("TR","TIME","none"),("CYCLE","TIME","none")],
+ "outputs" : [("BUSY","BOOL","none"),("XOUT","REAL","none")],
+ "comment" : _("Ramp\nThe RAMP function block is modelled on example given in the standard."),
+ "generate" : generate_block, "initialise" : initialise_block},
+ {"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."),
+ "generate" : generate_block, "initialise" : initialise_block},
+## {"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."),
+## "generate" : generate_block, "initialise" : initialise_block}
+ ]},
+ ]
+
+
+#-------------------------------------------------------------------------------
+# 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_list = [
+ ("ANY", None),
+ ("ANY_DERIVED", "ANY"),
+ ("ANY_ELEMENTARY", "ANY"),
+ ("ANY_MAGNITUDE", "ANY_ELEMENTARY"),
+ ("ANY_BIT", "ANY_ELEMENTARY"),
+ ("ANY_NBIT", "ANY_BIT"),
+ ("ANY_STRING", "ANY_ELEMENTARY"),
+ ("ANY_DATE", "ANY_ELEMENTARY"),
+ ("ANY_NUM", "ANY_MAGNITUDE"),
+ ("ANY_REAL", "ANY_NUM"),
+ ("ANY_INT", "ANY_NUM"),
+ ("ANY_SINT", "ANY_INT"),
+ ("ANY_UINT", "ANY_INT"),
+ ("BOOL", "ANY_BIT"),
+ ("SINT", "ANY_SINT"),
+ ("INT", "ANY_SINT"),
+ ("DINT", "ANY_SINT"),
+ ("LINT", "ANY_SINT"),
+ ("USINT", "ANY_UINT"),
+ ("UINT", "ANY_UINT"),
+ ("UDINT", "ANY_UINT"),
+ ("ULINT", "ANY_UINT"),
+ ("REAL", "ANY_REAL"),
+ ("LREAL", "ANY_REAL"),
+ ("TIME", "ANY_MAGNITUDE"),
+ ("DATE", "ANY_DATE"),
+ ("TOD", "ANY_DATE"),
+ ("DT", "ANY_DATE"),
+ ("STRING", "ANY_STRING"),
+ ("BYTE", "ANY_NBIT"),
+ ("WORD", "ANY_NBIT"),
+ ("DWORD", "ANY_NBIT"),
+ ("LWORD", "ANY_NBIT")
+ #("WSTRING", "ANY_STRING") # TODO
+]
+
+TypeHierarchy = dict(TypeHierarchy_list)
+
+"""
+returns true if the given data type is the same that "reference" meta-type or one of its types.
+"""
+def IsOfType(type, reference):
+ if reference is None:
+ return True
+ elif type == reference:
+ return True
+ else:
+ parent_type = TypeHierarchy[type]
+ if parent_type is not None:
+ return IsOfType(parent_type, reference)
+ return False
+
+"""
+returns list of all types that correspont to the ANY* meta type
+"""
+def GetSubTypes(type):
+ return [typename for typename, parenttype in TypeHierarchy.items() if not typename.startswith("ANY") and IsOfType(typename, type)]
+
+
+DataTypeRange_list = [
+ ("SINT", (-2**7, 2**7 - 1)),
+ ("INT", (-2**15, 2**15 - 1)),
+ ("DINT", (-2**31, 2**31 - 1)),
+ ("LINT", (-2**31, 2**31 - 1)),
+ ("USINT", (0, 2**8 - 1)),
+ ("UINT", (0, 2**16 - 1)),
+ ("UDINT", (0, 2**31 - 1)),
+ ("ULINT", (0, 2**31 - 1))
+]
+
+DataTypeRange = dict(DataTypeRange_list)
+
+
+
+#-------------------------------------------------------------------------------
+# Test identifier
+#-------------------------------------------------------------------------------
+
+
+
+# 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
+
+
+#-------------------------------------------------------------------------------
+# Standard functions list generation
+#-------------------------------------------------------------------------------
+
+
+"""
+take a .csv file and translate it it a "csv_table"
+"""
+def csv_file_to_table(file):
+ return [ map(string.strip,line.split(';')) for line in file.xreadlines()]
+
+"""
+seek into the csv table to a section ( section_name match 1st field )
+return the matching row without first field
+"""
+def find_section(section_name, table):
+ fields = [None]
+ while(fields[0] != section_name):
+ fields = table.pop(0)
+ return fields[1:]
+
+"""
+extract the standard functions standard parameter names and types...
+return a { ParameterName: Type, ...}
+"""
+def get_standard_funtions_input_variables(table):
+ variables = find_section("Standard_functions_variables_types", table)
+ standard_funtions_input_variables = {}
+ fields = [True,True]
+ while(fields[1]):
+ fields = table.pop(0)
+ variable_from_csv = dict([(champ, val) for champ, val in zip(variables, fields[1:]) if champ!=''])
+ standard_funtions_input_variables[variable_from_csv['name']] = variable_from_csv['type']
+ return standard_funtions_input_variables
+
+"""
+translate .csv file input declaration into PLCOpenEditor interessting values
+in : "(ANY_NUM, ANY_NUM)" and { ParameterName: Type, ...}
+return [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")]
+"""
+def csv_input_translate(str_decl, variables, base):
+ decl = str_decl.replace('(','').replace(')','').replace(' ','').split(',')
+ params = []
+
+ len_of_not_predifined_variable = len([True for param_type in decl if param_type not in variables])
+
+ for param_type in decl:
+ if param_type in variables.keys():
+ param_name = param_type
+ param_type = variables[param_type]
+ elif len_of_not_predifined_variable > 1:
+ param_name = "IN%d"%base
+ base += 1
+ else:
+ param_name = "IN"
+ params.append((param_name, param_type, "none"))
+ return params
+
+
+ANY_TO_ANY_LIST=[
+ # simple type conv are let as C cast
+ (("ANY_INT","ANY_BIT"),("ANY_NUM","ANY_BIT"), ("return_type", "__move_", "IN_type")),
+ (("ANY_REAL",),("ANY_REAL",), ("return_type", "__move_", "IN_type")),
+ # REAL_TO_INT
+ (("ANY_REAL",),("ANY_SINT",), ("return_type", "__real_to_sint", None)),
+ (("ANY_REAL",),("ANY_UINT",), ("return_type", "__real_to_uint", None)),
+ (("ANY_REAL",),("ANY_BIT",), ("return_type", "__real_to_bit", None)),
+ # TO_TIME
+ (("ANY_INT","ANY_BIT"),("ANY_DATE","TIME"), ("return_type", "__int_to_time", None)),
+ (("ANY_REAL",),("ANY_DATE","TIME"), ("return_type", "__real_to_time", None)),
+ (("ANY_STRING",), ("ANY_DATE","TIME"), ("return_type", "__string_to_time", None)),
+ # FROM_TIME
+ (("ANY_DATE","TIME"), ("ANY_REAL",), ("return_type", "__time_to_real", None)),
+ (("ANY_DATE","TIME"), ("ANY_INT","ANY_NBIT"), ("return_type", "__time_to_int", None)),
+ (("TIME",), ("ANY_STRING",), ("return_type", "__time_to_string", None)),
+ (("DATE",), ("ANY_STRING",), ("return_type", "__date_to_string", None)),
+ (("TOD",), ("ANY_STRING",), ("return_type", "__tod_to_string", None)),
+ (("DT",), ("ANY_STRING",), ("return_type", "__dt_to_string", None)),
+ # TO_STRING
+ (("BOOL",), ("ANY_STRING",), ("return_type", "__bool_to_string", None)),
+ (("ANY_BIT",), ("ANY_STRING",), ("return_type", "__bit_to_string", None)),
+ (("ANY_REAL",), ("ANY_STRING",), ("return_type", "__real_to_string", None)),
+ (("ANY_SINT",), ("ANY_STRING",), ("return_type", "__sint_to_string", None)),
+ (("ANY_UINT",), ("ANY_STRING",), ("return_type", "__uint_to_string", None)),
+ # FROM_STRING
+ (("ANY_STRING",), ("BOOL",), ("return_type", "__string_to_bool", None)),
+ (("ANY_STRING",), ("ANY_BIT",), ("return_type", "__string_to_bit", None)),
+ (("ANY_STRING",), ("ANY_SINT",), ("return_type", "__string_to_sint", None)),
+ (("ANY_STRING",), ("ANY_UINT",), ("return_type", "__string_to_uint", None)),
+ (("ANY_STRING",), ("ANY_REAL",), ("return_type", "__string_to_real", None))]
+
+
+BCD_TO_ANY_LIST=[
+ (("BYTE",),("USINT",), ("return_type", "__bcd_to_uint", None)),
+ (("WORD",),("UINT",), ("return_type", "__bcd_to_uint", None)),
+ (("DWORD",),("UDINT",), ("return_type", "__bcd_to_uint", None)),
+ (("LWORD",),("ULINT",), ("return_type", "__bcd_to_uint", None))]
+
+
+ANY_TO_BCD_LIST=[
+ (("USINT",),("BYTE",), ("return_type", "__uint_to_bcd", None)),
+ (("UINT",),("WORD",), ("return_type", "__uint_to_bcd", None)),
+ (("UDINT",),("DWORD",), ("return_type", "__uint_to_bcd", None)),
+ (("ULINT",),("LWORD",), ("return_type", "__uint_to_bcd", None))]
+
+
+def ANY_TO_ANY_FORMAT_GEN(any_to_any_list, fdecl):
+
+ for (InTypes, OutTypes, Format) in any_to_any_list:
+ outs = reduce(lambda a,b: a or b, map(lambda testtype : IsOfType(fdecl["outputs"][0][1],testtype), OutTypes))
+ inps = reduce(lambda a,b: a or b, map(lambda testtype : IsOfType(fdecl["inputs"][0][1],testtype), InTypes))
+ if inps and outs and fdecl["outputs"][0][1] != fdecl["inputs"][0][1]:
+ return Format
+
+ return None
+
+
+"""
+Returns this kind of declaration for all standard functions
+
+ [{"name" : "Numerical", 'list': [ {
+ 'baseinputnumber': 1,
+ 'comment': 'Addition',
+ 'extensible': True,
+ 'inputs': [ ('IN1', 'ANY_NUM', 'none'),
+ ('IN2', 'ANY_NUM', 'none')],
+ 'name': 'ADD',
+ 'outputs': [('OUT', 'ANY_NUM', 'none')],
+ 'type': 'function'}, ...... ] },.....]
+"""
+def get_standard_funtions(table):
+
+ variables = get_standard_funtions_input_variables(table)
+
+ fonctions = find_section("Standard_functions_type",table)
+
+ Standard_Functions_Decl = []
+ Current_section = None
+
+ translate = {
+ "extensible" : lambda x: {"yes":True, "no":False}[x],
+ "inputs" : lambda x:csv_input_translate(x,variables,baseinputnumber),
+ "outputs":lambda x:[("OUT",x,"none")]}
+
+ for fields in table:
+ if fields[1]:
+ # If function section name given
+ if fields[0]:
+ words = fields[0].split('"')
+ if len(words) > 1:
+ section_name = words[1]
+ else:
+ section_name = fields[0]
+ Current_section = {"name" : section_name, "list" : []}
+ Standard_Functions_Decl.append(Current_section)
+ Function_decl_list = []
+ if Current_section:
+ Function_decl = dict([(champ, val) for champ, val in zip(fonctions, fields[1:]) if champ])
+ Function_decl["generate"] = generate_block
+ Function_decl["initialise"] = lambda x,y:[]
+ baseinputnumber = int(Function_decl.get("baseinputnumber",1))
+ Function_decl["baseinputnumber"] = baseinputnumber
+ for param, value in Function_decl.iteritems():
+ if param in translate:
+ Function_decl[param] = translate[param](value)
+ Function_decl["type"] = "function"
+
+ if Function_decl["name"].startswith('*') or Function_decl["name"].endswith('*') :
+ input_ovrloading_types = GetSubTypes(Function_decl["inputs"][0][1])
+ output_types = GetSubTypes(Function_decl["outputs"][0][1])
+ else:
+ input_ovrloading_types = [None]
+ output_types = [None]
+
+ funcdeclname_orig = Function_decl["name"]
+ funcdeclname = Function_decl["name"].strip('*_')
+ fdc = Function_decl["inputs"][:]
+ for intype in input_ovrloading_types:
+ if intype != None:
+ Function_decl["inputs"] = []
+ for decl_tpl in fdc:
+ if IsOfType(intype, decl_tpl[1]):
+ Function_decl["inputs"] += [(decl_tpl[0], intype, decl_tpl[2])]
+ else:
+ Function_decl["inputs"] += [(decl_tpl)]
+
+ if funcdeclname_orig.startswith('*'):
+ funcdeclin = intype + '_' + funcdeclname
+ else:
+ funcdeclin = funcdeclname
+ else:
+ funcdeclin = funcdeclname
+
+ for outype in output_types:
+ if outype != None:
+ decl_tpl = Function_decl["outputs"][0]
+ Function_decl["outputs"] = [ (decl_tpl[0] , outype, decl_tpl[2])]
+ if funcdeclname_orig.endswith('*'):
+ funcdeclout = funcdeclin + '_' + outype
+ else:
+ funcdeclout = funcdeclin
+ else:
+ funcdeclout = funcdeclin
+ Function_decl["name"] = funcdeclout
+
+
+ fdecl = Function_decl
+ res = eval(Function_decl["python_eval_c_code_format"])
+
+ if res != None :
+ # create the copy of decl dict to be appended to section
+ Function_decl_copy = Function_decl.copy()
+ Current_section["list"].append(Function_decl_copy)
+ else:
+ raise "First function must be in a category"
+
+ return Standard_Functions_Decl
+
+std_decl = get_standard_funtions(csv_file_to_table(open(os.path.join(os.path.split(__file__)[0],"iec_std.csv"))))#, True)
+
+BlockTypes.extend(std_decl)
+
+for section in BlockTypes:
+ for desc in section["list"]:
+ words = desc["comment"].split('"')
+ if len(words) > 1:
+ desc["comment"] = words[1]
+ desc["usage"] = (
+ "\n (" +
+ str([ " " + fctdecl[1]+":"+fctdecl[0] for fctdecl in desc["inputs"]]).strip("[]").replace("'",'') +
+ " ) => (" +
+ str([ " " + fctdecl[1]+":"+fctdecl[0] for fctdecl in desc["outputs"]]).strip("[]").replace("'",'') +
+ " )")
+
+
+#-------------------------------------------------------------------------------
+# Languages Keywords
+#-------------------------------------------------------------------------------
+
+
+# Keywords for Pou Declaration
+POU_BLOCK_START_KEYWORDS = ["FUNCTION", "FUNCTION_BLOCK", "PROGRAM"]
+POU_BLOCK_END_KEYWORDS = ["END_FUNCTION", "END_FUNCTION_BLOCK", "END_PROGRAM"]
+POU_KEYWORDS = ["EN", "ENO", "F_EDGE", "R_EDGE"] + POU_BLOCK_START_KEYWORDS + POU_BLOCK_END_KEYWORDS
+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_BLOCK_START_KEYWORDS = ["TYPE", "STRUCT"]
+TYPE_BLOCK_END_KEYWORDS = ["END_TYPE", "END_STRUCT"]
+TYPE_KEYWORDS = ["ARRAY", "OF", "T", "D", "TIME_OF_DAY", "DATE_AND_TIME"] + TYPE_BLOCK_START_KEYWORDS + TYPE_BLOCK_END_KEYWORDS
+TYPE_KEYWORDS.extend([keyword for keyword in TypeHierarchy.keys() if keyword not in TYPE_KEYWORDS])
+
+
+# Keywords for Variable Declaration
+VAR_BLOCK_START_KEYWORDS = ["VAR", "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR_EXTERNAL"]
+VAR_BLOCK_END_KEYWORDS = ["END_VAR"]
+VAR_KEYWORDS = ["AT", "CONSTANT", "RETAIN", "NON_RETAIN"] + VAR_BLOCK_START_KEYWORDS + VAR_BLOCK_END_KEYWORDS
+
+
+# Keywords for Configuration Declaration
+CONFIG_BLOCK_START_KEYWORDS = ["CONFIGURATION", "RESOURCE", "VAR_ACCESS", "VAR_CONFIG", "VAR_GLOBAL"]
+CONFIG_BLOCK_END_KEYWORDS = ["END_CONFIGURATION", "END_RESOURCE", "END_VAR"]
+CONFIG_KEYWORDS = ["ON", "PROGRAM", "WITH", "READ_ONLY", "READ_WRITE", "TASK"] + CONFIG_BLOCK_START_KEYWORDS + CONFIG_BLOCK_END_KEYWORDS
+
+# Keywords for Structured Function Chart
+SFC_BLOCK_START_KEYWORDS = ["ACTION", "INITIAL_STEP", "STEP", "TRANSITION"]
+SFC_BLOCK_END_KEYWORDS = ["END_ACTION", "END_STEP", "END_TRANSITION"]
+SFC_KEYWORDS = ["FROM", "TO"] + SFC_BLOCK_START_KEYWORDS + SFC_BLOCK_START_KEYWORDS
+
+
+# Keywords for Instruction List
+IL_KEYWORDS = ["TRUE", "FALSE", "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", "JMPCN", "CAL", "CALC", "CALCN", "RET", "RETC", "RETCN"]
+
+
+# Keywords for Structured Text
+ST_BLOCK_START_KEYWORDS = ["IF", "ELSIF", "ELSE", "CASE", "FOR", "WHILE", "REPEAT"]
+ST_BLOCK_END_KEYWORDS = ["END_IF", "END_CASE", "END_FOR", "END_WHILE", "END_REPEAT"]
+ST_KEYWORDS = ["TRUE", "FALSE", "THEN", "OF", "TO", "BY", "DO", "DO", "UNTIL", "EXIT",
+ "RETURN", "NOT", "MOD", "AND", "XOR", "OR"] + ST_BLOCK_START_KEYWORDS + ST_BLOCK_END_KEYWORDS
+
+# All the keywords of IEC
+IEC_BLOCK_START_KEYWORDS = []
+IEC_BLOCK_END_KEYWORDS = []
+IEC_KEYWORDS = ["E", "TRUE", "FALSE"]
+for all_keywords, keywords_list in [(IEC_BLOCK_START_KEYWORDS, [POU_BLOCK_START_KEYWORDS, TYPE_BLOCK_START_KEYWORDS,
+ VAR_BLOCK_START_KEYWORDS, CONFIG_BLOCK_START_KEYWORDS,
+ SFC_BLOCK_START_KEYWORDS, ST_BLOCK_START_KEYWORDS]),
+ (IEC_BLOCK_END_KEYWORDS, [POU_BLOCK_END_KEYWORDS, TYPE_BLOCK_END_KEYWORDS,
+ VAR_BLOCK_END_KEYWORDS, CONFIG_BLOCK_END_KEYWORDS,
+ SFC_BLOCK_END_KEYWORDS, ST_BLOCK_END_KEYWORDS]),
+ (IEC_KEYWORDS, [POU_KEYWORDS, TYPE_KEYWORDS, VAR_KEYWORDS, CONFIG_KEYWORDS,
+ SFC_KEYWORDS, IL_KEYWORDS, ST_KEYWORDS])]:
+ for keywords in keywords_list:
+ all_keywords.extend([keyword for keyword in keywords if keyword not in all_keywords])
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plcopen/tc6_xml_v201.xsd Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1756 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns1="http://www.plcopen.org/xml/tc6.xsd" targetNamespace="http://www.plcopen.org/xml/tc6_0201" elementFormDefault="qualified" attributeFormDefault="unqualified">
+ <xsd:element name="project">
+ <xsd:annotation>
+ <xsd:documentation>The complete project</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="fileHeader">
+ <xsd:complexType>
+ <xsd:attribute name="companyName" type="xsd:string" use="required"/>
+ <xsd:attribute name="companyURL" type="xsd:anyURI" use="optional"/>
+ <xsd:attribute name="productName" type="xsd:string" use="required"/>
+ <xsd:attribute name="productVersion" type="xsd:string" use="required"/>
+ <xsd:attribute name="productRelease" type="xsd:string" use="optional"/>
+ <xsd:attribute name="creationDateTime" type="xsd:dateTime" use="required"/>
+ <xsd:attribute name="contentDescription" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contentHeader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Comment" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="coordinateInfo">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pageSize" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="fbd">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ld">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="sfc">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="scaling">
+ <xsd:complexType>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addDataInfo" type="ppx:addDataInfo" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="version" type="xsd:string" use="optional"/>
+ <xsd:attribute name="modificationDateTime" type="xsd:dateTime" use="optional"/>
+ <xsd:attribute name="organization" type="xsd:string" use="optional"/>
+ <xsd:attribute name="author" type="xsd:string" use="optional"/>
+ <xsd:attribute name="language" type="xsd:language" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Documentation language of the project e.g. "en-US"</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="types">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataTypes">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dataType" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="pous">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="pou" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="interface" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="returnType" type="ppx:dataType" minOccurs="0"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="localVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="tempVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="externalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varList"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="accessVars" type="ppx:varList"/>
+ </xsd:choice>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transitions" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="transition" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="body" type="ppx:body"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="body" type="ppx:body" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="pouType" type="ppx:pouType" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="instances">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configurations">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="configuration" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of resources and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="resource" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a group of programs and tasks and global variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="task" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Represents a periodic or triggered task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="single" type="xsd:string" use="optional"/>
+ <xsd:attribute name="interval" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Vendor specific: Either a constant duration as defined in the IEC or variable name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="priority" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="pouInstance" type="ppx:pouInstance" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="globalVars" type="ppx:varList" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="accessVars" type="ppx:varListAccess" minOccurs="0"/>
+ <xsd:element name="configVars" type="ppx:varListConfig" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="dataType">
+ <xsd:annotation>
+ <xsd:documentation>A generic data type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:group ref="ppx:elementaryTypes"/>
+ <xsd:group ref="ppx:derivedTypes"/>
+ <xsd:group ref="ppx:extended"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="rangeSigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with signed bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:string" use="required"/>
+ <xsd:attribute name="upper" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="rangeUnsigned">
+ <xsd:annotation>
+ <xsd:documentation>Defines a range with unsigned bounds</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="lower" type="xsd:string" use="required"/>
+ <xsd:attribute name="upper" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="value">
+ <xsd:annotation>
+ <xsd:documentation>A generic value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="simpleValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Value that can be represented as a single token string </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="arrayValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Array value consisting of a list of occurrances - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="repetitionValue" type="xsd:string" use="optional" default="1"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="structValue">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Struct value consisting of a list of member - value pairs</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:value">
+ <xsd:attribute name="member" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="body">
+ <xsd:annotation>
+ <xsd:documentation>Implementation part of a POU, action or transistion</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:choice>
+ <xsd:element name="IL" type="ppx:formattedText"/>
+ <xsd:element name="ST" type="ppx:formattedText"/>
+ <xsd:element name="FBD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="LD">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="SFC">
+ <xsd:complexType>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="ppx:commonObjects"/>
+ <xsd:group ref="ppx:fbdObjects"/>
+ <xsd:group ref="ppx:ldObjects"/>
+ <xsd:group ref="ppx:sfcObjects"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Additional userspecific information to the element</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="WorksheetName" type="xsd:string" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="varList">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations that share the same memory attributes (CONSTANT, RETAIN, NON_RETAIN, PERSISTENT)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:varListPlain">
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="constant" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="retain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonretain" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="persistent" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="nonpersistent" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="varListPlain">
+ <xsd:annotation>
+ <xsd:documentation>List of variable declarations without attributes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Declaration of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="type" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="address" type="xsd:string" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="varListAccess">
+ <xsd:annotation>
+ <xsd:documentation>List of access variable declarations</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="accessVariable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Declaration of an access variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="type" type="ppx:dataType"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="alias" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Name that is visible to the communication partner</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="instancePathAndName" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Variable name including instance path inside the configuration</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="direction" type="ppx:accessType" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="varListConfig">
+ <xsd:annotation>
+ <xsd:documentation>List of VAR_CONFIG variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="configVariable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Declaration of an access variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="type" type="ppx:dataType"/>
+ <xsd:element name="initialValue" type="ppx:value" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="instancePathAndName" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Variable name including instance path</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="address" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="position">
+ <xsd:annotation>
+ <xsd:documentation>Defines a graphical position in X, Y coordinates</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="x" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="y" type="xsd:decimal" use="required"/>
+ </xsd:complexType>
+ <xsd:complexType name="connection">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position" minOccurs="0" maxOccurs="unbounded">
+ <xsd:annotation>
+ <xsd:documentation>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).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ <xsd:attribute name="refLocalId" type="xsd:unsignedLong" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Identifies the element the connection starts from.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>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. </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointIn">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the consumer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:choice minOccurs="0">
+ <xsd:element name="connection" type="ppx:connection" maxOccurs="unbounded"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>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.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="connectionPointOut">
+ <xsd:annotation>
+ <xsd:documentation>Defines a connection point on the producer side</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the connection pin. Origin is the anchor position of the block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="expression" type="xsd:string" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="pouInstance">
+ <xsd:annotation>
+ <xsd:documentation>Represents a program or function block instance either running with or without a task</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="typeName" type="xsd:string" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="formattedText">
+ <xsd:annotation>
+ <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="addData">
+ <xsd:annotation>
+ <xsd:documentation>Application specific data defined in external schemata </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="data" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:any namespace="##any" processContents="lax"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:anyURI" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Uniquely identifies the additional data element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="handleUnknown" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Recommended processor handling for unknown data elements.
+Specifies if the processor should try to preserve the additional data element, dismiss the element (e.g. because the data is invalid if not updated correctly) or use the processors default behaviour for unknown data.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="preserve"/>
+ <xsd:enumeration value="discard"/>
+ <xsd:enumeration value="implementation"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="addDataInfo">
+ <xsd:annotation>
+ <xsd:documentation>List of additional data elements used in the document with description</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="info" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="description" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:anyURI" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Unique name of the additional data element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="version" type="xsd:decimal">
+ <xsd:annotation>
+ <xsd:documentation>Version of additional data, eg. schema version.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="vendor" type="xsd:anyURI" use="required">
+ <xsd:annotation>
+ <xsd:documentation>Vendor responsible for the definition of the additional data element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:group name="elementaryTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of elementary IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="BOOL"/>
+ <xsd:element name="BYTE"/>
+ <xsd:element name="WORD"/>
+ <xsd:element name="DWORD"/>
+ <xsd:element name="LWORD"/>
+ <xsd:element name="SINT"/>
+ <xsd:element name="INT"/>
+ <xsd:element name="DINT"/>
+ <xsd:element name="LINT"/>
+ <xsd:element name="USINT"/>
+ <xsd:element name="UINT"/>
+ <xsd:element name="UDINT"/>
+ <xsd:element name="ULINT"/>
+ <xsd:element name="REAL"/>
+ <xsd:element name="LREAL"/>
+ <xsd:element name="TIME"/>
+ <xsd:element name="DATE"/>
+ <xsd:element name="DT"/>
+ <xsd:element name="TOD"/>
+ <xsd:element name="string">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The single byte character string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="wstring">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The wide character (WORD) string type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="length" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ANY"/>
+ <xsd:element name="ANY_DERIVED"/>
+ <xsd:element name="ANY_ELEMENTARY"/>
+ <xsd:element name="ANY_MAGNITUDE"/>
+ <xsd:element name="ANY_NUM"/>
+ <xsd:element name="ANY_REAL"/>
+ <xsd:element name="ANY_INT"/>
+ <xsd:element name="ANY_BIT"/>
+ <xsd:element name="ANY_STRING"/>
+ <xsd:element name="ANY_DATE"/>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="derivedTypes">
+ <xsd:annotation>
+ <xsd:documentation>Collection of derived IEC 61131-3 datatypes</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="array">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="dimension" type="ppx:rangeSigned" maxOccurs="unbounded"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="derived">
+ <xsd:annotation>
+ <xsd:documentation>Reference to a user defined datatype or POU. Variable declarations use this type to declare e.g. function block instances.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>The user defined alias type</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="enum">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="values">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="value">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>An enumeration value used to build up enumeration types</xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="baseType" type="ppx:dataType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="struct" type="ppx:varListPlain"/>
+ <xsd:element name="subrangeSigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeSigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="subrangeUnsigned">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="range" type="ppx:rangeUnsigned"/>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="extended">
+ <xsd:annotation>
+ <xsd:documentation>Collection of datatypes not defined in IEC 61131-3</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="pointer">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="baseType" type="ppx:dataType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="commonObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which have no direct iec scope and can be used in any graphical body.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="comment">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="error">
+ <xsd:complexType mixed="false">
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a conversion error. Used to keep information which can not be interpreted by the importing system</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="content" type="ppx:formattedText"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="required"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connector">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="continuation">
+ <xsd:annotation>
+ <xsd:documentation>Counterpart of the connector element</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="actionBlock">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="action" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Association of an action with qualifier</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="relPosition" type="ppx:position">
+ <xsd:annotation>
+ <xsd:documentation>Relative position of the action. Origin is the anchor position of the action block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="reference" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Name of an action or boolean variable.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inline" type="ppx:body" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>Inline implementation of an action body.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="qualifier" use="optional" default="N">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="P1"/>
+ <xsd:enumeration value="N"/>
+ <xsd:enumeration value="P0"/>
+ <xsd:enumeration value="R"/>
+ <xsd:enumeration value="S"/>
+ <xsd:enumeration value="L"/>
+ <xsd:enumeration value="D"/>
+ <xsd:enumeration value="P"/>
+ <xsd:enumeration value="DS"/>
+ <xsd:enumeration value="DL"/>
+ <xsd:enumeration value="SD"/>
+ <xsd:enumeration value="SL"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="duration" type="xsd:string" use="optional"/>
+ <xsd:attribute name="indicator" type="xsd:string" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="vendorElement">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a call statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position">
+ <xsd:annotation>
+ <xsd:documentation>Anchor position of the box. Top left corner excluding the instance name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="alternativeText" type="ppx:formattedText">
+ <xsd:annotation>
+ <xsd:documentation>An alternative text to be displayed in generic representation of unknown elements.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="inputVariables" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The list of used input variables (consumers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes an inputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariables" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The list of used inOut variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a inOutVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVariables" minOccurs="0">
+ <xsd:annotation>
+ <xsd:documentation>The list of used output variables (producers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a outputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData">
+ <xsd:annotation>
+ <xsd:documentation>Additional, vendor specific data for the element. Also defines the vendor specific meaning of the element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="fbdObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in fbd. They can be used in all graphical bodies.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="block">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a call statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position">
+ <xsd:annotation>
+ <xsd:documentation>Anchor position of the box. Top left corner excluding the instance name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="inputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used input variables (consumers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes an inputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used inOut variables</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a inOutVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outputVariables">
+ <xsd:annotation>
+ <xsd:documentation>The list of used output variables (producers)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a outputVariable of a Function or a FunctionBlock</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="hidden" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="typeName" type="xsd:string" use="required"/>
+ <xsd:attribute name="instanceName" type="xsd:string" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable, literal or expression used as r-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="outVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable or expression used as l-value</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="inOutVariable">
+ <xsd:annotation>
+ <xsd:documentation>Expression used as producer and consumer</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="expression" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid iec variable e.g. avar[0].</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negatedIn" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeIn" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageIn" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="negatedOut" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edgeOut" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storageOut" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="label">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump label</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jump">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a jump statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="label" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="return">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing areturn statement</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="ldObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in ld and are an extension to fbd. They can be used in ld and sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="leftPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a left powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="rightPowerRail">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a right powerrail</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="coil">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a boolean variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="contact">
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Describes a graphical object representing a variable which can be used as l-value and r-value at the same time</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="variable" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>The operand is a valid boolean iec variable e.g. avar[0]</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="edge" type="ppx:edgeModifierType" use="optional" default="none"/>
+ <xsd:attribute name="storage" type="ppx:storageModifierType" use="optional" default="none"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:group name="sfcObjects">
+ <xsd:annotation>
+ <xsd:documentation>Collection of objects which are defined in sfc. They can only be used in sfc bodies</xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice>
+ <xsd:element name="step">
+ <xsd:annotation>
+ <xsd:documentation>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</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:annotation>
+ <xsd:documentation>Contains actions</xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOutAction" minOccurs="0">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="initialStep" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="macroStep">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="body" type="ppx:body" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="jumpStep">
+ <xsd:annotation>
+ <xsd:documentation>Jump to a step, macro step or simultaneous divergence. Acts like a step. Predecessor should be a transition.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="targetName" type="xsd:string" use="required"/>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="transition">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="condition" minOccurs="0">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="reference">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn"/>
+ <xsd:element name="inline">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:body">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="negated" type="xsd:boolean" use="optional" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="priority" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>The priority of a transition is evaluated, if the transition is connected to a selectionDivergence element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="executionOrderId" type="xsd:unsignedLong" use="optional">
+ <xsd:annotation>
+ <xsd:documentation>Used to identify the order of execution. Also used to identify one special block if there are several blocks with the same name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="selectionConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointIn"/>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousDivergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0"/>
+ <xsd:element name="connectionPointOut" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:extension base="ppx:connectionPointOut">
+ <xsd:attribute name="formalParameter" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="simultaneousConvergence">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="position" type="ppx:position"/>
+ <xsd:element name="connectionPointIn" type="ppx:connectionPointIn" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="connectionPointOut" type="ppx:connectionPointOut" minOccurs="0"/>
+ <xsd:element name="addData" type="ppx:addData" minOccurs="0"/>
+ <xsd:element name="documentation" type="ppx:formattedText" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="localId" type="xsd:unsignedLong" use="required"/>
+ <xsd:attribute name="height" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="width" type="xsd:decimal" use="optional"/>
+ <xsd:attribute name="globalId" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:simpleType name="edgeModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the edge detection behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="falling"/>
+ <xsd:enumeration value="rising"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="storageModifierType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the storage mode (S/R) behaviour of a variable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="set"/>
+ <xsd:enumeration value="reset"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="accessType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the different access types to an accessVariable</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="readOnly"/>
+ <xsd:enumeration value="readWrite"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="pouType">
+ <xsd:annotation>
+ <xsd:documentation>Defines the different types of a POU</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="function"/>
+ <xsd:enumeration value="functionBlock"/>
+ <xsd:enumeration value="program"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+</xsd:schema>
--- a/py_ext/PythonEditor.py Wed Sep 05 11:17:52 2012 +0200
+++ b/py_ext/PythonEditor.py Fri Sep 07 16:45:55 2012 +0200
@@ -3,7 +3,7 @@
import wx.stc as stc
import keyword
-from ConfTreeNodeEditor import ConfTreeNodeEditor
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
if wx.Platform == '__WXMSW__':
faces = { 'times': 'Times New Roman',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/util/BitmapLibrary.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,85 @@
+#!/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) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+
+import wx
+
+#-------------------------------------------------------------------------------
+# Library Structures
+#-------------------------------------------------------------------------------
+
+BitmapLibrary = {}
+BitmapFolders = []
+
+#-------------------------------------------------------------------------------
+# Library Helpers
+#-------------------------------------------------------------------------------
+
+def AddBitmapFolder(path):
+ if path not in BitmapFolders:
+ BitmapFolders.append(path)
+
+def SearchBitmap(bmp_name):
+ for folder in BitmapFolders:
+ bmp_path = os.path.join(folder, bmp_name + ".png")
+ if os.path.isfile(bmp_path):
+ return wx.Bitmap(bmp_path)
+ return None
+
+def GetBitmap(bmp_name1, bmp_name2=None, size=None):
+ bmp = BitmapLibrary.get((bmp_name1, bmp_name2, size))
+ if bmp is not None:
+ return bmp
+
+ if bmp_name2 is None:
+ bmp = SearchBitmap(bmp_name1)
+ else:
+ # Bitmap with two icon
+ bmp1 = SearchBitmap(bmp_name1)
+ bmp2 = SearchBitmap(bmp_name2)
+
+ if bmp1 is not None and bmp2 is not None:
+ # Calculate bitmap size
+ width = bmp1.GetWidth() + bmp2.GetWidth() - 1
+ height = max(bmp1.GetHeight(), bmp2.GetHeight())
+
+ # Create bitmap with both icons
+ bmp = wx.EmptyBitmap(width, height)
+ dc = wx.MemoryDC()
+ dc.SelectObject(bmp)
+ dc.Clear()
+ dc.DrawBitmap(bmp1, 0, 0)
+ dc.DrawBitmap(bmp2, bmp1.GetWidth() - 1, 0)
+ dc.Destroy()
+
+ elif bmp1 is not None:
+ bmp = bmp1
+ elif bmp2 is not None:
+ bmp = bmp2
+
+ if bmp is not None:
+ BitmapLibrary[(bmp_name1, bmp_name2, size)] = bmp
+
+ return bmp
--- a/util/BrowseValuesLibraryDialog.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-import wx
-
-
-class BrowseValuesLibraryDialog(wx.Dialog):
- """
- Modal dialog that helps in selecting predefined XML attributes sets out of hierarchically organized list
- """
-
- def __init__(self, parent, name, library, default=None):
- wx.Dialog.__init__(self,
- name='BrowseValueDialog', parent=parent,
- size=wx.Size(600, 400), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
- title=_('Browse %s values library') % name)
-
- self.SetClientSize(wx.Size(600, 400))
-
- self.staticText1 = wx.StaticText(
- label=_('Choose a value for %s:') % name, name='staticText1', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.ValuesLibrary = wx.TreeCtrl(
- name='ValuesLibrary', parent=self, pos=wx.Point(0, 0),
- size=wx.Size(0, 0), style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT)
-
- self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-
- self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId())
-
- self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
-
- self.flexGridSizer1.AddWindow(self.staticText1, 0, border=20, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
- self.flexGridSizer1.AddWindow(self.ValuesLibrary, 0, border=20, flag=wx.GROW|wx.LEFT|wx.RIGHT)
- self.flexGridSizer1.AddSizer(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
-
- self.flexGridSizer1.AddGrowableCol(0)
- self.flexGridSizer1.AddGrowableRow(1)
-
- self.SetSizer(self.flexGridSizer1)
-
- root = self.ValuesLibrary.AddRoot("")
- self.GenerateValuesLibraryBranch(root, library, default)
-
- def GenerateValuesLibraryBranch(self, root, children, default):
- for infos in children:
- item = self.ValuesLibrary.AppendItem(root, infos["name"])
- self.ValuesLibrary.SetPyData(item, infos["infos"])
- if infos["infos"] is not None and infos["infos"] == default:
- self.ValuesLibrary.SelectItem(item)
- self.ValuesLibrary.EnsureVisible(item)
- self.GenerateValuesLibraryBranch(item, infos["children"], default)
-
- def GetValueInfos(self):
- selected = self.ValuesLibrary.GetSelection()
- return self.ValuesLibrary.GetPyData(selected)
-
- def OnOK(self, event):
- selected = self.ValuesLibrary.GetSelection()
- if not selected.IsOk() or self.ValuesLibrary.GetPyData(selected) is None:
- message = wx.MessageDialog(self, _("No valid value selected!"), _("Error"), wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
- else:
- self.EndModal(wx.ID_OK)
-
--- a/util/FileManagementPanel.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,432 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-import os
-import shutil
-
-import wx
-
-from controls import EditorPanel
-from utils.BitmapLibrary import GetBitmap
-
-DRIVE, FOLDER, FILE = range(3)
-
-FILTER = _("All files (*.*)|*.*|CSV files (*.csv)|*.csv")
-
-def sort_folder(x, y):
- if x[1] == y[1]:
- return cmp(x[0], y[0])
- elif x[1] != FILE:
- return -1
- else:
- return 1
-
-def splitpath(path):
- head, tail = os.path.split(path)
- if head == "":
- return [tail]
- elif tail == "":
- return splitpath(head)
- return splitpath(head) + [tail]
-
-class FolderTree(wx.Panel):
-
- def __init__(self, parent, folder, filter, editable=True):
- wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
-
- main_sizer = wx.BoxSizer(wx.VERTICAL)
-
- self.Tree = wx.TreeCtrl(self,
- style=wx.TR_HAS_BUTTONS|
- wx.TR_SINGLE|
- wx.SUNKEN_BORDER|
- wx.TR_HIDE_ROOT|
- wx.TR_LINES_AT_ROOT|
- wx.TR_EDIT_LABELS)
- if wx.Platform == '__WXMSW__':
- self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnTreeItemExpanded, self.Tree)
- self.Tree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeLeftDown)
- else:
- self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnTreeItemExpanded, self.Tree)
- self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnTreeItemCollapsed, self.Tree)
- self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeBeginLabelEdit, self.Tree)
- self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeEndLabelEdit, self.Tree)
- main_sizer.AddWindow(self.Tree, 1, flag=wx.GROW)
-
- self.Filter = wx.ComboBox(self, style=wx.CB_READONLY)
- self.Bind(wx.EVT_COMBOBOX, self.OnFilterChanged, self.Filter)
- main_sizer.AddWindow(self.Filter, flag=wx.GROW)
-
- self.SetSizer(main_sizer)
-
- self.Folder = folder
- self.Editable = editable
-
- self.TreeImageList = wx.ImageList(16, 16)
- self.TreeImageDict = {}
- for item_type, bitmap in [(DRIVE, "tree_drive"),
- (FOLDER, "tree_folder"),
- (FILE, "tree_file")]:
- self.TreeImageDict[item_type] = self.TreeImageList.Add(GetBitmap(bitmap))
- self.Tree.SetImageList(self.TreeImageList)
-
- self.Filters = {}
- filter_parts = filter.split("|")
- for idx in xrange(0, len(filter_parts), 2):
- if filter_parts[idx + 1] == "*.*":
- self.Filters[filter_parts[idx]] = ""
- else:
- self.Filters[filter_parts[idx]] = filter_parts[idx + 1].replace("*", "")
- self.Filter.Append(filter_parts[idx])
- if idx == 0:
- self.Filter.SetStringSelection(filter_parts[idx])
-
- self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
-
- def _GetFolderChildren(self, folderpath, recursive=True):
- items = []
- if wx.Platform == '__WXMSW__' and folderpath == "/":
- for c in xrange(ord('a'), ord('z')):
- drive = os.path.join("%s:\\" % chr(c))
- if os.path.exists(drive):
- items.append((drive, DRIVE, self._GetFolderChildren(drive, False)))
- else:
- try:
- files = os.listdir(folderpath)
- except:
- return []
- for filename in files:
- if not filename.startswith("."):
- filepath = os.path.join(folderpath, filename)
- if os.path.isdir(filepath):
- if recursive:
- children = len(self._GetFolderChildren(filepath, False))
- else:
- children = 0
- items.append((filename, FOLDER, children))
- elif (self.CurrentFilter == "" or
- os.path.splitext(filename)[1] == self.CurrentFilter):
- items.append((filename, FILE, None))
- if recursive:
- items.sort(sort_folder)
- return items
-
- def GetTreeCtrl(self):
- return self.Tree
-
- def RefreshTree(self):
- root = self.Tree.GetRootItem()
- if not root.IsOk():
- root = self.Tree.AddRoot("")
- self.GenerateTreeBranch(root, self.Folder)
-
- def GenerateTreeBranch(self, root, folderpath):
- item, item_cookie = self.Tree.GetFirstChild(root)
- for idx, (filename, item_type, children) in enumerate(self._GetFolderChildren(folderpath)):
- if not item.IsOk():
- item = self.Tree.AppendItem(root, filename, self.TreeImageDict[item_type])
- if wx.Platform != '__WXMSW__':
- item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
- elif self.Tree.GetItemText(item) != filename:
- item = self.Tree.InsertItemBefore(root, idx, filename, self.TreeImageDict[item_type])
- filepath = os.path.join(folderpath, filename)
- if item_type != FILE:
- if self.Tree.IsExpanded(item):
- self.GenerateTreeBranch(item, filepath)
- elif children > 0:
- self.Tree.SetItemHasChildren(item)
- item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
- to_delete = []
- while item.IsOk():
- to_delete.append(item)
- item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
- for item in to_delete:
- self.Tree.Delete(item)
-
- def ExpandItem(self, item):
- self.GenerateTreeBranch(item, self.GetPath(item))
- self.Tree.Expand(item)
-
- def OnTreeItemActivated(self, event):
- self.ExpandItem(event.GetItem())
- event.Skip()
-
- def OnTreeLeftDown(self, event):
- item, flags = self.Tree.HitTest(event.GetPosition())
- if flags & wx.TREE_HITTEST_ONITEMBUTTON and not self.Tree.IsExpanded(item):
- self.ExpandItem(item)
- else:
- event.Skip()
-
- def OnTreeItemExpanded(self, event):
- item = event.GetItem()
- self.GenerateTreeBranch(item, self.GetPath(item))
- event.Skip()
-
- def OnTreeItemCollapsed(self, event):
- item = event.GetItem()
- self.Tree.DeleteChildren(item)
- self.Tree.SetItemHasChildren(item)
- event.Skip()
-
- def OnTreeBeginLabelEdit(self, event):
- item = event.GetItem()
- if self.Editable and not self.Tree.ItemHasChildren(item):
- event.Skip()
- else:
- event.Veto()
-
- def OnTreeEndLabelEdit(self, event):
- old_filepath = self.GetPath(event.GetItem())
- new_filepath = os.path.join(os.path.split(old_filepath)[0], event.GetLabel())
- if new_filepath != old_filepath:
- if not os.path.exists(new_filepath):
- os.rename(old_filepath, new_filepath)
- event.Skip()
- else:
- message = wx.MessageDialog(self,
- _("File '%s' already exists!") % event.GetLabel(),
- _("Error"), wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
- event.Veto()
-
- def OnFilterChanged(self, event):
- self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
- self.RefreshTree()
- event.Skip()
-
- def _SelectItem(self, root, parts):
- if len(parts) == 0:
- self.Tree.SelectItem(root)
- else:
- item, item_cookie = self.Tree.GetFirstChild(root)
- while item.IsOk():
- if self.Tree.GetItemText(item) == parts[0]:
- if (self.Tree.ItemHasChildren(item) and
- not self.Tree.IsExpanded(item)):
- self.Tree.Expand(item)
- wx.CallAfter(self._SelectItem, item, parts[1:])
- else:
- self._SelectItem(item, parts[1:])
- return
- item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
-
- def SetPath(self, path):
- if path.startswith(self.Folder):
- root = self.Tree.GetRootItem()
- if root.IsOk():
- relative_path = path.replace(os.path.join(self.Folder, ""), "")
- self._SelectItem(root, splitpath(relative_path))
-
- def GetPath(self, item=None):
- if item is None:
- item = self.Tree.GetSelection()
- if item.IsOk():
- filepath = self.Tree.GetItemText(item)
- parent = self.Tree.GetItemParent(item)
- while parent.IsOk() and parent != self.Tree.GetRootItem():
- filepath = os.path.join(self.Tree.GetItemText(parent), filepath)
- parent = self.Tree.GetItemParent(parent)
- return os.path.join(self.Folder, filepath)
- return self.Folder
-
-class FileManagementPanel(EditorPanel):
-
- def _init_Editor(self, parent):
- self.Editor = wx.Panel(parent)
-
- main_sizer = wx.BoxSizer(wx.HORIZONTAL)
-
- left_sizer = wx.BoxSizer(wx.VERTICAL)
- main_sizer.AddSizer(left_sizer, 1, border=5, flag=wx.GROW|wx.ALL)
-
- managed_dir_label = wx.StaticText(self.Editor, label=self.TagName + ":")
- left_sizer.AddWindow(managed_dir_label, border=5, flag=wx.GROW|wx.BOTTOM)
-
- self.ManagedDir = FolderTree(self.Editor, self.Folder, FILTER)
- left_sizer.AddWindow(self.ManagedDir, 1, flag=wx.GROW)
-
- managed_treectrl = self.ManagedDir.GetTreeCtrl()
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, managed_treectrl)
- if self.EnableDragNDrop:
- self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, managed_treectrl)
-
- button_sizer = wx.BoxSizer(wx.VERTICAL)
- main_sizer.AddSizer(button_sizer, border=5,
- flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL)
-
- for idx, (name, bitmap, help) in enumerate([
- ("DeleteButton", "remove_element", _("Remove file from left folder")),
- ("LeftCopyButton", "LeftCopy", _("Copy file from right folder to left")),
- ("RightCopyButton", "RightCopy", _("Copy file from left folder to right")),
- ("EditButton", "edit", _("Edit file"))]):
- button = wx.lib.buttons.GenBitmapButton(self.Editor,
- bitmap=GetBitmap(bitmap),
- size=wx.Size(28, 28), style=wx.NO_BORDER)
- button.SetToolTipString(help)
- setattr(self, name, button)
- if idx > 0:
- flag = wx.TOP
- else:
- flag = 0
- self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
- button_sizer.AddWindow(button, border=20, flag=flag)
-
- right_sizer = wx.BoxSizer(wx.VERTICAL)
- main_sizer.AddSizer(right_sizer, 1, border=5, flag=wx.GROW|wx.ALL)
-
- if wx.Platform == '__WXMSW__':
- system_dir_label = wx.StaticText(self.Editor, label=_("My Computer:"))
- else:
- system_dir_label = wx.StaticText(self.Editor, label=_("Home Directory:"))
- right_sizer.AddWindow(system_dir_label, border=5, flag=wx.GROW|wx.BOTTOM)
-
- self.SystemDir = FolderTree(self.Editor, self.HomeDirectory, FILTER, False)
- right_sizer.AddWindow(self.SystemDir, 1, flag=wx.GROW)
-
- system_treectrl = self.SystemDir.GetTreeCtrl()
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, system_treectrl)
-
- self.Editor.SetSizer(main_sizer)
-
- def __init__(self, parent, controler, name, folder, enable_dragndrop=False):
- self.Folder = os.path.realpath(folder)
- self.EnableDragNDrop = enable_dragndrop
-
- if wx.Platform == '__WXMSW__':
- self.HomeDirectory = "/"
- else:
- self.HomeDirectory = os.path.expanduser("~")
-
- EditorPanel.__init__(self, parent, name, None, None)
-
- self.Controler = controler
-
- self.EditableFileExtensions = []
- self.EditButton.Hide()
-
- self.SetIcon(GetBitmap("FOLDER"))
-
- def __del__(self):
- self.Controler.OnCloseEditor(self)
-
- def GetTitle(self):
- return self.TagName
-
- def SetEditableFileExtensions(self, extensions):
- self.EditableFileExtensions = extensions
- if len(self.EditableFileExtensions) > 0:
- self.EditButton.Show()
-
- def RefreshView(self):
- self.ManagedDir.RefreshTree()
- self.SystemDir.RefreshTree()
- self.RefreshButtonsState()
-
- def RefreshButtonsState(self):
- managed_filepath = self.ManagedDir.GetPath()
- system_filepath = self.SystemDir.GetPath()
-
- self.DeleteButton.Enable(os.path.isfile(managed_filepath))
- self.LeftCopyButton.Enable(os.path.isfile(system_filepath))
- self.RightCopyButton.Enable(os.path.isfile(managed_filepath))
- if len(self.EditableFileExtensions) > 0:
- self.EditButton.Enable(
- os.path.isfile(managed_filepath) and
- os.path.splitext(managed_filepath)[1] in self.EditableFileExtensions)
-
- def OnTreeItemChanged(self, event):
- self.RefreshButtonsState()
- event.Skip()
-
- def OnDeleteButton(self, event):
- filepath = self.ManagedDir.GetPath()
- if os.path.isfile(filepath):
- folder, filename = os.path.split(filepath)
-
- dialog = wx.MessageDialog(self,
- _("Do you really want to delete the file '%s'?") % filename,
- _("Delete File"), wx.YES_NO|wx.ICON_QUESTION)
- remove = dialog.ShowModal() == wx.ID_YES
- dialog.Destroy()
-
- if remove:
- os.remove(filepath)
- self.ManagedDir.RefreshTree()
- event.Skip()
-
- def OnEditButton(self, event):
- filepath = self.ManagedDir.GetPath()
- if (os.path.isfile(filepath) and
- os.path.splitext(filepath)[1] in self.EditableFileExtensions):
- self.Controler._OpenView(filepath + "::")
- event.Skip()
-
- def CopyFile(self, src, dst):
- if os.path.isfile(src):
- src_folder, src_filename = os.path.split(src)
- if os.path.isfile(dst):
- dst_folder, dst_filename = os.path.split(dst)
- else:
- dst_folder = dst
-
- dst_filepath = os.path.join(dst_folder, src_filename)
- if os.path.isfile(dst_filepath):
- dialog = wx.MessageDialog(self,
- _("The file '%s' already exist.\nDo you want to replace it?") % src_filename,
- _("Replace File"), wx.YES_NO|wx.ICON_QUESTION)
- copy = dialog.ShowModal() == wx.ID_YES
- dialog.Destroy()
- else:
- copy = True
-
- if copy:
- shutil.copyfile(src, dst_filepath)
- return dst_filepath
- return None
-
- def OnLeftCopyButton(self, event):
- filepath = self.CopyFile(self.SystemDir.GetPath(), self.ManagedDir.GetPath())
- if filepath is not None:
- self.ManagedDir.RefreshTree()
- self.ManagedDir.SetPath(filepath)
- event.Skip()
-
- def OnRightCopyButton(self, event):
- filepath = self.CopyFile(self.ManagedDir.GetPath(), self.SystemDir.GetPath())
- if filepath is not None:
- self.SystemDir.RefreshTree()
- self.SystemDir.SetPath(filepath)
- event.Skip()
-
- def OnTreeBeginDrag(self, event):
- filepath = self.ManagedDir.GetPath()
- if os.path.isfile(filepath):
- relative_filepath = filepath.replace(os.path.join(self.Folder, ""), "")
- data = wx.TextDataObject(str(("'%s'" % relative_filepath, "Constant")))
- dragSource = wx.DropSource(self)
- dragSource.SetData(data)
- dragSource.DoDragDrop()
-
\ No newline at end of file
--- a/util/TextCtrlAutoComplete.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-import wx
-import cPickle
-
-MAX_ITEM_COUNT = 10
-MAX_ITEM_SHOWN = 6
-if wx.Platform == '__WXMSW__':
- ITEM_INTERVAL_HEIGHT = 3
-else:
- ITEM_INTERVAL_HEIGHT = 6
-
-if wx.Platform == '__WXMSW__':
- popupclass = wx.PopupTransientWindow
-else:
- popupclass = wx.PopupWindow
-
-class PopupWithListbox(popupclass):
-
- def __init__(self, parent, choices=[]):
- popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
-
- self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
- if not wx.Platform == '__WXMSW__':
- self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
- self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
-
- self.SetChoices(choices)
-
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- def SetChoices(self, choices):
- max_text_width = 0
- max_text_height = 0
-
- self.ListBox.Clear()
- for choice in choices:
- self.ListBox.Append(choice)
- w, h = self.ListBox.GetTextExtent(choice)
- max_text_width = max(max_text_width, w)
- max_text_height = max(max_text_height, h)
-
- itemcount = min(len(choices), MAX_ITEM_SHOWN)
- width = self.Parent.GetSize()[0]
- height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
- if max_text_width + 10 > width:
- height += 15
- size = wx.Size(width, height)
- self.ListBox.SetSize(size)
- self.SetClientSize(size)
-
- def MoveSelection(self, direction):
- selected = self.ListBox.GetSelection()
- if selected == wx.NOT_FOUND:
- if direction >= 0:
- selected = 0
- else:
- selected = self.ListBox.GetCount() - 1
- else:
- selected = (selected + direction) % (self.ListBox.GetCount() + 1)
- if selected == self.ListBox.GetCount():
- selected = wx.NOT_FOUND
- self.ListBox.SetSelection(selected)
-
- def GetSelection(self):
- return self.ListBox.GetStringSelection()
-
- def ProcessLeftDown(self, event):
- selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
- if selected != wx.NOT_FOUND:
- wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
- return False
-
- def OnListBoxClick(self, event):
- selected = event.GetSelection()
- if selected != wx.NOT_FOUND:
- wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
- event.Skip()
-
- def OnKeyDown(self, event):
- self.Parent.ProcessEvent(event)
-
- def OnDismiss(self):
- self.Parent.listbox = None
- wx.CallAfter(self.Parent.DismissListBox)
-
-class TextCtrlAutoComplete(wx.TextCtrl):
-
- def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
- element_path=None, **therest):
- """
- Constructor works just like wx.TextCtrl except you can pass in a
- list of choices. You can also change the choice list at any time
- by calling setChoices.
- """
-
- therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
-
- wx.TextCtrl.__init__(self, parent, **therest)
- self.AppFrame = appframe
-
- #Some variables
- self._dropDownClick = dropDownClick
- self._lastinsertionpoint = None
-
- self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
- self.element_path = element_path
-
- self.listbox = None
-
- self.SetChoices(choices)
-
- #gp = self
- #while ( gp != None ) :
- # gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
- # gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
- # gp = gp.GetParent()
-
- self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
- self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged)
- self.Bind(wx.EVT_TEXT, self.OnEnteredText)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
-
- #If need drop down on left click
- if dropDownClick:
- self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
- self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
-
- def __del__(self):
- self.AppFrame = None
-
- def ChangeValue(self, value):
- wx.TextCtrl.ChangeValue(self, value)
- self.RefreshListBoxChoices()
-
- def OnEnteredText(self, event):
- wx.CallAfter(self.RefreshListBoxChoices)
- event.Skip()
-
- def OnKeyDown(self, event):
- """ Do some work when the user press on the keys:
- up and down: move the cursor
- """
- keycode = event.GetKeyCode()
- if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
- self.PopupListBox()
- if keycode == wx.WXK_DOWN:
- self.listbox.MoveSelection(1)
- else:
- self.listbox.MoveSelection(-1)
- elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
- self.SetValueFromSelected(self.listbox.GetSelection())
- elif event.GetKeyCode() == wx.WXK_ESCAPE:
- self.DismissListBox()
- else:
- event.Skip()
-
- def OnClickToggleDown(self, event):
- self._lastinsertionpoint = self.GetInsertionPoint()
- event.Skip()
-
- def OnClickToggleUp(self, event):
- if self.GetInsertionPoint() == self._lastinsertionpoint:
- wx.CallAfter(self.PopupListBox)
- self._lastinsertionpoint = None
- event.Skip()
-
- def OnControlChanged(self, event):
- res = self.GetValue()
- config = wx.ConfigBase.Get()
- listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
- if res and res not in listentries:
- listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
- config.Write(self.element_path, cPickle.dumps(listentries))
- config.Flush()
- self.SetChoices(listentries)
- self.DismissListBox()
- event.Skip()
-
- def SetChoices(self, choices):
- self._choices = choices
- self.RefreshListBoxChoices()
-
- def GetChoices(self):
- return self._choices
-
- def SetValueFromSelected(self, selected):
- """
- Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
- Will do nothing if no item is selected in the wx.ListCtrl.
- """
- if selected != "":
- self.SetValue(selected)
- self.DismissListBox()
-
- def RefreshListBoxChoices(self):
- if self.listbox is not None:
- text = self.GetValue()
- choices = [choice for choice in self._choices if choice.startswith(text)]
- self.listbox.SetChoices(choices)
-
- def PopupListBox(self):
- if self.listbox is None:
- self.listbox = PopupWithListbox(self)
-
- # Show the popup right below or above the button
- # depending on available screen space...
- pos = self.ClientToScreen((0, 0))
- sz = self.GetSize()
- self.listbox.Position(pos, (0, sz[1]))
-
- self.RefreshListBoxChoices()
-
- if wx.Platform == '__WXMSW__':
- self.listbox.Popup()
- else:
- self.listbox.Show()
- self.AppFrame.EnableScrolling(False)
-
- def DismissListBox(self):
- if self.listbox is not None:
- if wx.Platform == '__WXMSW__':
- self.listbox.Dismiss()
- else:
- self.listbox.Destroy()
- self.listbox = None
- self.AppFrame.EnableScrolling(True)
-
--- a/util/discovery.py Wed Sep 05 11:17:52 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-import socket
-import wx
-import wx.lib.mixins.listctrl as listmix
-from util.Zeroconf import *
-
-import connectors
-
-service_type = '_PYRO._tcp.local.'
-
-class AutoWidthListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
- def __init__(self, parent, id, name, pos=wx.DefaultPosition,
- size=wx.DefaultSize, style=0):
- wx.ListCtrl.__init__(self, parent, id, pos, size, style, name=name)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
-
-[ID_DISCOVERYDIALOG, ID_DISCOVERYDIALOGSTATICTEXT1,
- ID_DISCOVERYDIALOGSERVICESLIST, ID_DISCOVERYDIALOGREFRESHBUTTON,
- ID_DISCOVERYDIALOGLOCALBUTTON, ID_DISCOVERYDIALOGIPBUTTON,
-] = [wx.NewId() for _init_ctrls in range(6)]
-
-class DiscoveryDialog(wx.Dialog, listmix.ColumnSorterMixin):
-
- def _init_coll_MainSizer_Items(self, parent):
- parent.AddWindow(self.staticText1, 0, border=20, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.ServicesList, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
-
- def _init_coll_MainSizer_Growables(self, parent):
- parent.AddGrowableCol(0)
- parent.AddGrowableRow(1)
-
- def _init_coll_ButtonGridSizer_Items(self, parent):
- parent.AddWindow(self.RefreshButton, 0, border=0, flag=0)
- parent.AddWindow(self.LocalButton, 0, border=0, flag=0)
- parent.AddWindow(self.IpButton, 0, border=0, flag=0)
- parent.AddSizer(self.ButtonSizer, 0, border=0, flag=0)
-
- def _init_coll_ButtonGridSizer_Growables(self, parent):
- parent.AddGrowableCol(0)
- parent.AddGrowableCol(1)
- parent.AddGrowableRow(1)
-
- def _init_sizers(self):
- self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
- self.ButtonGridSizer = wx.FlexGridSizer(cols=4, hgap=5, rows=1, vgap=0)
-
- self._init_coll_MainSizer_Items(self.MainSizer)
- self._init_coll_MainSizer_Growables(self.MainSizer)
- self._init_coll_ButtonGridSizer_Items(self.ButtonGridSizer)
- self._init_coll_ButtonGridSizer_Growables(self.ButtonGridSizer)
-
- self.SetSizer(self.MainSizer)
-
- def _init_ctrls(self, prnt):
- wx.Dialog.__init__(self, id=ID_DISCOVERYDIALOG,
- name='DiscoveryDialog', parent=prnt,
- size=wx.Size(600, 600), style=wx.DEFAULT_DIALOG_STYLE,
- title='Service Discovery')
-
- self.staticText1 = wx.StaticText(id=ID_DISCOVERYDIALOGSTATICTEXT1,
- label=_('Services available:'), name='staticText1', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- # Set up list control
- self.ServicesList = AutoWidthListCtrl(id=ID_DISCOVERYDIALOGSERVICESLIST,
- name='ServicesList', parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 0),
- style=wx.LC_REPORT|wx.LC_EDIT_LABELS|wx.LC_SORT_ASCENDING|wx.LC_SINGLE_SEL)
- self.ServicesList.InsertColumn(0, 'NAME')
- self.ServicesList.InsertColumn(1, 'TYPE')
- self.ServicesList.InsertColumn(2, 'IP')
- self.ServicesList.InsertColumn(3, 'PORT')
- self.ServicesList.SetColumnWidth(0, 150)
- self.ServicesList.SetColumnWidth(1, 150)
- self.ServicesList.SetColumnWidth(2, 150)
- self.ServicesList.SetColumnWidth(3, 150)
- self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, id=ID_DISCOVERYDIALOGSERVICESLIST)
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, id=ID_DISCOVERYDIALOGSERVICESLIST)
-
- listmix.ColumnSorterMixin.__init__(self, 4)
-
- self.RefreshButton = wx.Button(id=ID_DISCOVERYDIALOGREFRESHBUTTON,
- label=_('Refresh'), name='RefreshButton', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.Bind(wx.EVT_BUTTON, self.OnRefreshButton, id=ID_DISCOVERYDIALOGREFRESHBUTTON)
-
- self.LocalButton = wx.Button(id=ID_DISCOVERYDIALOGLOCALBUTTON,
- label=_('Local'), name='LocalButton', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.Bind(wx.EVT_BUTTON, self.OnLocalButton, id=ID_DISCOVERYDIALOGLOCALBUTTON)
-
- self.IpButton = wx.Button(id=ID_DISCOVERYDIALOGIPBUTTON,
- label=_('Add IP'), name='IpButton', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.Bind(wx.EVT_BUTTON, self.OnIpButton, id=ID_DISCOVERYDIALOGIPBUTTON)
-
- self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTER)
-
- self._init_sizers()
-
- def __init__(self, parent):
- self._init_ctrls(parent)
-
- self.itemDataMap = {}
- self.nextItemId = 0
-
- self.URI = None
- self.Browser = None
-
- self.ZeroConfInstance = Zeroconf()
- self.RefreshList()
- self.LatestSelection=None
-
- def __del__(self):
- if self.Browser is not None : self.Browser.cancel()
- self.ZeroConfInstance.close()
-
- def RefreshList(self):
- if self.Browser is not None : self.Browser.cancel()
- self.Browser = ServiceBrowser(self.ZeroConfInstance, service_type, self)
-
- def OnRefreshButton(self, event):
- self.ServicesList.DeleteAllItems()
- self.RefreshList()
-
- def OnLocalButton(self, event):
- self.URI = "LOCAL://"
- self.EndModal(wx.ID_OK)
- event.Skip()
-
- def OnIpButton(self, event):
- if self.LatestSelection is not None:
- l = lambda col : self.getColumnText(self.LatestSelection,col)
- self.URI = "%s://%s:%s"%tuple(map(l,(1,2,3)))
- self.EndModal(wx.ID_OK)
- event.Skip()
-
- # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
- def GetListCtrl(self):
- return self.ServicesList
-
- def getColumnText(self, index, col):
- item = self.ServicesList.GetItem(index, col)
- return item.GetText()
-
- def OnItemSelected(self, event):
- self.SetURI(event.m_itemIndex)
- event.Skip()
-
- def OnItemActivated(self, event):
- self.SetURI(event.m_itemIndex)
- self.EndModal(wx.ID_OK)
- event.Skip()
-
-# def SetURI(self, idx):
-# connect_type = self.getColumnText(idx, 1)
-# connect_address = self.getColumnText(idx, 2)
-# connect_port = self.getColumnText(idx, 3)
-#
-# self.URI = "%s://%s:%s"%(connect_type, connect_address, connect_port)
-
- def SetURI(self, idx):
- self.LatestSelection = idx
- svcname = self.getColumnText(idx, 0)
- connect_type = self.getColumnText(idx, 1)
- self.URI = "%s://%s"%(connect_type, svcname + '.' + service_type)
-
- def GetURI(self):
- return self.URI
-
- def removeService(self, zeroconf, _type, name):
- wx.CallAfter(self._removeService, name)
-
-
- def _removeService(self, name):
- '''
- called when a service with the desired type goes offline.
- '''
-
- # loop through the list items looking for the service that went offline
- for idx in xrange(self.ServicesList.GetItemCount()):
- # this is the unique identifier assigned to the item
- item_id = self.ServicesList.GetItemData(idx)
-
- # this is the full typename that was received by addService
- item_name = self.itemDataMap[item_id][4]
-
- if item_name == name:
- self.ServicesList.DeleteItem(idx)
- break
-
- def addService(self, zeroconf, _type, name):
- wx.CallAfter(self._addService, _type, name)
-
- def _addService(self, _type, name):
- '''
- called when a service with the desired type is discovered.
- '''
- info = self.ZeroConfInstance.getServiceInfo(_type, name)
-
- svcname = name.split(".")[0]
- typename = _type.split(".")[0][1:]
- ip = str(socket.inet_ntoa(info.getAddress()))
- port = info.getPort()
-
- num_items = self.ServicesList.GetItemCount()
-
- # display the new data in the list
- new_item = self.ServicesList.InsertStringItem(num_items, svcname)
- self.ServicesList.SetStringItem(new_item, 1, "%s" % typename)
- self.ServicesList.SetStringItem(new_item, 2, "%s" % ip)
- self.ServicesList.SetStringItem(new_item, 3, "%s" % port)
-
- # record the new data for the ColumnSorterMixin
- # we assign every list item a unique id (that won't change when items
- # are added or removed)
- self.ServicesList.SetItemData(new_item, self.nextItemId)
-
- # the value of each column has to be stored in the itemDataMap
- # so that ColumnSorterMixin knows how to sort the column.
-
- # "name" is included at the end so that self.removeService
- # can access it.
- self.itemDataMap[self.nextItemId] = [ svcname, typename, ip, port, name ]
-
- self.nextItemId += 1
-
--- a/util/misc.py Wed Sep 05 11:17:52 2012 +0200
+++ b/util/misc.py Fri Sep 07 16:45:55 2012 +0200
@@ -4,7 +4,7 @@
import os,sys
-from TextViewer import TextViewer
+from editors.TextViewer import TextViewer
# helper func to check path write permission
def CheckPathPerm(path):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlclass/__init__.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Package initialisation
+
+from xmlclass import ClassFactory, GenerateClasses, GetAttributeValue, time_model, CreateNode, NodeSetAttr, NodeRenameAttr
+from xsdschema import XSDClassFactory, GenerateClassesFromXSD, GenerateClassesFromXSDstring
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlclass/po.xml Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<purchaseOrder orderDate="1999-10-20">
+ <shipTo country="US">
+ <name>Alice Smith</name>
+ <street>123 Maple Street</street>
+ <city>Mill Valley</city>
+ <state>CA</state>
+ <zip>90952</zip>
+ </shipTo>
+ <billTo country="US">
+ <name>Robert Smith</name>
+ <street>8 Oak Avenue</street>
+ <city>Old Town</city>
+ <state>PA</state>
+ <zip>95819</zip>
+ </billTo>
+ <comment>Hurry, my lawn is going wild!</comment>
+ <items>
+ <item partNum="872-AA">
+ <productName>Lawnmower</productName>
+ <quantity>1</quantity>
+ <USPrice>148.95</USPrice>
+ <comment>Confirm this is electric</comment>
+ </item>
+ <item partNum="926-AA">
+ <productName>Baby Monitor</productName>
+ <quantity>1</quantity>
+ <USPrice>39.98</USPrice>
+ <shipDate>1999-05-21</shipDate>
+ </item>
+ </items>
+</purchaseOrder>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlclass/test.xsd Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,66 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Purchase order schema for Example.com.
+ Copyright 2000 Example.com. All rights reserved.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
+
+ <xsd:element name="comment" type="xsd:string"/>
+
+ <xsd:complexType name="PurchaseOrderType">
+ <xsd:sequence>
+ <xsd:element name="shipTo" type="USAddress"/>
+ <xsd:element name="billTo" type="USAddress"/>
+ <xsd:element ref="comment" minOccurs="0"/>
+ <xsd:element name="items" type="Items"/>
+ </xsd:sequence>
+ <xsd:attribute name="orderDate" type="xsd:date"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="USAddress">
+ <xsd:sequence>
+ <xsd:element name="name" type="xsd:string"/>
+ <xsd:element name="street" type="xsd:string"/>
+ <xsd:element name="city" type="xsd:string"/>
+ <xsd:element name="state" type="xsd:string"/>
+ <xsd:element name="zip" type="xsd:decimal"/>
+ </xsd:sequence>
+ <xsd:attribute name="country" type="xsd:NMTOKEN"
+ fixed="US"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="Items">
+ <xsd:sequence>
+ <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="productName" type="xsd:string"/>
+ <xsd:element name="quantity">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:positiveInteger">
+ <xsd:maxExclusive value="100"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:element>
+ <xsd:element name="USPrice" type="xsd:decimal"/>
+ <xsd:element ref="comment" minOccurs="0"/>
+ <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="partNum" type="SKU" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <!-- Stock Keeping Unit, a code for identifying products -->
+ <xsd:simpleType name="SKU">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="\d{3}-[A-Z]{2}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlclass/xmlclass.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,1856 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os, sys
+import re
+import datetime
+from types import *
+from xml.dom import minidom
+from xml.sax.saxutils import escape, unescape, quoteattr
+from new import classobj
+
+def CreateNode(name):
+ node = minidom.Node()
+ node.nodeName = name
+ node._attrs = {}
+ node.childNodes = []
+ return node
+
+def NodeRenameAttr(node, old_name, new_name):
+ node._attrs[new_name] = node._attrs.pop(old_name)
+
+def NodeSetAttr(node, name, value):
+ attr = minidom.Attr(name)
+ text = minidom.Text()
+ text.data = value
+ attr.childNodes[0] = text
+ node._attrs[name] = attr
+
+"""
+Regular expression models for checking all kind of string values defined in XML
+standard
+"""
+Name_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*)$')
+Names_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*(?: [a-zA-Z_\:][\w\.\-\:]*)*)$')
+NMToken_model = re.compile('([\w\.\-\:]*)$')
+NMTokens_model = re.compile('([\w\.\-\:]*(?: [\w\.\-\:]*)*)$')
+QName_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)$')
+QNames_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*(?: (?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)*)$')
+NCName_model = re.compile('([a-zA-Z_][\w]*)$')
+URI_model = re.compile('((?:http://|/)?(?:[\w.-]*/?)*)$')
+LANGUAGE_model = re.compile('([a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)$')
+
+ONLY_ANNOTATION = re.compile("((?:annotation )?)")
+
+"""
+Regular expression models for extracting dates and times from a string
+"""
+time_model = re.compile('([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)(?:Z)?$')
+date_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$')
+datetime_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$')
+
+class xml_timezone(datetime.tzinfo):
+
+ def SetOffset(self, offset):
+ if offset == "Z":
+ self.__offset = timedelta(minutes = 0)
+ self.__name = "UTC"
+ else:
+ sign = {"-" : -1, "+" : 1}[offset[0]]
+ hours, minutes = [int(val) for val in offset[1:].split(":")]
+ self.__offset = timedelta(minutes=sign * (hours * 60 + minutes))
+ self.__name = ""
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return ZERO
+
+[SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE,
+ ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT,
+] = range(13)
+
+def NotSupportedYet(type):
+ """
+ Function that generates a function that point out to user that datatype
+ used is not supported by xmlclass yet
+ @param type: data type
+ @return: function generated
+ """
+ def GetUnknownValue(attr):
+ raise ValueError("\"%s\" type isn't supported by \"xmlclass\" yet!" % \
+ type)
+ return GetUnknownValue
+
+"""
+This function calculates the number of whitespace for indentation
+"""
+def getIndent(indent, balise):
+ first = indent * 2
+ second = first + len(balise) + 1
+ return u'\t'.expandtabs(first), u'\t'.expandtabs(second)
+
+
+def GetAttributeValue(attr, extract=True):
+ """
+ Function that extracts data from a tree node
+ @param attr: tree node containing data to extract
+ @param extract: attr is a tree node or not
+ @return: data extracted as string
+ """
+ if not extract:
+ return attr
+ if len(attr.childNodes) == 1:
+ return unicode(unescape(attr.childNodes[0].data))
+ else:
+ # content is a CDATA
+ text = u''
+ for node in attr.childNodes:
+ if not (node.nodeName == "#text" and node.data.strip() == u''):
+ text += unicode(unescape(node.data))
+ return text
+
+
+def GetNormalizedString(attr, extract=True):
+ """
+ Function that normalizes a string according to XML 1.0. Replace
+ tabulations, line feed and carriage return by white space
+ @param attr: tree node containing data to extract or data to normalize
+ @param extract: attr is a tree node or not
+ @return: data normalized as string
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ return value.replace("\t", " ").replace("\r", " ").replace("\n", " ")
+
+
+def GetToken(attr, extract=True):
+ """
+ Function that tokenizes a string according to XML 1.0. Remove any leading
+ and trailing white space and replace internal sequence of two or more
+ spaces by only one white space
+ @param attr: tree node containing data to extract or data to tokenize
+ @param extract: attr is a tree node or not
+ @return: data tokenized as string
+ """
+ return " ".join([part for part in
+ GetNormalizedString(attr, extract).split(" ")
+ if part])
+
+
+def GetHexInteger(attr, extract=True):
+ """
+ Function that extracts an hexadecimal integer from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as an integer
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if len(value) % 2 != 0:
+ raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)
+ try:
+ return int(value, 16)
+ except:
+ raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)
+
+
+def GenerateIntegerExtraction(minInclusive=None, maxInclusive=None,
+ minExclusive=None, maxExclusive=None):
+ """
+ Function that generates an extraction function for integer defining min and
+ max of integer value
+ @param minInclusive: inclusive minimum
+ @param maxInclusive: inclusive maximum
+ @param minExclusive: exclusive minimum
+ @param maxExclusive: exclusive maximum
+ @return: function generated
+ """
+ def GetInteger(attr, extract=True):
+ """
+ Function that extracts an integer from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as an integer
+ """
+
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ try:
+ # TODO: permit to write value like 1E2
+ value = int(value)
+ except:
+ raise ValueError("\"%s\" isn't a valid integer!" % value)
+ if minInclusive is not None and value < minInclusive:
+ raise ValueError("\"%d\" isn't greater or equal to %d!" % \
+ (value, minInclusive))
+ if maxInclusive is not None and value > maxInclusive:
+ raise ValueError("\"%d\" isn't lesser or equal to %d!" % \
+ (value, maxInclusive))
+ if minExclusive is not None and value <= minExclusive:
+ raise ValueError("\"%d\" isn't greater than %d!" % \
+ (value, minExclusive))
+ if maxExclusive is not None and value >= maxExclusive:
+ raise ValueError("\"%d\" isn't lesser than %d!" % \
+ (value, maxExclusive))
+ return value
+ return GetInteger
+
+
+def GenerateFloatExtraction(type, extra_values=[]):
+ """
+ Function that generates an extraction function for float
+ @param type: name of the type of float
+ @return: function generated
+ """
+ def GetFloat(attr, extract = True):
+ """
+ Function that extracts a float from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a float
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value in extra_values:
+ return value
+ try:
+ return float(value)
+ except:
+ raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
+ return GetFloat
+
+
+def GetBoolean(attr, extract=True):
+ """
+ Function that extracts a boolean from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a boolean
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value == "true" or value == "1":
+ return True
+ elif value == "false" or value == "0":
+ return False
+ else:
+ raise ValueError("\"%s\" isn't a valid boolean!" % value)
+
+
+def GetTime(attr, extract=True):
+ """
+ Function that extracts a time from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a time
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ result = time_model.match(value)
+ if result:
+ values = result.groups()
+ time_values = [int(v) for v in values[:2]]
+ seconds = float(values[2])
+ time_values.extend([int(seconds), int((seconds % 1) * 1000000)])
+ return datetime.time(*time_values)
+ else:
+ raise ValueError("\"%s\" isn't a valid time!" % value)
+
+
+def GetDate(attr, extract=True):
+ """
+ Function that extracts a date from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a date
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ result = date_model.match(value)
+ if result:
+ values = result.groups()
+ date_values = [int(v) for v in values[:3]]
+ if values[3] is not None:
+ tz = xml_timezone()
+ tz.SetOffset(values[3])
+ date_values.append(tz)
+ return datetime.date(*date_values)
+ else:
+ raise ValueError("\"%s\" isn't a valid date!" % value)
+
+
+def GetDateTime(attr, extract=True):
+ """
+ Function that extracts date and time from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as date and time
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ result = datetime_model.match(value)
+ if result:
+ values = result.groups()
+ datetime_values = [int(v) for v in values[:5]]
+ seconds = float(values[5])
+ datetime_values.extend([int(seconds), int((seconds % 1) * 1000000)])
+ if values[6] is not None:
+ tz = xml_timezone()
+ tz.SetOffset(values[6])
+ datetime_values.append(tz)
+ return datetime.datetime(*datetime_values)
+ else:
+ raise ValueError("\"%s\" isn't a valid datetime!" % value)
+
+
+def GenerateModelNameExtraction(type, model):
+ """
+ Function that generates an extraction function for string matching a model
+ @param type: name of the data type
+ @param model: model that data must match
+ @return: function generated
+ """
+ def GetModelName(attr, extract=True):
+ """
+ Function that extracts a string from a tree node or not and check that
+ string extracted or given match the model
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a string if matching
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ result = model.match(value)
+ if not result:
+ raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
+ return value
+ return GetModelName
+
+
+def GenerateLimitExtraction(min=None, max=None, unbounded=True):
+ """
+ Function that generates an extraction function for integer defining min and
+ max of integer value
+ @param min: minimum limit value
+ @param max: maximum limit value
+ @param unbounded: value can be "unbounded" or not
+ @return: function generated
+ """
+ def GetLimit(attr, extract=True):
+ """
+ Function that extracts a string from a tree node or not and check that
+ string extracted or given is in a list of values
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a string
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value == "unbounded":
+ if unbounded:
+ return value
+ else:
+ raise ValueError("Member limit can't be defined to \"unbounded\"!")
+ try:
+ limit = int(value)
+ except:
+ raise ValueError("\"%s\" isn't a valid value for this member limit!" % value)
+ if limit < 0:
+ raise ValueError("Member limit can't be negative!")
+ elif min is not None and limit < min:
+ raise ValueError("Member limit can't be lower than \"%d\"!" % min)
+ elif max is not None and limit > max:
+ raise ValueError("Member limit can't be upper than \"%d\"!" % max)
+ return limit
+ return GetLimit
+
+
+def GenerateEnumeratedExtraction(type, list):
+ """
+ Function that generates an extraction function for enumerated values
+ @param type: name of the data type
+ @param list: list of possible values
+ @return: function generated
+ """
+ def GetEnumerated(attr, extract=True):
+ """
+ Function that extracts a string from a tree node or not and check that
+ string extracted or given is in a list of values
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a string
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value in list:
+ return value
+ else:
+ raise ValueError("\"%s\" isn't a valid value for %s!" % \
+ (value, type))
+ return GetEnumerated
+
+
+def GetNamespaces(attr, extract=True):
+ """
+ Function that extracts a list of namespaces from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: list of namespaces
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value == "":
+ return []
+ elif value == "##any" or value == "##other":
+ namespaces = [value]
+ else:
+ namespaces = []
+ for item in value.split(" "):
+ if item == "##targetNamespace" or item == "##local":
+ namespaces.append(item)
+ else:
+ result = URI_model.match(item)
+ if result is not None:
+ namespaces.append(item)
+ else:
+ raise ValueError("\"%s\" isn't a valid value for namespace!" % value)
+ return namespaces
+
+
+def GenerateGetList(type, list):
+ """
+ Function that generates an extraction function for a list of values
+ @param type: name of the data type
+ @param list: list of possible values
+ @return: function generated
+ """
+ def GetLists(attr, extract=True):
+ """
+ Function that extracts a list of values from a tree node or a string
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: list of values
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ if value == "":
+ return []
+ elif value == "#all":
+ return [value]
+ else:
+ values = []
+ for item in value.split(" "):
+ if item in list:
+ values.append(item)
+ else:
+ raise ValueError("\"%s\" isn't a valid value for %s!" % \
+ (value, type))
+ return values
+ return GetLists
+
+
+def GenerateModelNameListExtraction(type, model):
+ """
+ Function that generates an extraction function for list of string matching
+ a model
+ @param type: name of the data type
+ @param model: model that list elements must match
+ @return: function generated
+ """
+ def GetModelNameList(attr, extract=True):
+ """
+ Function that extracts a list of string from a tree node or not and
+ check that all extracted items match the model
+ @param attr: tree node containing data to extract or data as a string
+ @param extract: attr is a tree node or not
+ @return: data as a list of string if matching
+ """
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ values = []
+ for item in value.split(" "):
+ result = model.match(item)
+ if result is not None:
+ values.append(item)
+ else:
+ raise ValueError("\"%s\" isn't a valid value for %s!" % \
+ (value, type))
+ return values
+ return GetModelNameList
+
+def GenerateAnyInfos(infos):
+ def ExtractAny(tree):
+ if tree.nodeName in ["#text", "#cdata-section"]:
+ return unicode(unescape(tree.data))
+ else:
+ return tree
+
+ def GenerateAny(value, name=None, indent=0):
+ if isinstance(value, (StringType, UnicodeType)):
+ try:
+ value = value.decode("utf-8")
+ except:
+ pass
+ return u'<![CDATA[%s]]>\n' % value
+ else:
+ return value.toprettyxml(indent=" "*indent, encoding="utf-8")
+
+ return {
+ "type": COMPLEXTYPE,
+ "extract": ExtractAny,
+ "generate": GenerateAny,
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType, minidom.Node))
+ }
+
+def GenerateTagInfos(infos):
+ def ExtractTag(tree):
+ if len(tree._attrs) > 0:
+ raise ValueError("\"%s\" musn't have attributes!" % infos["name"])
+ if len(tree.childNodes) > 0:
+ raise ValueError("\"%s\" musn't have children!" % infos["name"])
+ if infos["minOccurs"] == 0:
+ return True
+ else:
+ return None
+
+ def GenerateTag(value, name=None, indent=0):
+ if name is not None and not (infos["minOccurs"] == 0 and value is None):
+ ind1, ind2 = getIndent(indent, name)
+ return ind1 + "<%s/>\n" % name
+ else:
+ return ""
+
+ return {
+ "type": TAG,
+ "extract": ExtractTag,
+ "generate": GenerateTag,
+ "initial": lambda: None,
+ "check": lambda x: x == None or infos["minOccurs"] == 0 and value == True
+ }
+
+def FindTypeInfos(factory, infos):
+ if isinstance(infos, (UnicodeType, StringType)):
+ namespace, name = DecomposeQualifiedName(infos)
+ return factory.GetQualifiedNameInfos(name, namespace)
+ return infos
+
+def GetElementInitialValue(factory, infos):
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ if infos["minOccurs"] == 0 and infos["maxOccurs"] == 1:
+ if infos.has_key("default"):
+ return infos["elmt_type"]["extract"](infos["default"], False)
+ else:
+ return None
+ elif infos["minOccurs"] == 1 and infos["maxOccurs"] == 1:
+ return infos["elmt_type"]["initial"]()
+ else:
+ return [infos["elmt_type"]["initial"]() for i in xrange(infos["minOccurs"])]
+
+def HandleError(message, raise_exception):
+ if raise_exception:
+ raise ValueError(message)
+ return False
+
+def CheckElementValue(factory, name, infos, value, raise_exception=True):
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ if value is None and raise_exception:
+ if not (infos["minOccurs"] == 0 and infos["maxOccurs"] == 1):
+ return HandleError("Attribute '%s' isn't optional." % name, raise_exception)
+ elif infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
+ if not isinstance(value, ListType):
+ return HandleError("Attribute '%s' must be a list." % name, raise_exception)
+ if len(value) < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and len(value) > infos["maxOccurs"]:
+ return HandleError("List out of bounds for attribute '%s'." % name, raise_exception)
+ if not reduce(lambda x, y: x and y, map(infos["elmt_type"]["check"], value), True):
+ return HandleError("Attribute '%s' must be a list of valid elements." % name, raise_exception)
+ elif infos.has_key("fixed") and value != infos["fixed"]:
+ return HandleError("Value of attribute '%s' can only be '%s'." % (name, str(infos["fixed"])), raise_exception)
+ else:
+ return infos["elmt_type"]["check"](value)
+ return True
+
+def GetContentInfos(name, choices):
+ for choice_infos in choices:
+ if choices_infos["type"] == "sequence":
+ for element_infos in choices_infos["elements"]:
+ if element_infos["type"] == CHOICE:
+ if GetContentInfos(name, element_infos["choices"]):
+ return choices_infos
+ elif element_infos["name"] == name:
+ return choices_infos
+ elif choice_infos["name"] == name:
+ return choices_infos
+ return None
+
+def ComputeContentChoices(factory, name, infos):
+ choices = []
+ for choice in infos["choices"]:
+ if choice["type"] == "sequence":
+ choice["name"] = "sequence"
+ for sequence_element in choice["elements"]:
+ if sequence_element["type"] != CHOICE:
+ element_infos = factory.ExtractTypeInfos(sequence_element["name"], name, sequence_element["elmt_type"])
+ if element_infos is not None:
+ sequence_element["elmt_type"] = element_infos
+ elif choice["elmt_type"] == "tag":
+ choice["elmt_type"] = GenerateTagInfos(choice)
+ else:
+ choice_infos = factory.ExtractTypeInfos(choice["name"], name, choice["elmt_type"])
+ if choice_infos is not None:
+ choice["elmt_type"] = choice_infos
+ choices.append((choice["name"], choice))
+ return choices
+
+def ExtractContentElement(factory, tree, infos, content):
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
+ if isinstance(content, ListType) and len(content) > 0 and \
+ content[-1]["name"] == tree.nodeName:
+ content_item = content.pop(-1)
+ content_item["value"].append(infos["elmt_type"]["extract"](tree))
+ return content_item
+ elif not isinstance(content, ListType) and \
+ content is not None and \
+ content["name"] == tree.nodeName:
+ return {"name": tree.nodeName,
+ "value": content["value"] + [infos["elmt_type"]["extract"](tree)]}
+ else:
+ return {"name": tree.nodeName,
+ "value": [infos["elmt_type"]["extract"](tree)]}
+ else:
+ return {"name": tree.nodeName,
+ "value": infos["elmt_type"]["extract"](tree)}
+
+def GenerateContentInfos(factory, name, choices):
+ choices_dict = {}
+ for choice_name, infos in choices:
+ if choice_name == "sequence":
+ for element in infos["elements"]:
+ if element["type"] == CHOICE:
+ element["elmt_type"] = GenerateContentInfos(factory, name, ComputeContentChoices(factory, name, element))
+ elif choices_dict.has_key(element["name"]):
+ raise ValueError("'%s' element defined two times in choice" % choice_name)
+ else:
+ choices_dict[element["name"]] = infos
+ else:
+ if choices_dict.has_key(choice_name):
+ raise ValueError("'%s' element defined two times in choice" % choice_name)
+ choices_dict[choice_name] = infos
+
+ def GetContentInitial():
+ content_name, infos = choices[0]
+ if content_name == "sequence":
+ content_value = []
+ for i in xrange(infos["minOccurs"]):
+ for element_infos in infos["elements"]:
+ value = GetElementInitialValue(factory, element_infos)
+ if value is not None:
+ if element_infos["type"] == CHOICE:
+ content_value.append(value)
+ else:
+ content_value.append({"name": element_infos["name"], "value": value})
+ else:
+ content_value = GetElementInitialValue(factory, infos)
+ return {"name": content_name, "value": content_value}
+
+ def CheckContent(value):
+ if value["name"] != "sequence":
+ infos = choices_dict.get(value["name"], None)
+ if infos is not None:
+ return CheckElementValue(factory, value["name"], infos, value["value"], False)
+ elif len(value["value"]) > 0:
+ infos = choices_dict.get(value["value"][0]["name"], None)
+ if infos is None:
+ for choice_name, infos in choices:
+ if infos["type"] == "sequence":
+ for element_infos in infos["elements"]:
+ if element_infos["type"] == CHOICE:
+ infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"])
+ if infos is not None:
+ sequence_number = 0
+ element_idx = 0
+ while element_idx < len(value["value"]):
+ for element_infos in infos["elements"]:
+ element_value = None
+ if element_infos["type"] == CHOICE:
+ choice_infos = None
+ if element_idx < len(value["value"]):
+ for choice in element_infos["choices"]:
+ if choice["name"] == value["value"][element_idx]["name"]:
+ choice_infos = choice
+ element_value = value["value"][element_idx]["value"]
+ element_idx += 1
+ break
+ if ((choice_infos is not None and
+ not CheckElementValue(factory, choice_infos["name"], choice_infos, element_value, False)) or
+ (choice_infos is None and element_infos["minOccurs"] > 0)):
+ raise ValueError("Invalid sequence value in attribute 'content'")
+ else:
+ if element_idx < len(value["value"]) and element_infos["name"] == value["value"][element_idx]["name"]:
+ element_value = value["value"][element_idx]["value"]
+ element_idx += 1
+ if not CheckElementValue(factory, element_infos["name"], element_infos, element_value, False):
+ raise ValueError("Invalid sequence value in attribute 'content'")
+ sequence_number += 1
+ if sequence_number < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and sequence_number > infos["maxOccurs"]:
+ raise ValueError("Invalid sequence value in attribute 'content'")
+ return True
+ else:
+ for element_name, infos in choices:
+ if element_name == "sequence":
+ required = 0
+ for element in infos["elements"]:
+ if element["minOccurs"] > 0:
+ required += 1
+ if required == 0:
+ return True
+ return False
+
+ def ExtractContent(tree, content):
+ infos = choices_dict.get(tree.nodeName, None)
+ if infos is not None:
+ if infos["name"] == "sequence":
+ sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"] if element_infos["type"] != CHOICE])
+ element_infos = sequence_dict.get(tree.nodeName)
+ if content is not None and \
+ content["name"] == "sequence" and \
+ len(content["value"]) > 0 and \
+ choices_dict.get(content["value"][-1]["name"]) == infos:
+ return {"name": "sequence",
+ "value": content["value"] + [ExtractContentElement(factory, tree, element_infos, content["value"][-1])]}
+ else:
+ return {"name": "sequence",
+ "value": [ExtractContentElement(factory, tree, element_infos, None)]}
+ else:
+ return ExtractContentElement(factory, tree, infos, content)
+ else:
+ for choice_name, infos in choices:
+ if infos["type"] == "sequence":
+ for element_infos in infos["elements"]:
+ if element_infos["type"] == CHOICE:
+ try:
+ if content is not None and \
+ content["name"] == "sequence" and \
+ len(content["value"]) > 0:
+ return {"name": "sequence",
+ "value": content["value"] + [element_infos["elmt_type"]["extract"](tree, content["value"][-1])]}
+ else:
+ return {"name": "sequence",
+ "value": [element_infos["elmt_type"]["extract"](tree, None)]}
+ except:
+ pass
+ raise ValueError("Invalid element \"%s\" for content!" % tree.nodeName)
+
+ def GenerateContent(value, name=None, indent=0):
+ text = ""
+ if value["name"] != "sequence":
+ infos = choices_dict.get(value["name"], None)
+ if infos is not None:
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
+ for item in value["value"]:
+ text += infos["elmt_type"]["generate"](item, value["name"], indent)
+ else:
+ text += infos["elmt_type"]["generate"](value["value"], value["name"], indent)
+ elif len(value["value"]) > 0:
+ infos = choices_dict.get(value["value"][0]["name"], None)
+ if infos is None:
+ for choice_name, infos in choices:
+ if infos["type"] == "sequence":
+ for element_infos in infos["elements"]:
+ if element_infos["type"] == CHOICE:
+ infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"])
+ if infos is not None:
+ sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"]])
+ for element_value in value["value"]:
+ element_infos = sequence_dict.get(element_value["name"])
+ if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
+ for item in element_value["value"]:
+ text += element_infos["elmt_type"]["generate"](item, element_value["name"], indent)
+ else:
+ text += element_infos["elmt_type"]["generate"](element_value["value"], element_infos["name"], indent)
+ return text
+
+ return {
+ "type": COMPLEXTYPE,
+ "initial": GetContentInitial,
+ "check": CheckContent,
+ "extract": ExtractContent,
+ "generate": GenerateContent
+ }
+
+#-------------------------------------------------------------------------------
+# Structure extraction functions
+#-------------------------------------------------------------------------------
+
+
+def DecomposeQualifiedName(name):
+ result = QName_model.match(name)
+ if not result:
+ raise ValueError("\"%s\" isn't a valid QName value!" % name)
+ parts = result.groups()[0].split(':')
+ if len(parts) == 1:
+ return None, parts[0]
+ return parts
+
+def GenerateElement(element_name, attributes, elements_model,
+ accept_text=False):
+ def ExtractElement(factory, node):
+ attrs = factory.ExtractNodeAttrs(element_name, node, attributes)
+ children_structure = ""
+ children_infos = []
+ children = []
+ for child in node.childNodes:
+ if child.nodeName not in ["#comment", "#text"]:
+ namespace, childname = DecomposeQualifiedName(child.nodeName)
+ children_structure += "%s "%childname
+ result = elements_model.match(children_structure)
+ if not result:
+ raise ValueError("Invalid structure for \"%s\" children!. First element invalid." % node.nodeName)
+ valid = result.groups()[0]
+ if len(valid) < len(children_structure):
+ raise ValueError("Invalid structure for \"%s\" children!. Element number %d invalid." % (node.nodeName, len(valid.split(" ")) - 1))
+ for child in node.childNodes:
+ if child.nodeName != "#comment" and \
+ (accept_text or child.nodeName != "#text"):
+ if child.nodeName == "#text":
+ children.append(GetAttributeValue(node))
+ else:
+ namespace, childname = DecomposeQualifiedName(child.nodeName)
+ infos = factory.GetQualifiedNameInfos(childname, namespace)
+ if infos["type"] != SYNTAXELEMENT:
+ raise ValueError("\"%s\" can't be a member child!" % name)
+ if infos["extract"].has_key(element_name):
+ children.append(infos["extract"][element_name](factory, child))
+ else:
+ children.append(infos["extract"]["default"](factory, child))
+ return node.nodeName, attrs, children
+ return ExtractElement
+
+
+"""
+Class that generate class from an XML Tree
+"""
+class ClassFactory:
+
+ def __init__(self, document, filepath=None, debug=False):
+ self.Document = document
+ if filepath is not None:
+ self.BaseFolder, self.FileName = os.path.split(filepath)
+ else:
+ self.BaseFolder = self.FileName = None
+ self.Debug = debug
+
+ # Dictionary for stocking Classes and Types definitions created from
+ # the XML tree
+ self.XMLClassDefinitions = {}
+
+ self.DefinedNamespaces = {}
+ self.Namespaces = {}
+ self.SchemaNamespace = None
+ self.TargetNamespace = None
+
+ self.CurrentCompilations = []
+
+ # Dictionaries for stocking Classes and Types generated
+ self.ComputeAfter = []
+ if self.FileName is not None:
+ self.ComputedClasses = {self.FileName: {}}
+ else:
+ self.ComputedClasses = {}
+ self.ComputedClassesInfos = {}
+ self.AlreadyComputed = {}
+
+ def GetQualifiedNameInfos(self, name, namespace=None, canbenone=False):
+ if namespace is None:
+ if self.Namespaces[self.SchemaNamespace].has_key(name):
+ return self.Namespaces[self.SchemaNamespace][name]
+ for space, elements in self.Namespaces.iteritems():
+ if space != self.SchemaNamespace and elements.has_key(name):
+ return elements[name]
+ parts = name.split("_", 1)
+ if len(parts) > 1:
+ group = self.GetQualifiedNameInfos(parts[0], namespace)
+ if group is not None and group["type"] == ELEMENTSGROUP:
+ elements = []
+ if group.has_key("elements"):
+ elements = group["elements"]
+ elif group.has_key("choices"):
+ elements = group["choices"]
+ for element in elements:
+ if element["name"] == parts[1]:
+ return element
+ if not canbenone:
+ raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
+ elif self.Namespaces.has_key(namespace):
+ if self.Namespaces[namespace].has_key(name):
+ return self.Namespaces[namespace][name]
+ parts = name.split("_", 1)
+ if len(parts) > 1:
+ group = self.GetQualifiedNameInfos(parts[0], namespace)
+ if group is not None and group["type"] == ELEMENTSGROUP:
+ elements = []
+ if group.has_key("elements"):
+ elements = group["elements"]
+ elif group.has_key("choices"):
+ elements = group["choices"]
+ for element in elements:
+ if element["name"] == parts[1]:
+ return element
+ if not canbenone:
+ raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
+ elif not canbenone:
+ raise ValueError("Unknown namespace \"%s\"!" % namespace)
+ return None
+
+ def SplitQualifiedName(self, name, namespace=None, canbenone=False):
+ if namespace is None:
+ if self.Namespaces[self.SchemaNamespace].has_key(name):
+ return name, None
+ for space, elements in self.Namespaces.items():
+ if space != self.SchemaNamespace and elements.has_key(name):
+ return name, None
+ parts = name.split("_", 1)
+ if len(parts) > 1:
+ group = self.GetQualifiedNameInfos(parts[0], namespace)
+ if group is not None and group["type"] == ELEMENTSGROUP:
+ elements = []
+ if group.has_key("elements"):
+ elements = group["elements"]
+ elif group.has_key("choices"):
+ elements = group["choices"]
+ for element in elements:
+ if element["name"] == parts[1]:
+ return part[1], part[0]
+ if not canbenone:
+ raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
+ elif self.Namespaces.has_key(namespace):
+ if self.Namespaces[namespace].has_key(name):
+ return name, None
+ parts = name.split("_", 1)
+ if len(parts) > 1:
+ group = self.GetQualifiedNameInfos(parts[0], namespace)
+ if group is not None and group["type"] == ELEMENTSGROUP:
+ elements = []
+ if group.has_key("elements"):
+ elements = group["elements"]
+ elif group.has_key("choices"):
+ elements = group["choices"]
+ for element in elements:
+ if element["name"] == parts[1]:
+ return parts[1], parts[0]
+ if not canbenone:
+ raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
+ elif not canbenone:
+ raise ValueError("Unknown namespace \"%s\"!" % namespace)
+ return None, None
+
+ def ExtractNodeAttrs(self, element_name, node, valid_attrs):
+ attrs = {}
+ for qualified_name, attr in node._attrs.items():
+ namespace, name = DecomposeQualifiedName(qualified_name)
+ if name in valid_attrs:
+ infos = self.GetQualifiedNameInfos(name, namespace)
+ if infos["type"] != SYNTAXATTRIBUTE:
+ raise ValueError("\"%s\" can't be a member attribute!" % name)
+ elif name in attrs:
+ raise ValueError("\"%s\" attribute has been twice!" % name)
+ elif element_name in infos["extract"]:
+ attrs[name] = infos["extract"][element_name](attr)
+ else:
+ attrs[name] = infos["extract"]["default"](attr)
+ elif namespace == "xmlns":
+ infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace)
+ self.DefinedNamespaces[infos["extract"](attr)] = name
+ else:
+ raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % (qualified_name, node.nodeName))
+ for attr in valid_attrs:
+ if attr not in attrs and \
+ self.Namespaces[self.SchemaNamespace].has_key(attr) and \
+ self.Namespaces[self.SchemaNamespace][attr].has_key("default"):
+ if self.Namespaces[self.SchemaNamespace][attr]["default"].has_key(element_name):
+ default = self.Namespaces[self.SchemaNamespace][attr]["default"][element_name]
+ else:
+ default = self.Namespaces[self.SchemaNamespace][attr]["default"]["default"]
+ if default is not None:
+ attrs[attr] = default
+ return attrs
+
+ def ReduceElements(self, elements, schema=False):
+ result = []
+ for child_infos in elements:
+ if child_infos is not None:
+ if child_infos[1].has_key("name") and schema:
+ self.CurrentCompilations.append(child_infos[1]["name"])
+ namespace, name = DecomposeQualifiedName(child_infos[0])
+ infos = self.GetQualifiedNameInfos(name, namespace)
+ if infos["type"] != SYNTAXELEMENT:
+ raise ValueError("\"%s\" can't be a member child!" % name)
+ element = infos["reduce"](self, child_infos[1], child_infos[2])
+ if element is not None:
+ result.append(element)
+ if child_infos[1].has_key("name") and schema:
+ self.CurrentCompilations.pop(-1)
+ annotations = []
+ children = []
+ for element in result:
+ if element["type"] == "annotation":
+ annotations.append(element)
+ else:
+ children.append(element)
+ return annotations, children
+
+ def AddComplexType(self, typename, infos):
+ if not self.XMLClassDefinitions.has_key(typename):
+ self.XMLClassDefinitions[typename] = infos
+ else:
+ raise ValueError("\"%s\" class already defined. Choose another name!" % typename)
+
+ def ParseSchema(self):
+ pass
+
+ def ExtractTypeInfos(self, name, parent, typeinfos):
+ if isinstance(typeinfos, (StringType, UnicodeType)):
+ namespace, name = DecomposeQualifiedName(typeinfos)
+ infos = self.GetQualifiedNameInfos(name, namespace)
+ if infos["type"] == COMPLEXTYPE:
+ name, parent = self.SplitQualifiedName(name, namespace)
+ result = self.CreateClass(name, parent, infos)
+ if result is not None and not isinstance(result, (UnicodeType, StringType)):
+ self.Namespaces[self.TargetNamespace][result["name"]] = result
+ return result
+ elif infos["type"] == ELEMENT and infos["elmt_type"]["type"] == COMPLEXTYPE:
+ name, parent = self.SplitQualifiedName(name, namespace)
+ result = self.CreateClass(name, parent, infos["elmt_type"])
+ if result is not None and not isinstance(result, (UnicodeType, StringType)):
+ self.Namespaces[self.TargetNamespace][result["name"]] = result
+ return result
+ else:
+ return infos
+ elif typeinfos["type"] == COMPLEXTYPE:
+ return self.CreateClass(name, parent, typeinfos)
+ elif typeinfos["type"] == SIMPLETYPE:
+ return typeinfos
+
+ """
+ Methods that generates the classes
+ """
+ def CreateClasses(self):
+ self.ParseSchema()
+ for name, infos in self.Namespaces[self.TargetNamespace].items():
+ if infos["type"] == ELEMENT:
+ if not isinstance(infos["elmt_type"], (UnicodeType, StringType)) and \
+ infos["elmt_type"]["type"] == COMPLEXTYPE:
+ self.ComputeAfter.append((name, None, infos["elmt_type"], True))
+ while len(self.ComputeAfter) > 0:
+ result = self.CreateClass(*self.ComputeAfter.pop(0))
+ if result is not None and not isinstance(result, (UnicodeType, StringType)):
+ self.Namespaces[self.TargetNamespace][result["name"]] = result
+ elif infos["type"] == COMPLEXTYPE:
+ self.ComputeAfter.append((name, None, infos))
+ while len(self.ComputeAfter) > 0:
+ result = self.CreateClass(*self.ComputeAfter.pop(0))
+ if result is not None and \
+ not isinstance(result, (UnicodeType, StringType)):
+ self.Namespaces[self.TargetNamespace][result["name"]] = result
+ elif infos["type"] == ELEMENTSGROUP:
+ elements = []
+ if infos.has_key("elements"):
+ elements = infos["elements"]
+ elif infos.has_key("choices"):
+ elements = infos["choices"]
+ for element in elements:
+ if not isinstance(element["elmt_type"], (UnicodeType, StringType)) and \
+ element["elmt_type"]["type"] == COMPLEXTYPE:
+ self.ComputeAfter.append((element["name"], infos["name"], element["elmt_type"]))
+ while len(self.ComputeAfter) > 0:
+ result = self.CreateClass(*self.ComputeAfter.pop(0))
+ if result is not None and \
+ not isinstance(result, (UnicodeType, StringType)):
+ self.Namespaces[self.TargetNamespace][result["name"]] = result
+ return self.ComputedClasses
+
+ def CreateClass(self, name, parent, classinfos, baseclass = False):
+ if parent is not None:
+ classname = "%s_%s" % (parent, name)
+ else:
+ classname = name
+
+ # Checks that classe haven't been generated yet
+ if self.AlreadyComputed.get(classname, False):
+ if baseclass:
+ self.AlreadyComputed[classname].IsBaseClass = baseclass
+ return self.ComputedClassesInfos.get(classname, None)
+
+ # If base classes haven't been generated
+ bases = []
+ base_infos = classinfos.get("base", None)
+ if base_infos is not None:
+ result = self.ExtractTypeInfos("base", name, base_infos)
+ if result is None:
+ namespace, base_name = DecomposeQualifiedName(base_infos)
+ if self.AlreadyComputed.get(base_name, False):
+ self.ComputeAfter.append((name, parent, classinfos))
+ if self.TargetNamespace is not None:
+ return "%s:%s" % (self.TargetNamespace, classname)
+ else:
+ return classname
+ elif result is not None:
+ if self.FileName is not None:
+ classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None)
+ if classinfos["base"] is None:
+ for filename, classes in self.ComputedClasses.iteritems():
+ if filename != self.FileName:
+ classinfos["base"] = classes.get(result["name"], None)
+ if classinfos["base"] is not None:
+ break
+ else:
+ classinfos["base"] = self.ComputedClasses.get(result["name"], None)
+ if classinfos["base"] is None:
+ raise ValueError("No class found for base type")
+ bases.append(classinfos["base"])
+ bases.append(object)
+ bases = tuple(bases)
+ classmembers = {"__doc__": classinfos.get("doc", ""), "IsBaseClass": baseclass}
+
+ self.AlreadyComputed[classname] = True
+
+ for attribute in classinfos["attributes"]:
+ infos = self.ExtractTypeInfos(attribute["name"], name, attribute["attr_type"])
+ if infos is not None:
+ if infos["type"] != SIMPLETYPE:
+ raise ValueError("\"%s\" type is not a simple type!" % attribute["attr_type"])
+ attrname = attribute["name"]
+ if attribute["use"] == "optional":
+ classmembers[attrname] = None
+ classmembers["add%s"%attrname] = generateAddMethod(attrname, self, attribute)
+ classmembers["delete%s"%attrname] = generateDeleteMethod(attrname)
+ else:
+ classmembers[attrname] = infos["initial"]()
+ classmembers["set%s"%attrname] = generateSetMethod(attrname)
+ classmembers["get%s"%attrname] = generateGetMethod(attrname)
+ else:
+ raise ValueError("\"%s\" type unrecognized!" % attribute["attr_type"])
+ attribute["attr_type"] = infos
+
+ for element in classinfos["elements"]:
+ if element["type"] == CHOICE:
+ elmtname = element["name"]
+ choices = ComputeContentChoices(self, name, element)
+ classmembers["get%schoices"%elmtname] = generateGetChoicesMethod(element["choices"])
+ if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
+ classmembers["append%sbytype" % elmtname] = generateAppendChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
+ classmembers["insert%sbytype" % elmtname] = generateInsertChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
+ else:
+ classmembers["set%sbytype" % elmtname] = generateSetChoiceByTypeMethod(self, element["choices"])
+ infos = GenerateContentInfos(self, name, choices)
+ elif element["type"] == ANY:
+ elmtname = element["name"] = "text"
+ element["minOccurs"] = element["maxOccurs"] = 1
+ infos = GenerateAnyInfos(element)
+ else:
+ elmtname = element["name"]
+ if element["elmt_type"] == "tag":
+ infos = GenerateTagInfos(element)
+ else:
+ infos = self.ExtractTypeInfos(element["name"], name, element["elmt_type"])
+ if infos is not None:
+ element["elmt_type"] = infos
+ if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
+ classmembers[elmtname] = []
+ classmembers["append%s" % elmtname] = generateAppendMethod(elmtname, element["maxOccurs"], self, element)
+ classmembers["insert%s" % elmtname] = generateInsertMethod(elmtname, element["maxOccurs"], self, element)
+ classmembers["remove%s" % elmtname] = generateRemoveMethod(elmtname, element["minOccurs"])
+ classmembers["count%s" % elmtname] = generateCountMethod(elmtname)
+ else:
+ if element["minOccurs"] == 0:
+ classmembers[elmtname] = None
+ classmembers["add%s" % elmtname] = generateAddMethod(elmtname, self, element)
+ classmembers["delete%s" % elmtname] = generateDeleteMethod(elmtname)
+ elif not isinstance(element["elmt_type"], (UnicodeType, StringType)):
+ classmembers[elmtname] = element["elmt_type"]["initial"]()
+ else:
+ classmembers[elmtname] = None
+ classmembers["set%s" % elmtname] = generateSetMethod(elmtname)
+ classmembers["get%s" % elmtname] = generateGetMethod(elmtname)
+
+ classmembers["__init__"] = generateInitMethod(self, classinfos)
+ classmembers["getStructure"] = generateStructureMethod(classinfos)
+ classmembers["loadXMLTree"] = generateLoadXMLTree(self, classinfos)
+ classmembers["generateXMLText"] = generateGenerateXMLText(self, classinfos)
+ classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos)
+ classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos)
+ classmembers["setElementValue"] = generateSetElementValue(self, classinfos)
+ classmembers["singleLineAttributes"] = True
+ classmembers["compatibility"] = lambda x, y: None
+ classmembers["extraAttrs"] = {}
+
+ class_definition = classobj(str(classname), bases, classmembers)
+ setattr(class_definition, "__setattr__", generateSetattrMethod(self, class_definition, classinfos))
+ class_infos = {"type": COMPILEDCOMPLEXTYPE,
+ "name": classname,
+ "check": generateClassCheckFunction(class_definition),
+ "initial": generateClassCreateFunction(class_definition),
+ "extract": generateClassExtractFunction(class_definition),
+ "generate": class_definition.generateXMLText}
+
+ if self.FileName is not None:
+ self.ComputedClasses[self.FileName][classname] = class_definition
+ else:
+ self.ComputedClasses[classname] = class_definition
+ self.ComputedClassesInfos[classname] = class_infos
+
+ return class_infos
+
+ """
+ Methods that print the classes generated
+ """
+ def PrintClasses(self):
+ items = self.ComputedClasses.items()
+ items.sort()
+ if self.FileName is not None:
+ for filename, classes in items:
+ print "File '%s':" % filename
+ class_items = classes.items()
+ class_items.sort()
+ for classname, xmlclass in class_items:
+ print "%s: %s" % (classname, str(xmlclass))
+ else:
+ for classname, xmlclass in items:
+ print "%s: %s" % (classname, str(xmlclass))
+
+ def PrintClassNames(self):
+ classnames = self.XMLClassDefinitions.keys()
+ classnames.sort()
+ for classname in classnames:
+ print classname
+
+"""
+Method that generate the method for checking a class instance
+"""
+def generateClassCheckFunction(class_definition):
+ def classCheckfunction(instance):
+ return isinstance(instance, class_definition)
+ return classCheckfunction
+
+"""
+Method that generate the method for creating a class instance
+"""
+def generateClassCreateFunction(class_definition):
+ def classCreatefunction():
+ return class_definition()
+ return classCreatefunction
+
+"""
+Method that generate the method for extracting a class instance
+"""
+def generateClassExtractFunction(class_definition):
+ def classExtractfunction(node):
+ instance = class_definition()
+ instance.loadXMLTree(node)
+ return instance
+ return classExtractfunction
+
+"""
+Method that generate the method for loading an xml tree by following the
+attributes list defined
+"""
+def generateSetattrMethod(factory, class_definition, classinfos):
+ attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
+ optional_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "optional"])
+ elements = dict([(element["name"], element) for element in classinfos["elements"]])
+
+ def setattrMethod(self, name, value):
+ if attributes.has_key(name):
+ attributes[name]["attr_type"] = FindTypeInfos(factory, attributes[name]["attr_type"])
+ if value is None:
+ if optional_attributes.get(name, False):
+ return object.__setattr__(self, name, None)
+ else:
+ raise ValueError("Attribute '%s' isn't optional." % name)
+ elif attributes[name].has_key("fixed") and value != attributes[name]["fixed"]:
+ raise ValueError, "Value of attribute '%s' can only be '%s'."%(name, str(attributes[name]["fixed"]))
+ elif attributes[name]["attr_type"]["check"](value):
+ return object.__setattr__(self, name, value)
+ else:
+ raise ValueError("Invalid value for attribute '%s'." % (name))
+ elif elements.has_key(name):
+ if CheckElementValue(factory, name, elements[name], value):
+ return object.__setattr__(self, name, value)
+ else:
+ raise ValueError("Invalid value for attribute '%s'." % (name))
+ elif classinfos.has_key("base"):
+ return classinfos["base"].__setattr__(self, name, value)
+ elif class_definition.__dict__.has_key(name):
+ return object.__setattr__(self, name, value)
+ else:
+ raise AttributeError("'%s' can't have an attribute '%s'." % (self.__class__.__name__, name))
+
+ return setattrMethod
+
+"""
+Method that generate the method for generating the xml tree structure model by
+following the attributes list defined
+"""
+def ComputeMultiplicity(name, infos):
+ if infos["minOccurs"] == 0:
+ if infos["maxOccurs"] == "unbounded":
+ return "(?:%s)*" % name
+ elif infos["maxOccurs"] == 1:
+ return "(?:%s)?" % name
+ else:
+ return "(?:%s){,%d}" % (name, infos["maxOccurs"])
+ elif infos["minOccurs"] == 1:
+ if infos["maxOccurs"] == "unbounded":
+ return "(?:%s)+" % name
+ elif infos["maxOccurs"] == 1:
+ return "(?:%s)" % name
+ else:
+ return "(?:%s){1,%d}" % (name, infos["maxOccurs"])
+ else:
+ if infos["maxOccurs"] == "unbounded":
+ return "(?:%s){%d,}" % (name, infos["minOccurs"], name)
+ else:
+ return "(?:%s){%d,%d}" % (name, infos["minOccurs"],
+ infos["maxOccurs"])
+
+def GetStructure(classinfos):
+ elements = []
+ for element in classinfos["elements"]:
+ if element["type"] == ANY:
+ infos = element.copy()
+ infos["minOccurs"] = 0
+ elements.append(ComputeMultiplicity("#text |#cdata-section |\w* ", infos))
+ elif element["type"] == CHOICE:
+ choices = []
+ for infos in element["choices"]:
+ if infos["type"] == "sequence":
+ structure = "(?:%s)" % GetStructure(infos)
+ else:
+ structure = "%s " % infos["name"]
+ choices.append(ComputeMultiplicity(structure, infos))
+ elements.append(ComputeMultiplicity("|".join(choices), element))
+ elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
+ elements.append("(?:#text |#cdata-section )?")
+ else:
+ elements.append(ComputeMultiplicity("%s " % element["name"], element))
+ if classinfos.get("order", True) or len(elements) == 0:
+ return "".join(elements)
+ else:
+ raise ValueError("XSD structure not yet supported!")
+
+def generateStructureMethod(classinfos):
+ def getStructureMethod(self):
+ structure = GetStructure(classinfos)
+ if classinfos.has_key("base"):
+ return classinfos["base"].getStructure(self) + structure
+ return structure
+ return getStructureMethod
+
+"""
+Method that generate the method for loading an xml tree by following the
+attributes list defined
+"""
+def generateLoadXMLTree(factory, classinfos):
+ attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
+ elements = dict([(element["name"], element) for element in classinfos["elements"]])
+
+ def loadXMLTreeMethod(self, tree, extras=[], derived=False):
+ self.extraAttrs = {}
+ self.compatibility(tree)
+ if not derived:
+ children_structure = ""
+ for node in tree.childNodes:
+ if not (node.nodeName == "#text" and node.data.strip() == "") and node.nodeName != "#comment":
+ children_structure += "%s " % node.nodeName
+ structure_pattern = self.getStructure()
+ if structure_pattern != "":
+ structure_model = re.compile("(%s)$" % structure_pattern)
+ result = structure_model.match(children_structure)
+ if not result:
+ raise ValueError("Invalid structure for \"%s\" children!." % tree.nodeName)
+ required_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "required"])
+ if classinfos.has_key("base"):
+ extras.extend([attr["name"] for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
+ classinfos["base"].loadXMLTree(self, tree, extras, True)
+ for attrname, attr in tree._attrs.iteritems():
+ if attributes.has_key(attrname):
+ attributes[attrname]["attr_type"] = FindTypeInfos(factory, attributes[attrname]["attr_type"])
+ object.__setattr__(self, attrname, attributes[attrname]["attr_type"]["extract"](attr))
+ elif not classinfos.has_key("base") and not attrname in extras and not self.extraAttrs.has_key(attrname):
+ self.extraAttrs[attrname] = GetAttributeValue(attr)
+ required_attributes.pop(attrname, None)
+ if len(required_attributes) > 0:
+ raise ValueError("Required attributes %s missing for \"%s\" element!" % (", ".join(["\"%s\""%name for name in required_attributes]), tree.nodeName))
+ first = {}
+ for node in tree.childNodes:
+ name = node.nodeName
+ if name == "#text" and node.data.strip() == "" or name == "#comment":
+ continue
+ elif elements.has_key(name):
+ elements[name]["elmt_type"] = FindTypeInfos(factory, elements[name]["elmt_type"])
+ if elements[name]["maxOccurs"] == "unbounded" or elements[name]["maxOccurs"] > 1:
+ if first.get(name, True):
+ object.__setattr__(self, name, [elements[name]["elmt_type"]["extract"](node)])
+ first[name] = False
+ else:
+ getattr(self, name).append(elements[name]["elmt_type"]["extract"](node))
+ else:
+ object.__setattr__(self, name, elements[name]["elmt_type"]["extract"](node))
+ elif elements.has_key("text"):
+ if elements["text"]["maxOccurs"] == "unbounded" or elements["text"]["maxOccurs"] > 1:
+ if first.get("text", True):
+ object.__setattr__(self, "text", [elements["text"]["elmt_type"]["extract"](node)])
+ first["text"] = False
+ else:
+ getattr(self, "text").append(elements["text"]["elmt_type"]["extract"](node))
+ else:
+ object.__setattr__(self, "text", elements["text"]["elmt_type"]["extract"](node))
+ elif elements.has_key("content"):
+ if name in ["#cdata-section", "#text"]:
+ if elements["content"]["elmt_type"]["type"] == SIMPLETYPE:
+ object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node.data, False))
+ else:
+ content = getattr(self, "content")
+ if elements["content"]["maxOccurs"] == "unbounded" or elements["content"]["maxOccurs"] > 1:
+ if first.get("content", True):
+ object.__setattr__(self, "content", [elements["content"]["elmt_type"]["extract"](node, None)])
+ first["content"] = False
+ else:
+ content.append(elements["content"]["elmt_type"]["extract"](node, content))
+ else:
+ object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node, content))
+ return loadXMLTreeMethod
+
+
+"""
+Method that generates the method for generating an xml text by following the
+attributes list defined
+"""
+def generateGenerateXMLText(factory, classinfos):
+ def generateXMLTextMethod(self, name, indent=0, extras={}, derived=False):
+ ind1, ind2 = getIndent(indent, name)
+ if not derived:
+ text = ind1 + u'<%s' % name
+ else:
+ text = u''
+
+ first = True
+
+ if not classinfos.has_key("base"):
+ extras.update(self.extraAttrs)
+ for attr, value in extras.iteritems():
+ if not first and not self.singleLineAttributes:
+ text += u'\n%s' % (ind2)
+ text += u' %s=%s' % (attr, quoteattr(value))
+ first = False
+ extras.clear()
+ for attr in classinfos["attributes"]:
+ if attr["use"] != "prohibited":
+ attr["attr_type"] = FindTypeInfos(factory, attr["attr_type"])
+ value = getattr(self, attr["name"], None)
+ if value != None:
+ computed_value = attr["attr_type"]["generate"](value)
+ else:
+ computed_value = None
+ if attr["use"] != "optional" or (value != None and \
+ computed_value != attr.get("default", attr["attr_type"]["generate"](attr["attr_type"]["initial"]()))):
+ if classinfos.has_key("base"):
+ extras[attr["name"]] = computed_value
+ else:
+ if not first and not self.singleLineAttributes:
+ text += u'\n%s' % (ind2)
+ text += ' %s=%s' % (attr["name"], quoteattr(computed_value))
+ first = False
+ if classinfos.has_key("base"):
+ first, new_text = classinfos["base"].generateXMLText(self, name, indent, extras, True)
+ text += new_text
+ else:
+ first = True
+ for element in classinfos["elements"]:
+ element["elmt_type"] = FindTypeInfos(factory, element["elmt_type"])
+ value = getattr(self, element["name"], None)
+ if element["minOccurs"] == 0 and element["maxOccurs"] == 1:
+ if value is not None:
+ if first:
+ text += u'>\n'
+ first = False
+ text += element["elmt_type"]["generate"](value, element["name"], indent + 1)
+ elif element["minOccurs"] == 1 and element["maxOccurs"] == 1:
+ if first:
+ text += u'>\n'
+ first = False
+ if element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
+ text += element["elmt_type"]["generate"](value)
+ else:
+ text += element["elmt_type"]["generate"](value, element["name"], indent + 1)
+ else:
+ if first and len(value) > 0:
+ text += u'>\n'
+ first = False
+ for item in value:
+ text += element["elmt_type"]["generate"](item, element["name"], indent + 1)
+ if not derived:
+ if first:
+ text += u'/>\n'
+ else:
+ text += ind1 + u'</%s>\n' % (name)
+ return text
+ else:
+ return first, text
+ return generateXMLTextMethod
+
+def gettypeinfos(name, facets):
+ if facets.has_key("enumeration") and facets["enumeration"][0] is not None:
+ return facets["enumeration"][0]
+ elif facets.has_key("maxInclusive"):
+ limits = {"max" : None, "min" : None}
+ if facets["maxInclusive"][0] is not None:
+ limits["max"] = facets["maxInclusive"][0]
+ elif facets["maxExclusive"][0] is not None:
+ limits["max"] = facets["maxExclusive"][0] - 1
+ if facets["minInclusive"][0] is not None:
+ limits["min"] = facets["minInclusive"][0]
+ elif facets["minExclusive"][0] is not None:
+ limits["min"] = facets["minExclusive"][0] + 1
+ if limits["max"] is not None or limits["min"] is not None:
+ return limits
+ return name
+
+def generateGetElementAttributes(factory, classinfos):
+ def getElementAttributes(self):
+ attr_list = []
+ if classinfos.has_key("base"):
+ attr_list.extend(classinfos["base"].getElementAttributes(self))
+ for attr in classinfos["attributes"]:
+ if attr["use"] != "prohibited":
+ attr_params = {"name" : attr["name"], "use" : attr["use"],
+ "type" : gettypeinfos(attr["attr_type"]["basename"], attr["attr_type"]["facets"]),
+ "value" : getattr(self, attr["name"], "")}
+ attr_list.append(attr_params)
+ return attr_list
+ return getElementAttributes
+
+def generateGetElementInfos(factory, classinfos):
+ attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
+ elements = dict([(element["name"], element) for element in classinfos["elements"]])
+
+ def getElementInfos(self, name, path=None, derived=False):
+ attr_type = "element"
+ value = None
+ use = "required"
+ children = []
+ if path is not None:
+ parts = path.split(".", 1)
+ if attributes.has_key(parts[0]):
+ if len(parts) != 0:
+ raise ValueError("Wrong path!")
+ attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"],
+ attributes[parts[0]]["attr_type"]["facets"])
+ value = getattr(self, parts[0], "")
+ elif elements.has_key(parts[0]):
+ if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
+ if len(parts) != 0:
+ raise ValueError("Wrong path!")
+ attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"],
+ elements[parts[0]]["elmt_type"]["facets"])
+ value = getattr(self, parts[0], "")
+ elif parts[0] == "content":
+ return self.content["value"].getElementInfos(self.content["name"], path)
+ else:
+ attr = getattr(self, parts[0], None)
+ if attr is None:
+ raise ValueError("Wrong path!")
+ if len(parts) == 1:
+ return attr.getElementInfos(parts[0])
+ else:
+ return attr.getElementInfos(parts[0], parts[1])
+ else:
+ raise ValueError("Wrong path!")
+ else:
+ if not derived:
+ children.extend(self.getElementAttributes())
+ if classinfos.has_key("base"):
+ children.extend(classinfos["base"].getElementInfos(self, name, derived=True)["children"])
+ for element_name, element in elements.items():
+ if element["minOccurs"] == 0:
+ use = "optional"
+ if element_name == "content" and element["type"] == CHOICE:
+ attr_type = [(choice["name"], None) for choice in element["choices"]]
+ if self.content is None:
+ value = ""
+ else:
+ value = self.content["name"]
+ if self.content["value"] is not None:
+ if self.content["name"] == "sequence":
+ choices_dict = dict([(choice["name"], choice) for choice in element["choices"]])
+ sequence_infos = choices_dict.get("sequence", None)
+ if sequence_infos is not None:
+ children.extend([item.getElementInfos(infos["name"]) for item, infos in zip(self.content["value"], sequence_infos["elements"])])
+ else:
+ children.extend(self.content["value"].getElementInfos(self.content["name"])["children"])
+ elif element["elmt_type"]["type"] == SIMPLETYPE:
+ children.append({"name": element_name, "require": element["minOccurs"] != 0,
+ "type": gettypeinfos(element["elmt_type"]["basename"],
+ element["elmt_type"]["facets"]),
+ "value": getattr(self, element_name, None)})
+ else:
+ instance = getattr(self, element_name, None)
+ if instance is None:
+ instance = element["elmt_type"]["initial"]()
+ children.append(instance.getElementInfos(element_name))
+ return {"name": name, "type": attr_type, "value": value, "use": use, "children": children}
+ return getElementInfos
+
+def generateSetElementValue(factory, classinfos):
+ attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
+ elements = dict([(element["name"], element) for element in classinfos["elements"]])
+
+ def setElementValue(self, path, value):
+ if path is not None:
+ parts = path.split(".", 1)
+ if attributes.has_key(parts[0]):
+ if len(parts) != 1:
+ raise ValueError("Wrong path!")
+ if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
+ setattr(self, parts[0], value)
+ else:
+ setattr(self, parts[0], attributes[parts[0]]["attr_type"]["extract"](value, False))
+ elif elements.has_key(parts[0]):
+ if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
+ if len(parts) != 1:
+ raise ValueError("Wrong path!")
+ if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
+ setattr(self, parts[0], value)
+ else:
+ setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False))
+ else:
+ instance = getattr(self, parts[0], None)
+ if instance is None and elements[parts[0]]["minOccurs"] == 0:
+ instance = elements[parts[0]]["elmt_type"]["initial"]()
+ setattr(self, parts[0], instance)
+ if instance != None:
+ if len(parts) > 1:
+ instance.setElementValue(parts[1], value)
+ else:
+ instance.setElementValue(None, value)
+ elif elements.has_key("content"):
+ if len(parts) > 0:
+ self.content["value"].setElementValue(path, value)
+ elif classinfos.has_key("base"):
+ classinfos["base"].setElementValue(self, path, value)
+ elif elements.has_key("content"):
+ if value == "":
+ if elements["content"]["minOccurs"] == 0:
+ self.setcontent(None)
+ else:
+ raise ValueError("\"content\" element is required!")
+ else:
+ self.setcontentbytype(value)
+ return setElementValue
+
+"""
+Methods that generates the different methods for setting and getting the attributes
+"""
+def generateInitMethod(factory, classinfos):
+ def initMethod(self):
+ self.extraAttrs = {}
+ if classinfos.has_key("base"):
+ classinfos["base"].__init__(self)
+ for attribute in classinfos["attributes"]:
+ attribute["attr_type"] = FindTypeInfos(factory, attribute["attr_type"])
+ if attribute["use"] == "required":
+ setattr(self, attribute["name"], attribute["attr_type"]["initial"]())
+ elif attribute["use"] == "optional":
+ if attribute.has_key("default"):
+ setattr(self, attribute["name"], attribute["attr_type"]["extract"](attribute["default"], False))
+ else:
+ setattr(self, attribute["name"], None)
+ for element in classinfos["elements"]:
+ setattr(self, element["name"], GetElementInitialValue(factory, element))
+ return initMethod
+
+def generateSetMethod(attr):
+ def setMethod(self, value):
+ setattr(self, attr, value)
+ return setMethod
+
+def generateGetMethod(attr):
+ def getMethod(self):
+ return getattr(self, attr, None)
+ return getMethod
+
+def generateAddMethod(attr, factory, infos):
+ def addMethod(self):
+ if infos["type"] == ATTRIBUTE:
+ infos["attr_type"] = FindTypeInfos(factory, infos["attr_type"])
+ initial = infos["attr_type"]["initial"]
+ extract = infos["attr_type"]["extract"]
+ elif infos["type"] == ELEMENT:
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ initial = infos["elmt_type"]["initial"]
+ extract = infos["elmt_type"]["extract"]
+ else:
+ raise ValueError("Invalid class attribute!")
+ if infos.has_key("default"):
+ setattr(self, attr, extract(infos["default"], False))
+ else:
+ setattr(self, attr, initial())
+ return addMethod
+
+def generateDeleteMethod(attr):
+ def deleteMethod(self):
+ setattr(self, attr, None)
+ return deleteMethod
+
+def generateAppendMethod(attr, maxOccurs, factory, infos):
+ def appendMethod(self, value):
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ attr_list = getattr(self, attr)
+ if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
+ if infos["elmt_type"]["check"](value):
+ attr_list.append(value)
+ else:
+ raise ValueError("\"%s\" value isn't valid!" % attr)
+ else:
+ raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
+ return appendMethod
+
+def generateInsertMethod(attr, maxOccurs, factory, infos):
+ def insertMethod(self, index, value):
+ infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
+ attr_list = getattr(self, attr)
+ if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
+ if infos["elmt_type"]["check"](value):
+ attr_list.insert(index, value)
+ else:
+ raise ValueError("\"%s\" value isn't valid!" % attr)
+ else:
+ raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
+ return insertMethod
+
+def generateGetChoicesMethod(choice_types):
+ def getChoicesMethod(self):
+ return [choice["name"] for choice in choice_types]
+ return getChoicesMethod
+
+def generateSetChoiceByTypeMethod(factory, choice_types):
+ choices = dict([(choice["name"], choice) for choice in choice_types])
+ def setChoiceMethod(self, type):
+ if not choices.has_key(type):
+ raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
+ choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
+ new_element = choices[type]["elmt_type"]["initial"]()
+ self.content = {"name": type, "value": new_element}
+ return new_element
+ return setChoiceMethod
+
+def generateAppendChoiceByTypeMethod(maxOccurs, factory, choice_types):
+ choices = dict([(choice["name"], choice) for choice in choice_types])
+ def appendChoiceMethod(self, type):
+ if not choices.has_key(type):
+ raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
+ choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
+ if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
+ new_element = choices[type]["elmt_type"]["initial"]()
+ self.content.append({"name": type, "value": new_element})
+ return new_element
+ else:
+ raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
+ return appendChoiceMethod
+
+def generateInsertChoiceByTypeMethod(maxOccurs, factory, choice_types):
+ choices = dict([(choice["name"], choice) for choice in choice_types])
+ def insertChoiceMethod(self, index, type):
+ if not choices.has_key(type):
+ raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
+ choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
+ if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
+ new_element = choices[type]["elmt_type"]["initial"]()
+ self.content.insert(index, {"name" : type, "value" : new_element})
+ return new_element
+ else:
+ raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
+ return insertChoiceMethod
+
+def generateRemoveMethod(attr, minOccurs):
+ def removeMethod(self, index):
+ attr_list = getattr(self, attr)
+ if len(attr_list) > minOccurs:
+ getattr(self, attr).pop(index)
+ else:
+ raise ValueError("There can't be less than %d values in \"%s\"!" % (minOccurs, attr))
+ return removeMethod
+
+def generateCountMethod(attr):
+ def countMethod(self):
+ return len(getattr(self, attr))
+ return countMethod
+
+"""
+This function generate the classes from a class factory
+"""
+def GenerateClasses(factory):
+ ComputedClasses = factory.CreateClasses()
+ if factory.FileName is not None and len(ComputedClasses) == 1:
+ globals().update(ComputedClasses[factory.FileName])
+ return ComputedClasses[factory.FileName]
+ else:
+ globals().update(ComputedClasses)
+ return ComputedClasses
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlclass/xsdschema.py Fri Sep 07 16:45:55 2012 +0200
@@ -0,0 +1,2538 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard.
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os, re
+import datetime
+from xml.dom import minidom
+from types import *
+
+from xmlclass import *
+
+def GenerateDictFacets(facets):
+ return dict([(name, (None, False)) for name in facets])
+
+def GenerateSimpleTypeXMLText(function):
+ def generateXMLTextMethod(value, name=None, indent=0):
+ text = ""
+ if name is not None:
+ ind1, ind2 = getIndent(indent, name)
+ text += ind1 + "<%s>" % name
+ text += function(value)
+ if name is not None:
+ text += "</%s>\n" % name
+ return text
+ return generateXMLTextMethod
+
+def GenerateFloatXMLText(extra_values=[]):
+ def generateXMLTextMethod(value, name=None, indent=0):
+ text = ""
+ if name is not None:
+ ind1, ind2 = getIndent(indent, name)
+ text += ind1 + "<%s>" % name
+ if value in extra_values or value % 1 != 0 or isinstance(value, IntType):
+ text += str(value)
+ else:
+ text += "%.0f" % value
+ if name is not None:
+ text += "</%s>\n" % name
+ return text
+ return generateXMLTextMethod
+
+DEFAULT_FACETS = GenerateDictFacets(["pattern", "whiteSpace", "enumeration"])
+NUMBER_FACETS = GenerateDictFacets(DEFAULT_FACETS.keys() + ["maxInclusive", "maxExclusive", "minInclusive", "minExclusive"])
+DECIMAL_FACETS = GenerateDictFacets(NUMBER_FACETS.keys() + ["totalDigits", "fractionDigits"])
+STRING_FACETS = GenerateDictFacets(DEFAULT_FACETS.keys() + ["length", "minLength", "maxLength"])
+
+ALL_FACETS = ["pattern", "whiteSpace", "enumeration", "maxInclusive",
+ "maxExclusive", "minInclusive", "minExclusive", "totalDigits",
+ "fractionDigits", "length", "minLength", "maxLength"]
+
+
+#-------------------------------------------------------------------------------
+# Structure reducing functions
+#-------------------------------------------------------------------------------
+
+
+# Documentation elements
+
+def ReduceAppInfo(factory, attributes, elements):
+ return {"type": "appinfo", "source": attributes.get("source", None),
+ "content": "\n".join(elements)}
+
+
+def ReduceDocumentation(factory, attributes, elements):
+ return {"type": "documentation", "source": attributes.get("source", None),
+ "language": attributes.get("lang", "any"), "content": "\n".join(elements)}
+
+
+def ReduceAnnotation(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ annotation = {"type": "annotation", "appinfo": [], "documentation": {}}
+ for child in children:
+ if child["type"] == "appinfo":
+ annotation["appinfo"].append((child["source"], child["content"]))
+ elif child["type"] == "documentation":
+ if child["source"] is not None:
+ text = "(source: %(source)s):\n%(content)s\n\n"%child
+ else:
+ text = child["content"] + "\n\n"
+ if not annotation["documentation"].has_key(child["language"]):
+ annotation["documentation"] = text
+ else:
+ annotation["documentation"] += text
+ return annotation
+
+# Simple type elements
+
+def GenerateFacetReducing(facetname, canbefixed):
+ def ReduceFacet(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ if attributes.has_key("value"):
+ facet = {"type": facetname, "value": attributes["value"], "doc": annotations}
+ if canbefixed:
+ facet["fixed"] = attributes.get("fixed", False)
+ return facet
+ raise ValueError("A value must be defined for the \"%s\" facet!" % facetname)
+ return ReduceFacet
+
+
+def ReduceList(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ list = {"type": "list", "itemType": attributes.get("itemType", None), "doc": annotations}
+
+ if len(children) > 0 and children[0]["type"] == SIMPLETYPE:
+ if list["itemType"] is None:
+ list["itemType"] = children[0]
+ else:
+ raise ValueError("Only one base type can be defined for restriction!")
+ if list["itemType"] is None:
+ raise ValueError("No base type has been defined for list!")
+ return list
+
+
+def ReduceUnion(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ union = {"type": "union", "memberTypes": attributes.get("memberTypes", []), "doc": annotations}
+
+ for child in children:
+ if child["type"] == SIMPLETYPE:
+ union["memberTypes"].appendchild
+ if len(union["memberTypes"]) == 0:
+ raise ValueError("No base type has been defined for union!")
+ return union
+
+
+def CreateSimpleType(factory, attributes, typeinfos):
+ # Initialize type informations
+ facets = {}
+ simpleType = {"type": SIMPLETYPE, "final": attributes.get("final", [])}
+ if attributes.has_key("name"):
+ simpleType["name"] = attributes["name"]
+
+ if typeinfos["type"] in ["restriction", "extension"]:
+ # Search for base type definition
+ if isinstance(typeinfos["base"], (StringType, UnicodeType)):
+ basetypeinfos = factory.FindSchemaElement(typeinfos["base"], SIMPLETYPE)
+ if basetypeinfos is None:
+ raise "\"%s\" isn't defined!" % typeinfos["base"]
+ else:
+ basetypeinfos = typeinfos["base"]
+
+ # Check that base type is a simple type
+ if basetypeinfos["type"] != SIMPLETYPE:
+ raise ValueError("Base type given isn't a simpleType!")
+
+ simpleType["basename"] = basetypeinfos["basename"]
+
+ # Check that derivation is allowed
+ if basetypeinfos.has_key("final"):
+ if "#all" in basetypeinfos["final"]:
+ raise ValueError("Base type can't be derivated!")
+ if "restriction" in basetypeinfos["final"] and typeinfos["type"] == "restriction":
+ raise ValueError("Base type can't be derivated by restriction!")
+
+ # Extract simple type facets
+ for facet in typeinfos.get("facets", []):
+ facettype = facet["type"]
+ if not basetypeinfos["facets"].has_key(facettype):
+ raise ValueError("\"%s\" facet can't be defined for \"%s\" type!" % (facettype, type))
+ elif basetypeinfos["facets"][facettype][1]:
+ raise ValueError("\"%s\" facet is fixed on base type!" % facettype)
+ value = facet["value"]
+ basevalue = basetypeinfos["facets"][facettype][0]
+ if facettype in ["enumeration", "pattern"]:
+ value = basetypeinfos["extract"](value, False)
+ if len(facets) == 0:
+ facets[facettype] = ([value], False)
+ continue
+ elif facets.keys() == [facettype]:
+ facets[facettype][0].append(value)
+ continue
+ else:
+ raise ValueError("\"%s\" facet can't be defined with another facet type!" % facettype)
+ elif facets.has_key("enumeration"):
+ raise ValueError("\"enumeration\" facet can't be defined with another facet type!")
+ elif facets.has_key("pattern"):
+ raise ValueError("\"pattern\" facet can't be defined with another facet type!")
+ elif facets.has_key(facettype):
+ raise ValueError("\"%s\" facet can't be defined two times!" % facettype)
+ elif facettype == "length":
+ if facets.has_key("minLength"):
+ raise ValueError("\"length\" and \"minLength\" facets can't be defined at the same time!")
+ if facets.has_key("maxLength"):
+ raise ValueError("\"length\" and \"maxLength\" facets can't be defined at the same time!")
+ try:
+ value = int(value)
+ except:
+ raise ValueError("\"length\" must be an integer!")
+ if value < 0:
+ raise ValueError("\"length\" can't be negative!")
+ elif basevalue is not None and basevalue != value:
+ raise ValueError("\"length\" can't be different from \"length\" defined in base type!")
+ elif facettype == "minLength":
+ if facets.has_key("length"):
+ raise ValueError("\"length\" and \"minLength\" facets can't be defined at the same time!")
+ try:
+ value = int(value)
+ except:
+ raise ValueError("\"minLength\" must be an integer!")
+ if value < 0:
+ raise ValueError("\"minLength\" can't be negative!")
+ elif facets.has_key("maxLength") and value > facets["maxLength"]:
+ raise ValueError("\"minLength\" must be lesser than or equal to \"maxLength\"!")
+ elif basevalue is not None and basevalue < value:
+ raise ValueError("\"minLength\" can't be lesser than \"minLength\" defined in base type!")
+ elif facettype == "maxLength":
+ if facets.has_key("length"):
+ raise ValueError("\"length\" and \"maxLength\" facets can't be defined at the same time!")
+ try:
+ value = int(value)
+ except:
+ raise ValueError("\"maxLength\" must be an integer!")
+ if value < 0:
+ raise ValueError("\"maxLength\" can't be negative!")
+ elif facets.has_key("minLength") and value < facets["minLength"]:
+ raise ValueError("\"minLength\" must be lesser than or equal to \"maxLength\"!")
+ elif basevalue is not None and basevalue > value:
+ raise ValueError("\"maxLength\" can't be greater than \"maxLength\" defined in base type!")
+ elif facettype == "minInclusive":
+ if facets.has_key("minExclusive"):
+ raise ValueError("\"minExclusive\" and \"minInclusive\" facets can't be defined at the same time!")
+ value = basetypeinfos["extract"](facet["value"], False)
+ if facets.has_key("maxInclusive") and value > facets["maxInclusive"][0]:
+ raise ValueError("\"minInclusive\" must be lesser than or equal to \"maxInclusive\"!")
+ elif facets.has_key("maxExclusive") and value >= facets["maxExclusive"][0]:
+ raise ValueError("\"minInclusive\" must be lesser than \"maxExclusive\"!")
+ elif facettype == "minExclusive":
+ if facets.has_key("minInclusive"):
+ raise ValueError("\"minExclusive\" and \"minInclusive\" facets can't be defined at the same time!")
+ value = basetypeinfos["extract"](facet["value"], False)
+ if facets.has_key("maxInclusive") and value >= facets["maxInclusive"][0]:
+ raise ValueError("\"minExclusive\" must be lesser than \"maxInclusive\"!")
+ elif facets.has_key("maxExclusive") and value >= facets["maxExclusive"][0]:
+ raise ValueError("\"minExclusive\" must be lesser than \"maxExclusive\"!")
+ elif facettype == "maxInclusive":
+ if facets.has_key("maxExclusive"):
+ raise ValueError("\"maxExclusive\" and \"maxInclusive\" facets can't be defined at the same time!")
+ value = basetypeinfos["extract"](facet["value"], False)
+ if facets.has_key("minInclusive") and value < facets["minInclusive"][0]:
+ raise ValueError("\"minInclusive\" must be lesser than or equal to \"maxInclusive\"!")
+ elif facets.has_key("minExclusive") and value <= facets["minExclusive"][0]:
+ raise ValueError("\"minExclusive\" must be lesser than \"maxInclusive\"!")
+ elif facettype == "maxExclusive":
+ if facets.has_key("maxInclusive"):
+ raise ValueError("\"maxExclusive\" and \"maxInclusive\" facets can't be defined at the same time!")
+ value = basetypeinfos["extract"](facet["value"], False)
+ if facets.has_key("minInclusive") and value <= facets["minInclusive"][0]:
+ raise ValueError("\"minInclusive\" must be lesser than \"maxExclusive\"!")
+ elif facets.has_key("minExclusive") and value <= facets["minExclusive"][0]:
+ raise ValueError("\"minExclusive\" must be lesser than \"maxExclusive\"!")
+ elif facettype == "whiteSpace":
+ if basevalue == "collapse" and value in ["preserve", "replace"] or basevalue == "replace" and value == "preserve":
+ raise ValueError("\"whiteSpace\" is incompatible with \"whiteSpace\" defined in base type!")
+ elif facettype == "totalDigits":
+ if facets.has_key("fractionDigits") and value <= facets["fractionDigits"][0]:
+ raise ValueError("\"fractionDigits\" must be lesser than or equal to \"totalDigits\"!")
+ elif basevalue is not None and value > basevalue:
+ raise ValueError("\"totalDigits\" can't be greater than \"totalDigits\" defined in base type!")
+ elif facettype == "fractionDigits":
+ if facets.has_key("totalDigits") and value <= facets["totalDigits"][0]:
+ raise ValueError("\"fractionDigits\" must be lesser than or equal to \"totalDigits\"!")
+ elif basevalue is not None and value > basevalue:
+ raise ValueError("\"totalDigits\" can't be greater than \"totalDigits\" defined in base type!")
+ facets[facettype] = (value, facet.get("fixed", False))
+
+ # Report not redefined facet from base type to new created type
+ for facettype, facetvalue in basetypeinfos["facets"].items():
+ if not facets.has_key(facettype):
+ facets[facettype] = facetvalue
+
+ # Generate extract value for new created type
+ def ExtractSimpleTypeValue(attr, extract=True):
+ value = basetypeinfos["extract"](attr, extract)
+ for facetname, (facetvalue, facetfixed) in facets.items():
+ if facetvalue is not None:
+ if facetname == "enumeration" and value not in facetvalue:
+ raise ValueError("\"%s\" not in enumerated values" % value)
+ elif facetname == "length" and len(value) != facetvalue:
+ raise ValueError("value must have a length of %d" % facetvalue)
+ elif facetname == "minLength" and len(value) < facetvalue:
+ raise ValueError("value must have a length of %d at least" % facetvalue)
+ elif facetname == "maxLength" and len(value) > facetvalue:
+ raise ValueError("value must have a length of %d at most" % facetvalue)
+ elif facetname == "minInclusive" and value < facetvalue:
+ raise ValueError("value must be greater than or equal to %s" % str(facetvalue))
+ elif facetname == "minExclusive" and value <= facetvalue:
+ raise ValueError("value must be greater than %s" % str(facetvalue))
+ elif facetname == "maxInclusive" and value > facetvalue:
+ raise ValueError("value must be lesser than or equal to %s" % str(facetvalue))
+ elif facetname == "maxExclusive" and value >= facetvalue:
+ raise ValueError("value must be lesser than %s" % str(facetvalue))
+ elif facetname == "pattern":
+ model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue)))
+ result = model.match(value)
+ if result is None:
+ if len(facetvalue) > 1:
+ raise ValueError("value doesn't follow any of the patterns %s" % ",".join(facetvalue))
+ else:
+ raise ValueError("value doesn't follow the pattern %s" % facetvalue[0])
+ elif facetname == "whiteSpace":
+ if facetvalue == "replace":
+ value = GetNormalizedString(value, False)
+ elif facetvalue == "collapse":
+ value = GetToken(value, False)
+ return value
+
+ def CheckSimpleTypeValue(value):
+ for facetname, (facetvalue, facetfixed) in facets.items():
+ if facetvalue is not None:
+ if facetname == "enumeration" and value not in facetvalue:
+ return False
+ elif facetname == "length" and len(value) != facetvalue:
+ return False
+ elif facetname == "minLength" and len(value) < facetvalue:
+ return False
+ elif facetname == "maxLength" and len(value) > facetvalue:
+ return False
+ elif facetname == "minInclusive" and value < facetvalue:
+ return False
+ elif facetname == "minExclusive" and value <= facetvalue:
+ return False
+ elif facetname == "maxInclusive" and value > facetvalue:
+ return False
+ elif facetname == "maxExclusive" and value >= facetvalue:
+ return False
+ elif facetname == "pattern":
+ model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue)))
+ result = model.match(value)
+ if result is None:
+ if len(facetvalue) > 1:
+ raise ValueError("value doesn't follow any of the patterns %s" % ",".join(facetvalue))
+ else:
+ raise ValueError("value doesn't follow the pattern %s" % facetvalue[0])
+ return True
+
+ def SimpleTypeInitialValue():
+ for facetname, (facetvalue, facetfixed) in facets.items():
+ if facetvalue is not None:
+ if facetname == "enumeration":
+ return facetvalue[0]
+ elif facetname == "length":
+ return " "*facetvalue
+ elif facetname == "minLength":
+ return " "*minLength
+ elif facetname == "minInclusive" and facetvalue > 0:
+ return facetvalue
+ elif facetname == "minExclusive" and facetvalue >= 0:
+ return facetvalue + 1
+ elif facetname == "maxInclusive" and facetvalue < 0:
+ return facetvalue
+ elif facetname == "maxExclusive" and facetvalue <= 0:
+ return facetvalue - 1
+ return basetypeinfos["initial"]()
+
+ GenerateSimpleType = basetypeinfos["generate"]
+
+ elif typeinfos["type"] == "list":
+ # Search for item type definition
+ if isinstance(typeinfos["itemType"], (StringType, UnicodeType)):
+ itemtypeinfos = factory.FindSchemaElement(typeinfos["itemType"], SIMPLETYPE)
+ if itemtypeinfos is None:
+ raise "\"%s\" isn't defined!" % typeinfos["itemType"]
+ else:
+ itemtypeinfos = typeinfos["itemType"]
+
+ # Check that item type is a simple type
+ if itemtypeinfos["type"] != SIMPLETYPE:
+ raise ValueError, "Item type given isn't a simpleType!"
+
+ simpleType["basename"] = "list"
+
+ # Check that derivation is allowed
+ if itemtypeinfos.has_key("final"):
+ if itemtypeinfos["final"].has_key("#all"):
+ raise ValueError("Item type can't be derivated!")
+ if itemtypeinfos["final"].has_key("list"):
+ raise ValueError("Item type can't be derivated by list!")
+
+ # Generate extract value for new created type
+ def ExtractSimpleTypeValue(attr, extract = True):
+ values = []
+ for value in GetToken(attr, extract).split(" "):
+ values.append(itemtypeinfos["extract"](value, False))
+ return values
+
+ def CheckSimpleTypeValue(value):
+ for item in value:
+ result = itemtypeinfos["check"](item)
+ if not result:
+ return result
+ return True
+
+ SimpleTypeInitialValue = lambda: []
+
+ GenerateSimpleType = GenerateSimpleTypeXMLText(lambda x: " ".join(map(itemtypeinfos["generate"], x)))
+
+ facets = GenerateDictFacets(["length", "maxLength", "minLength", "enumeration", "pattern"])
+ facets["whiteSpace"] = ("collapse", False)
+
+ elif typeinfos["type"] == "union":
+ # Search for member types definition
+ membertypesinfos = []
+ for membertype in typeinfos["memberTypes"]:
+ if isinstance(membertype, (StringType, UnicodeType)):
+ infos = factory.FindSchemaElement(membertype, SIMPLETYPE)
+ if infos is None:
+ raise ValueError("\"%s\" isn't defined!" % membertype)
+ else:
+ infos = membertype
+
+ # Check that member type is a simple type
+ if infos["type"] != SIMPLETYPE:
+ raise ValueError("Member type given isn't a simpleType!")
+
+ # Check that derivation is allowed
+ if infos.has_key("final"):
+ if infos["final"].has_key("#all"):
+ raise ValueError("Item type can't be derivated!")
+ if infos["final"].has_key("union"):
+ raise ValueError("Member type can't be derivated by union!")
+
+ membertypesinfos.append(infos)
+
+ simpleType["basename"] = "union"
+
+ # Generate extract value for new created type
+ def ExtractSimpleTypeValue(attr, extract = True):
+ if extract:
+ value = GetAttributeValue(attr)
+ else:
+ value = attr
+ for infos in membertypesinfos:
+ try:
+ return infos["extract"](attr, False)
+ except:
+ pass
+ raise ValueError("\"%s\" isn't valid for type defined for union!")
+
+ def CheckSimpleTypeValue(value):
+ for infos in membertypesinfos:
+ result = infos["check"](value)
+ if result:
+ return result
+ return False
+
+ SimpleTypeInitialValue = membertypesinfos[0]["initial"]
+
+ def GenerateSimpleTypeFunction(value):
+ if isinstance(value, BooleanType):
+ return {True: "true", False: "false"}[value]
+ else:
+ return str(value)
+ GenerateSimpleType = GenerateSimpleTypeXMLText(GenerateSimpleTypeFunction)
+
+ facets = GenerateDictFacets(["pattern", "enumeration"])
+
+ simpleType["facets"] = facets
+ simpleType["extract"] = ExtractSimpleTypeValue
+ simpleType["initial"] = SimpleTypeInitialValue
+ simpleType["check"] = CheckSimpleTypeValue
+ simpleType["generate"] = GenerateSimpleType
+ return simpleType
+
+def ReduceSimpleType(factory, attributes, elements):
+ # Reduce all the simple type children
+ annotations, children = factory.ReduceElements(elements)
+
+ simpleType = CreateSimpleType(factory, attributes, children[0])
+ simpleType["doc"] = annotations
+
+ return simpleType
+
+# Complex type
+
+def ExtractAttributes(factory, elements, base=None):
+ attrs = []
+ attrnames = {}
+ if base is not None:
+ basetypeinfos = factory.FindSchemaElement(base)
+ if not isinstance(basetypeinfos, (UnicodeType, StringType)) and basetypeinfos["type"] == COMPLEXTYPE:
+ attrnames = dict(map(lambda x:(x["name"], True), basetypeinfos["attributes"]))
+
+ for element in elements:
+ if element["type"] == ATTRIBUTE:
+ if attrnames.get(element["name"], False):
+ raise ValueError("\"%s\" attribute has been defined two times!" % element["name"])
+ else:
+ attrnames[element["name"]] = True
+ attrs.append(element)
+ elif element["type"] == "attributeGroup":
+ attrgroup = factory.FindSchemaElement(element["ref"], ATTRIBUTESGROUP)
+ for attr in attrgroup["attributes"]:
+ if attrnames.get(attr["name"], False):
+ raise ValueError("\"%s\" attribute has been defined two times!" % attr["name"])
+ else:
+ attrnames[attr["name"]] = True
+ attrs.append(attr)
+ elif element["type"] == "anyAttribute":
+ raise ValueError("\"anyAttribute\" element isn't supported yet!")
+ return attrs
+
+
+def ReduceRestriction(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ restriction = {"type": "restriction", "base": attributes.get("base", None), "facets": [], "doc": annotations}
+ if len(children) > 0 and children[0]["type"] == SIMPLETYPE:
+ if restriction["base"] is None:
+ restriction["base"] = children.pop(0)
+ else:
+ raise ValueError("Only one base type can be defined for restriction!")
+ if restriction["base"] is None:
+ raise ValueError("No base type has been defined for restriction!")
+
+ while len(children) > 0 and children[0]["type"] in ALL_FACETS:
+ restriction["facets"].append(children.pop(0))
+ restriction["attributes"] = ExtractAttributes(factory, children, restriction["base"])
+ return restriction
+
+
+def ReduceExtension(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ if not attributes.has_key("base"):
+ raise ValueError("No base type has been defined for extension!")
+ extension = {"type": "extension", "attributes": [], "elements": [], "base": attributes["base"], "doc": annotations}
+ if len(children) > 0:
+ if children[0]["type"] in ["group", "all", CHOICE, "sequence"]:
+ group = children.pop(0)
+ if group["type"] in ["all", "sequence"]:
+ extension["elements"] = group["elements"]
+ extension["order"] = group["order"]
+ elif group["type"] == CHOICE:
+ content = group.copy()
+ content["name"] = "content"
+ extension["elements"].append(content)
+ elif group["type"] == "group":
+ elmtgroup = factory.FindSchemaElement(child["ref"], ELEMENTSGROUP)
+ if elmtgroup.has_key("elements"):
+ extension["elements"] = elmtgroup["elements"]
+ extension["order"] = elmtgroup["order"]
+ else:
+ content = elmtgroup.copy()
+ content["name"] = "content"
+ extension["elements"].append(content)
+ extension["attributes"] = ExtractAttributes(factory, children)
+ return extension
+
+
+def ReduceSimpleContent(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ simpleContent = children[0].copy()
+
+ basetypeinfos = factory.FindSchemaElement(simpleContent["base"])
+ if basetypeinfos["type"] == SIMPLETYPE:
+ contenttypeinfos = simpleContent.copy()
+ simpleContent.pop("base")
+ elif basetypeinfos["type"] == COMPLEXTYPE and \
+ len(basetypeinfos["elements"]) == 1 and \
+ basetypeinfos["elements"][0]["name"] == "content" and \
+ basetypeinfos["elements"][0].has_key("elmt_type") and \
+ basetypeinfos["elements"][0]["elmt_type"]["type"] == SIMPLETYPE:
+ contenttypeinfos = simpleContent.copy()
+ contenttypeinfos["base"] = basetypeinfos["elements"][0]["elmt_type"]
+ else:
+ raise ValueError("No compatible base type defined for simpleContent!")
+ contenttypeinfos = CreateSimpleType(factory, attributes, contenttypeinfos)
+
+ simpleContent["elements"] = [{"name": "content", "type": ELEMENT,
+ "elmt_type": contenttypeinfos, "doc": annotations,
+ "minOccurs": 1, "maxOccurs": 1}]
+ simpleContent["type"] = "simpleContent"
+ return simpleContent
+
+
+def ReduceComplexContent(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ complexContent = children[0].copy()
+ complexContent["type"] = "complexContent"
+ return complexContent
+
+
+def ReduceComplexType(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ if len(children) > 0:
+ if children[0]["type"] in ["simpleContent", "complexContent"]:
+ complexType = children[0].copy()
+ complexType.update(attributes)
+ complexType["type"] = COMPLEXTYPE
+ return complexType
+ elif children[0]["type"] in ["group", "all", CHOICE, "sequence"]:
+ complexType = {"type": COMPLEXTYPE, "elements": [], "order": True, "doc": annotations}
+ complexType.update(attributes)
+ group = children.pop(0)
+ if group["type"] in ["all", "sequence"]:
+ choice_number = 0
+ for element in group["elements"]:
+ if element["type"] == CHOICE:
+ choice_number += 1
+ if (group["minOccurs"] == 0 or group["maxOccurs"] != 1) and len(group["elements"]) > 1 or choice_number > 1:
+ content = {"type": CHOICE, "name": "content", "choices": [group], "minOccurs": 1, "maxOccurs": 1}
+ complexType["elements"].append(content)
+ else:
+ if len(group["elements"]) == 1:
+ if group["minOccurs"] == 0:
+ group["elements"][0]["minOccurs"] = group["minOccurs"]
+ if group["maxOccurs"] != 1:
+ group["elements"][0]["maxOccurs"] = group["maxOccurs"]
+ for element in group["elements"]:
+ if element["type"] == CHOICE:
+ element["name"] = "content"
+ complexType["elements"] = group["elements"]
+ complexType["order"] = group["order"]
+ elif group["type"] == CHOICE:
+ content = group.copy()
+ content["name"] = "content"
+ complexType["elements"].append(content)
+ elif group["type"] == "group":
+ elmtgroup = factory.FindSchemaElement(child["ref"], ELEMENTSGROUP)
+ if elmtgroup.has_key("elements"):
+ complexType["elements"] = elmtgroup["elements"]
+ complexType["order"] = elmtgroup["order"]
+ else:
+ content = elmtgroup.copy()
+ content["name"] = "content"
+ complexType["elements"].append(content)
+ else:
+ complexType = {"elements": [], "order": True, "doc": annotations}
+ complexType.update(attributes)
+ complexType["type"] = COMPLEXTYPE
+ complexType["attributes"] = ExtractAttributes(factory, children)
+ return complexType
+ else:
+ raise ValueError("\"ComplexType\" can't be empty!")
+
+
+# Attribute elements
+
+def ReduceAnyAttribute(factory, attributes, elements):
+ return {"type" : "anyAttribute"}
+
+
+def ReduceAttribute(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ if attributes.has_key("default"):
+ if attributes.has_key("fixed"):
+ raise ValueError("\"default\" and \"fixed\" can't be defined at the same time!")
+ elif attributes.get("use", "optional") != "optional":
+ raise ValueError("if \"default\" present, \"use\" can only have the value \"optional\"!")
+
+ attribute = {"type": ATTRIBUTE, "attr_type": attributes.get("type", None), "doc": annotations}
+ if len(children) > 0:
+ if attribute["attr_type"] is None:
+ attribute["attr_type"] = children[0]
+ else:
+ raise ValueError("Only one type can be defined for attribute!")
+
+ if attributes.has_key("ref"):
+ if attributes.has_key("name"):
+ raise ValueError("\"ref\" and \"name\" can't be defined at the same time!")
+ elif attributes.has_key("form"):
+ raise ValueError("\"ref\" and \"form\" can't be defined at the same time!")
+ elif attribute["attr_type"] is not None:
+ raise ValueError("if \"ref\" is present, no type can be defined!")
+ elif attribute["attr_type"] is None:
+ raise ValueError("No type has been defined for attribute \"%s\"!" % attributes["name"])
+
+ if attributes.has_key("type"):
+ tmp_attrs = attributes.copy()
+ tmp_attrs.pop("type")
+ attribute.update(tmp_attrs)
+ else:
+ attribute.update(attributes)
+ return attribute
+
+
+def ReduceAttributeGroup(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ if attributes.has_key("ref"):
+ return {"type": "attributeGroup", "ref": attributes["ref"], "doc": annotations}
+ else:
+ return {"type": ATTRIBUTESGROUP, "attributes": ExtractAttributes(factory, children), "doc": annotations}
+
+
+# Elements groups
+
+def ReduceAny(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ any = {"type": ANY, "doc": annotations}
+ any.update(attributes)
+ return any
+
+def ReduceElement(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ types = []
+ constraints = []
+ for child in children:
+ if child["type"] == CONSTRAINT:
+ constraints.append(child)
+ else:
+ types.append(child)
+
+ if attributes.has_key("default") and attributes.has_key("fixed"):
+ raise ValueError("\"default\" and \"fixed\" can't be defined at the same time!")
+
+ if attributes.has_key("ref"):
+ for attr in ["name", "default", "fixed", "form", "block", "type"]:
+ if attributes.has_key(attr):
+ raise ValueError("\"ref\" and \"%s\" can't be defined at the same time!" % attr)
+ if attributes.has_key("nillable"):
+ raise ValueError("\"ref\" and \"nillable\" can't be defined at the same time!")
+ if len(types) > 0:
+ raise ValueError("No type and no constraints can be defined where \"ref\" is defined!")
+
+ infos = factory.FindSchemaElement(attributes["ref"], ELEMENT)
+ if infos is not None:
+ element = infos.copy()
+ element["constraints"] = constraints
+ element["minOccurs"] = attributes["minOccurs"]
+ element["maxOccurs"] = attributes["maxOccurs"]
+ return element
+ else:
+ raise ValueError("\"%s\" base type isn't defined or circular referenced!" % name)
+
+ elif attributes.has_key("name"):
+ element = {"type": ELEMENT, "elmt_type": attributes.get("type", None), "constraints": constraints, "doc": annotations}
+ if len(types) > 0:
+ if element["elmt_type"] is None:
+ element["elmt_type"] = types[0]
+ else:
+ raise ValueError("Only one type can be defined for attribute!")
+ elif element["elmt_type"] is None:
+ element["elmt_type"] = "tag"
+ element["type"] = TAG
+
+ if attributes.has_key("type"):
+ tmp_attrs = attributes.copy()
+ tmp_attrs.pop("type")
+ element.update(tmp_attrs)
+ else:
+ element.update(attributes)
+ return element
+ else:
+ raise ValueError("\"Element\" must have at least a \"ref\" or a \"name\" defined!")
+
+def ReduceAll(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ for child in children:
+ if children["maxOccurs"] == "unbounded" or children["maxOccurs"] > 1:
+ raise ValueError("\"all\" item can't have \"maxOccurs\" attribute greater than 1!")
+
+ return {"type": "all", "elements": children, "minOccurs": attributes["minOccurs"],
+ "maxOccurs": attributes["maxOccurs"], "order": False, "doc": annotations}
+
+
+def ReduceChoice(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ choices = []
+ for child in children:
+ if child["type"] in [ELEMENT, ANY, TAG]:
+ choices.append(child)
+ elif child["type"] == "sequence":
+ child["minOccurs"] = child["maxOccurs"] = 1
+ choices.append(child)
+ #raise ValueError("\"sequence\" in \"choice\" is not supported. Create instead a new complex type!")
+ elif child["type"] == CHOICE:
+ choices.extend(child["choices"])
+ elif child["type"] == "group":
+ elmtgroup = factory.FindSchemaElement(child["ref"], ELEMENTSGROUP)
+ if not elmtgroup.has_key("choices"):
+ raise ValueError("Only group composed of \"choice\" can be referenced in \"choice\" element!")
+ choices_tmp = []
+ for choice in elmtgroup["choices"]:
+ if not isinstance(choice["elmt_type"], (UnicodeType, StringType)) and choice["elmt_type"]["type"] == COMPLEXTYPE:
+ elmt_type = "%s_%s" % (elmtgroup["name"], choice["name"])
+ if factory.TargetNamespace is not None:
+ elmt_type = "%s:%s" % (factory.TargetNamespace, elmt_type)
+ new_choice = choice.copy()
+ new_choice["elmt_type"] = elmt_type
+ choices_tmp.append(new_choice)
+ else:
+ choices_tmp.append(choice)
+ choices.extend(choices_tmp)
+
+ for choice in choices:
+ attributes["minOccurs"] = min(attributes["minOccurs"], choice["minOccurs"])
+ choice["minOccurs"] = 1
+
+ return {"type": CHOICE, "choices": choices, "minOccurs": attributes["minOccurs"],
+ "maxOccurs": attributes["maxOccurs"], "doc": annotations}
+
+
+def ReduceSequence(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ sequence = []
+ for child in children:
+ if child["type"] in [ELEMENT, ANY, TAG, CHOICE]:
+ sequence.append(child)
+ elif child["type"] == "sequence":
+ sequence.extend(child["elements"])
+ elif child["type"] == "group":
+ elmtgroup = factory.FindSchemaElement(child["ref"], ELEMENTSGROUP)
+ if not elmtgroup.has_key("elements") or not elmtgroup["order"]:
+ raise ValueError("Only group composed of \"sequence\" can be referenced in \"sequence\" element!")
+ elements_tmp = []
+ for element in elmtgroup["elements"]:
+ if not isinstance(element["elmt_type"], (UnicodeType, StringType)) and element["elmt_type"]["type"] == COMPLEXTYPE:
+ elmt_type = "%s_%s"%(elmtgroup["name"], element["name"])
+ if factory.TargetNamespace is not None:
+ elmt_type = "%s:%s" % (factory.TargetNamespace, elmt_type)
+ new_element = element.copy()
+ new_element["elmt_type"] = elmt_type
+ elements_tmp.append(new_element)
+ else:
+ elements_tmp.append(element)
+ sequence.extend(elements_tmp)
+
+ return {"type": "sequence", "elements": sequence, "minOccurs": attributes["minOccurs"],
+ "maxOccurs": attributes["maxOccurs"], "order": True, "doc": annotations}
+
+
+def ReduceGroup(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ if attributes.has_key("ref"):
+ return {"type": "group", "ref": attributes["ref"], "doc": annotations}
+ else:
+ element = children[0]
+ group = {"type": ELEMENTSGROUP, "doc": annotations}
+ if element["type"] == CHOICE:
+ group["choices"] = element["choices"]
+ else:
+ group.update({"elements": element["elements"], "order": group["order"]})
+ group.update(attributes)
+ return group
+
+# Constraint elements
+
+def ReduceUnique(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ unique = {"type": CONSTRAINT, "const_type": "unique", "selector": children[0], "fields": children[1:]}
+ unique.update(attributes)
+ return unique
+
+def ReduceKey(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ key = {"type": CONSTRAINT, "const_type": "key", "selector": children[0], "fields": children[1:]}
+ key.update(attributes)
+ return key
+
+def ReduceKeyRef(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ keyref = {"type": CONSTRAINT, "const_type": "keyref", "selector": children[0], "fields": children[1:]}
+ keyref.update(attributes)
+ return keyref
+
+def ReduceSelector(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ selector = {"type": CONSTRAINT, "const_type": "selector"}
+ selector.update(attributes)
+ return selector
+
+def ReduceField(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ field = {"type": CONSTRAINT, "const_type": "field"}
+ field.update(attributes)
+ return field
+
+
+# Inclusion elements
+
+def ReduceImport(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ raise ValueError("\"import\" element isn't supported yet!")
+
+def ReduceInclude(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+
+ if factory.FileName is None:
+ raise ValueError("Include in XSD string not yet supported")
+ filepath = attributes["schemaLocation"]
+ if filepath is not None and not os.path.exists(filepath):
+ filepath = os.path.join(factory.BaseFolder, filepath)
+ if not os.path.exists(filepath):
+ raise ValueError("No file '%s' found for include" % attributes["schemaLocation"])
+ xsdfile = open(filepath, 'r')
+ include_factory = XSDClassFactory(minidom.parse(xsdfile), filepath)
+ xsdfile.close()
+ include_factory.CreateClasses()
+
+ if factory.TargetNamespace == include_factory.TargetNamespace:
+ factory.Namespaces[factory.TargetNamespace].update(include_factory.Namespaces[include_factory.TargetNamespace])
+ else:
+ factory.Namespaces[include_factory.TargetNamespace] = include_factory.Namespaces[include_factory.TargetNamespace]
+ factory.ComputedClasses.update(include_factory.ComputedClasses)
+ return None
+
+def ReduceRedefine(factory, attributes, elements):
+ annotations, children = factory.ReduceElements(elements)
+ raise ValueError("\"redefine\" element isn't supported yet!")
+
+
+# Schema element
+
+def ReduceSchema(factory, attributes, elements):
+ factory.AttributeFormDefault = attributes["attributeFormDefault"]
+ factory.ElementFormDefault = attributes["elementFormDefault"]
+ factory.BlockDefault = attributes["blockDefault"]
+ factory.FinalDefault = attributes["finalDefault"]
+
+ if attributes.has_key("targetNamespace"):
+ factory.TargetNamespace = factory.DefinedNamespaces.get(attributes["targetNamespace"], None)
+ factory.Namespaces[factory.TargetNamespace] = {}
+
+ annotations, children = factory.ReduceElements(elements, True)
+
+ for child in children:
+ if child.has_key("name"):
+ infos = factory.GetQualifiedNameInfos(child["name"], factory.TargetNamespace, True)
+ if infos is None:
+ factory.Namespaces[factory.TargetNamespace][child["name"]] = child
+ elif not CompareSchema(infos, child):
+ raise ValueError("\"%s\" is defined twice in targetNamespace!" % child["name"])
+
+def CompareSchema(schema, reference):
+ if isinstance(schema, ListType):
+ if not isinstance(reference, ListType) or len(schema) != len(reference):
+ return False
+ for i, value in enumerate(schema):
+ result = CompareSchema(value, reference[i])
+ if not result:
+ return result
+ return True
+ elif isinstance(schema, DictType):
+ if not isinstance(reference, DictType) or len(schema) != len(reference):
+ return False
+ for name, value in schema.items():
+ ref_value = reference.get(name, None)
+ if ref_value is None and value != None:
+ return False
+ result = CompareSchema(value, ref_value)
+ if not result:
+ return result
+ return True
+ elif isinstance(schema, FunctionType):
+ if not isinstance(reference, FunctionType) or schema.__name__ != reference.__name__:
+ return False
+ else:
+ return True
+ return schema == reference
+
+#-------------------------------------------------------------------------------
+# Base class for XSD schema extraction
+#-------------------------------------------------------------------------------
+
+
+class XSDClassFactory(ClassFactory):
+
+ def __init__(self, document, filepath=None, debug=False):
+ ClassFactory.__init__(self, document, filepath, debug)
+ self.Namespaces["xml"] = {
+ "lang": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("lang", LANGUAGE_model)
+ }
+ }
+ }
+ self.Namespaces["xsi"] = {
+ "noNamespaceSchemaLocation": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": NotSupportedYet("noNamespaceSchemaLocation")
+ }
+ },
+ "nil": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": NotSupportedYet("nil")
+ }
+ },
+ "schemaLocation": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": NotSupportedYet("schemaLocation")
+ }
+ },
+ "type": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": NotSupportedYet("type")
+ }
+ }
+ }
+
+ def ParseSchema(self):
+ for child in self.Document.childNodes:
+ if child.nodeType == self.Document.ELEMENT_NODE:
+ schema = child
+ break
+ for qualified_name, attr in schema._attrs.items():
+ value = GetAttributeValue(attr)
+ if value == "http://www.w3.org/2001/XMLSchema":
+ namespace, name = DecomposeQualifiedName(qualified_name)
+ if namespace == "xmlns":
+ self.DefinedNamespaces["http://www.w3.org/2001/XMLSchema"] = name
+ self.SchemaNamespace = name
+ else:
+ self.DefinedNamespaces["http://www.w3.org/2001/XMLSchema"] = self.SchemaNamespace
+ self.Namespaces[self.SchemaNamespace] = XSD_NAMESPACE
+ self.Schema = XSD_NAMESPACE["schema"]["extract"]["default"](self, schema)
+ ReduceSchema(self, self.Schema[1], self.Schema[2])
+
+ def FindSchemaElement(self, element_name, element_type=None):
+ namespace, name = DecomposeQualifiedName(element_name)
+ element = self.GetQualifiedNameInfos(name, namespace, True)
+ if element is None and namespace == self.TargetNamespace and name not in self.CurrentCompilations:
+ self.CurrentCompilations.append(name)
+ element = self.CreateSchemaElement(name, element_type)
+ self.CurrentCompilations.pop(-1)
+ if element is not None:
+ self.Namespaces[self.TargetNamespace][name] = element
+ if element is None:
+ if name in self.CurrentCompilations:
+ if self.Debug:
+ print "Warning : \"%s\" is circular referenced!" % element_name
+ else:
+ raise ValueError("\"%s\" isn't defined!" % element_name)
+ if element_type is not None and element["type"] != element_type:
+ raise ValueError("\"%s\" isn't of the expected type!" % element_name)
+ return element
+
+ def CreateSchemaElement(self, element_name, element_type):
+ for type, attributes, elements in self.Schema[2]:
+ namespace, name = DecomposeQualifiedName(type)
+ if attributes.get("name", None) == element_name:
+ element_infos = None
+ if element_type in (ATTRIBUTE, None) and name == "attribute":
+ element_infos = ReduceAttribute(self, attributes, elements)
+ elif element_type in (ELEMENT, None) and name == "element":
+ element_infos = ReduceElement(self, attributes, elements)
+ elif element_type in (ATTRIBUTESGROUP, None) and name == "attributeGroup":
+ element_infos = ReduceAttributeGroup(self, attributes, elements)
+ elif element_type in (ELEMENTSGROUP, None) and name == "group":
+ element_infos = ReduceGroup(self, attributes, elements)
+ elif element_type in (SIMPLETYPE, None) and name == "simpleType":
+ element_infos = ReduceSimpleType(self, attributes, elements)
+ elif element_type in (COMPLEXTYPE, None) and name == "complexType":
+ element_infos = ReduceComplexType(self, attributes, elements)
+ if element_infos is not None:
+ self.Namespaces[self.TargetNamespace][element_name] = element_infos
+ return element_infos
+ return None
+
+"""
+This function opens the xsd file and generate the classes from the xml tree
+"""
+def GenerateClassesFromXSD(filepath):
+ xsdfile = open(filepath, 'r')
+ factory = XSDClassFactory(minidom.parse(xsdfile), filepath)
+ xsdfile.close()
+ return GenerateClasses(factory)
+
+"""
+This function generate the classes from the xsd given as a string
+"""
+def GenerateClassesFromXSDstring(xsdstring):
+ return GenerateClasses(XSDClassFactory(minidom.parseString(xsdstring)))
+
+
+#-------------------------------------------------------------------------------
+# XSD schema syntax elements
+#-------------------------------------------------------------------------------
+
+XSD_NAMESPACE = {
+
+#-------------------------------------------------------------------------------
+# Syntax elements definition
+#-------------------------------------------------------------------------------
+
+ "all": {"struct": """
+ <all
+ id = ID
+ maxOccurs = 1 : 1
+ minOccurs = (0 | 1) : 1
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, element*)
+ </all>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("all", ["id", "maxOccurs", "minOccurs"],
+ re.compile("((?:annotation )?(?:element )*)"))
+ },
+ "reduce": ReduceAll
+ },
+
+ "annotation": {"struct": """
+ <annotation
+ id = ID
+ {any attributes with non-schema namespace . . .}>
+ Content: (appinfo | documentation)*
+ </annotation>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("annotation", ["id"],
+ re.compile("((?:app_info |documentation )*)"))
+ },
+ "reduce": ReduceAnnotation
+ },
+
+ "any": {"struct": """
+ <any
+ id = ID
+ maxOccurs = (nonNegativeInteger | unbounded) : 1
+ minOccurs = nonNegativeInteger : 1
+ namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) ) : ##any
+ processContents = (lax | skip | strict) : strict
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </any>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("any",
+ ["id", "maxOccurs", "minOccurs", "namespace", "processContents"],
+ re.compile("((?:annotation )?(?:simpleType )*)"))
+ },
+ "reduce": ReduceAny
+ },
+
+ "anyAttribute": {"struct": """
+ <anyAttribute
+ id = ID
+ namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) ) : ##any
+ processContents = (lax | skip | strict) : strict
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </anyAttribute>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("anyAttribute",
+ ["id", "namespace", "processContents"], ONLY_ANNOTATION)
+ },
+ "reduce": ReduceAnyAttribute
+ },
+
+ "appinfo": {"struct": """
+ <appinfo
+ source = anyURI
+ {any attributes with non-schema namespace . . .}>
+ Content: ({any})*
+ </appinfo>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("appinfo", ["source"], re.compile("(.*)"), True)
+ },
+ "reduce": ReduceAppInfo
+ },
+
+ "attribute": {"struct": """
+ <attribute
+ default = string
+ fixed = string
+ form = (qualified | unqualified)
+ id = ID
+ name = NCName
+ ref = QName
+ type = QName
+ use = (optional | prohibited | required) : optional
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, simpleType?)
+ </attribute>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("attribute",
+ ["default", "fixed", "form", "id", "name", "ref", "type", "use"],
+ re.compile("((?:annotation )?(?:simpleType )?)")),
+ "schema": GenerateElement("attribute",
+ ["default", "fixed", "form", "id", "name", "type"],
+ re.compile("((?:annotation )?(?:simpleType )?)"))
+ },
+ "reduce": ReduceAttribute
+ },
+
+ "attributeGroup": {"struct": """
+ <attributeGroup
+ id = ID
+ name = NCName
+ ref = QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, ((attribute | attributeGroup)*, anyAttribute?))
+ </attributeGroup>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("attributeGroup",
+ ["id", "ref"], ONLY_ANNOTATION),
+ "schema": GenerateElement("attributeGroup",
+ ["id", "name"],
+ re.compile("((?:annotation )?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?))"))
+ },
+ "reduce": ReduceAttributeGroup
+ },
+
+ "choice": {"struct": """
+ <choice
+ id = ID
+ maxOccurs = (nonNegativeInteger | unbounded) : 1
+ minOccurs = nonNegativeInteger : 1
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (element | group | choice | sequence | any)*)
+ </choice>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("choice", ["id", "maxOccurs", "minOccurs"],
+ re.compile("((?:annotation )?(?:element |group |choice |sequence |any )*)"))
+ },
+ "reduce": ReduceChoice
+ },
+
+ "complexContent": {"struct": """
+ <complexContent
+ id = ID
+ mixed = boolean
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (restriction | extension))
+ </complexContent>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("complexContent", ["id", "mixed"],
+ re.compile("((?:annotation )?(?:restriction |extension ))"))
+ },
+ "reduce": ReduceComplexContent
+ },
+
+ "complexType": {"struct": """
+ <complexType
+ abstract = boolean : false
+ block = (#all | List of (extension | restriction))
+ final = (#all | List of (extension | restriction))
+ id = ID
+ mixed = boolean : false
+ name = NCName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (simpleContent | complexContent | ((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?))))
+ </complexType>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("complexType",
+ ["abstract", "block", "final", "id", "mixed", "name"],
+ re.compile("((?:annotation )?(?:simpleContent |complexContent |(?:(?:group |all |choice |sequence )?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?))))"))
+ },
+ "reduce": ReduceComplexType
+ },
+
+ "documentation": {"struct" : """
+ <documentation
+ source = anyURI
+ xml:lang = language
+ {any attributes with non-schema namespace . . .}>
+ Content: ({any})*
+ </documentation>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("documentation",
+ ["source", "lang"], re.compile("(.*)"), True)
+ },
+ "reduce": ReduceDocumentation
+ },
+
+ "element": {"struct": """
+ <element
+ abstract = boolean : false
+ block = (#all | List of (extension | restriction | substitution))
+ default = string
+ final = (#all | List of (extension | restriction))
+ fixed = string
+ form = (qualified | unqualified)
+ id = ID
+ maxOccurs = (nonNegativeInteger | unbounded) : 1
+ minOccurs = nonNegativeInteger : 1
+ name = NCName
+ nillable = boolean : false
+ ref = QName
+ substitutionGroup = QName
+ type = QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, ((simpleType | complexType)?, (unique | key | keyref)*))
+ </element>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("element",
+ ["abstract", "block", "default", "final", "fixed", "form", "id", "maxOccurs", "minOccurs", "name", "nillable", "ref", "substitutionGroup", "type"],
+ re.compile("((?:annotation )?(?:simpleType |complexType )?(?:unique |key |keyref )*)")),
+ "schema": GenerateElement("element",
+ ["abstract", "block", "default", "final", "fixed", "form", "id", "name", "nillable", "substitutionGroup", "type"],
+ re.compile("((?:annotation )?(?:simpleType |complexType )?(?:unique |key |keyref )*)"))
+ },
+ "reduce": ReduceElement
+ },
+
+ "enumeration": {"struct": """
+ <enumeration
+ id = ID
+ value = anySimpleType
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </enumeration>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("enumeration", ["id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("enumeration", False)
+ },
+
+ "extension": {"struct": """
+ <extension
+ base = QName
+ id = ID
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, ((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?)))
+ </extension>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("extension", ["base", "id"],
+ re.compile("((?:annotation )?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?))")),
+ "complexContent": GenerateElement("extension", ["base", "id"],
+ re.compile("((?:annotation )?(?:group |all |choice |sequence )?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?))"))
+ },
+ "reduce": ReduceExtension
+ },
+
+ "field": {"struct": """
+ <field
+ id = ID
+ xpath = a subset of XPath expression, see below
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </field>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("field", ["id", "xpath"], ONLY_ANNOTATION)
+ },
+ "reduce": ReduceField
+ },
+
+ "fractionDigits": {"struct": """
+ <fractionDigits
+ fixed = boolean : false
+ id = ID
+ value = nonNegativeInteger
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </fractionDigits>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("fractionDigits",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("fractionDigits", True)
+ },
+
+ "group": {"struct": """
+ <group
+ id = ID
+ maxOccurs = (nonNegativeInteger | unbounded) : 1
+ minOccurs = nonNegativeInteger : 1
+ name = NCName
+ ref = QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (all | choice | sequence)?)
+ </group>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("group",
+ ["id", "maxOccurs", "minOccurs", "ref"],
+ re.compile("((?:annotation )?(?:all |choice |sequence )?)")),
+ "schema": GenerateElement("group",
+ ["id", "name"],
+ re.compile("((?:annotation )?(?:all |choice |sequence )?)"))
+ },
+ "reduce": ReduceGroup
+ },
+
+ "import": {"struct": """
+ <import
+ id = ID
+ namespace = anyURI
+ schemaLocation = anyURI
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </import>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("import",
+ ["id", "namespace", "schemaLocation"], ONLY_ANNOTATION)
+ },
+ "reduce": ReduceImport
+ },
+
+ "include": {"struct": """
+ <include
+ id = ID
+ schemaLocation = anyURI
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </include>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("include",
+ ["id", "schemaLocation"], ONLY_ANNOTATION)
+ },
+ "reduce": ReduceInclude
+ },
+
+ "key": {"struct": """
+ <key
+ id = ID
+ name = NCName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (selector, field+))
+ </key>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("key", ["id", "name"],
+ re.compile("((?:annotation )?(?:selector (?:field )+))"))
+ },
+ "reduce": ReduceKey
+ },
+
+ "keyref": {"struct": """
+ <keyref
+ id = ID
+ name = NCName
+ refer = QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (selector, field+))
+ </keyref>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("keyref", ["id", "name", "refer"],
+ re.compile("((?:annotation )?(?:selector (?:field )+))"))
+ },
+ "reduce": ReduceKeyRef
+ },
+
+ "length": {"struct" : """
+ <length
+ fixed = boolean : false
+ id = ID
+ value = nonNegativeInteger
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </length>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("length",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("length", True)
+ },
+
+ "list": {"struct": """
+ <list
+ id = ID
+ itemType = QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, simpleType?)
+ </list>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("list", ["id", "itemType"],
+ re.compile("((?:annotation )?(?:simpleType )?)$"))
+ },
+ "reduce": ReduceList
+ },
+
+ "maxExclusive": {"struct": """
+ <maxInclusive
+ fixed = boolean : false
+ id = ID
+ value = anySimpleType
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </maxInclusive>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("maxExclusive",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("maxExclusive", True)
+ },
+
+ "maxInclusive": {"struct": """
+ <maxExclusive
+ fixed = boolean : false
+ id = ID
+ value = anySimpleType
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </maxExclusive>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("maxInclusive",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("maxInclusive", True)
+ },
+
+ "maxLength": {"struct": """
+ <maxLength
+ fixed = boolean : false
+ id = ID
+ value = nonNegativeInteger
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </maxLength>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("maxLength",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("maxLength", True)
+ },
+
+ "minExclusive": {"struct": """
+ <minExclusive
+ fixed = boolean : false
+ id = ID
+ value = anySimpleType
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </minExclusive>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("minExclusive",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("minExclusive", True)
+ },
+
+ "minInclusive": {"struct": """
+ <minInclusive
+ fixed = boolean : false
+ id = ID
+ value = anySimpleType
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </minInclusive>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("minInclusive",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("minInclusive", True)
+ },
+
+ "minLength": {"struct": """
+ <minLength
+ fixed = boolean : false
+ id = ID
+ value = nonNegativeInteger
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </minLength>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("minLength",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("minLength", True)
+ },
+
+ "pattern": {"struct": """
+ <pattern
+ id = ID
+ value = string
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </pattern>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("pattern", ["id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("pattern", False)
+ },
+
+ "redefine": {"struct": """
+ <redefine
+ id = ID
+ schemaLocation = anyURI
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation | (simpleType | complexType | group | attributeGroup))*
+ </redefine>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("refine", ["id", "schemaLocation"],
+ re.compile("((?:annotation |(?:simpleType |complexType |group |attributeGroup ))*)"))
+ },
+ "reduce": ReduceRedefine
+ },
+
+ "restriction": {"struct": """
+ <restriction
+ base = QName
+ id = ID
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?))
+ </restriction>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("restriction", ["base", "id"],
+ re.compile("((?:annotation )?(?:(?:simpleType )?(?:(?:minExclusive |minInclusive |maxExclusive |maxInclusive |totalDigits |fractionDigits |length |minLength |maxLength |enumeration |whiteSpace |pattern )*)))")),
+ "simpleContent": GenerateElement("restriction", ["base", "id"],
+ re.compile("((?:annotation )?(?:(?:simpleType )?(?:(?:minExclusive |minInclusive |maxExclusive |maxInclusive |totalDigits |fractionDigits |length |minLength |maxLength |enumeration |whiteSpace |pattern )*)?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?)))")),
+ "complexContent": GenerateElement("restriction", ["base", "id"],
+ re.compile("((?:annotation )?(?:(?:simpleType )?(?:group |all |choice |sequence )?(?:(?:attribute |attributeGroup )*(?:anyAttribute )?)))")),
+ },
+ "reduce": ReduceRestriction
+ },
+
+ "schema": {"struct": """
+ <schema
+ attributeFormDefault = (qualified | unqualified) : unqualified
+ blockDefault = (#all | List of (extension | restriction | substitution)) : ''
+ elementFormDefault = (qualified | unqualified) : unqualified
+ finalDefault = (#all | List of (extension | restriction | list | union)) : ''
+ id = ID
+ targetNamespace = anyURI
+ version = token
+ xml:lang = language
+ {any attributes with non-schema namespace . . .}>
+ Content: ((include | import | redefine | annotation)*, (((simpleType | complexType | group | attributeGroup) | element | attribute | notation), annotation*)*)
+ </schema>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("schema",
+ ["attributeFormDefault", "blockDefault", "elementFormDefault", "finalDefault", "id", "targetNamespace", "version", "lang"],
+ re.compile("((?:include |import |redefine |annotation )*(?:(?:(?:simpleType |complexType |group |attributeGroup )|element |attribute |annotation )(?:annotation )*)*)"))
+ }
+ },
+
+ "selector": {"struct": """
+ <selector
+ id = ID
+ xpath = a subset of XPath expression, see below
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </selector>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("selector", ["id", "xpath"], ONLY_ANNOTATION)
+ },
+ "reduce": ReduceSelector
+ },
+
+ "sequence": {"struct": """
+ <sequence
+ id = ID
+ maxOccurs = (nonNegativeInteger | unbounded) : 1
+ minOccurs = nonNegativeInteger : 1
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (element | group | choice | sequence | any)*)
+ </sequence>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("sequence", ["id", "maxOccurs", "minOccurs"],
+ re.compile("((?:annotation )?(?:element |group |choice |sequence |any )*)"))
+ },
+ "reduce": ReduceSequence
+ },
+
+ "simpleContent": {"struct" : """
+ <simpleContent
+ id = ID
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (restriction | extension))
+ </simpleContent>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("simpleContent", ["id"],
+ re.compile("((?:annotation )?(?:restriction |extension ))"))
+ },
+ "reduce": ReduceSimpleContent
+ },
+
+ "simpleType": {"struct" : """
+ <simpleType
+ final = (#all | List of (list | union | restriction))
+ id = ID
+ name = NCName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (restriction | list | union))
+ </simpleType>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("simpleType", ["final", "id", "name"],
+ re.compile("((?:annotation )?(?:restriction |list |union ))"))
+ },
+ "reduce": ReduceSimpleType
+ },
+
+ "totalDigits": {"struct" : """
+ <totalDigits
+ fixed = boolean : false
+ id = ID
+ value = positiveInteger
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </totalDigits>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("totalDigits",
+ ["fixed", "id", "value"], ONLY_ANNOTATION),
+ },
+ "reduce": GenerateFacetReducing("totalDigits", True)
+ },
+
+ "union": {"struct": """
+ <union
+ id = ID
+ memberTypes = List of QName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, simpleType*)
+ </union>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("union", ["id", "memberTypes"],
+ re.compile("((?:annotation )?(?:simpleType )*)"))
+ },
+ "reduce": ReduceUnion
+ },
+
+ "unique": {"struct": """
+ <unique
+ id = ID
+ name = NCName
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?, (selector, field+))
+ </unique>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("unique", ["id", "name"],
+ re.compile("((?:annotation )?(?:selector |(?:field )+))"))
+ },
+ "reduce": ReduceUnique
+ },
+
+ "whiteSpace": {"struct" : """
+ <whiteSpace
+ fixed = boolean : false
+ id = ID
+ value = (collapse | preserve | replace)
+ {any attributes with non-schema namespace . . .}>
+ Content: (annotation?)
+ </whiteSpace>""",
+ "type": SYNTAXELEMENT,
+ "extract": {
+ "default": GenerateElement("whiteSpace",
+ ["fixed", "id", "value"], ONLY_ANNOTATION)
+ },
+ "reduce": GenerateFacetReducing("whiteSpace", True)
+ },
+
+#-------------------------------------------------------------------------------
+# Syntax attributes definition
+#-------------------------------------------------------------------------------
+
+ "abstract": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetBoolean
+ },
+ "default": {
+ "default": False
+ }
+ },
+
+ "attributeFormDefault": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateEnumeratedExtraction("member attributeFormDefault", ["qualified", "unqualified"])
+ },
+ "default": {
+ "default": "unqualified"
+ }
+ },
+
+ "base": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member base", QName_model)
+ }
+ },
+
+ "block": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateGetList("block", ["restriction", "extension", "substitution"])
+ }
+ },
+
+ "blockDefault": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateGetList("block", ["restriction", "extension", "substitution"])
+ },
+ "default": {
+ "default": ""
+ }
+ },
+
+ "default": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetAttributeValue
+ }
+ },
+
+ "elementFormDefault": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateEnumeratedExtraction("member elementFormDefault", ["qualified", "unqualified"])
+ },
+ "default": {
+ "default": "unqualified"
+ }
+ },
+
+ "final": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateGetList("final", ["restriction", "extension", "substitution"]),
+ "simpleType": GenerateGetList("final", ["list", "union", "restriction"])
+ }
+ },
+
+ "finalDefault": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateGetList("finalDefault", ["restriction", "extension", "list", "union"])
+ },
+ "default": {
+ "default": ""
+ }
+ },
+
+ "fixed": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetBoolean,
+ "attribute": GetAttributeValue,
+ "element": GetAttributeValue
+ },
+ "default": {
+ "default": False,
+ "attribute": None,
+ "element": None
+ }
+ },
+
+ "form": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateEnumeratedExtraction("member form", ["qualified", "unqualified"])
+ }
+ },
+
+ "id": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member id", NCName_model)
+ }
+ },
+
+ "itemType": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member itemType", QName_model)
+ }
+ },
+
+ "memberTypes": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameListExtraction("member memberTypes", QNames_model)
+ },
+ },
+
+ "maxOccurs": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateLimitExtraction(),
+ "all": GenerateLimitExtraction(1, 1, False)
+ },
+ "default": {
+ "default": 1
+ }
+ },
+
+ "minOccurs": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateLimitExtraction(unbounded = False),
+ "all": GenerateLimitExtraction(0, 1, False)
+ },
+ "default": {
+ "default": 1
+ }
+ },
+
+ "mixed": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetBoolean
+ },
+ "default": {
+ "default": None,
+ "complexType": False
+ }
+ },
+
+ "name": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member name", NCName_model)
+ }
+ },
+
+ "namespace": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member namespace", URI_model),
+ "any": GetNamespaces
+ },
+ "default": {
+ "default": None,
+ "any": "##any"
+ }
+ },
+
+ "nillable": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetBoolean
+ },
+ },
+
+ "processContents": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateEnumeratedExtraction("member processContents", ["lax", "skip", "strict"])
+ },
+ "default": {
+ "default": "strict"
+ }
+ },
+
+ "ref": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member ref", QName_model)
+ }
+ },
+
+ "refer": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member refer", QName_model)
+ }
+ },
+
+ "schemaLocation": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member schemaLocation", URI_model)
+ }
+ },
+
+ "source": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member source", URI_model)
+ }
+ },
+
+ "substitutionGroup": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member substitutionGroup", QName_model)
+ }
+ },
+
+ "targetNamespace": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member targetNamespace", URI_model)
+ }
+ },
+
+ "type": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateModelNameExtraction("member type", QName_model)
+ }
+ },
+
+ "use": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GenerateEnumeratedExtraction("member usage", ["required", "optional", "prohibited"])
+ },
+ "default": {
+ "default": "optional"
+ }
+ },
+
+ "value": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetAttributeValue,
+ "fractionDigits": GenerateIntegerExtraction(minInclusive=0),
+ "length": GenerateIntegerExtraction(minInclusive=0),
+ "maxLength": GenerateIntegerExtraction(minInclusive=0),
+ "minLength": GenerateIntegerExtraction(minInclusive=0),
+ "totalDigits": GenerateIntegerExtraction(minExclusive=0),
+ "whiteSpace": GenerateEnumeratedExtraction("value", ["collapse", "preserve", "replace"])
+ }
+ },
+
+ "version": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+ "default": GetToken
+ }
+ },
+
+ "xpath": {
+ "type": SYNTAXATTRIBUTE,
+ "extract": {
+# "default": NotSupportedYet("xpath")
+ "default": GetAttributeValue
+ }
+ },
+
+#-------------------------------------------------------------------------------
+# Simple types definition
+#-------------------------------------------------------------------------------
+
+ "string": {
+ "type": SIMPLETYPE,
+ "basename": "string",
+ "extract": GetAttributeValue,
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x : x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "normalizedString": {
+ "type": SIMPLETYPE,
+ "basename": "normalizedString",
+ "extract": GetNormalizedString,
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x : x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "token": {
+ "type": SIMPLETYPE,
+ "basename": "token",
+ "extract": GetToken,
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x : x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "base64Binary": {
+ "type": SIMPLETYPE,
+ "basename": "base64Binary",
+ "extract": NotSupportedYet("base64Binary"),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, (IntType, LongType))
+ },
+
+ "hexBinary": {
+ "type": SIMPLETYPE,
+ "basename": "hexBinary",
+ "extract": GetHexInteger,
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: ("%."+str(int(round(len("%X"%x)/2.)*2))+"X")%x),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, (IntType, LongType))
+ },
+
+ "integer": {
+ "type": SIMPLETYPE,
+ "basename": "integer",
+ "extract": GenerateIntegerExtraction(),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "positiveInteger": {
+ "type": SIMPLETYPE,
+ "basename": "positiveInteger",
+ "extract": GenerateIntegerExtraction(minExclusive=0),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "negativeInteger": {
+ "type": SIMPLETYPE,
+ "basename": "negativeInteger",
+ "extract": GenerateIntegerExtraction(maxExclusive=0),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "nonNegativeInteger": {
+ "type": SIMPLETYPE,
+ "basename": "nonNegativeInteger",
+ "extract": GenerateIntegerExtraction(minInclusive=0),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "nonPositiveInteger": {
+ "type": SIMPLETYPE,
+ "basename": "nonPositiveInteger",
+ "extract": GenerateIntegerExtraction(maxInclusive=0),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "long": {
+ "type": SIMPLETYPE,
+ "basename": "long",
+ "extract": GenerateIntegerExtraction(minInclusive=-2**63,maxExclusive=2**63),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "unsignedLong": {
+ "type": SIMPLETYPE,
+ "basename": "unsignedLong",
+ "extract": GenerateIntegerExtraction(minInclusive=0,maxExclusive=2**64),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "int": {
+ "type": SIMPLETYPE,
+ "basename": "int",
+ "extract": GenerateIntegerExtraction(minInclusive=-2**31,maxExclusive=2**31),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "unsignedInt": {
+ "type": SIMPLETYPE,
+ "basename": "unsignedInt",
+ "extract": GenerateIntegerExtraction(minInclusive=0,maxExclusive=2**32),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "short": {
+ "type": SIMPLETYPE,
+ "basename": "short",
+ "extract": GenerateIntegerExtraction(minInclusive=-2**15,maxExclusive=2**15),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "unsignedShort": {
+ "type": SIMPLETYPE,
+ "basename": "unsignedShort",
+ "extract": GenerateIntegerExtraction(minInclusive=0,maxExclusive=2**16),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "byte": {
+ "type": SIMPLETYPE,
+ "basename": "byte",
+ "extract": GenerateIntegerExtraction(minInclusive=-2**7,maxExclusive=2**7),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "unsignedByte": {
+ "type": SIMPLETYPE,
+ "basename": "unsignedByte",
+ "extract": GenerateIntegerExtraction(minInclusive=0,maxExclusive=2**8),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: 0,
+ "check": lambda x: isinstance(x, IntType)
+ },
+
+ "decimal": {
+ "type": SIMPLETYPE,
+ "basename": "decimal",
+ "extract": GenerateFloatExtraction("decimal"),
+ "facets": DECIMAL_FACETS,
+ "generate": GenerateFloatXMLText(),
+ "initial": lambda: 0.,
+ "check": lambda x: isinstance(x, (IntType, FloatType))
+ },
+
+ "float": {
+ "type": SIMPLETYPE,
+ "basename": "float",
+ "extract": GenerateFloatExtraction("float", ["INF", "-INF", "NaN"]),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateFloatXMLText(["INF", "-INF", "NaN"]),
+ "initial": lambda: 0.,
+ "check": lambda x: {"INF" : True, "-INF" : True, "NaN" : True}.get(x, isinstance(x, (IntType, FloatType)))
+ },
+
+ "double": {
+ "type": SIMPLETYPE,
+ "basename": "double",
+ "extract": GenerateFloatExtraction("double", ["INF", "-INF", "NaN"]),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateFloatXMLText(["INF", "-INF", "NaN"]),
+ "initial": lambda: 0.,
+ "check": lambda x: {"INF" : True, "-INF" : True, "NaN" : True}.get(x, isinstance(x, (IntType, FloatType)))
+ },
+
+ "boolean": {
+ "type": SIMPLETYPE,
+ "basename": "boolean",
+ "extract": GetBoolean,
+ "facets": GenerateDictFacets(["pattern", "whiteSpace"]),
+ "generate": GenerateSimpleTypeXMLText(lambda x:{True : "true", False : "false"}[x]),
+ "initial": lambda: False,
+ "check": lambda x: isinstance(x, BooleanType)
+ },
+
+ "duration": {
+ "type": SIMPLETYPE,
+ "basename": "duration",
+ "extract": NotSupportedYet("duration"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "dateTime": {
+ "type": SIMPLETYPE,
+ "basename": "dateTime",
+ "extract": GetDateTime,
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(datetime.datetime.isoformat),
+ "initial": lambda: datetime.datetime(1,1,1,0,0,0,0),
+ "check": lambda x: isinstance(x, datetime.datetime)
+ },
+
+ "date": {
+ "type": SIMPLETYPE,
+ "basename": "date",
+ "extract": GetDate,
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(datetime.date.isoformat),
+ "initial": lambda: datetime.date(1,1,1),
+ "check": lambda x: isinstance(x, datetime.date)
+ },
+
+ "time": {
+ "type": SIMPLETYPE,
+ "basename": "time",
+ "extract": GetTime,
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(datetime.time.isoformat),
+ "initial": lambda: datetime.time(0,0,0,0),
+ "check": lambda x: isinstance(x, datetime.time)
+ },
+
+ "gYear": {
+ "type": SIMPLETYPE,
+ "basename": "gYear",
+ "extract": NotSupportedYet("gYear"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "gYearMonth": {
+ "type": SIMPLETYPE,
+ "basename": "gYearMonth",
+ "extract": NotSupportedYet("gYearMonth"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "gMonth": {
+ "type": SIMPLETYPE,
+ "basename": "gMonth",
+ "extract": NotSupportedYet("gMonth"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "gMonthDay": {
+ "type": SIMPLETYPE,
+ "basename": "gMonthDay",
+ "extract": NotSupportedYet("gMonthDay"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "gDay": {
+ "type": SIMPLETYPE,
+ "basename": "gDay",
+ "extract": NotSupportedYet("gDay"),
+ "facets": NUMBER_FACETS,
+ "generate": GenerateSimpleTypeXMLText(str),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "Name": {
+ "type": SIMPLETYPE,
+ "basename": "Name",
+ "extract": GenerateModelNameExtraction("Name", Name_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "QName": {
+ "type": SIMPLETYPE,
+ "basename": "QName",
+ "extract": GenerateModelNameExtraction("QName", QName_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "NCName": {
+ "type": SIMPLETYPE,
+ "basename": "NCName",
+ "extract": GenerateModelNameExtraction("NCName", NCName_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "anyURI": {
+ "type": SIMPLETYPE,
+ "basename": "anyURI",
+ "extract": GenerateModelNameExtraction("anyURI", URI_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "language": {
+ "type": SIMPLETYPE,
+ "basename": "language",
+ "extract": GenerateModelNameExtraction("language", LANGUAGE_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "en",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "ID": {
+ "type": SIMPLETYPE,
+ "basename": "ID",
+ "extract": GenerateModelNameExtraction("ID", Name_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "IDREF": {
+ "type": SIMPLETYPE,
+ "basename": "IDREF",
+ "extract": GenerateModelNameExtraction("IDREF", Name_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "IDREFS": {
+ "type": SIMPLETYPE,
+ "basename": "IDREFS",
+ "extract": GenerateModelNameExtraction("IDREFS", Names_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "ENTITY": {
+ "type": SIMPLETYPE,
+ "basename": "ENTITY",
+ "extract": GenerateModelNameExtraction("ENTITY", Name_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "ENTITIES": {
+ "type": SIMPLETYPE,
+ "basename": "ENTITIES",
+ "extract": GenerateModelNameExtraction("ENTITIES", Names_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "NOTATION": {
+ "type": SIMPLETYPE,
+ "basename": "NOTATION",
+ "extract": GenerateModelNameExtraction("NOTATION", Name_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "NMTOKEN": {
+ "type": SIMPLETYPE,
+ "basename": "NMTOKEN",
+ "extract": GenerateModelNameExtraction("NMTOKEN", NMToken_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ "NMTOKENS": {
+ "type": SIMPLETYPE,
+ "basename": "NMTOKENS",
+ "extract": GenerateModelNameExtraction("NMTOKENS", NMTokens_model),
+ "facets": STRING_FACETS,
+ "generate": GenerateSimpleTypeXMLText(lambda x: x),
+ "initial": lambda: "",
+ "check": lambda x: isinstance(x, (StringType, UnicodeType))
+ },
+
+ # Complex Types
+ "anyType": {"type": COMPLEXTYPE, "extract": lambda x:None},
+}
+
+if __name__ == '__main__':
+ classes = GenerateClassesFromXSD("test.xsd")
+
+ # Code for test of test.xsd
+ xmlfile = open("po.xml", 'r')
+ tree = minidom.parse(xmlfile)
+ xmlfile.close()
+ test = classes["PurchaseOrderType"]()
+ for child in tree.childNodes:
+ if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "purchaseOrder":
+ test.loadXMLTree(child)
+ test.items.item[0].setquantity(2)
+ testfile = open("test.xml", 'w')
+ testfile.write(u'<?xml version=\"1.0\"?>\n')
+ testfile.write(test.generateXMLText("purchaseOrder").encode("utf-8"))
+ testfile.close()