# HG changeset patch
# User Laurent Bessard
# Date 1362001245 -3600
# Node ID 58d07e039896ec9d85ab4531c2b4c2b5fe29b7b3
# Parent c9b0340ea0f5ef6292cf744de0fb2b70c98551af
Added panel for managing ESI files from project and from database including module PDO alignment setting
diff -r c9b0340ea0f5 -r 58d07e039896 etherlab/ConfigEditor.py
--- a/etherlab/ConfigEditor.py Thu Feb 07 00:59:50 2013 +0100
+++ b/etherlab/ConfigEditor.py Wed Feb 27 22:40:45 2013 +0100
@@ -1,9 +1,13 @@
+import os
+
import wx
import wx.grid
import wx.gizmos
-
-from controls import CustomGrid, CustomTable
+import wx.lib.buttons
+
+from controls import CustomGrid, CustomTable, FolderTree
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
+from util.BitmapLibrary import GetBitmap
[ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3)
@@ -13,157 +17,59 @@
else:
parent.Append(helpString=help, id=id, kind=kind, item=text)
-def GetSyncManagersTableColnames():
- _ = lambda x : x
- return ["#", _("Name"), _("Start Address"), _("Default Size"), _("Control Byte"), _("Enable")]
-
-class SyncManagersTable(CustomTable):
-
- def GetValue(self, row, col):
- if row < self.GetNumberRows():
- if col == 0:
- return row
- return self.data[row].get(self.GetColLabelValue(col, False), "")
-
def GetVariablesTableColnames():
_ = lambda x : x
- return ["#", _("Name"), _("Index"), _("SubIndex"), _("Type"), _("PDO index"), _("PDO name"), _("PDO type")]
-
-[ID_NODEEDITOR, ID_NODEEDITORVENDORLABEL,
- ID_NODEEDITORVENDOR, ID_NODEEDITORPRODUCTCODELABEL,
- ID_NODEEDITORPRODUCTCODE, ID_NODEEDITORREVISIONNUMBERLABEL,
- ID_NODEEDITORREVISIONNUMBER, ID_NODEEDITORPHYSICSLABEL,
- ID_NODEEDITORPHYSICS, ID_NODEEDITORSYNCMANAGERSLABEL,
- ID_NODEEDITORSYNCMANAGERSGRID, ID_NODEEDITORVARIABLESLABEL,
- ID_NODEEDITORVARIABLESGRID,
-] = [wx.NewId() for _init_ctrls in range(13)]
+ return ["#", _("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")]
+
+ACCESS_TYPES = {
+ 'ro': 'R',
+ 'wo': 'W',
+ 'rw': 'R/W'}
+
+def GetAccessValue(access, pdo_mapping):
+ value = ACCESS_TYPES.get(access)
+ if pdo_mapping != "":
+ value += "/P"
+ return value
class NodeEditor(ConfTreeNodeEditor):
- ID = ID_NODEEDITOR
CONFNODEEDITOR_TABS = [
(_("Ethercat node"), "_create_EthercatNodeEditor")]
- def _init_coll_MainSizer_Items(self, parent):
- parent.AddSizer(self.SlaveInfosDetailsSizer, 0, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.SyncManagersLabel, 0, border=5, flag=wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.SyncManagersGrid, 0, border=5, flag=wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.VariablesLabel, 0, border=5, flag=wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.VariablesGrid, 0, border=5, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.GROW)
-
- def _init_coll_MainSizer_Growables(self, parent):
- parent.AddGrowableCol(0)
- parent.AddGrowableRow(2, 1)
- parent.AddGrowableRow(4, 2)
-
- def _init_coll_SlaveInfosDetailsSizer_Items(self, parent):
- parent.AddWindow(self.VendorLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW)
- parent.AddWindow(self.Vendor, 0, border=0, flag=wx.GROW)
- parent.AddWindow(self.ProductCodeLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW)
- parent.AddWindow(self.ProductCode, 0, border=0, flag=wx.GROW)
- parent.AddWindow(self.RevisionNumberLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW)
- parent.AddWindow(self.RevisionNumber, 0, border=0, flag=wx.GROW)
- parent.AddWindow(self.PhysicsLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW)
- parent.AddWindow(self.Physics, 0, border=0, flag=wx.GROW)
-
- def _init_coll_SlaveInfosDetailsSizer_Growables(self, parent):
- parent.AddGrowableCol(1)
- parent.AddGrowableCol(3)
-
- def _init_sizers(self):
- self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
- self.SlaveInfosDetailsSizer = wx.FlexGridSizer(cols=4, hgap=5, rows=2, vgap=5)
-
- self._init_coll_MainSizer_Growables(self.MainSizer)
- self._init_coll_MainSizer_Items(self.MainSizer)
- self._init_coll_SlaveInfosDetailsSizer_Growables(self.SlaveInfosDetailsSizer)
- self._init_coll_SlaveInfosDetailsSizer_Items(self.SlaveInfosDetailsSizer)
-
- self.EthercatNodeEditor.SetSizer(self.MainSizer)
-
def _create_EthercatNodeEditor(self, prnt):
- self.EthercatNodeEditor = wx.ScrolledWindow(id=-1, name='SlavePanel', parent=prnt,
- size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER|wx.HSCROLL|wx.VSCROLL)
- self.EthercatNodeEditor.Bind(wx.EVT_SIZE, self.OnEthercatNodeEditorResize)
-
- self.VendorLabel = wx.StaticText(id=ID_NODEEDITORVENDORLABEL,
- label=_('Vendor:'), name='VendorLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.Vendor = wx.TextCtrl(id=ID_NODEEDITORVENDOR, value='',
- name='Vendor', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 24), style=wx.TE_READONLY)
-
- self.ProductCodeLabel = wx.StaticText(id=ID_NODEEDITORPRODUCTCODELABEL,
- label=_('Product code:'), name='ProductCodeLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.ProductCode = wx.TextCtrl(id=ID_NODEEDITORPRODUCTCODE, value='',
- name='ProductCode', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 24), style=wx.TE_READONLY)
-
- self.RevisionNumberLabel = wx.StaticText(id=ID_NODEEDITORREVISIONNUMBERLABEL,
- label=_('Revision number:'), name='RevisionNumberLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.RevisionNumber = wx.TextCtrl(id=ID_NODEEDITORREVISIONNUMBER, value='',
- name='RevisionNumber', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 24), style=wx.TE_READONLY)
-
- self.PhysicsLabel = wx.StaticText(id=ID_NODEEDITORPHYSICSLABEL,
- label=_('Physics:'), name='PhysicsLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.Physics = wx.TextCtrl(id=ID_NODEEDITORPHYSICS, value='',
- name='Physics', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 24), style=wx.TE_READONLY)
-
- self.SyncManagersLabel = wx.StaticText(id=ID_NODEEDITORSYNCMANAGERSLABEL,
- label=_('Sync managers:'), name='SyncManagersLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.SyncManagersGrid = CustomGrid(id=ID_NODEEDITORSYNCMANAGERSGRID,
- name='SyncManagersGrid', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 200), style=wx.VSCROLL)
-
- self.VariablesLabel = wx.StaticText(id=ID_NODEEDITORVARIABLESLABEL,
- label=_('Variable entries:'), name='VariablesLabel', parent=self.EthercatNodeEditor,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
-
- self.VariablesGrid = wx.gizmos.TreeListCtrl(id=ID_NODEEDITORVARIABLESGRID,
- name='VariablesGrid', parent=self.EthercatNodeEditor, pos=wx.Point(0, 0),
- size=wx.Size(0, 400), style=wx.TR_DEFAULT_STYLE |
- wx.TR_ROW_LINES |
- wx.TR_COLUMN_LINES |
- wx.TR_HIDE_ROOT |
- wx.TR_FULL_ROW_HIGHLIGHT)
- self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN, self.OnVariablesGridLeftClick)
+ self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+
+ variables_label = wx.StaticText(self.EthercatNodeEditor,
+ label=_('Variable entries:'))
+ main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT)
+
+ self.VariablesGrid = wx.gizmos.TreeListCtrl(self.EthercatNodeEditor,
+ style=wx.TR_DEFAULT_STYLE |
+ wx.TR_ROW_LINES |
+ wx.TR_COLUMN_LINES |
+ wx.TR_HIDE_ROOT |
+ wx.TR_FULL_ROW_HIGHLIGHT)
+ self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
+ self.OnVariablesGridLeftClick)
+ main_sizer.AddWindow(self.VariablesGrid, border=10,
+ flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
- self._init_sizers()
-
+ self.EthercatNodeEditor.SetSizer(main_sizer)
+
return self.EthercatNodeEditor
def __init__(self, parent, controler, window):
ConfTreeNodeEditor.__init__(self, parent, controler, window)
- self.SyncManagersTable = SyncManagersTable(self, [], GetSyncManagersTableColnames())
- self.SyncManagersGrid.SetTable(self.SyncManagersTable)
- self.SyncManagersGridColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT,
- wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT]
- self.SyncManagersGridColSizes = [40, 150, 100, 100, 100, 100]
- self.SyncManagersGrid.SetRowLabelSize(0)
- for col in range(self.SyncManagersTable.GetNumberCols()):
- attr = wx.grid.GridCellAttr()
- attr.SetAlignment(self.SyncManagersGridColAlignements[col], wx.ALIGN_CENTRE)
- self.SyncManagersGrid.SetColAttr(col, attr)
- self.SyncManagersGrid.SetColMinimalWidth(col, self.SyncManagersGridColSizes[col])
- self.SyncManagersGrid.AutoSizeColumn(col, False)
-
for colname, colsize, colalign in zip(GetVariablesTableColnames(),
- [40, 150, 100, 100, 150, 100, 150, 100],
+ [40, 150, 100, 100, 150, 100],
[wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT,
- wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT,
- wx.ALIGN_LEFT, wx.ALIGN_LEFT]):
+ wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]):
self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
self.VariablesGrid.SetMainColumn(1)
@@ -178,20 +84,8 @@
def RefreshSlaveInfos(self):
slave_infos = self.Controler.GetSlaveInfos()
if slave_infos is not None:
- self.Vendor.SetValue(slave_infos["vendor"])
- self.ProductCode.SetValue(slave_infos["product_code"])
- self.RevisionNumber.SetValue(slave_infos["revision_number"])
- self.Physics.SetValue(slave_infos["physics"])
- self.SyncManagersTable.SetData(slave_infos["sync_managers"])
- self.SyncManagersTable.ResetView(self.SyncManagersGrid)
self.RefreshVariablesGrid(slave_infos["entries"])
else:
- self.Vendor.SetValue("")
- self.ProductCode.SetValue("")
- self.RevisionNumber.SetValue("")
- self.Physics.SetValue("")
- self.SyncManagersTable.SetData([])
- self.SyncManagersTable.ResetView(self.SyncManagersGrid)
self.RefreshVariablesGrid([])
def RefreshVariablesGrid(self, entries):
@@ -202,10 +96,7 @@
self.VariablesGrid.Expand(root)
def GenerateVariablesGridBranch(self, root, entries, colnames, idx=0):
- if wx.VERSION >= (2, 6, 0):
- item, root_cookie = self.VariablesGrid.GetFirstChild(root)
- else:
- item, root_cookie = self.VariablesGrid.GetFirstChild(root, 0)
+ item, root_cookie = self.VariablesGrid.GetFirstChild(root)
no_more_items = not item.IsOk()
for entry in entries:
@@ -216,7 +107,10 @@
if col == 0:
self.VariablesGrid.SetItemText(item, str(idx), 0)
else:
- self.VariablesGrid.SetItemText(item, entry.get(colname, ""), col)
+ value = entry.get(colname, "")
+ if colname == "Access":
+ value = GetAccessValue(value, entry.get("PDOMapping", ""))
+ self.VariablesGrid.SetItemText(item, value, col)
if entry["PDOMapping"] == "":
self.VariablesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
self.VariablesGrid.SetItemPyData(item, entry)
@@ -263,16 +157,275 @@
event.Skip()
- def OnEthercatNodeEditorResize(self, event):
- self.EthercatNodeEditor.GetBestSize()
- xstart, ystart = self.EthercatNodeEditor.GetViewStart()
- window_size = self.EthercatNodeEditor.GetClientSize()
- maxx, maxy = self.EthercatNodeEditor.GetMinSize()
+CIA402NodeEditor = NodeEditor
+
+
+def GetModulesTableColnames():
+ _ = lambda x : x
+ return [_("Name"), _("PDO alignment (bits)")]
+
+class LibraryEditorPanel(wx.ScrolledWindow):
+
+ def __init__(self, parent, module_library, buttons):
+ wx.ScrolledWindow.__init__(self, parent,
+ style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
+ self.Bind(wx.EVT_SIZE, self.OnResize)
+
+ self.ModuleLibrary = module_library
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=5)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(1)
+ main_sizer.AddGrowableRow(3)
+
+ ESI_files_label = wx.StaticText(self,
+ label=_("ESI Files:"))
+ main_sizer.AddWindow(ESI_files_label, border=10,
+ flag=wx.TOP|wx.LEFT|wx.RIGHT)
+
+ folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
+ folder_tree_sizer.AddGrowableCol(0)
+ folder_tree_sizer.AddGrowableRow(0)
+ main_sizer.AddSizer(folder_tree_sizer, border=10,
+ flag=wx.GROW|wx.LEFT|wx.RIGHT)
+
+ self.ESIFiles = FolderTree(self, self.GetPath(), editable=False)
+ self.ESIFiles.SetFilter(".xml")
+ self.ESIFiles.SetMinSize(wx.Size(600, 300))
+ folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
+
+ buttons_sizer = wx.BoxSizer(wx.VERTICAL)
+ folder_tree_sizer.AddSizer(buttons_sizer,
+ flag=wx.ALIGN_CENTER_VERTICAL)
+
+ for idx, (name, bitmap, help, callback) in enumerate(buttons):
+ 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)
+ if idx > 0:
+ flag = wx.TOP
+ else:
+ flag = 0
+ if callback is None:
+ callback = getattr(self, "On" + name, None)
+ if callback is not None:
+ self.Bind(wx.EVT_BUTTON, callback, button)
+ buttons_sizer.AddWindow(button, border=10, flag=flag)
+
+ modules_label = wx.StaticText(self,
+ label=_("Modules library:"))
+ main_sizer.AddSizer(modules_label, border=10,
+ flag=wx.LEFT|wx.RIGHT)
+
+ self.ModulesGrid = wx.gizmos.TreeListCtrl(self,
+ style=wx.TR_DEFAULT_STYLE |
+ wx.TR_ROW_LINES |
+ wx.TR_COLUMN_LINES |
+ wx.TR_HIDE_ROOT |
+ wx.TR_FULL_ROW_HIGHLIGHT)
+ self.ModulesGrid.SetMinSize(wx.Size(600, 300))
+ self.ModulesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DCLICK,
+ self.OnModulesGridLeftDClick)
+ main_sizer.AddWindow(self.ModulesGrid, border=10,
+ flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ for colname, colsize, colalign in zip(GetModulesTableColnames(),
+ [400, 150],
+ [wx.ALIGN_LEFT, wx.ALIGN_RIGHT]):
+ self.ModulesGrid.AddColumn(_(colname), colsize, colalign)
+ self.ModulesGrid.SetMainColumn(0)
+
+ def GetPath(self):
+ return self.ModuleLibrary.GetPath()
+
+ def GetSelectedFilePath(self):
+ return self.ESIFiles.GetPath()
+
+ def RefreshView(self):
+ self.ESIFiles.RefreshTree()
+ self.RefreshModulesGrid()
+
+ def RefreshModulesGrid(self):
+ root = self.ModulesGrid.GetRootItem()
+ if not root.IsOk():
+ root = self.ModulesGrid.AddRoot("Modules")
+ self.GenerateModulesGridBranch(root,
+ self.ModuleLibrary.GetModulesLibrary(),
+ GetVariablesTableColnames())
+ self.ModulesGrid.Expand(root)
+
+ def GenerateModulesGridBranch(self, root, modules, colnames):
+ item, root_cookie = self.ModulesGrid.GetFirstChild(root)
+
+ no_more_items = not item.IsOk()
+ for module in modules:
+ if no_more_items:
+ item = self.ModulesGrid.AppendItem(root, "")
+ self.ModulesGrid.SetItemText(item, module["name"], 0)
+ if module["infos"] is not None:
+ self.ModulesGrid.SetItemText(item, str(module["infos"]["alignment"]), 1)
+ else:
+ self.ModulesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
+ self.ModulesGrid.SetItemPyData(item, module["infos"])
+ self.GenerateModulesGridBranch(item, module["children"], colnames)
+ if not no_more_items:
+ item, root_cookie = self.ModulesGrid.GetNextChild(root, root_cookie)
+ no_more_items = not item.IsOk()
+
+ if not no_more_items:
+ to_delete = []
+ while item.IsOk():
+ to_delete.append(item)
+ item, root_cookie = self.ModulesGrid.GetNextChild(root, root_cookie)
+ for item in to_delete:
+ self.ModulesGrid.Delete(item)
+
+ def OnImportButton(self, event):
+ dialog = wx.FileDialog(self,
+ _("Choose an XML file"),
+ os.getcwd(), "",
+ _("XML files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
+
+ if dialog.ShowModal() == wx.ID_OK:
+ filepath = dialog.GetPath()
+ if self.ModuleLibrary.ImportModuleLibrary(filepath):
+ wx.CallAfter(self.RefreshView)
+ else:
+ message = wx.MessageDialog(self,
+ _("No such XML file: %s\n") % filepath,
+ _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+ dialog.Destroy()
+
+ event.Skip()
+
+ def OnDeleteButton(self, event):
+ filepath = self.GetSelectedFilePath()
+ 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.ModuleLibrary.LoadModules()
+ wx.CallAfter(self.RefreshView)
+ event.Skip()
+
+ def OnModulesGridLeftDClick(self, event):
+ item, flags, col = self.ModulesGrid.HitTest(event.GetPosition())
+ if item.IsOk():
+ entry_infos = self.ModulesGrid.GetItemPyData(item)
+ if entry_infos is not None and col == 1:
+ dialog = wx.TextEntryDialog(self,
+ _("Set PDO alignment (bits):"),
+ _("%s PDO alignment") % self.ModulesGrid.GetItemText(item),
+ str(entry_infos["alignment"]))
+
+ if dialog.ShowModal() == wx.ID_OK:
+ try:
+ self.ModuleLibrary.SetAlignment(
+ entry_infos["vendor"],
+ entry_infos["product_code"],
+ entry_infos["revision_number"],
+ int(dialog.GetValue()))
+ wx.CallAfter(self.RefreshModulesGrid)
+ except ValueError:
+ message = wx.MessageDialog(self,
+ _("Module PDO alignment must be an integer!"),
+ _("Error"), wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
+
+ dialog.Destroy()
+
+ event.Skip()
+
+ def OnResize(self, event):
+ self.GetBestSize()
+ xstart, ystart = self.GetViewStart()
+ window_size = self.GetClientSize()
+ maxx, maxy = self.GetMinSize()
posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
- self.EthercatNodeEditor.Scroll(posx, posy)
- self.EthercatNodeEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
+ self.Scroll(posx, posy)
+ self.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
event.Skip()
-CIA402NodeEditor = NodeEditor
+class DatabaseManagementDialog(wx.Dialog):
+
+ def __init__(self, parent, database):
+ wx.Dialog.__init__(self, parent,
+ size=wx.Size(700, 500), title=_('ESI Files Database management'),
+ style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
+
+ main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
+ main_sizer.AddGrowableCol(0)
+ main_sizer.AddGrowableRow(0)
+
+ self.DatabaseEditor = LibraryEditorPanel(self, database,
+ [("ImportButton", "ImportESI", _("Import file to ESI files database"), None),
+ ("DeleteButton", "remove_element", _("Remove file from database"), None)])
+ main_sizer.AddWindow(self.DatabaseEditor, border=10,
+ flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+
+ button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+ button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
+ button_sizer.GetCancelButton().SetLabel(_("Close"))
+ main_sizer.AddSizer(button_sizer, border=10,
+ flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
+
+ self.SetSizer(main_sizer)
+
+ self.DatabaseEditor.RefreshView()
+
+ def GetValue(self):
+ return self.DatabaseEditor.GetSelectedFilePath()
+
+class LibraryEditor(ConfTreeNodeEditor):
+
+ CONFNODEEDITOR_TABS = [
+ (_("Modules Library"), "_create_ModuleLibraryEditor")]
+
+ def _create_ModuleLibraryEditor(self, prnt):
+ self.ModuleLibraryEditor = LibraryEditorPanel(prnt,
+ self.Controler.GetModulesLibraryInstance(),
+ [("ImportButton", "ImportESI", _("Import ESI file"), None),
+ ("AddButton", "ImportDatabase", _("Add file from ESI files database"), self.OnAddButton),
+ ("DeleteButton", "remove_element", _("Remove file from library"), None)])
+
+ return self.ModuleLibraryEditor
+
+ def __init__(self, parent, controler, window):
+ ConfTreeNodeEditor.__init__(self, parent, controler, window)
+
+ self.RefreshView()
+
+ def RefreshView(self):
+ ConfTreeNodeEditor.RefreshView(self)
+ self.ModuleLibraryEditor.RefreshView()
+
+ def OnAddButton(self, event):
+ dialog = DatabaseManagementDialog(self,
+ self.Controler.GetModulesDatabaseInstance())
+
+ if dialog.ShowModal() == wx.ID_OK:
+ module_library = self.Controler.GetModulesLibraryInstance()
+ module_library.ImportModuleLibrary(dialog.GetValue())
+
+ dialog.Destroy()
+
+ wx.CallAfter(self.ModuleLibraryEditor.RefreshView)
+
+ event.Skip()
+
diff -r c9b0340ea0f5 -r 58d07e039896 etherlab/etherlab.py
--- a/etherlab/etherlab.py Thu Feb 07 00:59:50 2013 +0100
+++ b/etherlab/etherlab.py Wed Feb 27 22:40:45 2013 +0100
@@ -3,12 +3,13 @@
from xml.dom import minidom
import wx
+import csv
from xmlclass import *
from POULibrary import POULibrary
from ConfigTreeNode import ConfigTreeNode
from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
-from ConfigEditor import NodeEditor, CIA402NodeEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE
+from ConfigEditor import NodeEditor, CIA402NodeEditor, LibraryEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE
try:
from MotionLibrary import Headers, AxisXSD
@@ -571,7 +572,7 @@
"product_code": slave["product_code"],
"revision_number":slave["revision_number"],
}
- device = self.GetModuleInfos(type_infos)
+ device, alignment = self.GetModuleInfos(type_infos)
if device is not None:
if HAS_MCL and _EthercatCIA402SlaveCTN.NODE_PROFILE in device.GetProfileNumbers():
CTNType = "EthercatCIA402Slave"
@@ -651,7 +652,7 @@
slave = self.GetSlave(slave_pos)
if slave is not None:
type_infos = slave.getType()
- device = self.GetModuleInfos(type_infos)
+ device, alignement = self.GetModuleInfos(type_infos)
if device is not None:
infos = type_infos.copy()
entries = device.GetEntriesList()
@@ -688,7 +689,7 @@
if slave is not None:
type_infos = slave.getType()
- device = self.GetModuleInfos(type_infos)
+ device, alignement = self.GetModuleInfos(type_infos)
if device is not None:
sync_managers = []
for sync_manager in device.getSm():
@@ -1016,7 +1017,7 @@
slave_pos = (slave_alias, alias[slave_alias])
# Extract slave device informations
- device = self.Controler.GetModuleInfos(type_infos)
+ device, alignement = self.Controler.GetModuleInfos(type_infos)
if device is not None:
# Extract slaves variables to be mapped
@@ -1473,10 +1474,7 @@
"Type": subitem.getType(),
"BitSize": subitem.getBitSize(),
"Access": subitem_access,
- "PDOMapping": subitem_pdomapping,
- "PDO index": "",
- "PDO name": "",
- "PDO type": ""}
+ "PDOMapping": subitem_pdomapping}
else:
entry_access = ""
entry_pdomapping = ""
@@ -1495,10 +1493,7 @@
"Type": entry_type,
"BitSize": object.getBitSize(),
"Access": entry_access,
- "PDOMapping": entry_pdomapping,
- "PDO index": "",
- "PDO name": "",
- "PDO type": ""}
+ "PDOMapping": entry_pdomapping}
for TxPdo in self.getTxPdo():
ExtractPdoInfos(TxPdo, "Transmit", entries)
@@ -1578,53 +1573,33 @@
"Name": ExtractName(pdo_entry.getName()),
"Type": entry_type.getcontent(),
"Access": access,
- "PDOMapping": pdomapping,
- "PDO index": pdo_index,
- "PDO name": pdo_name,
- "PDO type": pdo_type}
-
-class RootClass:
-
- CTNChildrenTypes = [("EthercatNode",_EthercatCTN,"Ethercat Master")]
-
- def __init__(self):
- self.LoadModulesLibrary()
-
- def GetModulesLibraryPath(self):
- library_path = os.path.join(self.CTNPath(), "modules")
- if not os.path.exists(library_path):
- os.mkdir(library_path)
- return library_path
-
- def _ImportModuleLibrary(self):
- dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose an XML file"), os.getcwd(), "", _("XML files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
- if dialog.ShowModal() == wx.ID_OK:
- filepath = dialog.GetPath()
- if os.path.isfile(filepath):
- shutil.copy(filepath, self.GetModulesLibraryPath())
- self.LoadModulesLibrary()
- else:
- self.GetCTRoot().logger.write_error(_("No such XML file: %s\n") % filepath)
- dialog.Destroy()
-
- ConfNodeMethods = [
- {"bitmap" : "ImportESI",
- "name" : _("Import module library"),
- "tooltip" : _("Import module library"),
- "method" : "_ImportModuleLibrary"},
- ]
-
- def CTNGenerate_C(self, buildpath, locations):
- return [],"",False
-
- def LoadModulesLibrary(self):
- self.ModulesLibrary = {}
-
- library_path = self.GetModulesLibraryPath()
-
- files = os.listdir(library_path)
+ "PDOMapping": pdomapping}
+
+DEFAULT_ALIGNMENT = 8
+
+class ModulesLibrary:
+
+ def __init__(self, path, parent_library=None):
+ self.Path = path
+ if not os.path.exists(self.Path):
+ os.makedirs(self.Path)
+ self.ParentLibrary = parent_library
+
+ self.LoadModules()
+ self.LoadAlignments()
+
+ def GetPath(self):
+ return self.Path
+
+ def GetAlignmentFilePath(self):
+ return os.path.join(self.Path, "alignments.cfg")
+
+ def LoadModules(self):
+ self.Library = {}
+
+ files = os.listdir(self.Path)
for file in files:
- filepath = os.path.join(library_path, file)
+ filepath = os.path.join(self.Path, file)
if os.path.isfile(filepath) and os.path.splitext(filepath)[-1] == ".xml":
xmlfile = open(filepath, 'r')
xml_tree = minidom.parse(xmlfile)
@@ -1639,9 +1614,9 @@
if modules_infos is not None:
vendor = modules_infos.getVendor()
- vendor_category = self.ModulesLibrary.setdefault(ExtractHexDecValue(vendor.getId()),
- {"name": ExtractName(vendor.getName(), _("Miscellaneous")),
- "groups": {}})
+ vendor_category = self.Library.setdefault(ExtractHexDecValue(vendor.getId()),
+ {"name": ExtractName(vendor.getName(), _("Miscellaneous")),
+ "groups": {}})
for group in modules_infos.getDescriptions().getGroups().getGroup():
group_type = group.getType()
@@ -1656,10 +1631,10 @@
if not vendor_category["groups"].has_key(device_group):
raise ValueError, "Not such group \"%\"" % device_group
vendor_category["groups"][device_group]["devices"].append((device.getType().getcontent(), device))
-
+
def GetModulesLibrary(self, profile_filter=None):
library = []
- for vendor_id, vendor in self.ModulesLibrary.iteritems():
+ for vendor_id, vendor in self.Library.iteritems():
groups = []
children_dict = {}
for group_type, group in vendor["groups"].iteritems():
@@ -1671,12 +1646,16 @@
device_dict = {}
for device_type, device in group["devices"]:
if profile_filter is None or profile_filter in device.GetProfileNumbers():
+ product_code = device.getType().getProductCode()
+ revision_number = device.getType().getRevisionNo()
+ alignment = self.GetAlignment(vendor_id, product_code, revision_number)
device_infos = {"name": ExtractName(device.getName()),
"type": ETHERCAT_DEVICE,
"infos": {"device_type": device_type,
"vendor": vendor_id,
- "product_code": device.getType().getProductCode(),
- "revision_number": device.getType().getRevisionNo()},
+ "product_code": product_code,
+ "revision_number": revision_number,
+ "alignment": alignment},
"children": []}
group_infos["children"].append(device_infos)
device_type_occurrences = device_dict.setdefault(device_type, [])
@@ -1698,17 +1677,115 @@
"children": groups})
library.sort(lambda x, y: cmp(x["name"], y["name"]))
return library
-
- def GetModuleInfos(self, type_infos):
- vendor = self.ModulesLibrary.get(ExtractHexDecValue(type_infos["vendor"]), None)
- if vendor is not None:
- for group_name, group in vendor["groups"].iteritems():
- for device_type, device in group["devices"]:
- product_code = ExtractHexDecValue(device.getType().getProductCode())
- revision_number = ExtractHexDecValue(device.getType().getRevisionNo())
- if (product_code == ExtractHexDecValue(type_infos["product_code"]) and
- revision_number == ExtractHexDecValue(type_infos["revision_number"])):
- return device
- return None
+
+ def GetModuleInfos(self, module_infos):
+ vendor = ExtractHexDecValue(module_infos["vendor"])
+ vendor_infos = self.Library.get(vendor)
+ if vendor_infos is not None:
+ for group_name, group_infos in vendor_infos["groups"].iteritems():
+ for device_type, device_infos in group_infos["devices"]:
+ product_code = ExtractHexDecValue(device_infos.getType().getProductCode())
+ revision_number = ExtractHexDecValue(device_infos.getType().getRevisionNo())
+ if (product_code == ExtractHexDecValue(module_infos["product_code"]) and
+ revision_number == ExtractHexDecValue(module_infos["revision_number"])):
+ return device_infos, self.GetAlignment(vendor, product_code, revision_number)
+ return None, None
+
+ def ImportModuleLibrary(self, filepath):
+ if os.path.isfile(filepath):
+ shutil.copy(filepath, self.Path)
+ self.LoadModules()
+ return True
+ return False
+
+ def LoadAlignments(self):
+ self.Alignments = {}
+
+ csvfile_path = self.GetAlignmentFilePath()
+ if os.path.exists(csvfile_path):
+ csvfile = open(csvfile_path, "rb")
+ sample = csvfile.read(1024)
+ csvfile.seek(0)
+ dialect = csv.Sniffer().sniff(sample)
+ has_header = csv.Sniffer().has_header(sample)
+ reader = csv.reader(csvfile, dialect)
+ for row in reader:
+ if has_header:
+ has_header = False
+ else:
+ try:
+ self.Alignments[tuple(map(int, row[:3]))] = row[3]
+ except:
+ pass
+ csvfile.close()
+
+ def SaveAlignments(self):
+ csvfile = open(self.GetAlignmentFilePath(), "wb")
+ writer = csv.writer(csvfile, delimiter=';')
+ writer.writerow(['Vendor', 'product_code', 'revision_number', 'alignment'])
+ for (vendor, product_code, revision_number), alignment in self.Alignments.iteritems():
+ writer.writerow([vendor, product_code, revision_number, alignment])
+ csvfile.close()
+
+ def SetAlignment(self, vendor, product_code, revision_number, alignment):
+ vendor = ExtractHexDecValue(vendor)
+ product_code = ExtractHexDecValue(product_code)
+ revision_number = ExtractHexDecValue(revision_number)
+
+ self.Alignments[tuple([vendor, product_code, revision_number])] = alignment
+ self.SaveAlignments()
+
+ def GetAlignment(self, vendor, product_code, revision_number):
+ vendor = ExtractHexDecValue(vendor)
+ product_code = ExtractHexDecValue(product_code)
+ revision_number = ExtractHexDecValue(revision_number)
+
+ alignment = self.Alignments.get(tuple([vendor, product_code, revision_number]))
+ if alignment is not None:
+ return alignment
+
+ if self.ParentLibrary is not None:
+ return self.ParentLibrary.GetAlignment(vendor, product_code, revision_number)
+ return DEFAULT_ALIGNMENT
+
+USERDATA_DIR = wx.StandardPaths.Get().GetUserDataDir()
+if wx.Platform != '__WXMSW__':
+ USERDATA_DIR += '_files'
+
+ModulesDatabase = ModulesLibrary(
+ os.path.join(USERDATA_DIR, "ethercat_modules"))
+
+class RootClass:
+
+ CTNChildrenTypes = [("EthercatNode",_EthercatCTN,"Ethercat Master")]
+ EditorType = LibraryEditor
+
+ def __init__(self):
+ self.ModulesLibrary = None
+ self.LoadModulesLibrary()
+
+ def GetModulesLibraryPath(self):
+ return os.path.join(self.CTNPath(), "modules")
+
+ def CTNGenerate_C(self, buildpath, locations):
+ return [],"",False
+
+ def LoadModulesLibrary(self):
+ if self.ModulesLibrary is None:
+ self.ModulesLibrary = ModulesLibrary(self.GetModulesLibraryPath(), ModulesDatabase)
+ else:
+ self.ModulesLibrary.LoadModulesLibrary()
+
+ def GetModulesDatabaseInstance(self):
+ return ModulesDatabase
+
+ def GetModulesLibraryInstance(self):
+ return self.ModulesLibrary
+
+ def GetModulesLibrary(self, profile_filter=None):
+ return self.ModulesLibrary.GetModulesLibrary(profile_filter)
+
+ def GetModuleInfos(self, module_infos):
+ return self.ModulesLibrary.GetModuleInfos(module_infos)
diff -r c9b0340ea0f5 -r 58d07e039896 etherlab/images/ImportDatabase.png
Binary file etherlab/images/ImportDatabase.png has changed
diff -r c9b0340ea0f5 -r 58d07e039896 etherlab/images/icons.svg
--- a/etherlab/images/icons.svg Thu Feb 07 00:59:50 2013 +0100
+++ b/etherlab/images/icons.svg Wed Feb 27 22:40:45 2013 +0100
@@ -15,7 +15,7 @@
height="1052.3622"
id="svg2"
sodipodi:version="0.32"
- inkscape:version="0.48.2 r9819"
+ inkscape:version="0.48.3.1 r9886"
sodipodi:docname="icons.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%%ImportESI ScanNetwork editSlave editCIA402Slave CIA402AxisRef %%
+ y="120.42097">%%ImportESI ImportDatabase ScanNetwork editSlave editCIA402Slave CIA402AxisRef %%
@@ -59285,7 +61653,7 @@
@@ -59573,7 +61941,8 @@
height="1052.3622" />
+ id="g12739"
+ transform="translate(110,0)">
+ style="fill-rule:evenodd;stroke:url(#linearGradient13636);stroke-width:0.12755789pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13638);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13640);stroke-width:0.02369117pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13642);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13644);fill-opacity:1;fill-rule:evenodd;stroke:#7f755d;stroke-width:0.0312406pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13646);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13648);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="opacity:0.66134183;fill:url(#linearGradient13650);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13652);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13654);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13656);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13602);fill-opacity:1;stroke:none" />
+ style="fill:url(#linearGradient13604);fill-opacity:1;stroke:none" />
@@ -60835,7 +63204,7 @@
sodipodi:nodetypes="cccccc"
id="path1919-2"
d="m 6.3306155,244.87972 c 0,0 -2.616026,-2.68246 -3.762417,-3.01369 -1.146391,-0.33124 -2.78605395,0.63625 -2.78605395,0.63625 l -8.95013235,8.40586 4.9440407,3.25217 10.5545626,-9.28059 z"
- style="fill-rule:evenodd;stroke:url(#linearGradient20066-5);stroke-width:0.12755789pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill-rule:evenodd;stroke:url(#linearGradient13614);stroke-width:0.12755789pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13616);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13618);stroke-width:0.02369117pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13620);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13622);fill-opacity:1;fill-rule:evenodd;stroke:#7f755d;stroke-width:0.0312406pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ style="fill:url(#linearGradient13624);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13626);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="opacity:0.66134183;fill:url(#linearGradient13628);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13630);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13632);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ style="fill:url(#linearGradient13634);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+