diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/docs/tutorial | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
15 files changed, 870 insertions, 0 deletions
diff --git a/share/extensions/docs/tutorial/index.rst b/share/extensions/docs/tutorial/index.rst new file mode 100644 index 0000000..0e9551a --- /dev/null +++ b/share/extensions/docs/tutorial/index.rst @@ -0,0 +1,49 @@ +.. _authors-tutorial: + +Tutorial +======== + +Introduction +------------ + +Extensions are small programs that extend Inkscape’s functionality. They +can provide features for specific tasks, experimentation, or art styles. +They can also add support for special hardware or different export +formats. While many extensions are included with Inkscape, you can also +install extensions written by third parties or write your own. + +This guide aims to provide you with enough information to write your own +extension using the python programming language and a text editor. You +should have the latest version of Inkscape installed. While Inkscape +extensions generally work across platforms, this guide will use Linux as +its default operating system. Linux is not required for writing +extensions, but you may need to modify the location of files on your +computer as you follow along. You may also need to use other “helper” +programs, for example a different text editor, depending on your +operating system. + +When a user selects an extension from the menu within Inkscape, Inkscape +opens a pop-up dialog that presents information to the user and/or +allows the user to enter any parameters that are necessary to use the +extension. An extension can also be configured to run immediately +without an intermediate dialog, for example if it does not have any +adjustable parameters. When the user clicks **Apply** in the dialog, the +parameters are passed to the extension program along with the svg file +and a list of selected objects. The extension program itself then runs, +and the data that it returns will be used to update Inkscape’s svg +canvas or to save data to disk. + +We’ve made this process easier for you by creating a set of template +extensions which you can download as the starting point for this +tutorial. If you are a beginner we recommend that you start with the +Effect Extension Template. Input, Output and other types of extensions +will be covered in the more advanced topics later. + +.. toctree:: + :maxdepth: 1 + + my-first-effect-extension + simple-path-extension + my-first-text-extension + my-first-import-extension + diff --git a/share/extensions/docs/tutorial/my-first-effect-extension.rst b/share/extensions/docs/tutorial/my-first-effect-extension.rst new file mode 100644 index 0000000..7666288 --- /dev/null +++ b/share/extensions/docs/tutorial/my-first-effect-extension.rst @@ -0,0 +1,130 @@ +.. _first-effect-extension: + +My first effect extension +========================= + +Resources +--------- + +- :download:`Template Files <resources/template_effect.zip>` +- :download:`Example solution <resources/make_red_extension.zip>` + +Introduction +------------ +Effect extensions take the svg from Inkscape, modify it in some way and +pass the modified version back to Inkscape to be rendered onto the +canvas. This can be very powerful, allowing everything from randomising +colours to manipulating path elements in external programs. + +We are going to write an effect extension that will simply change the fill +any selected object to red. + +Step One +--------- + +Extract the ``Effect Extension Template`` files into a folder on your +computer. You should have two files, one inx file and one python file. +Move or link these files into your extensions directory as you would +when installing extensions manually. This is the directory listed at +``Edit > Preferences > System: User extensions``. + +Edit the inx file in a text editor and change the name of the extension +to ``Make Red Extension`` and the id to +``org.inkscape.tutorial.make_red_extension`` by changing these lines near +the top: + +.. code:: xml + + <?xml version="1.0" encoding="UTF-8"?> + <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>Make Red Extension</name> + <id>org.inkscape.tutorial.make_red_extension</id> + [...] + +Toward the end of the .inx file, change the submenu to ``Color`` +specify that this extension will be listed in the ``Color`` submenu +under the ``Extensions`` menu: + +.. code:: xml + + [...] + <effect> + <!--object-type>path</object-type--> + <effects-menu> + <submenu name="Color"/> + </effects-menu> + </effect> + [...] + +Step Two +--------- + +Next, open ``my_effect_extension.py`` in your text editor. You can see +this is an inkex extension because it contains a python class that +inherits from ``inkex.EffectExtension``. Change the class name to +``MakeRedExtension``: + +.. code:: python + + [...] + class MakeRedExtension(inkex.EffectExtension): + [...] + +Reflect this change down in the ``__main__`` section of the code by +changing the class name to ``MakeRedExtension`` there: + +.. code:: python + + [...] + if __name__ == '__main__': + MakeRedExtension().run() + +When a standard inkex-based python effect extension is run, it will call +a method called :func:`~inkex.base.InkscapeExtension.effect` on your extension's class. So, most of the +code you need to write will go there. Edit +``my_effect_extension.py``\ 's ``effect()`` method to look like the +following, **being sure that the indentation is correct so that** +``effect()`` **is recognized as a method of the MakeRedExtension class**: + +.. code:: python + + for elem in self.svg.selection: + elem.style['fill'] = 'red' + elem.style['fill-opacity'] = 1 + elem.style['opacity'] = 1 + + + +Code Explanation +~~~~~~~~~~~~~~~~ + +We want to change the color of all selected objects to red. For this we need to loop +through each of the selected paths. The first line of :func:`inkex.base.InkscapeExtension.effect` +does this. The :attr:`~inkex.elements._svg.SvgDocumentElement.selection` attribute of ``self.svg`` +contains the currently selected objects. + +.. hint:: + ``self.svg`` contains the SVG document in its current state - passed by Inkscape - and + is already parsed for us, so we don't have to manipulate the XML manually. Instead, inkex offers + an object-oriented interface to all the SVG element types. + +Each element has a ``style`` attribute: it's one of the +:attr:`~inkex.elements._base.BaseElement.WRAPPED_ATTRS` of each element, so ``elem.style`` is a +:class:`~inkex.styles.Style` object (you can think of it as a dictionary). And in this dictionary, +we set the value for ``'fill'`` to ``'red'``. We also set the ``'fill-opacity'`` to ``1``, in case +the object was transparent previously. + + +Final Step +------------ + +That’s it! There’s no need to set, save or do anything else as we’ve +modified the style in place. + +Save your python script, and re-launch Inkscape. If inkscape was already +open, close it first. You should find your new extension available in +the ``Effect`` menu. + +Draw some shapes in Inkscape. Select some of the shapes and use the extension. +The fill of all objects should change to red. + diff --git a/share/extensions/docs/tutorial/my-first-import-extension.rst b/share/extensions/docs/tutorial/my-first-import-extension.rst new file mode 100644 index 0000000..6c296bd --- /dev/null +++ b/share/extensions/docs/tutorial/my-first-import-extension.rst @@ -0,0 +1,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 diff --git a/share/extensions/docs/tutorial/my-first-text-extension.rst b/share/extensions/docs/tutorial/my-first-text-extension.rst new file mode 100644 index 0000000..7fb9a31 --- /dev/null +++ b/share/extensions/docs/tutorial/my-first-text-extension.rst @@ -0,0 +1,223 @@ +My first text extension +======================= + +This article will teach you the basics of writing a Text Extension for +Inkscape using the ``inkex`` Extensions API. + +Introduction +------------ + +Any extension that manipulates text in any way is a Text Extension. Text +Extensions can broadly be categorized in two types based on the type of +manipulation they do to text: + +1. **Manipulating the text itself** - like changing case. +2. **Changing attributes of the text** like font, color, size, etc. + There can also be extensions which are a hybrid of both the above + types. + +In this article we will create an extension named ``Stronger Text``. It +is a *Type 2* Text Extension and it will make the selected text bold and +it will also italicize it. We will also see an example of creating a +*Type 1* Text Extension. + +.. hint:: + + This article assumes you create all 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 ``stronger_text.inx`` file and a ``stronger_text.py`` file. + +- ``stronger_text.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 declaration of any + interface for your extension. +- ``stronger_text.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_stronger_text.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 +------------------------------------ + +Our extension **Stronger Text** will have a very basic ``.inx`` file. + +.. code:: xml + + <?xml version="1.0" encoding="UTF-8"?> + <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <name>Stronger Text</name> + <id>org.inkscape.text.strongertext</id> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Text"> + </submenu> + </effects-menu> + </effect> + <script> + <command location="inx" interpreter="python">stronger_text.py</command> + </script> + </inkscape-extension> + +Explanation +~~~~~~~~~~~ + +The lines below help Inkscape uniquely identify our extension so that it +can be displayed under the **Extensions** menu. You should modify these +two lines for your own extension: + +.. code:: xml + + [...] + <name>Stronger Text</name> + <id>org.inkscape.text.strongertext</id> + [...] + +Towards the end of the ``.inx`` file, we change the submenu to ``Text``. +It specifies that this extension should be listed in the **Text** +submenu under the **Extensions** menu in Inkscape UI: + +.. code:: xml + + [...] + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu name="Text"> + </submenu> + </effects-menu> + </effect> + [...] + +Now - Save the file - Close any open Inkscape windows - Relaunch +Inkscape + +After successfuly completing these steps, you should see the Stronger +Text extension in Inkscape UI. + +.. figure:: resources/Inkscape_Recognizes_Our_Extension.gif + :alt: Inkscape Recognizes Our Extension + + Inkscape Recognizes Our Extension + +Currently it doesn’t do anything as we haven’t written anything in the +``stronger_text.py`` file. + +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:: py + + import inkex + +Every Text Extension inherits from the :class:`~inkex.extensions.TextExtension` class provided +by the ``inkex`` API. Let’s name our class ``StrongerText`` (an +arbitrary name) and inherit the :class:`~inkex.extensions.TextExtension` class. + +.. code:: py + + import inkex + + class StrongerText(inkex.TextExtension): + #implement functionality here + pass + +The ``TextExtension`` class provides us with methods to manipulate the +text. + +Our extension ``Stronger Text`` is a *Type 2* extension as it needs to +change the attributes of text. To get access to the attributes of the +text, we need to override the :func:`~inkex.extensions.TextExtension.process_element` method. + +.. code:: py + + import inkex + + class StrongerText(inkex.TextExtension): + def process_element(self, text_node): + text_node.style["font-weight"] = "bold" + text_node.style["font-style"] = "italic" + #text_node.style["attribute-name"] = "value" + + StrongerText().run() + +.. _explanation-1: + +Explanation +~~~~~~~~~~~ + +| ``text_node.style`` provides us with a dictionary (technically it’s an + ``OrderedDict``) which we can use to change the attributes of the text + by using the attribute name as a key. +| When a standard inkex-based Text Extension is run, it will call a + method called :func:`~inkex.extensions.TextExtension.effect` on the ``TextExtension`` class which then + eventually calls our(``StrongerText``\ ’s) ``process_element`` + function. \**\* If your extension is a *Type 1* Text Extension, i.e, + it’s purpose is to take text from Inkscape, transform/manipulate it, + and return it to Inkscape, you just need to override the + :func:`~inkex.extensions.TextExtension.process_chardata` function like so: + +.. code:: py + + import inkex + + class UpperCase(inkex.TextExtension): + def process_chardata(self, text): + # do some more manipulations here if you want + return text.upper() + + UpperCase().run() + +.. _explanation-2: + +Explanation +~~~~~~~~~~~ + +The ``text`` parameter in the :func:`~inkex.extensions.TextExtension.process_chardata` function receives +the text from Inkscape as a Python string. In the :func:`~inkex.extensions.TextExtension.process_chardata` +function itself, we can manipulate the string in any way to our +liking. We then return the manipulated string. + +Infact, if you’ve used the ``UPPERCASE`` extension in Inkscape from +the ``Extensions``>\ ``Text``>\ ``Change Case`` submenu, it is +identical to the Extension we just wrote! + +.. hint:: + + This *Type 1* Extension + will have an almost identical ``.inx`` file with changes in places + like the ``<name>`` and ``<id>`` tags and a separate ``.py`` file with + the above code in it. + +Moment of Truth +--------------- + +Now, we should test our extension to see it in action. - Select some +Text (preferably a font that has bold and italics styles available) - +Click on the **Stronger Text** extension + +The result should appear like this: + +.. figure:: resources/Text_Ext_Moment_of_truth.gif + :alt: A gif showing that the extension works. + + Did It Work? (yes, indeed) diff --git a/share/extensions/docs/tutorial/resources/Call_Extension_Failure.png b/share/extensions/docs/tutorial/resources/Call_Extension_Failure.png Binary files differnew file mode 100644 index 0000000..33629c3 --- /dev/null +++ b/share/extensions/docs/tutorial/resources/Call_Extension_Failure.png diff --git a/share/extensions/docs/tutorial/resources/Inkscape_Recognizes_Our_Extension.gif b/share/extensions/docs/tutorial/resources/Inkscape_Recognizes_Our_Extension.gif Binary files differnew file mode 100644 index 0000000..677c90a --- /dev/null +++ b/share/extensions/docs/tutorial/resources/Inkscape_Recognizes_Our_Extension.gif diff --git a/share/extensions/docs/tutorial/resources/Smiles_Format.gif b/share/extensions/docs/tutorial/resources/Smiles_Format.gif Binary files differnew file mode 100644 index 0000000..ab5bf4b --- /dev/null +++ b/share/extensions/docs/tutorial/resources/Smiles_Format.gif diff --git a/share/extensions/docs/tutorial/resources/Text_Ext_Moment_of_truth.gif b/share/extensions/docs/tutorial/resources/Text_Ext_Moment_of_truth.gif Binary files differnew file mode 100644 index 0000000..3ecc301 --- /dev/null +++ b/share/extensions/docs/tutorial/resources/Text_Ext_Moment_of_truth.gif diff --git a/share/extensions/docs/tutorial/resources/call_extension_success.gif b/share/extensions/docs/tutorial/resources/call_extension_success.gif Binary files differnew file mode 100644 index 0000000..d1ed5c6 --- /dev/null +++ b/share/extensions/docs/tutorial/resources/call_extension_success.gif diff --git a/share/extensions/docs/tutorial/resources/make_red_extension.zip b/share/extensions/docs/tutorial/resources/make_red_extension.zip Binary files differnew file mode 100644 index 0000000..6873a9b --- /dev/null +++ b/share/extensions/docs/tutorial/resources/make_red_extension.zip diff --git a/share/extensions/docs/tutorial/resources/stroke-parity-widget.png b/share/extensions/docs/tutorial/resources/stroke-parity-widget.png Binary files differnew file mode 100644 index 0000000..48d8b9b --- /dev/null +++ b/share/extensions/docs/tutorial/resources/stroke-parity-widget.png diff --git a/share/extensions/docs/tutorial/resources/stroke_parity_extension.zip b/share/extensions/docs/tutorial/resources/stroke_parity_extension.zip Binary files differnew file mode 100644 index 0000000..06b0e7b --- /dev/null +++ b/share/extensions/docs/tutorial/resources/stroke_parity_extension.zip diff --git a/share/extensions/docs/tutorial/resources/template_effect.zip b/share/extensions/docs/tutorial/resources/template_effect.zip Binary files differnew file mode 100644 index 0000000..be5a051 --- /dev/null +++ b/share/extensions/docs/tutorial/resources/template_effect.zip diff --git a/share/extensions/docs/tutorial/resources/vanillin.smi b/share/extensions/docs/tutorial/resources/vanillin.smi new file mode 100644 index 0000000..964c95e --- /dev/null +++ b/share/extensions/docs/tutorial/resources/vanillin.smi @@ -0,0 +1 @@ +COc1cc(C=O)ccc1O diff --git a/share/extensions/docs/tutorial/simple-path-extension.rst b/share/extensions/docs/tutorial/simple-path-extension.rst new file mode 100644 index 0000000..143b6b1 --- /dev/null +++ b/share/extensions/docs/tutorial/simple-path-extension.rst @@ -0,0 +1,162 @@ +A more complex example: reading path data and changing the style +================================================================ + +.. note:: + This tutorial builds on the files created in :ref:`first-effect-extension`. + +Resources +--------- + +- :download:`Example solution <resources/stroke_parity_extension.zip>` + +Introduction +------------------ + +We are going to write an effect extension that will change any path with +an even number of nodes to a user-defined stroke and any path with an odd +number of nodes to a different user-defined stroke color. + +From :ref:`first-effect-extension`, we already know how to create the extension boilerplate +(without parameters), how to iterate over the selection and how to modify the style. + +This extension adds two more things: Paths and Parameters. + +Setting up the inx file +----------------------- + +Duplicate the files from the `make_red_extension`, and change all the filenames, class names +and extension ID to `stroke_parity_extension`. + +Now for the parameters for the colors. We're also going to add a checkbox to remove the fill of the +selected paths. The params are defined after ``<id>`` and before ``<effect>`` in the inx file: + +.. code:: xml + + <param name="remove_fill" type="bool" gui-text="Remove fill">false</param> + <label appearance="header">Stroke color for path based on number of nodes</label> + <param name="tab" type="notebook"> + <page name="even" gui-text="Even"> + <param name="even_color" type="color" gui-text="Even stroke color:">4278190335</param> + </page> + <page name="odd" gui-text="Odd"> + <param name="odd_color" type="color" gui-text="Odd stroke color:">65535</param> + </page> + </param> + +The default colors are defined as content of the ``<param>`` element, and they are passed as +integers in RGBA format, so for a "red" color (i.e. ``#ff000ff``), one has to write +``256^3*255 + 255 = 4278190335``. + +Color widgets are quite large, so the two widgets are added in a notebook (i.e. two tabs). + +You can also change the submenu the extension will be listed in to ``Modify Path``. + +Restart Inkscape. If you click the extension in the menu, it should look something like this: + +.. figure:: resources/stroke-parity-widget.png + +Adding the parameters to the python file +---------------------------------------- + +When Inkscape calls the extension in the current state, an error message is shown:: + + usage: stroke_parity_extension.py [-h] [--output OUTPUT] [--id IDS] + [--selected-nodes SELECTED_NODES] + [INPUT_FILE] + stroke_parity_extension.py: error: unrecognized arguments: --remove_fill=false --tab=even --even_color=4278190335 --odd_color=65535 + +We also have to tell the ``MakeRedExtension`` class how to parse the parameters. This is done in the +:func:`~inkex.base.InkscapeExtension.add_arguments` method. We have four parameters: the two colors, +the checkbox and the currently selected tab. + +.. code:: python + + def add_arguments(self, pars): + pars.add_argument("--even_color", type=inkex.Color, default=inkex.Color("red")) + pars.add_argument("--odd_color", type=inkex.Color, default=inkex.Color("blue")) + pars.add_argument("--remove_fill", type=inkex.Boolean, default=False) + pars.add_argument("--tab", type=str, default="stroke") + +In ``effect``, these parameters are available as e.g. ``self.options.even_color``. + +.. hint:: + + :func:`inkex.Boolean <inkex.utils.Boolean>` and :class:`inkex.Color <inkex.colors.Color>` are + special types that preprocess the parsed parameter, so that ``self.options.even_color`` is an + :class:`inkex.colors.Color` object and not the string ``"4278190335"``. + +Processing the paths +-------------------- + +Modify the ``effect`` method as follows: + +.. code-block:: python + :linenos: + + def effect(self): + for elem in self.svg.selection.filter(inkex.PathElement): + elem.set('inkscape:modified_by_tutorial', 'Yes') + elem.style['stroke-width'] = 2.0 + if len(elem.path) % 2: # odd number + elem.style.set_color(self.options.odd_color, 'stroke') + else: + elem.style.set_color(self.options.even_color, 'stroke') + + if self.options.remove_fill: + elem.style["fill"] = None + +Code Explanation +~~~~~~~~~~~~~~~~ + +Firstly, we need to loop through each of the selected paths. We already now how to do this +from the first tutorial, but we now filter the selection to only contain +:class:`~inkex.elements._polygons.PathElement` objects - since we want to count the number of nodes. +If other objects, such as a text object or rectangle, are selected, they are ignored. So, for each +iteration of the loop, ``elem`` will contain one of the selected path +objects. + +The second line sets an attribute ``inkscape:modified_by_tutorial`` on +the xml element ``elem``. The attribute API will handle the ``inkscape`` +namespace for us, so we can use a simple colon to indicate the +namespace. This is how all non-special attributes are set and gotten. +But on top of this simple API we don’t have to worry about parsing the +path, transform or the style attributes. Instead the inkex API does all +the parsing for us and provides us with a way to change styles, modify +paths and even do transformations without manual parsing. + +Then we set a stroke-width of 2, using the standard style API which is +assigning into a type of ordered dictionary. + +Next we use the Path API to get the path data of the element, using +:attr:`~inkex.elements._base.ShapeElement.path`. This is an :class:`inkex.paths.Path` object, +which is a list of all path commands (such as Moveto, Lineto, Curveto...). We can use the length +of the list to determine the number of segments, which is (for the simple case of open paths) equal +to the number of nodes. + +We then use the Color API to assign the correct stroke color. The parameters are passed in RGBA format, +and we can use the :func:`~inkex.styles.Style.set_color` function to set opacity and stroke at the same time. +Stroke and stroke opacity are two different style properties, so we can’t set them with a simple +assignment. + +Last we set the fill to ``None`` (``"none"`` would work as well) - if the checkbox for this is +checked. + +Testing the extension +--------------------- + +As we've already learned, there’s no need to set, save or do anything else as we’ve +modified the style in place. + +Save your python script, and re-launch Inkscape. If inkscape was already +open, close it first. You should find your new extension available in +the ``Modify Path`` menu. + +Draw some shapes with the pen tool, select some of the shapes and use the extension. +You should see the stroke color change for each of the objects selected. + +.. note:: + For closed paths, the extension gives incorrect results, because the closing "Z" / "z" + command does not add a new node, but is counted by ``len(path)``. + + This can be avoided by manually counting segments except that are not ZoneClose (zoneClose) commands. + |