I've been writing my first Inkscape extension, and as part of the learning process I've made a handful of small examples that demonstrate some user interface widgets. I'd very much appreciate any comments, suggestions or bug reports as I'm new to both GTK and Python. Hopefully they'll also be useful to other developers looking to write an extension😀
Most extensions to Inkscape use an .inx file that describes a static user interface, then a Python script is run one time when the user clicks "Apply".
To create a dynamic UI (that changes as the user clicks buttons, selects from dropdown menus, etc) you can build it in GTK using a Python script that runs immediately. Requires Inkscape v1.0 or newer.
These short examples demonstrate a handful of functions in GTK+ 3. Note that the .inx file for each dynamic extension must use:
To manually install these extensions, download and unpack the archive file. Copy the 'dynamic_examples' folder to the location listed by the Inkscape menu:
Restart Inkscape and the new extensions will be available.
Button (action) + Label (text)
A minimal dynamic extension. Everything is wrapped inside the new class ButtonLabel(), derived from inkex.EffectExtension. The effect() method is called as soon as the extension is run.
A window is created with Gtk.Window(), setting the title, and links a handler to exit if the close icon is clicked. See PyGTK introduction
Layout uses a vertical Gtk.Box() to separate the text label and button. See PyGTK layout
The script counts up the total number of shape elements in the current document and displays it as text with Gtk.Label(). See PyGTK label and Inkscape inkex.elements._base
Finally a Gtk.Button() is created, with a click handler as another way to exit the script. See PyGTK button_widgets
SpinButton (number) + RadioButton (options)
Demo of the Gtk.SpinButton() widget for numeric entry, with up/down arrows. Uses Gtk.Adjustment() to set lower/upper bounds & step values. See PyGTK button_widgets
Two Gtk.RadioButton() in a group are used to choose between metric and 'freedom' units of distance.
Layout uses a vertical Gtk.Box(), and again for two horizontal rows.
Some of the labels have simple HTML markup using Gtk.Label().set_markup()
Nothing is read or changed in the Inkscape document.
ColorButton + LinkButton (URL)
Demo of the Gtk.ColorButton() for simple colour selection. See GTK ColorButton
The script adds up the total length of path elements with that stroke colour and displays it as text. Just from the currently selected objects, or the full document. Objects like rectangles, circles & text are not included unless converted to paths first.
Getting the actual length of each path is a bit involved, using built-in functions to convert into "a predictable list of cubic bezier curves". See Inkscape inkex.paths
Note how the colour button returns Gdk.RGBA (red/green/blue channels are 0.0→1.0) whereas document colours are inkex.colors.Color (0→255).
Thanks so much for your kind words! I really appreciate it ☺ I'll post about my examples on that chat channel.
I found your Page Numbering extension and it was really helpful to get me started on my own extension.
I've haven't tried using Glade yet, but looks like it would be a big time saver for more complex user interfaces? Unfortunately I think it's no longer being developed, with the last release v3.40 in 2022. [Glade - A User Interface Designer]
box.add() used instead of pack_start(), to simplify the code where possible
color_link.py now supports shape objects (circle, ellipse, rectangle), by converting them into paths before calculating the length. Text objects are not supported svg.descendants() / selection.get() are used to get all elements, instead of svg.findall() / selection.filter() for just paths
color_link.py replaced color_equal() with rgba2color(), so now converts the colour, and then uses == to directly compare Color() objects
README.html added with documentation in HTML format
Now checks Inkscape version and gives a friendly error message if Inkscape is too old
Tested on Inkscapes before v1.3, fixed deprecation warnings with Gtk.Label() positional arguments
Accessibility improvements to README.html, as well as responsive design for mobile screens
Dynamic extensions need at least Inkscape v1.1 to work, and users will see a long and incomprehensible Python traceback if they use Inkscape v1.0.x. To avoid this you can easily check if a certain feature exists in the inkex library, and give a friendly error message if it is not recent enough for your extension:
# Friendly error message if run from older, incompatible version of Inkscape
import inkex, gi, sys
if "get_user_directory" not in dir(inkex.utils):
inkex.utils.errormsg("This extension needs Inkscape v1.1 or later, please upgrade")
sys.exit()
Similar tests can be used to check for a different version of Inkscape if your extension requires that to work.
I've haven't tried using Glade yet, but looks like it would be a big time saver for more complex user interfaces? Unfortunately I think it's no longer being developed, with the last release v3.40 in 2022. [Glade - A User Interface Designer]
I've been writing my first Inkscape extension, and as part of the learning process I've made a handful of small examples that demonstrate some user interface widgets. I'd very much appreciate any comments, suggestions or bug reports as I'm new to both GTK and Python. Hopefully they'll also be useful to other developers looking to write an extension 😀
Most extensions to Inkscape use an
.inx
file that describes a static user interface, then a Python script is run one time when the user clicks "Apply".To create a dynamic UI (that changes as the user clicks buttons, selects from dropdown menus, etc) you can build it in GTK using a Python script that runs immediately. Requires Inkscape v1.0 or newer.
These short examples demonstrate a handful of functions in GTK+ 3. Note that the
.inx
file for each dynamic extension must use:<effect implements-custom-gui="true">
Full details of inkex and Python GTK:
How to install
To manually install these extensions, download and unpack the archive file. Copy the '
dynamic_examples
' folder to the location listed by the Inkscape menu:Edit > Preferences > System: User extensions
For Windows users this will be something like:
C:\Users\USERNAME\AppData\Roaming\inkscape\extensions
Restart Inkscape and the new extensions will be available.
Button (action) + Label (text)
A minimal dynamic extension. Everything is wrapped inside the new class
ButtonLabel()
, derived frominkex.EffectExtension
. Theeffect()
method is called as soon as the extension is run.A window is created with
Gtk.Window()
, setting the title, and links a handler to exit if the close icon is clicked. See PyGTK introductionLayout uses a vertical
Gtk.Box()
to separate the text label and button. See PyGTK layoutThe script counts up the total number of shape elements in the current document and displays it as text with
Gtk.Label()
. See PyGTK label and Inkscape inkex.elements._baseFinally a
Gtk.Button()
is created, with a click handler as another way to exit the script. See PyGTK button_widgetsSpinButton (number) + RadioButton (options)
Demo of the
Gtk.SpinButton()
widget for numeric entry, with up/down arrows. UsesGtk.Adjustment()
to set lower/upper bounds & step values. See PyGTK button_widgetsTwo
Gtk.RadioButton()
in a group are used to choose between metric and 'freedom' units of distance.Layout uses a vertical
Gtk.Box()
, and again for two horizontal rows.Some of the labels have simple HTML markup using
Gtk.Label().set_markup()
Nothing is read or changed in the Inkscape document.
ColorButton + LinkButton (URL)
Demo of the
Gtk.ColorButton()
for simple colour selection. See GTK ColorButtonThe script adds up the total length of path elements with that stroke colour and displays it as text. Just from the currently selected objects, or the full document. Objects like rectangles, circles & text are not included unless converted to paths first.
Getting the actual length of each path is a bit involved, using built-in functions to convert into "a predictable list of cubic bezier curves". See Inkscape inkex.paths
Note how the colour button returns
Gdk.RGBA
(red/green/blue channels are 0.0→1.0) whereas document colours areinkex.colors.Color
(0→255).Also displays a clickable link using
Gtk.LinkButton()
. See PyGTK button_widgetsComboBoxes (dropdown menus)
Demo of the
Gtk.ComboBox()
widget for two dropdown menus in a Country→Cities hierarchy. See PyGTK comboboxThe city dropdown is rebuilt whenever a different country is selected, and a (Unicode) text label is updated when a different city is selected.
The dropdowns don't need to be hard-wired in the script: they could be read at runtime from a database or web API, for example.
Layout uses
Gtk.Grid()
for a 2×2 grid to align the dropdown menus. See PyGTK layoutThere is a separate "Apply" button that runs an (empty) method. This is where an extension would normally perform some action to the document.
Nothing is read or changed in the Inkscape document.
Very nice examples.
They are very well written and easy to read.
Also you have linked to https://lazka.github.io/pgi-docs/Gtk-3.0/index.html in your code - which is great for python gtk.
From reading the code, it seems you have a better grasp of python than me :)
I can see you have hand coded the GTK elements.
For my efforts I used an external glade file and and and external css file.
It might be worth popping over to https://chat.inkscape.org/channel/inkscape_extensions
I'm sure they would be interested in adding any tutorials you would like to add to the extensions api docs - gtk examples are much needed !
Well done.
Thanks so much for your kind words! I really appreciate it ☺ I'll post about my examples on that chat channel.
I found your Page Numbering extension and it was really helpful to get me started on my own extension.
I've haven't tried using Glade yet, but looks like it would be a big time saver for more complex user interfaces? Unfortunately I think it's no longer being developed, with the last release v3.40 in 2022. [Glade - A User Interface Designer]
Interesting that you didn't use any of the bundled inkex.gui API for the interface examples. Was the API too restrictive?
What is inkex.gui for? Is it only needed if you want to load a GtkBuilder file created in Glade?
I didn't notice any signposting in the inkex tutorial pages that pushed me towards inkex.gui, or examples that made use of it.
A few minor improvements with this release:
box.add()
used instead ofpack_start()
, to simplify the code where possiblesvg.descendants()
/selection.get()
are used to get all elements, instead ofsvg.findall()
/selection.filter()
for just pathscolor_equal()
withrgba2color()
, so now converts the colour, and then uses == to directly compareColor()
objectsSome more small improvements with this release:
Gtk.Label()
positional argumentsDynamic extensions need at least Inkscape v1.1 to work, and users will see a long and incomprehensible Python traceback if they use Inkscape v1.0.x. To avoid this you can easily check if a certain feature exists in the inkex library, and give a friendly error message if it is not recent enough for your extension:
Similar tests can be used to check for a different version of Inkscape if your extension requires that to work.
Just tested on the newly released Inkscape v1.4, and no problems ☺
New release with the following improvements:
.glade
XML file to create the UI withGtk.Builder()
Download here: https://inkscape.org/~Nikki.Smith/%E2%98%85dynamic-gui-example-extensions-with-gtk
Thank's. Very usefful (like very very).
Glade seem to be cambalache now https://gitlab.gnome.org/jpu/cambalache