diff options
Diffstat (limited to 'scripting')
-rw-r--r-- | scripting/build_tools/extract_docstrings.py | 405 | ||||
-rw-r--r-- | scripting/build_tools/fix_swig_imports.py | 77 | ||||
-rw-r--r-- | scripting/dlist.i | 67 | ||||
-rw-r--r-- | scripting/kicad.i | 160 | ||||
-rw-r--r-- | scripting/kicadplugins.i | 271 | ||||
-rw-r--r-- | scripting/python_scripting.cpp | 364 | ||||
-rw-r--r-- | scripting/python_scripting.h | 62 | ||||
-rw-r--r-- | scripting/wx.i | 301 | ||||
-rw-r--r-- | scripting/wx_python_helpers.cpp | 196 | ||||
-rw-r--r-- | scripting/wx_python_helpers.h | 18 |
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 |