The last two lines of triangle.py files are shown below. The first line is
boilerplate code. If we run this module on terminal, the Python interpreter will set the
__name__ to __main__ and the code Triangle().run() will execute.
if __name__ == '__main__':
Triangle().run()
The first part of the second line Triangle() initializes an instance of
Triangle class. We can think of the Triangle().run() as two statements like this.
t = Triangle()
t.run()
In this section we will discuss class initialization part t = Triangle(), and
in the next section we will examine the run part t.run().
The class Triangle itself does not define an __init__ method, and
the __init__ methods in superclasses will be invoked automatically. The
diagram below shows the superclasses of Triangle.
Both SvgInputMixin and InkscapeExtension classes have __init__ methods, and
those will be invoked.
Here is the code in the __init__ method of SvgInputMixin class. It simply calls
add_argument method twice. The super().__init__() statement invokes __init__
method in object because super refers to object.
# __init__ in SvgInputMixin class, base.py
def __init__(self):
super().__init__()
self.arg_parser.add_argument(
"--id", action="append", type=str, dest="ids", default=[],
help="id attribute of object to manipulate")
self.arg_parser.add_argument(
"--selected-nodes", action="append", type=str,
dest="selected_nodes", default=[],
help="id:subpath:position of selected nodes, if any")
Let’s look at the __init__ method in InkscapeExtension class.
# __init__ in InkscapeExtension class
def __init__(self):
# type: () -> None
NSS.update(self.extra_nss)
self.file_io = None # type: Optional[IO]
self.options = Namespace()
self.document = None # type: Union[None, bytes, str, etree]
self.arg_parser = ArgumentParser(description=self.__doc__)
self.arg_parser.add_argument(
"input_file", nargs="?", metavar="INPUT_FILE",
type=filename_arg, default=None,
help="Filename of the input file (default is stdin)")
self.arg_parser.add_argument(
"--output", type=str, default=None,
help="Optional output filename for saving " +
"the result (default is stdout).")
self.add_arguments(self.arg_parser)
localize()
The NSS.update(...) line is for XML namespaces. The NSS itself is an abbreviation
for Namespace Specific String.
The __init__ method initializes four instance variables file_io, options,
document, and arg_parser. It calls the add_argument methods of ArgumentParser
class twice, and then it calls the add_arguments class method. The add_arguments method
is overridden in Triangle class, so the add_arguments method in the Triangle class
will be called. The method calls add_argument of ArgumentParser class seven times.
Note the method name is add_argument in ArgumentParser class and it is add_arguments
(notice the plural) in Triangle class.
# add_argument method in Triangle class
def add_arguments(self, pars):
logging.debug(f'Triangle add_arguments method starts') ##
pars.add_argument("--s_a", type=float,
default=100.0, help="Side Length a")
pars.add_argument("--s_b", type=float,
default=100.0, help="Side Length b")
pars.add_argument("--s_c", type=float,
default=100.0, help="Side Length c")
pars.add_argument("--a_a", type=float,
default=60.0, help="Angle a")
pars.add_argument("--a_b", type=float,
default=30.0, help="Angle b")
pars.add_argument("--a_c", type=float,
default=90.0, help="Angle c")
pars.add_argument("--mode", default='3_sides',
help="Side Length c")
logging.debug(f'Triangle add_arguments method ends') ##
We can add a pair of logging statements at the beginning and end of the method to
find out the call sequence. Similar logging statements are added to the two __init__
methods, and the result is shown below.
DEBUG: SvgInputMixin __init__ starts
DEBUG: InkscapeExtension __init__ starts
DEBUG: Triangle add_arguments method starts
DEBUG: Triangle add_arguments method ends
DEBUG: InkscapeExtension __init__ ends
DEBUG: SvgInputMixin __init__ ends
The interesting part here is that the __init__ method in SvgInputMixin is called
first. When the Python interpreter encounters the self.arg_parser instance variable,
it can’t find the definition. It suspends this __init__ method call and starts
invoking __init__ method in InkscapeExtension class. It finds the instance variable
definition for self.arg_parser on this line of code.
self.arg_parser = ArgumentParser(description=self.__doc__)
The add_arguments method
starts and ends as expected because it is called within the __init__ method of
InkscapeExtension. The __init__ method in InkscapeExtension class returns first,
and then the __init__ method in SvgInputMixin class returns. This is something in
Python I don’t know until I work on this example. There are always new things to
learn in Python.
After the initialization, the run method is where everything happens. Here is
the code of run method again in InkscapeExtension class.
def run(self, args=None, output=stdout):
# type: (Optional[List[str]], Union[str, IO]) -> None
"""Main entrypoint for any Inkscape Extension"""
try:
if args is None:
args = sys.argv[1:]
self.parse_arguments(args)
if self.options.input_file is None:
self.options.input_file = sys.stdin
if self.options.output is None:
self.options.output = output
self.load_raw()
self.save_raw(self.effect())
except AbortExtension as err:
err.write()
sys.exit(ABORT_STATUS)
finally:
self.clean_up()
Let’s take a close look at the code between try and except lines.
From last chapter, we know that the sys.argv value is a list of arguments.
The args value is a list starting from the second item.
['triangle.py', '--s_a=100', '--s_b=100',
'--s_c=100', '--a_a=60', '--a_b=30', '--a_c=90',
'--mode=3_sides', '/tmp/ink_ext_XXXXXX.svgVYXM70']
The parse_arguments method in InkscapeExtension class simply calls
parse_args method of arg_parser object. The returned value is assigned
to the options instance variable, which is a Namespace object.
def parse_arguments(self, args):
# type: (List[str]) -> None
"""Parse the given arguments and set 'self.options'"""
self.options = self.arg_parser.parse_args(args)
After the parse_arguments method call, the self.options value is
the Namespace object shown below. We can access variables in the object like
a property (e.g., self.options.input_file). Notice the ids and selected_nodes
instance variables in Namespace. Inkscape passes those two values to the
extension.
Namespace(input_file='/tmp/ink_ext_XXXXXX.svgVJUG70',
output=None, s_a=100.0, s_b=100.0, s_c=100.0,
a_a=60.0, a_b=30.0, a_c=90.0, mode='3_sides',
ids=[], selected_nodes=[])
The next four statements changes the self.options.output and self.options.input_file
values if they are None. The self.options.input_file does not change in
the Triangle example. The value /tmp/ink_ext_XXXXXX.svgVJUG70 refers to a
temporary file Inkscape creates and passes to the extension.
We can think of the last two lines as the three lines shown below. Upon this point, the program is working on initialization and setting up variables. These three lines of code is doing the actually work, loading the temporary svg file, modifying it, and sending it back to Inkscape. We will examine those method calls in the next chapter.
self.load_raw()
e = self.effect()
self.save_raw(e)
From the above discussion, we should have a basic understanding on how Inkscape extensions
work. When we launch Inkcape, it will check files under user extension
and system extension directories, and create menu items under the Extensions
top level menu.
When we click an extension menu such as Triangle, Inkscape will setup the
input and output stream of Python environment and call the Python interpreter
installed at this path /usr/bin/python3. It also passes the following
arguments to the Python interpreter.
['triangle.py', '--s_a=100', '--s_b=100',
'--s_c=100', '--a_a=60', '--a_b=30', '--a_c=90',
'--mode=3_sides', '/tmp/ink_ext_XXXXXX.svgVYXM70']
This is similar to enter a command on a bash terminal.
$/usr/bin/python3 triangle.py --s_a=100 --s_b=100 --s_c=100 \
--a_a=60 --a_b=30 --a_c=90 --mode=3_sides \
/tmp/ink_ext_XXXXXX.svgVYXM70
The argparse is the Python standard library module which turns command
line options (like --s_a=100) and arguments (like /tmp/ink...) into
variables we can access (self.options.s_a) in the program.
Python documentation site has an
official argparse module tutorial.
The argparse module was introduced in Python 3.4, which supersedes the optparse
in Python2. The old Inkscape extensions before Inkscape 1.0 use optparse module.
3. How Extensions Work