summaryrefslogtreecommitdiff
path: root/scripting
diff options
context:
space:
mode:
Diffstat (limited to 'scripting')
-rw-r--r--scripting/build_tools/extract_docstrings.py405
-rw-r--r--scripting/build_tools/fix_swig_imports.py77
-rw-r--r--scripting/dlist.i67
-rw-r--r--scripting/kicad.i160
-rw-r--r--scripting/kicadplugins.i271
-rw-r--r--scripting/python_scripting.cpp364
-rw-r--r--scripting/python_scripting.h62
-rw-r--r--scripting/wx.i301
-rw-r--r--scripting/wx_python_helpers.cpp196
-rw-r--r--scripting/wx_python_helpers.h18
10 files changed, 1921 insertions, 0 deletions
diff --git a/scripting/build_tools/extract_docstrings.py b/scripting/build_tools/extract_docstrings.py
new file mode 100644
index 0000000..103654e
--- /dev/null
+++ b/scripting/build_tools/extract_docstrings.py
@@ -0,0 +1,405 @@
+#!/usr/bin/env python
+"""Doxygen XML to SWIG docstring converter.
+
+Converts Doxygen generated XML files into a file containing docstrings
+that can be used by SWIG >1.3.23
+
+Usage:
+
+ extract-docstrings.py input_py_wrapper.py input_xml_dir output_directory
+
+input_py_wrapper.py is a swig generated file, with/without docstrings,
+ so we can get to know which classes are inspected by swig
+
+input_xml_dir is your doxygen generated XML directory
+
+output_directory is the directory where output will be written
+
+"""
+
+# This code is implemented using Mark Pilgrim's code as a guideline:
+# http://www.faqs.org/docs/diveintopython/kgp_divein.html
+# Based in doxy2swig.py
+# Author: Prabhu Ramachandran
+# License: BSD style
+
+
+from xml.dom import minidom
+import re
+import textwrap
+import sys
+import types
+import os.path
+
+
+def my_open_read(source):
+ if hasattr(source, "read"):
+ return source
+ else:
+ return open(source)
+
+def my_open_write(dest):
+ if hasattr(dest, "write"):
+ return dest
+ else:
+ return open(dest, 'w')
+
+
+class Doxy2SWIG:
+ """Converts Doxygen generated XML files into a file containing
+ docstrings that can be used by SWIG-1.3.x that have support for
+ feature("docstring"). Once the data is parsed it is stored in
+ self.pieces.
+
+ """
+
+ def __init__(self, src):
+ """Initialize the instance given a source object (file or
+ filename).
+
+ """
+ f = my_open_read(src)
+ self.my_dir = os.path.dirname(f.name)
+ self.xmldoc = minidom.parse(f).documentElement
+ f.close()
+
+ self.pieces = []
+ self.pieces.append('\n// File: %s\n'%\
+ os.path.basename(f.name))
+
+ self.space_re = re.compile(r'\s+')
+ self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
+ self.multi = 0
+ self.ignores = ('inheritancegraph', 'param', 'listofallmembers',
+ 'innerclass', 'name', 'declname', 'incdepgraph',
+ 'invincdepgraph', 'programlisting', 'type',
+ 'references', 'referencedby', 'location',
+ 'collaborationgraph', 'reimplements',
+ 'reimplementedby', 'derivedcompoundref',
+ 'basecompoundref')
+ #self.generics = []
+
+ def generate(self):
+ """Parses the file set in the initialization. The resulting
+ data is stored in `self.pieces`.
+
+ """
+ self.parse(self.xmldoc)
+
+ def parse(self, node):
+ """Parse a given node. This function in turn calls the
+ `parse_<nodeType>` functions which handle the respective
+ nodes.
+
+ """
+ pm = getattr(self, "parse_%s"%node.__class__.__name__)
+ pm(node)
+
+ def parse_Document(self, node):
+ self.parse(node.documentElement)
+
+ def parse_Text(self, node):
+ txt = node.data
+ txt = txt.replace('\\', r'\\\\')
+ txt = txt.replace('"', r'\"')
+ # ignore pure whitespace
+ m = self.space_re.match(txt)
+ if m and len(m.group()) == len(txt):
+ pass
+ else:
+ self.add_text(textwrap.fill(txt))
+
+ def parse_Element(self, node):
+ """Parse an `ELEMENT_NODE`. This calls specific
+ `do_<tagName>` handers for different elements. If no handler
+ is available the `generic_parse` method is called. All
+ tagNames specified in `self.ignores` are simply ignored.
+
+ """
+ name = node.tagName
+ ignores = self.ignores
+ if name in ignores:
+ return
+ attr = "do_%s" % name
+ if hasattr(self, attr):
+ handlerMethod = getattr(self, attr)
+ handlerMethod(node)
+ else:
+ self.generic_parse(node)
+ #if name not in self.generics: self.generics.append(name)
+
+ def add_text(self, value):
+ """Adds text corresponding to `value` into `self.pieces`."""
+ if type(value) in (types.ListType, types.TupleType):
+ self.pieces.extend(value)
+ else:
+ self.pieces.append(value)
+
+ def get_specific_nodes(self, node, names):
+ """Given a node and a sequence of strings in `names`, return a
+ dictionary containing the names as keys and child
+ `ELEMENT_NODEs`, that have a `tagName` equal to the name.
+
+ """
+ nodes = [(x.tagName, x) for x in node.childNodes \
+ if x.nodeType == x.ELEMENT_NODE and \
+ x.tagName in names]
+ return dict(nodes)
+
+ def generic_parse(self, node, pad=0):
+ """A Generic parser for arbitrary tags in a node.
+
+ Parameters:
+
+ - node: A node in the DOM.
+ - pad: `int` (default: 0)
+
+ If 0 the node data is not padded with newlines. If 1 it
+ appends a newline after parsing the childNodes. If 2 it
+ pads before and after the nodes are processed. Defaults to
+ 0.
+
+ """
+ npiece = 0
+ if pad:
+ npiece = len(self.pieces)
+ if pad == 2:
+ self.add_text('\n')
+ for n in node.childNodes:
+ self.parse(n)
+ if pad:
+ if len(self.pieces) > npiece:
+ self.add_text('\n')
+
+ def space_parse(self, node):
+ self.add_text(' ')
+ self.generic_parse(node)
+
+ do_ref = space_parse
+ do_emphasis = space_parse
+ do_bold = space_parse
+ do_computeroutput = space_parse
+ do_formula = space_parse
+
+ def do_compoundname(self, node):
+ self.add_text('\n\n')
+ data = node.firstChild.data
+ self.add_text('%%feature("docstring") %s "\n'%data)
+
+ def do_compounddef(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('class', 'struct'):
+ prot = node.attributes['prot'].value
+ if prot <> 'public':
+ return
+ names = ('compoundname', 'briefdescription',
+ 'detaileddescription', 'includes')
+ first = self.get_specific_nodes(node, names)
+ for n in names:
+ if first.has_key(n):
+ self.parse(first[n])
+ self.add_text(['";','\n'])
+ for n in node.childNodes:
+ if n not in first.values():
+ self.parse(n)
+ elif kind in ('file', 'namespace'):
+ nodes = node.getElementsByTagName('sectiondef')
+ for n in nodes:
+ self.parse(n)
+
+ def do_includes(self, node):
+ self.add_text('C++ includes: ')
+ self.generic_parse(node, pad=1)
+
+ def do_parameterlist(self, node):
+ self.add_text(['\n', '\n', 'Parameters:', '\n'])
+ self.generic_parse(node, pad=1)
+
+ def do_para(self, node):
+ self.add_text('\n')
+ self.generic_parse(node, pad=1)
+
+ def do_parametername(self, node):
+ self.add_text('\n')
+ try:
+ self.add_text("%s: "%node.firstChild.data)
+ except AttributeError:
+ self.add_text("???: ")
+
+ def do_parameterdefinition(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_detaileddescription(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_briefdescription(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_memberdef(self, node):
+ prot = node.attributes['prot'].value
+ id = node.attributes['id'].value
+ kind = node.attributes['kind'].value
+ tmp = node.parentNode.parentNode.parentNode
+ compdef = tmp.getElementsByTagName('compounddef')[0]
+ cdef_kind = compdef.attributes['kind'].value
+
+ if prot == 'public':
+ first = self.get_specific_nodes(node, ('definition', 'name'))
+ name = first['name'].firstChild.data
+ if name[:8] == 'operator': # Don't handle operators yet.
+ return
+
+ defn = first['definition'].firstChild.data
+ self.add_text('\n')
+ self.add_text('%feature("docstring") ')
+
+ anc = node.parentNode.parentNode
+ if cdef_kind in ('file', 'namespace'):
+ ns_node = anc.getElementsByTagName('innernamespace')
+ if not ns_node and cdef_kind == 'namespace':
+ ns_node = anc.getElementsByTagName('compoundname')
+ if ns_node:
+ ns = ns_node[0].firstChild.data
+ self.add_text(' %s::%s "\n%s'%(ns, name, defn))
+ else:
+ self.add_text(' %s "\n%s'%(name, defn))
+ elif cdef_kind in ('class', 'struct'):
+ # Get the full function name.
+ anc_node = anc.getElementsByTagName('compoundname')
+ cname = anc_node[0].firstChild.data
+ self.add_text(' %s::%s "\n%s'%(cname, name, defn))
+
+
+ for n in node.childNodes:
+ if n not in first.values():
+ self.parse(n)
+ self.add_text(['";', '\n'])
+
+ def do_definition(self, node):
+ data = node.firstChild.data
+ self.add_text('%s "\n%s'%(data, data))
+
+ def do_sectiondef(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('public-func', 'func'):
+ self.generic_parse(node)
+
+ def do_simplesect(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('date', 'rcs', 'version'):
+ pass
+ elif kind == 'warning':
+ self.add_text(['\n', 'WARNING: '])
+ self.generic_parse(node)
+ elif kind == 'see':
+ self.add_text('\n')
+ self.add_text('See: ')
+ self.generic_parse(node)
+ else:
+ self.generic_parse(node)
+
+ def do_argsstring(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_member(self, node):
+ kind = node.attributes['kind'].value
+ refid = node.attributes['refid'].value
+ if kind == 'function' and refid[:9] == 'namespace':
+ self.generic_parse(node)
+
+ def do_doxygenindex(self, node):
+ self.multi = 1
+ comps = node.getElementsByTagName('compound')
+ for c in comps:
+ refid = c.attributes['refid'].value
+ fname = refid + '.xml'
+ if not os.path.exists(fname):
+ fname = os.path.join(self.my_dir, fname)
+ print "parsing file: %s"%fname
+ p = Doxy2SWIG(fname)
+ p.generate()
+ self.pieces.extend(self.clean_pieces(p.pieces))
+
+ def write(self, fname):
+ o = my_open_write(fname)
+ if self.multi:
+ o.write("".join(self.pieces))
+ else:
+ o.write("".join(self.clean_pieces(self.pieces)))
+ o.close()
+
+ def clean_pieces(self, pieces):
+ """Cleans the list of strings given as `pieces`. It replaces
+ multiple newlines by a maximum of 2 and returns a new list.
+ It also wraps the paragraphs nicely.
+
+ """
+ ret = []
+ count = 0
+ for i in pieces:
+ if i == '\n':
+ count = count + 1
+ else:
+ if i == '";':
+ if count:
+ ret.append('\n')
+ elif count > 2:
+ ret.append('\n\n')
+ elif count:
+ ret.append('\n'*count)
+ count = 0
+ ret.append(i)
+
+ _data = "".join(ret)
+ ret = []
+ for i in _data.split('\n\n'):
+ if i == 'Parameters:':
+ ret.extend(['Parameters:\n-----------', '\n\n'])
+ elif i.find('// File:') > -1: # leave comments alone.
+ ret.extend([i, '\n'])
+ else:
+ _tmp = textwrap.fill(i.strip())
+ _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
+ ret.extend([_tmp, '\n\n'])
+ return ret
+
+
+def get_python_classes(input_py):
+ with open(input_py) as f:
+ data = f.read()
+ classes_supers = re.findall(r'class[ ]+([\w_]+)(\([\w_, ]+\))?:',data)
+ classes = (classname for classname,superclass in classes_supers)
+ return classes
+ return []
+
+def main(input_py, input_xml, output_dir):
+
+ classes = get_python_classes(input_py)
+
+ with file("%s/docstrings.i"%output_dir,'w') as f_index:
+
+ for classname in classes:
+
+
+ class_file = "%s/class%s.xml"%(input_xml,classname.replace("_","__"))
+ swig_file = "%s/%s.i"%(output_dir,classname.lower())
+
+ if os.path.isfile(class_file):
+ print "processing:",class_file," ->",swig_file
+ p = Doxy2SWIG(class_file)
+ p.generate()
+ p.write(swig_file)
+ f_index.write('%%include "%s.i"\n'% classname.lower())
+ #else:
+ # print "ignoring class %s, as %s does not exist" %(classname,class_file)
+
+
+
+
+
+if __name__ == '__main__':
+ print sys.argv
+ if len(sys.argv) != 4:
+ print __doc__
+ sys.exit(1)
+ main(sys.argv[1], sys.argv[2],sys.argv[3])
diff --git a/scripting/build_tools/fix_swig_imports.py b/scripting/build_tools/fix_swig_imports.py
new file mode 100644
index 0000000..bcf9dbd
--- /dev/null
+++ b/scripting/build_tools/fix_swig_imports.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# the purpose of this script is rewriting the swig_import_helper
+# call so it will not load _xxxxx.so/dso from inside a kicad executable
+# because the kicad executable itself sill provide an _xxxxx module
+# that's linked inside itself.
+#
+# for the normal module import it should work the same way with this
+# fix in the swig_import_helper
+#
+
+from sys import argv,exit
+
+if len(argv)<2:
+ print "usage:"
+ print " fix_swig_imports.py file.py"
+ print ""
+ print " will fix the swig import code for working inside KiCad"
+ print " where it happended that the external _pcbnew.so/dll was"
+ print " loaded too -and the internal _pcbnew module was to be used"
+ exit(1)
+
+
+filename = argv[1]
+
+f = open(filename,"rb")
+lines = f.readlines()
+f.close()
+
+
+doneOk = False
+
+if (len(lines)<4000):
+ print "still building"
+ exit(0)
+
+txt = ""
+
+for l in lines:
+ if l.startswith("if _swig_python_version_info >= (2, 7, 0):"): # ok with swig version >= 3.0.10
+ l = l.replace("_swig_python_version_info >= (2, 7, 0)","False")
+ doneOk = True
+ elif l.startswith("elif _swig_python_version_info >= (2, 6, 0):"): # needed with swig version >= 3.0.10
+ l = l.replace("_swig_python_version_info >= (2, 6, 0)","False")
+ doneOk = True
+ if l.startswith("if version_info >= (2, 7, 0):"): # ok with swig version >= 3.0.9
+ l = l.replace("version_info >= (2, 7, 0)","False")
+ doneOk = True
+ elif l.startswith("elif version_info >= (2, 6, 0):"): # needed with swig version >= 3.0.9
+ l = l.replace("version_info >= (2, 6, 0)","False")
+ doneOk = True
+ elif l.startswith("if version_info >= (2,6,0):"): # ok with swig version <= 3.0.2
+ l = l.replace("version_info >= (2,6,0)","False")
+ doneOk = True
+ elif l.startswith("if version_info >= (2, 6, 0):"): # needed with swig version 3.0.3
+ l = l.replace("version_info >= (2, 6, 0)","False")
+ doneOk = True
+ elif l.startswith("if False:"): # it was already patched?
+ doneOk = True
+ txt = txt + l
+
+f = open(filename,"wb")
+f.write(txt)
+f.close()
+
+if doneOk:
+ print "swig_import_helper fixed for",filename
+else:
+ print "Error: the swig import helper was not fixed, check",filename
+ print " and fix this script: fix_swig_imports.py"
+ exit(2)
+
+
+exit(0)
+
+
+
diff --git a/scripting/dlist.i b/scripting/dlist.i
new file mode 100644
index 0000000..9ea7712
--- /dev/null
+++ b/scripting/dlist.i
@@ -0,0 +1,67 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/* DLIST python iteration code, to allow standard iteration over DLIST */
+
+%extend DLIST
+{
+ %pythoncode
+ %{
+ class DLISTIter:
+ def __init__(self,aList):
+ self.last = aList # last item is the start of list
+
+ def next(self): # get the next item
+
+ item = self.last
+ try:
+ item = item.Get()
+ except:
+ pass
+
+ if item is None: # if the item is None, then finish the iteration
+ raise StopIteration
+ else:
+ ret = None
+
+ # first item in list has "Get" as a DLIST
+ try:
+ ret = self.last.Get()
+ except:
+ ret = self.last # next items do not..
+
+ self.last = self.last.Next()
+
+ # when the iterated object can be casted down in inheritance, just do it..
+
+ if 'Cast' in dir(ret):
+ ret = ret.Cast()
+
+ return ret
+
+ def __iter__(self):
+ return self.DLISTIter(self)
+
+ %}
+}
diff --git a/scripting/kicad.i b/scripting/kicad.i
new file mode 100644
index 0000000..073c112
--- /dev/null
+++ b/scripting/kicad.i
@@ -0,0 +1,160 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file kicad.i
+ * @brief General wrappers for kicad / wx structures and classes
+ */
+
+%include <std_vector.i>
+%include <std_basic_string.i>
+%include <std_string.i>
+%include <std_map.i>
+
+/* ignore some constructors of EDA_ITEM that will make the build fail */
+
+%nodefaultctor EDA_ITEM;
+%ignore EDA_ITEM::EDA_ITEM( EDA_ITEM* parent, KICAD_T idType );
+%ignore EDA_ITEM::EDA_ITEM( KICAD_T idType );
+%ignore EDA_ITEM::EDA_ITEM( const EDA_ITEM& base );
+
+/* swig tries to wrap SetBack/SetNext on derived classes, but this method is
+ private for most childs, so if we don't ignore it won't compile */
+
+%ignore EDA_ITEM::SetBack;
+%ignore EDA_ITEM::SetNext;
+
+/* ignore other functions that cause trouble */
+
+%ignore InitKiCadAbout;
+%ignore GetCommandOptions;
+
+%rename(getWxRect) operator wxRect;
+%ignore operator <<;
+%ignore operator=;
+
+
+/* headers/imports that must be included in the _wrapper.cpp at top */
+
+%{
+ #include <cstddef>
+ #include <dlist.h>
+ #include <base_struct.h>
+ #include <class_eda_rect.h>
+ #include <common.h>
+ #include <wx_python_helpers.h>
+ #include <cstddef>
+ #include <vector>
+ #include <bitset>
+
+ #include <class_title_block.h>
+ #include <class_colors_design_settings.h>
+ #include <class_marker_base.h>
+ #include <eda_text.h>
+ #include <convert_from_iu.h>
+ #include <convert_to_biu.h>
+%}
+
+/* all the wx wrappers for wxString, wxPoint, wxRect, wxChar .. */
+%include <wx.i>
+
+/* exception handling */
+
+/* the IO_ERROR exception handler, not working yet... */
+/*
+%exception
+{
+ try {
+ $function
+ }
+ catch (IO_ERROR e) {
+ PyErr_SetString(PyExc_IOError,"IO error");
+ return NULL;
+ }
+}
+*/
+
+/* header files that must be wrapped */
+
+%include <dlist.h>
+%include <base_struct.h>
+%include <class_eda_rect.h>
+%include <common.h>
+%include <class_title_block.h>
+%include <class_colors_design_settings.h>
+%include <class_marker_base.h>
+%include <eda_text.h>
+%include <convert_from_iu.h>
+%include <convert_to_biu.h>
+%include <fpid.h>
+
+/* special iteration wrapper for DLIST objects */
+%include "dlist.i"
+
+/* std template mappings */
+%template(intVector) std::vector<int>;
+%template(str_utf8_Map) std::map< std::string,UTF8 >;
+
+// wrapper of BASE_SEQ (see typedef std::vector<LAYER_ID> BASE_SEQ;)
+%template(base_seqVect) std::vector<enum LAYER_ID>;
+
+// TODO: wrapper of BASE_SET (see std::bitset<LAYER_ID_COUNT> BASE_SET;)
+
+
+/* KiCad plugin handling */
+%include "kicadplugins.i"
+
+// map CPolyLine and classes used in CPolyLine:
+#include <../polygon/PolyLine.h>
+%include <../polygon/PolyLine.h>
+
+// ignore warning relative to operator = and operator ++:
+#pragma SWIG nowarn=362,383
+
+// Rename operators defined in utf8.h
+%rename(utf8_to_charptr) operator char* () const;
+%rename(utf8_to_wxstring) operator wxString () const;
+
+#include <utf8.h>
+%include <utf8.h>
+
+%extend UTF8
+{
+ const char* Cast_to_CChar() { return (self->c_str()); }
+
+ %pythoncode
+ %{
+
+ # Get the char buffer of the UTF8 string
+ def GetChars(self):
+ return self.Cast_to_CChar()
+
+ # Convert the UTF8 string to a python string
+ # Same as GetChars(), but more easy to use in print command
+ def __str__(self):
+ return self.GetChars()
+
+ %}
+}
+
diff --git a/scripting/kicadplugins.i b/scripting/kicadplugins.i
new file mode 100644
index 0000000..9a310fa
--- /dev/null
+++ b/scripting/kicadplugins.i
@@ -0,0 +1,271 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+ /**
+ * This file builds the base classes for all kind of python plugins that
+ * can be included into kicad.
+ * they provide generic code to all the classes:
+ *
+ * KiCadPlugin
+ * /|\
+ * |
+ * |\-FilePlugin
+ * |\-FootprintWizardPlugin
+ * |\-ActionPlugin
+ *
+ * It defines the LoadPlugins() function that loads all the plugins
+ * available in the system
+ *
+ */
+
+/*
+ * Remark:
+ * Avoid using the print function in python wizards
+ *
+ * Be aware print messages create IO exceptions, because the wizard
+ * is run from Pcbnew. And if pcbnew is not run from a console, there is
+ * no io channel to read the output of print function.
+ * When the io buffer is full, a IO exception is thrown.
+ */
+
+%pythoncode
+{
+
+KICAD_PLUGINS={}
+
+def ReloadPlugin(name):
+ if not KICAD_PLUGINS.has_key(name):
+ return False
+
+ KICAD_PLUGINS[name]["object"].deregister()
+ mod = reload(KICAD_PLUGINS[name]["module"])
+ KICAD_PLUGINS[name]["object"]= mod.register()
+
+
+def ReloadPlugins():
+ import os.path
+ for k in KICAD_PLUGINS.keys():
+ plugin = KICAD_PLUGINS[k]
+
+ filename = plugin["filename"]
+ mtime = plugin["modification_time"]
+ now_mtime = os.path.getmtime(filename)
+
+ if mtime!=now_mtime:
+ # /* print filename, " is modified, reloading" */
+ KICAD_PLUGINS[k]["modification_time"]=now_mtime
+ ReloadPlugin(k)
+
+
+def LoadPlugins(plugpath):
+ import os
+ import sys
+
+ kicad_path = os.environ.get('KICAD_PATH')
+ plugin_directories=[]
+
+ if plugpath:
+ plugin_directories.append(plugpath)
+
+ if kicad_path:
+ plugin_directories.append(os.path.join(kicad_path, 'scripting', 'plugins'))
+
+ if sys.platform.startswith('linux'):
+ plugin_directories.append(os.environ['HOME']+'/.kicad_plugins/')
+ plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/plugins/')
+
+ for plugins_dir in plugin_directories:
+ if not os.path.isdir(plugins_dir):
+ continue
+
+ sys.path.append(plugins_dir)
+
+ for module in os.listdir(plugins_dir):
+ if os.path.isdir(plugins_dir+module):
+ __import__(module, locals(), globals())
+
+ if module == '__init__.py' or module[-3:] != '.py':
+ continue
+
+ mod = __import__(module[:-3], locals(), globals())
+
+ module_filename = plugins_dir+"/"+module
+ mtime = os.path.getmtime(module_filename)
+ if hasattr(mod,'register'):
+ KICAD_PLUGINS[module]={"filename":module_filename,
+ "modification_time":mtime,
+ "object":mod.register(),
+ "module":mod}
+
+
+
+class KiCadPlugin:
+ def __init__(self):
+ pass
+
+ def register(self):
+ if isinstance(self,FilePlugin):
+ pass # register to file plugins in C++
+ if isinstance(self,FootprintWizardPlugin):
+ PYTHON_FOOTPRINT_WIZARDS.register_wizard(self)
+ return
+
+ if isinstance(self,ActionPlugin):
+ pass # register to action plugins in C++
+
+ return
+
+ def deregister(self):
+ if isinstance(self,FilePlugin):
+ pass # register to file plugins in C++
+ if isinstance(self,FootprintWizardPlugin):
+ PYTHON_FOOTPRINT_WIZARDS.deregister_wizard(self)
+ return
+
+ if isinstance(self,ActionPlugin):
+ pass # register to action plugins in C++
+
+ return
+
+
+
+
+class FilePlugin(KiCadPlugin):
+ def __init__(self):
+ KiCadPlugin.__init__(self)
+
+
+from math import ceil, floor, sqrt
+
+class FootprintWizardPlugin(KiCadPlugin):
+ def __init__(self):
+ KiCadPlugin.__init__(self)
+ self.defaults()
+
+ def defaults(self):
+ self.module = None
+ self.parameters = {}
+ self.parameter_errors={}
+ self.name = "Undefined Footprint Wizard plugin"
+ self.description = ""
+ self.image = ""
+ self.buildmessages = ""
+
+ def GetName(self):
+ return self.name
+
+ def GetImage(self):
+ return self.image
+
+ def GetDescription(self):
+ return self.description
+
+
+ def GetNumParameterPages(self):
+ return len(self.parameters)
+
+ def GetParameterPageName(self,page_n):
+ return self.page_order[page_n]
+
+ def GetParameterNames(self,page_n):
+ name = self.GetParameterPageName(page_n)
+ return self.parameter_order[name]
+
+ def GetParameterValues(self,page_n):
+ name = self.GetParameterPageName(page_n)
+ names = self.GetParameterNames(page_n)
+ values = [self.parameters[name][n] for n in names]
+ return map(lambda x: str(x), values) # list elements as strings
+
+ def GetParameterErrors(self,page_n):
+ self.CheckParameters()
+ name = self.GetParameterPageName(page_n)
+ names = self.GetParameterNames(page_n)
+ values = [self.parameter_errors[name][n] for n in names]
+ return map(lambda x: str(x), values) # list elements as strings
+
+ def CheckParameters(self):
+ return ""
+
+ def ConvertValue(self,v):
+ try:
+ v = float(v)
+ except:
+ pass
+ if type(v) is float:
+ if ceil(v) == floor(v):
+ v = int(v)
+ return v
+
+
+ def SetParameterValues(self,page_n,values):
+ name = self.GetParameterPageName(page_n)
+ keys = self.GetParameterNames(page_n)
+ for n, key in enumerate(keys):
+ val = self.ConvertValue(values[n])
+ self.parameters[name][key] = val
+
+
+ def ClearErrors(self):
+ errs={}
+
+ for page in self.parameters.keys():
+ page_dict = self.parameters[page]
+ page_params = {}
+ for param in page_dict.keys():
+ page_params[param]=""
+
+ errs[page]=page_params
+
+ self.parameter_errors = errs
+
+
+ def GetFootprint( self ):
+ self.BuildFootprint()
+ return self.module
+
+ def BuildFootprint(self):
+ return
+
+ def GetBuildMessages( self ):
+ return self.buildmessages
+
+ def Show(self):
+ print "Footprint Wizard Name: ",self.GetName()
+ print "Footprint Wizard Description: ",self.GetDescription()
+ n_pages = self.GetNumParameterPages()
+ print " setup pages: ",n_pages
+ for page in range(0,n_pages):
+ name = self.GetParameterPageName(page)
+ values = self.GetParameterValues(page)
+ names = self.GetParameterNames(page)
+ print "page %d) %s"%(page,name)
+ for n in range (0,len(values)):
+ print "\t%s\t:\t%s"%(names[n],values[n])
+
+class ActionPlugin(KiCadPlugin):
+ def __init__(self):
+ KiCadPlugin.__init__(self)
+
+}
diff --git a/scripting/python_scripting.cpp b/scripting/python_scripting.cpp
new file mode 100644
index 0000000..c74abf5
--- /dev/null
+++ b/scripting/python_scripting.cpp
@@ -0,0 +1,364 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2015 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file python_scripting.cpp
+ * @brief methods to add scripting capabilities inside pcbnew
+ */
+
+#include <python_scripting.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fctsys.h>
+#include <wxstruct.h>
+#include <common.h>
+#include <colors.h>
+#include <macros.h>
+
+/* init functions defined by swig */
+
+extern "C" void init_kicad( void );
+
+extern "C" void init_pcbnew( void );
+
+#define EXTRA_PYTHON_MODULES 10 // this is the number of python
+ // modules that we want to add into the list
+
+
+/* python inittab that links module names to module init functions
+ * we will rebuild it to include the original python modules plus
+ * our own ones
+ */
+
+struct _inittab* SwigImportInittab;
+static int SwigNumModules = 0;
+
+static bool wxPythonLoaded = false; // true if the wxPython scripting layer was successfully loaded
+
+bool IsWxPythonLoaded()
+{
+ return wxPythonLoaded;
+}
+
+
+/* Add a name + initfuction to our SwigImportInittab */
+
+static void swigAddModule( const char* name, void (* initfunc)() )
+{
+ SwigImportInittab[SwigNumModules].name = (char*) name;
+ SwigImportInittab[SwigNumModules].initfunc = initfunc;
+ SwigNumModules++;
+ SwigImportInittab[SwigNumModules].name = (char*) 0;
+ SwigImportInittab[SwigNumModules].initfunc = 0;
+}
+
+
+/* Add the builtin python modules */
+
+static void swigAddBuiltin()
+{
+ int i = 0;
+
+ /* discover the length of the pyimport inittab */
+ while( PyImport_Inittab[i].name )
+ i++;
+
+ /* allocate memory for the python module table */
+ SwigImportInittab = (struct _inittab*) malloc(
+ sizeof(struct _inittab) * (i + EXTRA_PYTHON_MODULES) );
+
+ /* copy all pre-existing python modules into our newly created table */
+ i = 0;
+
+ while( PyImport_Inittab[i].name )
+ {
+ swigAddModule( PyImport_Inittab[i].name, PyImport_Inittab[i].initfunc );
+ i++;
+ }
+}
+
+
+/* Function swigAddModules
+ * adds the internal modules we offer to the python scripting, so they will be
+ * available to the scripts we run.
+ *
+ */
+
+static void swigAddModules()
+{
+ swigAddModule( "_pcbnew", init_pcbnew );
+
+ // finally it seems better to include all in just one module
+ // but in case we needed to include any other modules,
+ // it must be done like this:
+ // swigAddModule( "_kicad", init_kicad );
+}
+
+
+/* Function swigSwitchPythonBuiltin
+ * switches python module table to our built one .
+ *
+ */
+
+static void swigSwitchPythonBuiltin()
+{
+ PyImport_Inittab = SwigImportInittab;
+}
+
+
+/* Function pcbnewInitPythonScripting
+ * Initializes all the python environment and publish our interface inside it
+ * initializes all the wxpython interface, and returns the python thread control structure
+ *
+ */
+
+PyThreadState* g_PythonMainTState;
+
+bool pcbnewInitPythonScripting( const char * aUserPluginsPath )
+{
+ swigAddBuiltin(); // add builtin functions
+ swigAddModules(); // add our own modules
+ swigSwitchPythonBuiltin(); // switch the python builtin modules to our new list
+
+ Py_Initialize();
+
+#ifdef KICAD_SCRIPTING_WXPYTHON
+ PyEval_InitThreads();
+
+#ifndef __WINDOWS__ // import wxversion.py currently not working under winbuilder, and not useful.
+ char cmd[1024];
+ // Make sure that that the correct version of wxPython is loaded. In systems where there
+ // are different versions of wxPython installed this can lead to select wrong wxPython
+ // version being selected.
+ snprintf( cmd, sizeof(cmd), "import wxversion; wxversion.select('%s')", WXPYTHON_VERSION );
+
+ int retv = PyRun_SimpleString( cmd );
+
+ if( retv != 0 )
+ {
+ wxLogError( wxT( "Python error %d occurred running string `%s`" ), retv, cmd );
+ PyErr_Print();
+ Py_Finalize();
+ return false;
+ }
+#endif // ifndef __WINDOWS__
+
+ // Load the wxPython core API. Imports the wx._core_ module and sets a
+ // local pointer to a function table located there. The pointer is used
+ // internally by the rest of the API functions.
+ if( !wxPyCoreAPI_IMPORT() )
+ {
+ wxLogError( wxT( "***** Error importing the wxPython API! *****" ) );
+ PyErr_Print();
+ Py_Finalize();
+ return false;
+ }
+
+ wxPythonLoaded = true;
+
+ // Save the current Python thread state and release the
+ // Global Interpreter Lock.
+
+ g_PythonMainTState = wxPyBeginAllowThreads();
+#endif // ifdef KICAD_SCRIPTING_WXPYTHON
+
+ // load pcbnew inside python, and load all the user plugins, TODO: add system wide plugins
+ {
+ char cmd[1024];
+ PyLOCK lock;
+ snprintf( cmd, sizeof(cmd), "import sys, traceback\n"
+ "sys.path.append(\".\")\n"
+ "import pcbnew\n"
+ "pcbnew.LoadPlugins(\"%s\")", aUserPluginsPath );
+ PyRun_SimpleString( cmd );
+ }
+
+ return true;
+}
+
+
+void pcbnewFinishPythonScripting()
+{
+#ifdef KICAD_SCRIPTING_WXPYTHON
+ wxPyEndAllowThreads( g_PythonMainTState );
+#endif
+ Py_Finalize();
+}
+
+
+#if defined( KICAD_SCRIPTING_WXPYTHON )
+
+void RedirectStdio()
+{
+ // This is a helpful little tidbit to help debugging and such. It
+ // redirects Python's stdout and stderr to a window that will popup
+ // only on demand when something is printed, like a traceback.
+ const char* python_redirect =
+ "import sys\n"
+ "import wx\n"
+ "output = wx.PyOnDemandOutputWindow()\n"
+ "sys.stderr = output\n";
+
+ PyLOCK lock;
+
+ PyRun_SimpleString( python_redirect );
+}
+
+
+wxWindow* CreatePythonShellWindow( wxWindow* parent )
+{
+ const char* pycrust_panel =
+ "import wx\n"
+ "from wx.py import shell, version\n"
+ "\n"
+ "intro = \"PyCrust %s - KiCAD Python Shell\" % version.VERSION\n"
+ "\n"
+ "def makeWindow(parent):\n"
+ " pycrust = shell.Shell(parent, -1, introText=intro)\n"
+ " return pycrust\n"
+ "\n";
+
+
+ wxWindow* window = NULL;
+ PyObject* result;
+
+ // As always, first grab the GIL
+ PyLOCK lock;
+
+ // Now make a dictionary to serve as the global namespace when the code is
+ // executed. Put a reference to the builtins module in it.
+
+ PyObject* globals = PyDict_New();
+ PyObject* builtins = PyImport_ImportModule( "__builtin__" );
+
+ PyDict_SetItemString( globals, "__builtins__", builtins );
+ Py_DECREF( builtins );
+
+ // Execute the code to make the makeWindow function we defined above
+ result = PyRun_String( pycrust_panel, Py_file_input, globals, globals );
+
+ // Was there an exception?
+ if( !result )
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ Py_DECREF( result );
+
+ // Now there should be an object named 'makeWindow' in the dictionary that
+ // we can grab a pointer to:
+ PyObject* func = PyDict_GetItemString( globals, "makeWindow" );
+ wxASSERT( PyCallable_Check( func ) );
+
+ // Now build an argument tuple and call the Python function. Notice the
+ // use of another wxPython API to take a wxWindows object and build a
+ // wxPython object that wraps it.
+
+ PyObject* arg = wxPyMake_wxObject( parent, false );
+ wxASSERT( arg != NULL );
+
+ PyObject* tuple = PyTuple_New( 1 );
+ PyTuple_SET_ITEM( tuple, 0, arg );
+
+ result = PyEval_CallObject( func, tuple );
+
+ // Was there an exception?
+ if( !result )
+ PyErr_Print();
+ else
+ {
+ // Otherwise, get the returned window out of Python-land and
+ // into C++-ville...
+ bool success = wxPyConvertSwigPtr( result, (void**) &window, _T( "wxWindow" ) );
+ (void) success;
+
+ wxASSERT_MSG( success, _T( "Returned object was not a wxWindow!" ) );
+ Py_DECREF( result );
+ }
+
+ // Release the python objects we still have
+ Py_DECREF( globals );
+ Py_DECREF( tuple );
+
+ return window;
+}
+
+
+#endif
+
+wxArrayString PyArrayStringToWx( PyObject* aArrayString )
+{
+ wxArrayString ret;
+
+ int list_size = PyList_Size( aArrayString );
+
+ for( int n = 0; n<list_size; n++ )
+ {
+ PyObject* element = PyList_GetItem( aArrayString, n );
+
+ ret.Add( FROM_UTF8( PyString_AsString( element ) ), 1 );
+ }
+
+ return ret;
+}
+
+
+wxString PyErrStringWithTraceback()
+{
+ wxString err;
+
+ if( !PyErr_Occurred() )
+ return err;
+
+ PyObject* type;
+ PyObject* value;
+ PyObject* traceback;
+
+ PyErr_Fetch( &type, &value, &traceback );
+
+ PyObject* tracebackModuleString = PyString_FromString( (char*) "traceback" );
+ PyObject* tracebackModule = PyImport_Import( tracebackModuleString );
+
+
+ PyObject* formatException = PyObject_GetAttrString( tracebackModule,
+ (char*) "format_exception" );
+ PyObject* args = Py_BuildValue( "(O,O,O)", type, value, traceback );
+
+ PyObject* result = PyObject_CallObject( formatException, args );
+
+ Py_DECREF( args );
+
+ wxArrayString res = PyArrayStringToWx( result );
+
+ for( unsigned i = 0; i<res.Count(); i++ )
+ {
+ err += res[i] + wxT( "\n" );
+ }
+
+ PyErr_Clear();
+
+ return err;
+}
diff --git a/scripting/python_scripting.h b/scripting/python_scripting.h
new file mode 100644
index 0000000..8d0ae17
--- /dev/null
+++ b/scripting/python_scripting.h
@@ -0,0 +1,62 @@
+#ifndef __PYTHON_SCRIPTING_H
+#define __PYTHON_SCRIPTING_H
+
+// undefs explained here: https://bugzilla.redhat.com/show_bug.cgi?id=427617
+
+#ifdef _POSIX_C_SOURCE
+ #undef _POSIX_C_SOURCE
+#endif
+#ifdef _XOPEN_SOURCE
+ #undef _XOPEN_SOURCE
+#endif
+
+#include <Python.h>
+#ifndef NO_WXPYTHON_EXTENSION_HEADERS
+#ifdef KICAD_SCRIPTING_WXPYTHON
+ #include <wx/wxPython/wxPython.h>
+#endif
+#endif
+
+#include <wx/string.h>
+#include <wx/arrstr.h>
+
+/* Function pcbnewInitPythonScripting
+ * Initializes the Python engine inside pcbnew
+ */
+
+bool pcbnewInitPythonScripting( const char * aUserPluginsPath );
+void pcbnewFinishPythonScripting();
+
+
+#ifdef KICAD_SCRIPTING_WXPYTHON
+
+void RedirectStdio();
+wxWindow* CreatePythonShellWindow( wxWindow* parent );
+
+class PyLOCK
+{
+ wxPyBlock_t b;
+public:
+
+ // @todo, find out why these are wxPython specific. We need the GIL regardless.
+ // Should never assume python will only have one thread calling it.
+ PyLOCK() { b = wxPyBeginBlockThreads(); }
+ ~PyLOCK() { wxPyEndBlockThreads( b ); }
+};
+
+
+#else
+class PyLOCK
+{
+ PyGILState_STATE gil_state;
+public:
+ PyLOCK() { gil_state = PyGILState_Ensure(); }
+ ~PyLOCK() { PyGILState_Release( gil_state ); }
+};
+
+#endif
+
+wxArrayString PyArrayStringToWx( PyObject* arr );
+wxString PyErrStringWithTraceback();
+
+#endif // __PYTHON_SCRIPTING_H
diff --git a/scripting/wx.i b/scripting/wx.i
new file mode 100644
index 0000000..aa03a3f
--- /dev/null
+++ b/scripting/wx.i
@@ -0,0 +1,301 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file wx.i
+ * @brief wx wrappers for basic things, wxString, wxPoint, wxRect, etc..
+ * all the wx objects are very complex, and we don't want to pull
+ * and swig all depending objects, so we just define the methods
+ * we want to wrap.
+ */
+
+%{
+#include <wx_python_helpers.h>
+%}
+
+// encoding setup, ascii by default ///////////////////////////////////////////
+
+void wxSetDefaultPyEncoding(const char* encoding);
+const char* wxGetDefaultPyEncoding();
+
+
+// wxRect class wrapper ///////////////////////////////////////////////////////
+
+class wxRect
+{
+public:
+ wxRect() : x(0), y(0), width(0), height(0) { }
+ wxRect(int xx, int yy, int ww, int hh): x(xx), y(yy), width(ww), height(hh) { }
+ wxRect(const wxPoint& topLeft, const wxPoint& bottomRight);
+ wxRect(const wxPoint& pt, const wxSize& size)
+ : x(pt.x), y(pt.y), width(size.x), height(size.y) { }
+ wxRect(const wxSize& size): x(0), y(0), width(size.x), height(size.y) { }
+
+ int GetX() const { return x; }
+ void SetX(int xx) { x = xx; }
+
+ int GetY() const { return y; }
+ void SetY(int yy) { y = yy; }
+
+ int GetWidth() const { return width; }
+ void SetWidth(int w) { width = w; }
+
+ int GetHeight() const { return height; }
+ void SetHeight(int h) { height = h; }
+
+ wxPoint GetPosition() const { return wxPoint(x, y); }
+ void SetPosition( const wxPoint &p ) { x = p.x; y = p.y; }
+
+ int x, y, width, height;
+
+ %extend
+ {
+ /* extend the wxRect object so it can be converted into a tuple */
+ PyObject* Get()
+ {
+ PyObject* res = PyTuple_New(4);
+ PyTuple_SET_ITEM(res, 0, PyInt_FromLong(self->x));
+ PyTuple_SET_ITEM(res, 1, PyInt_FromLong(self->y));
+ PyTuple_SET_ITEM(res, 2, PyInt_FromLong(self->width));
+ PyTuple_SET_ITEM(res, 3, PyInt_FromLong(self->height));
+ return res;
+ }
+ }
+
+
+ %pythoncode
+ {
+
+ def __eq__(self,other):
+ return self.x==other.x and self.y==other.y and self.width==other.width and self.height==other.height
+ def __str__(self): return str(self.Get())
+ def __repr__(self): return 'wxRect'+str(self.Get())
+ def __len__(self): return len(self.Get())
+ def __getitem__(self, index): return self.Get()[index]
+ def __setitem__(self, index, val):
+ if index == 0: self.SetX(val)
+ elif index == 1: self.SetY(val)
+ elif index == 2: self.SetWidth(val)
+ elif index == 3: self.SetHeight(val)
+ else: raise IndexError
+ def __nonzero__(self): return self.Get() != (0,0,0,0)
+ __safe_for_unpickling__ = True
+ }
+
+};
+
+// wxSize class wrapper ///////////////////////////////////////////////////////
+
+class wxSize
+{
+public:
+ int x,y;
+ wxSize(int xx, int yy) : x(xx), y(yy) { }
+ wxSize(double xx, double yy) : x(xx), y(yy) {}
+ %extend
+ {
+ PyObject* Get()
+ {
+ PyObject* res = PyTuple_New(2);
+ PyTuple_SET_ITEM(res, 0, PyInt_FromLong(self->x));
+ PyTuple_SET_ITEM(res, 1, PyInt_FromLong(self->y));
+ return res;
+ }
+ }
+
+ ~wxSize();
+
+ void SetWidth(int w);
+ void SetHeight(int h);
+ int GetWidth() const;
+ int GetHeight() const;
+
+
+ %pythoncode
+ {
+ def Scale(self,xscale,yscale):
+ return wxSize(self.x*xscale,self.y*yscale)
+ def __eq__(self,other):
+ return self.GetWidth()==other.GetWidth() and self.GetHeight()==other.GetHeight()
+ def __str__(self): return str(self.Get())
+ def __repr__(self): return 'wxSize'+str(self.Get())
+ def __len__(self): return len(self.Get())
+ def __getitem__(self, index): return self.Get()[index]
+ def __setitem__(self, index, val):
+ if index == 0: self.SetWidth(val)
+ elif index == 1: self.SetHeight(val)
+ else: raise IndexError
+ def __nonzero__(self): return self.Get() != (0,0)
+ __safe_for_unpickling__ = True
+
+ }
+};
+
+// wxPoint class wrapper to (xx,yy) tuple /////////////////////////////////////
+
+class wxPoint
+{
+public:
+ int x, y;
+ wxPoint(int xx, int yy);
+ wxPoint(double xx, double yy) : x(xx), y(yy) {}
+ ~wxPoint();
+ %extend {
+ wxPoint __add__(const wxPoint& pt) { return *self + pt; }
+ wxPoint __sub__(const wxPoint& pt) { return *self - pt; }
+
+ void Set(long x, long y) { self->x = x; self->y = y; }
+ PyObject* Get()
+ {
+ PyObject* tup = PyTuple_New(2);
+ PyTuple_SET_ITEM(tup, 0, PyInt_FromLong(self->x));
+ PyTuple_SET_ITEM(tup, 1, PyInt_FromLong(self->y));
+ return tup;
+ }
+ }
+
+ %pythoncode {
+ def __eq__(self,other): return (self.x==other.x and self.y==other.y)
+ def __ne__(self,other): return not (self==other)
+ def __str__(self): return str(self.Get())
+ def __repr__(self): return 'wxPoint'+str(self.Get())
+ def __len__(self): return len(self.Get())
+ def __getitem__(self, index): return self.Get()[index]
+ def __setitem__(self, index, val):
+ if index == 0:
+ self.x = val
+ elif index == 1:
+ self.y = val
+ else:
+ raise IndexError
+ def __nonzero__(self): return self.Get() != (0,0)
+
+ }
+};
+
+
+// wxChar typemaps ///////////////////////////////////////////////////////////
+
+/* they handle the conversion from/to strings */
+
+%typemap(in) wxChar { wxString str = Py2wxString($input); $1 = str[0]; }
+%typemap(out) wxChar { wxString str($1); $result = wx2PyString(str); }
+
+// wxString wrappers /////////////////////////////////////////////////////////
+
+%typemap(out) wxString&
+{
+%#if wxUSE_UNICODE
+ $result = PyUnicode_FromWideChar($1->c_str(), $1->Len());
+%#else
+ $result = PyString_FromStringAndSize($1->c_str(), $1->Len());
+%#endif
+}
+
+%apply wxString& { wxString* }
+
+%typemap(out) wxString
+{
+%#if wxUSE_UNICODE
+ $result = PyUnicode_FromWideChar($1.c_str(), $1.Len());
+%#else
+ $result = PyString_FromStringAndSize($1.c_str(), $1.Len());
+%#endif
+}
+
+%typemap(varout) wxString
+{
+%#if wxUSE_UNICODE
+ $result = PyUnicode_FromWideChar($1.c_str(), $1.Len());
+%#else
+ $result = PyString_FromStringAndSize($1.c_str(), $1.Len());
+%#endif
+}
+
+%typemap(in) wxString& (bool temp=false)
+{
+ $1 = newWxStringFromPy($input);
+ if ($1 == NULL) SWIG_fail;
+ temp = true;
+}
+
+%typemap(freearg) wxString&
+{
+ if (temp$argnum)
+ delete $1;
+}
+
+
+%typemap(in) wxString {
+ wxString* sptr = newWxStringFromPy($input);
+ if (sptr == NULL) SWIG_fail;
+ $1 = *sptr;
+ delete sptr;
+}
+
+%typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER) wxString& {
+ $1 = PyString_Check($input) || PyUnicode_Check($input);
+}
+
+
+// wxArrayString wrappers //////////////////////////////////////////////////////
+%typemap(in) wxArrayString& (bool temp=false) {
+ if (!PySequence_Check($input))
+ {
+ PyErr_SetString(PyExc_TypeError, "Not a sequence of strings");
+ SWIG_fail;
+ }
+
+ $1 = new wxArrayString;
+ temp = true;
+ int last=PySequence_Length($input);
+ for (int i=0; i<last; i++)
+ {
+ PyObject* pyStr = PySequence_GetItem($input, i);
+ wxString* wxS = newWxStringFromPy(pyStr);
+ if (PyErr_Occurred())
+ SWIG_fail;
+ $1->Add(*wxS);
+ delete wxS;
+ Py_DECREF(pyStr);
+ }
+}
+
+%typemap(freearg) wxArrayString&
+{
+ if (temp$argnum)
+ delete $1;
+}
+
+%typemap(out) wxArrayString&
+{
+ $result = wxArrayString2PyList(*$1);
+}
+
+%typemap(out) wxArrayString
+{
+ $result = wxArrayString2PyList($1);
+}
+
+%template(wxPoint_Vector) std::vector<wxPoint>;
diff --git a/scripting/wx_python_helpers.cpp b/scripting/wx_python_helpers.cpp
new file mode 100644
index 0000000..587fe2a
--- /dev/null
+++ b/scripting/wx_python_helpers.cpp
@@ -0,0 +1,196 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Miguel Angel Ajo <miguelangel@nbee.es>
+ * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * 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 2
+ * 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, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * @file wx_python_helpers.cpp
+ * @brief Python wrapping helpers for wx structures/objects
+ */
+
+#include <Python.h>
+#include <wx/intl.h>
+#include <wx/string.h>
+#include <wx/arrstr.h>
+
+
+#define WX_DEFAULTENCODING_SIZE 64
+
+static char wxPythonEncoding[WX_DEFAULTENCODING_SIZE] = "ascii";
+
+
+PyObject* wxArrayString2PyList( const wxArrayString& lst )
+{
+ PyObject* list = PyList_New( 0 );
+
+ for( size_t i = 0; i < lst.GetCount(); i++ )
+ {
+#if wxUSE_UNICODE
+ PyObject* pyStr = PyUnicode_FromWideChar( lst[i].c_str(),
+ lst[i].Len()
+ );
+#else
+ PyObject* pyStr = PyString_FromStringAndSize( lst[i].c_str(),
+ lst[i].Len()
+ );
+#endif
+ PyList_Append( list, pyStr );
+ Py_DECREF( pyStr );
+ }
+
+ return list;
+}
+
+
+wxString* newWxStringFromPy( PyObject* src )
+{
+ bool must_unref_str = false;
+
+ wxString* result = NULL;
+ PyObject* obj = src;
+
+#if wxUSE_UNICODE
+ bool must_unref_obj = false;
+ // Unicode string to python unicode string
+ PyObject* uni_str = src;
+
+ // if not an str or unicode, try to str(src)
+ if( !PyString_Check( src ) && !PyUnicode_Check( src ) )
+ {
+ obj = PyObject_Str( src );
+ must_unref_obj = true;
+
+ if( PyErr_Occurred() )
+ return NULL;
+ }
+
+ if( PyString_Check( obj ) )
+ {
+ uni_str = PyUnicode_FromEncodedObject( obj, wxPythonEncoding, "strict" );
+ must_unref_str = true;
+
+ if( PyErr_Occurred() )
+ return NULL;
+ }
+
+ result = new wxString();
+ size_t len = PyUnicode_GET_SIZE( uni_str );
+
+ if( len )
+ {
+ PyUnicode_AsWideChar( (PyUnicodeObject*) uni_str,
+ wxStringBuffer( *result, len ), len );
+ }
+
+ if( must_unref_str )
+ {
+ Py_DECREF( uni_str );
+ }
+
+ if( must_unref_obj )
+ {
+ Py_DECREF( obj );
+ }
+
+#else
+ // normal string (or object) to normal python string
+ PyObject* str = src;
+
+ if( PyUnicode_Check( src ) ) // if it's unicode convert to normal string
+ {
+ str = PyUnicode_AsEncodedString( src, wxPythonEncoding, "strict" );
+
+ if( PyErr_Occurred() )
+ return NULL;
+ }
+ else if( !PyString_Check( src ) ) // if it's not a string, str(obj)
+ {
+ str = PyObject_Str( src );
+ must_unref_str = true;
+
+ if( PyErr_Occurred() )
+ return NULL;
+ }
+
+ // get the string pointer and size
+ char* str_ptr;
+ Py_ssize_t str_size;
+ PyString_AsStringAndSize( str, &str_ptr, &str_size );
+
+ // build the wxString from our pointer / size
+ result = new wxString( str_ptr, str_size );
+
+ if( must_unref_str )
+ {
+ Py_DECREF( str );
+ }
+
+#endif
+
+ return result;
+}
+
+
+wxString Py2wxString( PyObject* src )
+{
+ wxString result;
+ wxString* resPtr = newWxStringFromPy( src );
+
+ // In case of exception clear it and return an empty string
+ if( resPtr==NULL )
+ {
+ PyErr_Clear();
+ return wxEmptyString;
+ }
+
+ result = *resPtr;
+
+ delete resPtr;
+
+ return result;
+}
+
+
+PyObject* wx2PyString( const wxString& src )
+{
+ PyObject* str;
+
+#if wxUSE_UNICODE
+ str = PyUnicode_FromWideChar( src.c_str(), src.Len() );
+#else
+ str = PyString_FromStringAndSize( src.c_str(), src.Len() );
+#endif
+ return str;
+}
+
+
+void wxSetDefaultPyEncoding( const char* encoding )
+{
+ strncpy( wxPythonEncoding, encoding, WX_DEFAULTENCODING_SIZE );
+ wxPythonEncoding[ WX_DEFAULTENCODING_SIZE - 1 ] = '\0';
+}
+
+
+const char* wxGetDefaultPyEncoding()
+{
+ return wxPythonEncoding;
+}
diff --git a/scripting/wx_python_helpers.h b/scripting/wx_python_helpers.h
new file mode 100644
index 0000000..2da11d6
--- /dev/null
+++ b/scripting/wx_python_helpers.h
@@ -0,0 +1,18 @@
+#ifndef __wx_helpers_h
+#define __wx_helpers_h
+
+#include <Python.h>
+#include <wx/intl.h>
+#include <wx/string.h>
+#include <wx/arrstr.h>
+
+
+PyObject* wxArrayString2PyList( const wxArrayString& lst );
+wxString* newWxStringFromPy( PyObject* source );
+wxString Py2wxString( PyObject* source );
+PyObject* wx2PyString( const wxString& src );
+
+void wxSetDefaultPyEncoding( const char* encoding );
+const char* wxGetDefaultPyEncoding();
+
+#endif