summaryrefslogtreecommitdiffstats
path: root/share/extensions/docs/tutorial/my-first-import-extension.rst
blob: 6c296bdd931110425483b5cae69896abc6e72cfa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
My first import extension
=========================

Resources
---------

:download:`vanillin.smi <resources/vanillin.smi>`

Introduction
------------

This article will teach you the basics of writing an Import Extension for
Inkscape using the ``inkex`` Extensions API.

Import Extensions are used to add support for a file format that
Inkscape does not support out of the box. It is rather complex to implement an entire file format,
but often there are already tools available to convert the file to a format that Inkscape can read, 
such as SVG or PDF. In this case, the extension depends on external programs that convert that 
specific file format to a svg that Inkscape can then read.

The way these extensions are used is not through the ``Extensions`` menu
but instead these provide options in\ ``File``>\ ``Open`` or
``File``>\ ``Import`` dialog.

The general flow of a Import Extension is as follows:

1) You select the specific file format from the File Format drop down
   menu of the ``File``>\ ``Open`` or ``File``>\ ``Import``\ dialog.
2) You select one or more files of that specific file format. You click
   on ``Open`` button.
3) Inkscape will then either open the svg file in a new window or will
   import it depending on which dialog of ``File`` menu was used.

.. hint::

   You can entirely skip 1) and 2) and just double click on
   the file. If your extension is set up correctly, you won’t notice the
   difference between opening a regular file and your file.

In this article we will create an extension named
``Organic Bond Visualizer``. It will produce the bond structure of an
organic compound given a
`SMILES <https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system>`__
file. The extension uses an external program called ``indigo-depict``
which has its GitHub repository
`here <https://github.com/epam/Indigo>`__. (It is a part of the
``indigo-utils`` package in Debian/Ubuntu repositories. For other
platforms you might need to build it from source.)

.. note:: A note on **PATH**

    Since the extension depends on external programs, it is important that
    these external programs can be found by our extensions. For that to
    happen, either these programs **should** be on your *system PATH* or an
    absolute path for the program should be used in the code.

    Depending on the operating system, the steps required for adding
    programs to the PATH might vary.

    You can get away with setting the PATH variable entirely by using an
    absolute path for the program but it is almost always a bad idea to
    hard-code paths in the code.

.. hint::

   This article assumes you create all extension related files in the
   User Extensions directory which is listed at
   ``Edit``>\ ``Preferences``>\ ``System`` - ``User Extensions:`` in
   Inkscape.

Step 0 : The Boilerplate
------------------------

Like any other Inkscape extension, this extension will also have two
files. So, create a ``organic_bond_vis.inx`` file and a
``organic_bond_vis.py`` file.

-  ``organic_bond_vis.inx`` - It will have the necessary information for
   Inkscape to be able to recognize your extension as a valid Inkscape
   extension. It will also have the info required to present the
   extension as an option in ``File``>\ ``Open`` or
   ``File``>\ ``Import`` dialog.
-  ``organic_bond_vis.py`` - It will have the actual Python code your
   extension will execute.

.. hint::

   There is another file that is worth mentioning here - the **test**
   file which in our case will be ``test_organic_bond_vis.py``. It is
   not required to be present for an extension per se, but as a best
   practice, the extension code should always be accompanied by test
   code.

Step 1 : Populate the ``*.inx`` file
------------------------------------

.. code:: xml

   <?xml version="1.0" encoding="UTF-8"?>
   <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
       <name>Organic Bond Visualizer</name>
       <id>org.inkscape.input.bondvisualizer</id>
       <dependency type="executable" location="path">indigo-depict</dependency>
       <input>
           <extension>.smi</extension>
           <mimetype>chemical/x-daylight-smiles</mimetype>
           <filetypename>SMILES(*.smi)</filetypename>
           <filetypetooltip>SMILES</filetypetooltip>
       </input>
       <script>
           <command location="inx" interpreter="python">organic_bond_vis.py</command>
       </script>
   </inkscape-extension>

The lines below help Inkscape uniquely identify our extension. You
should modify these two lines for your own extension:

.. code:: xml

   [...]
     <name>Organic Bond Visualizer</name>
     <id>org.inkscape.input.bondvisualizer</id>
   [...]

Now we declare the dependency of our extension:

.. code:: xml

       <dependency type="executable" location="path">indigo-depict</dependency>

The ``<input>`` tag is what specifies that this extension should add a
new format in the list of available file formats Inkscape can
open/import.

