March 18, 2011
Reference : GD-2011-0008-A
Author : Thor Vollset and Roar Larsen
Tordivel AS
The Scorpion Python Plugin Interface is defined to be able to used python and .Net as tightly integrated extentions to the Scorpion Vision Software.
The specification was defined in February 2011 to be able to defined a large number of graphical elements to easily extend Scorpion Vision Software 9.0. The first work on plugin were started in 2009.
1.1 Config Property
The new components shall manage it persistence in the same manner as the Standard ActiveX Plugins with getConfig and setConfig methods. Configuration as text. The suggested text format is Scorpion SPB format.
1.2 Naming conventions
Plugins should follow the global python world coding standards
1.2 Revisions
The primary purpose is to create protected scope for the plugin.
  def <name>(<params>):
    <imports >
    < object definition >
    return plugin instance
See sample plugin object in section 4.
  executeCmd(command,parameters)        # return true = success else fail
  configure()                 # return true if config is changed Â
                      # open configuration dialogue
The following functions shall be available for the Plugins Â
Reference to Scorpion Online Help - TBD
The plugin template is defined in this section.
class MyPlugin(object):
 '''
 MyPlugin - sample plugin template
 Version 1.0
 TORDIVEL AS, roar@tordivel.no
 '''
 def __init__(self,name):
  self.name=name
  import sys
  if 'Scorpion' in sys.modules:
   RegisterCallback('System.ImageComplete',self.onImageComplete)
   RegisterCallback('System.Timer',self.onTimer)
   RegisterCallback('System.MouseMove',self.onMouseMove)
   RegisterCallback('System.MouseDown',self.onMouseDown)
   RegisterCallback('System.MouseUp',self.onMouseUp)
  else:
   RegisterCallback('OnInit',self.onInit)
   RegisterCallback('OnClose',self.onClose)
   RegisterCallback('OnImageComplete',self.onImageComplete)
   RegisterCallback('OnImage',self.onImage)
   RegisterCallback('OnTimer',self.onTimer)
   RegisterCallback('OnMouseMove',self.onMouseMove)
   RegisterCallback('OnMouseDown',self.onMouseDown)
   RegisterCallback('OnMouseUp',self.onMouseUp)
 def __str__(self):
  '''
  return unique plugin instance name
  '''
  return '%s_%s'%(self.__class__.__name__,self.name)
 def getConfig(self):
  '''
  return plugin configuration as string
  '''
  import SPB
  spb=SPB.CreateSpb()
  #TODO: insert configuration into spb
  spb.setInt('cfgver',1) #use cfgver to handle plugin compatibility
  return spb.xml
 def setConfig(self,value):
  '''
  set plugin configuration from string
  '''
  import SPB
  spb=SPB.CreateSpb(value)
  #TODO: extract configuration from spb
  if spb.getInt('cfgver')<>1:
   print '%s: Invalid config version'%self
 def configure(self):
  '''
  launch plugin configuration dialog,
  return True/False if configuratrion changed
  '''
  return False
 def onInit(self):
  print '%s.onInit()'%(self)
 def onClose(self):
  print '%s.onClose()'%(self)
 def onImageComplete(self,image):
  print '%s.onImageComplete(%s)'%(self,image)
 def onImage(self):
  print '%s.onImage()'%(self)
 def onTimer(self,timer):
  print '%s.onTimer(%s)'%(self,timer)
 def onMouseMove(self,image,shift,x,y):
  print '%s.onMouseMove(%s,%i,%f,%f)'%(self,image,shift,x,y)
 def onMouseDown(self,image,shift,x,y):
  print '%s.onMouseDown(%s,%i,%f,%f)'%(self,image,shift,x,y)
 def onMouseUp(self,image,shift,x,y):
  print '%s.onMouseUp(%s,%i,%f,%f)'%(self,image,shift,x,y)
This section shows the principal structure of the construction methods used to define scope to make the plugin complete without sideeffects.
##########################################################################
#
# LogoPlugin.py
# Plugin Sample
#
# Version 1.0.0.2
# 2011-02-02
#
##########################################################################
# Revision history
#
# 1.0.0.1 - 02feb2011: Initial version
# 1.0.0.2 - 19mar2011:
 # Converted to new Plugin Architecture
 # Use Python Documentation Standard for comments