.. code:: xml

   [...]
       <input>
              <extension>.smi</extension>
              <mimetype>chemical/x-daylight-smiles</mimetype>
              <filetypename>SMILES(*.smi)</filetypename>
              <filetypetooltip>SMILES</filetypetooltip>
       </input>
   [...]

Inside the ``<extension>`` tag we declare the suffix of our new format
(including ``.``)

Some more examples of suffixes could be ``.ps`` , ``.fig``, etc. The
text inside ``<filetypename>`` is what appears in the
``File``>\ ``Open`` or ``File``>\ ``Import`` dialogs.

Towards the end, we add the name of our ``.py`` file inside the
``<command>`` tag.

.. code:: xml

   [...]
     <script>
           <command location="inx" interpreter="python">organic_bond_vis.py</command>
     </script>
   [...]

Now

-  Save the file
-  Close any open Inkscape windows
-  Relaunch Inkscape

You should now see a ``SMILES(*.smi)`` option in the drop down menu.

.. figure:: resources/Smiles_Format.gif
   :alt: Smiles_Format

   Smiles_Format

If you were to select the ``vanillin.smi`` file and open it, you would
get an error saying:

.. figure:: resources/Call_Extension_Failure.png
   :alt: Call_Extension_Failure

   Call_Extension_Failure

This is because we haven’t written anything in the ``.py`` file of our
extension.

Step 2 : Write the code in ``*.py`` file
----------------------------------------

First Things First
~~~~~~~~~~~~~~~~~~

To be able to use any extension functionality, you need to import the
``inkex`` module.

.. code:: python

   import inkex

There is a specialized class for input extensions that only call an external program to
convert the file. Every Call Extension inherits from the :class:`~inkex.extensions.CallExtension` 
class provided by the ``inkex`` API. Let’s name our class ``OrganicBondVisualizer`` (an
arbitrary name) and inherit the :class:`~inkex.extensions.CallExtension`  class.

.. code:: python

   import inkex

   class OrganicBondVisualizer(inkex.CallExtension):
     #implement functionality here
     pass

Specify the formats
~~~~~~~~~~~~~~~~~~~

We now specify the input file format and output file format. (both
without the ``.``). The output file format by default is ``svg``.

.. code:: python

   import inkex
   from inkex import command

   class OrganicBondVisualizer(inkex.CallExtension):

       input_ext = 'smi'
       output_ext = 'svg'

       #implement functionality here
       pass

Override
~~~~~~~~

To be able to call an another program, we need to override the :func:`~inkex.extensions.CallExtension.call`
function in our class. We need to import the :mod:`~inkex.command` module as it
contains the actual implementation to call external programs. Inside the
overridden method we call :mod:`~inkex.command` module’s :func:`~inkex.command.call` function.

.. code:: python

   import inkex
   from inkex import command

   class OrganicBondVisualizer(inkex.CallExtension):

       input_ext = 'smi'
       output_ext = 'svg'

       def call(self, input_file, output_file):
           command.call('indigo-depict', input_file, output_file)

The first argument for :mod:`~inkex.command`\ ’s :func:`~inkex.command.call` function is the name of
the external program. It can also be a path string like
``'/usr/bin/program'``. Although internally ``inkex`` converts the names
to path strings like these, you should not use them yourself. The reason
being that the user of this extension may not have the command at the
same path as the absolute path specified by us, the extension author.

   **Note**: *Windows* users don’t need to specify ``.exe`` in the name
   of the program.

The ``input_file`` and ``output_file`` are path strings received by the
:func:`~inkex.extensions.CallExtension.call` function from inkex. We then just pass these to 
:mod:`~inkex.command`\ ’s :func:`~inkex.command.call` function.

Make it all happen
~~~~~~~~~~~~~~~~~~

We now just add the ``__main__`` part where the extension runs.

.. code:: python

   import inkex
   from inkex import command

   class OrganicBondVisualizer(inkex.CallExtension):

       input_ext = 'smi'
       output_ext = 'svg'

       def call(self, input_file, output_file):
           command.call('indigo-depict', input_file, output_file)

   if __name__ == '__main__':
       OrganicBondVisualizer().run()

Now save the file.

Moment of Truth
---------------

Now, we should test our extension to see in action.

-  Open a new Inkscape window
-  Click on the ``File``>\ ``Import`` dialog
-  Select the ``.smi`` file (An example ``.smi`` file is attached above
   under `Resources <#resources>`__) and double-click on it. You should
   see the following output:

.. figure:: resources/call_extension_success.gif
   :alt: Did_It_Work

   Did_It_Work