def CreatePlugin(hWnd,name=''):
 '''
 standard plugin creation
 '''
 import os
 import types
 import SPB
 import dotNetUtils
 try:   import clr as CLR
 except: import CLR
 import CLR.System.Windows.Forms as WinForms
 from CLR.System.Reflection import Assembly
 Assembly.LoadFrom(os.path.join(os.path.dirname(os.path.realpath(__file__)), "DynamicPropertyClass.dll"))
 from CLR.PythonHelpers import DynamicPropertyClass
 from CLR.PythonHelpers import CustomProperty
 from Scorpion import GetStringValue
 from Scorpion import GetBoolValue
 from Scorpion import PluginChanged
 class LogoPanel(WinForms.Panel):
  '''
  LogoPanel 1.1 - .net plugin panel showing an image - typically logo
  TORDIVEL AS, 18mar2011, roar@tordivel.no
  '''
  class PropertiesDialog(WinForms.Form):
   '''
   configuration dialog for LogonPanel
   '''
   def __init__(self):
    self.dataObject = None
    self.okButton = WinForms.Button()
    self.cancelButton = WinForms.Button()
    self.propertyGrid = WinForms.PropertyGrid()
    self._initializeControls()
   def _initializeControls(self):
    self.SuspendLayout()
    # okButton
    self.okButton.Anchor = WinForms.AnchorStyles.Top + WinForms.AnchorStyles.Right
    self.okButton.Location = CLR.System.Drawing.Point(205, 12)
    self.okButton.Name = "okButton"
    self.okButton.Size = CLR.System.Drawing.Size(75, 23)
    self.okButton.TabIndex = 0
    self.okButton.Text = "OK"
    self.okButton.UseVisualStyleBackColor = True
    self.okButton.DialogResult = WinForms.DialogResult.OK;
    # cancelButton
    self.cancelButton.Anchor = WinForms.AnchorStyles.Top + WinForms.AnchorStyles.Right
    self.cancelButton.DialogResult = WinForms.DialogResult.Cancel
    self.cancelButton.Location = CLR.System.Drawing.Point(205, 42)
    self.cancelButton.Name = "cancelButton"
    self.cancelButton.Size = CLR.System.Drawing.Size(75, 23)
    self.cancelButton.TabIndex = 1
    self.cancelButton.Text = "Cancel"
    self.cancelButton.UseVisualStyleBackColor = True
    # propertyGrid
    self.propertyGrid.Anchor = WinForms.AnchorStyles.Top + WinForms.AnchorStyles.Bottom + WinForms.AnchorStyles.Left + WinForms.AnchorStyles.Right
    self.propertyGrid.Location = CLR.System.Drawing.Point(13, 12)
    self.propertyGrid.Name = "propertyGrid"
    self.propertyGrid.Size = CLR.System.Drawing.Size(186, 249)
    self.propertyGrid.TabIndex = 2
    self.propertyGrid.PropertyValueChanged += self._onPropertyValueChanged
    # SettingsForm
    self.AcceptButton = self.okButton
    self.AutoScaleMode = WinForms.AutoScaleMode.Font
    self.CancelButton = self.cancelButton
    self.ClientSize = CLR.System.Drawing.Size(292, 273)
    self.Controls.Add(self.propertyGrid)
    self.Controls.Add(self.cancelButton)
    self.Controls.Add(self.okButton)
    self.Name = "SettingsForm"
    self.StartPosition = WinForms.FormStartPosition.CenterParent
    self.Text = "Settings"
    self.ResumeLayout(False)
    self.Size = CLR.System.Drawing.Size(640, 480)
   def _onPropertyValueChanged(self, sender, propertyValueChangedEventArgs):
    if isinstance(self.dataObject, types.DictionaryType):
     if self.dataObject.has_key(propertyValueChangedEventArgs.ChangedItem.Label):
      newValue = propertyValueChangedEventArgs.ChangedItem.Value
      oldValue = self.dataObject[propertyValueChangedEventArgs.ChangedItem.Label]
      try:
       if isinstance(oldValue, str):
        newValue = str(newValue)
       elif isinstance(oldValue, int):
        newValue = int(newValue)
       elif isinstance(oldValue, float):
        newValue = float(newValue)
       elif isinstance(oldValue, bool):
        newValue = bool(newValue)
       elif isinstance(oldValue, list):
        newValue = [newValue]
        newValue.extend(oldValue[1:])
      except Exception, msg:
       print str(msg)
      if newValue != None:
       self.dataObject[propertyValueChangedEventArgs.ChangedItem.Label] = newValue
    sender.Refresh()
   def SetDataObject(self, dataObject, readOnly = True, categories={}):
    # Transform dictionary for quick access
    tmp = {}
    for categoryName, propNameList in categories.iteritems():
     for propertyName in propNameList:
      tmp[propertyName] = categoryName
    categories = tmp
    self.dataObject = dataObject
    properties = DynamicPropertyClass()
    if isinstance(self.dataObject, types.DictionaryType):
     for key, value in self.dataObject.iteritems():
      prop = CustomProperty(key, value, readOnly, True)
      if hasattr(value, "Category"):
       prop.Category = str(getattr(value, "Category"))
      elif categories.has_key(key):
       prop.Category = str(categories[key])
      if isinstance(value, types.ListType):
       if len(value) > 0:
        prop.Value = value[0]
       prop.IsList = True;
       for item in value[1:]:
        prop.AddListItem(str(item))
      properties.Add(prop)
    else:
     pass
    self.propertyGrid.SelectedObject = properties
  class Settings(object):
   def __init__(self):
    self.FileName  = "tordivel_colour_RGB_sidestilt.gif"
    self.Alignment = "CenterImage"
    self.BackColor = CLR.System.Drawing.SystemColors.Control
   def GetAlignmentEnum(self):
    return CLR.System.Enum.Parse(CLR.System.Windows.Forms.PictureBoxSizeMode, self.Alignment)
   def GetDataObject(self):
    return { "FileName"  : self.FileName,
         "Alignment" : [self.Alignment, "AutoSize", "CenterImage", "Normal", "StretchImage", "Zoom" ],
         "BackColor" : self.BackColor }
   def UpdateDataObject(self, dataObject):
    self.FileName  = dataObject["FileName"]
    self.Alignment = dataObject["Alignment"][0]
    self.BackColor = dataObject["BackColor"]
   def GetCategories(self):
    return { "Image"   : [ "FileName", "Alignment", "BackColor" ] }
  def __init__(self,name):
   self.name=name
   self.settings = LogoPanel.Settings()
   self.Dock = WinForms.DockStyle.Fill
   self.DockPadding.All = 0
   self.profilePath = GetStringValue("System.Profile")
   # Create picture box
   self._pictureBox = WinForms.PictureBox()
   self._pictureBox.Location  = CLR.System.Drawing.Point(0, 0)
   self._pictureBox.Name    = "pictureBox"
   self._pictureBox.Dock    = WinForms.DockStyle.Fill
   self.Controls.Add(self._pictureBox)
   self._load()
   # Create context menu
   self.components = CLR.System.ComponentModel.Container()
   self.contextMenuStrip = WinForms.ContextMenuStrip(self.components)
   self.settingsToolStripMenuItem = WinForms.ToolStripMenuItem()
   # contextMenuStrip
   self.contextMenuStrip.Items.Add(self.settingsToolStripMenuItem)
   self.contextMenuStrip.Name = "contextMenuStrip"
   self.contextMenuStrip.Size = CLR.System.Drawing.Size(153, 48)
   self.contextMenuStrip.Opening += CLR.System.ComponentModel.CancelEventHandler(self.contextMenuStrip_Opening)
   # settingsToolStripMenuItem
   self.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"
   self.settingsToolStripMenuItem.Size = CLR.System.Drawing.Size(152, 22)
   self.settingsToolStripMenuItem.Text = "Settings"
   self.settingsToolStripMenuItem.Click += self.settingsToolStripMenuItem_Click
   self.ContextMenuStrip = self.contextMenuStrip;
  def _loadImage(self, path, folder, imageName):
   fullPath = os.path.join(path, folder, imageName)
   altPath = os.path.join(path, imageName)
   if os.path.isfile(fullPath):
     return CLR.System.Drawing.Image.FromFile(fullPath)
   elif os.path.isfile(altPath):
     return CLR.System.Drawing.Image.FromFile(altPath)
   elif os.path.isfile(imageName):
     return CLR.System.Drawing.Image.FromFile(imageName)
  def _load(self):
   self._pictureBox.Image   = self._loadImage(self.profilePath, "Python", self.settings.FileName)
   self._pictureBox.SizeMode  = self.settings.GetAlignmentEnum()
   self._pictureBox.BackColor = self.settings.BackColor
  def contextMenuStrip_Opening(self, sender, args):
   if not GetBoolValue("System.Service"):
    args.Cancel = True
  def settingsToolStripMenuItem_Click(self, sender, args):
   self.configure()
  def __str__(self):
   '''
   return unique plugin instance name
   '''
   return '%s_%s'%(self.__class__.__name__,self.name)
  def getConfig(self):
   '''
   return plugin configuration as string
   '''
   import SPB
   spb=SPB.CreateSpb()
   spb.setText('Filename',self.settings.FileName)
   spb.setText('Alignment',self.settings.Alignment)
   spb.setInt('BackColor',self.settings.BackColor.ToArgb())
   return spb.xml
  def setConfig(self,value):
   '''
   set plugin configuration from string
   '''
   import SPB
   spb=SPB.CreateSpb(value)
   try:
    if spb.isEntry('Filename'):self.settings.FileName=spb.getText('Filename')
    if spb.isEntry('Alignment'):self.settings.Alignment=spb.getText('Alignment')
    if spb.isEntry('BackColor'):self.settings.BackColor=self.settings.BackColor.FromArgb(spb.getInt('BackColor'))
    self._load()
   except:
    print self,': invalid configuration'
  def configure(self):
   '''
   launch the plugin configuration dialog
   '''
   dlg = LogoPanel.PropertiesDialog()
   dlg.SetDataObject(self.settings.GetDataObject(), False, self.settings.GetCategories())
   if dlg.ShowDialog() == WinForms.DialogResult.OK:
    self.settings.UpdateDataObject(dlg.dataObject)
    self._load();
    PluginChanged(self)   #notify host changed
 backPanel = dotNetUtils.GetDotNetPanel(hWnd)
 panel = LogoPanel(name)
 panel.Parent = backPanel
 return panel