Gimp 3 was recently released, with a huge number of breaking changes, so almost all plugins for gimp written in python stopped working. My plugin is no exception. Due to the lack of any sensible instructions on migration on the Internet, I had to spend half a day to figure it out. I hope this article will save you some time.
The entire guide is written for windows, but I am sure that users of other systems are quite capable of adapting it to their needs.
Here you can find api for libgimp and python, in version 3.0 they are structurally very similar, python bindings almost mirror their implementation in libgimp.
https://developer.gimp.org/api/3.0/
Official plugin tutorial.
https://testing.docs.gimp.org/3.0/en/gimp-using-python-plug-in-tutorial.html
Another official tutorial. Lots of useful information on the new implementation of Python plugins.
https://developer.gimp.org/resource/writing-a-plug-in/tutorial-python/
It's already out of date, but you can still learn something interesting.
https://z-uo.medium.com/create-python3-plugin-for-gimp-the-basics-94ede94e9d1f
I decided that this part should be first because writing scripts for embed interpreters is always a pain in the ass and I would kill for such recommendations in every single case.
Start gimp with the --verbose flag, this will show additional information about errors and other things.
I don't think life without a debugger is complete, but in many embedded environments it is impossible to connect it, fortunately this does not apply to Gimp.
localRoot and remoteRoot should point to the directory with plugins.
{
"name": "Attach to GIMP Python",
"type": "python",
"request": "attach",
"port": 5678, // The port for the debug server
"host": "localhost",
"pathMappings": [
{
"localRoot": "d:\\my-plugins-folder",
"remoteRoot": "d:\\my-plugins-folder"
}
]
},By default, gimp does not have debugpy, so you need to add it manually. In my case, the path with the libraries was
c:\Users\User\AppData\Local\Programs\GIMP 3\lib\python3.12\
To avoid the hassle, I simply downloaded the whl file and unpacked it into this directory (it's a simple zip archive)
import debugpy
debugpy.listen(("localhost", 5678))
debugpy.wait_for_client()wait_for_client implies that the script execution is completely frozen until the debugger is connected.
There are some interesting issues that require further study. Gimp remembers its state after the initial launch of a new version of the plugin and wait_for_client in the root of the file no longer works, so make changes to the plugin file to start a new debugging session. After performing the python registration, the interpreter also often dies from inactivity, so there is nothing to connect to, so use debugpy.wait_for_client() in all places when the interpreter is restarted, for example in the run method. In a simple case, after changes in the code, you need to restart the entire gimp, how to improve this is written below.
At least since version 3.0.4 it seems the main plugin module is re-read when the procedure is called, but I'll leave this chapter just in case.
You can split your project into modules, useful for quick reloading when you can change the plugin code without restarting gimp entirely.
Directory structure
plugin_folder
modules
__init__.py - empty file
module.py - file from which we can import
main.py - main plugin file
main.py
import inspect
import os
import sys
import importlib
currentframe = os.path.dirname(inspect.getfile(inspect.currentframe()))
sys.path.append(currentframe)
import modules.module
class MyFirstPlugin (Gimp.PlugIn):
# ...
def run(self, procedure, run_mode, image, drawables, config, run_data):
debugpy.wait_for_client()
# Now after each run of this procedure the entire module modules/module.py will be reloaded
importlib.reload(modules.module)
modules.module.my_function(image)
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())import inspect
import os
import sys
currentframe = os.path.dirname(inspect.getfile(inspect.currentframe()))
sys.path.append(currentframe)
sys.stderr = open(os.path.join(currentframe, "errors.txt"), 'w', buffering=1)
sys.stdout = open(os.path.join(currentframe, "log.txt"), 'w', buffering=1)Now errors.txt and log.txt will be located in the same folder as the plugin file. print() also outputs to log.txt
Standard logging in Gimp is performed using the Gimp.message and Gimp.message_set_handler methods.
# Send message to status bar.
Gimp.message_set_handler(Gimp.MessageHandlerType.MESSAGE_BOX)
# Send a message to the terminal (in Windows, this is the window that appears when you run it with the --verbose flag)
Gimp.message_set_handler(Gimp.MessageHandlerType.CONSOLE)
# Send a message to the "Error Console" dialog.
Gimp.message_set_handler(Gimp.MessageHandlerType.ERROR_CONSOLE)
Gimp.message('My message!')In any unclear situation, use inspection in the debugger to understand what properties of objects are available. I also recommend reading the tutorials from the Resources section, there is a lot of useful information there that is not reflected in this article.
Previously, you could put script files directly in the root of the extensions directory. But in Gimp 3, each individual plugin's code must be in a subdirectory. The plugin directory must have the same name as the main file (myplugin/myplugin.py). For Linux and MacOS the file must be executable.
The main structure of the plugin is described here, don't worry, it's practically gimpfu.register, only in a new wrapper. https://testing.docs.gimp.org/3.0/en/gimp-using-python-plug-in-tutorial.html
import gimp
import gobject...etc
Should be translated to
from gi.repository import Gimp
from gi.repository import GObjectThe full list is from gi.repository, but it will obviously change.
Atk
Babl
GLib
GModule
GObject
Gdk
GdkPixbuf
Gegl
Gimp
Gio
Gtk
HarfBuzz
Pango
cairo
freetype2
To select a specific version of a module, use the require_version function, for example gi.require_version('Gimp', '3.0'). Most likely, it will be useful in the future, when module versions start changing, but your plugin will still work, and you can migrate it to a new version of the module at your own pace. This means you should always request a specific version before importing.
The gimpfu, pdb module is no longer used. pdb still exists (Gimp.get_pdb()), but no longer contains the procedure-reflecting methods, details below.
pdb.<procedure_name> commands are no longer present. A common call is used for this:
# search for a procedure in the database
procedure = Gimp.get_pdb().lookup_procedure('gimp-image-get-channels');
# Create arguments
config = procedure.create_config();
config.set_property('image', image);
# Result as a ValueArray
result = procedure.run(config);
# First element - success indication
success = result.index(0);
# Subsequent elements - returned values
channels = result.index(1)You can also get examples of calling a procedure by selecting Browse in the Python Fu console. Look carefully at what methods for adding parameters to the config are offered in the procedure call examples, sometimes you may need not just set_property, but set_core_object_array or set_color_array.
I also wrote a small helper function, maybe it will be useful to you.
Calling procedures is often not required. Since they are almost always implemented as object methods.
For example, as you can see in the libgimp documentation, the "gimp-item-is-group" operation reflects the declaration of the function gimp_item_is_group which is respectively expressed in the python api as Gimp.Item.is_group.
All properties of objects are now replaced with getter methods, I think all of them, so if you see that an object's property is read, rewrite it to a method and check with the api.
For example, image.width has turned into image.get_width()
Of course the most famous difference between python 2 and python 3 is print, don't forget to replace print "something" with print("something")
Some commands require a File instance to read or write, it can be created by the Gio.File.new_for_path factory
file = Gio.File.new_for_path(image_path)
img = Gimp.file_load(Gimp.RunMode.NONINTERACTIVE, file)Also the information about the path to the image file has moved to the File instance, you will most likely encounter this in other places as well.
img.get_file().get_path()layer.get_offsets() now returns a boolean as the first element, be careful. Other methods may also start returning unusual results, check in the api documentation.
Well, that's all the problems I encountered while converting my not-so-small plugin to Gimp 3. But you will definitely have your own problems. I hope this information will help you not to get lost. The article will remain here, any comments and additions are welcome.
cool , I'll definitely let you know my own problems too in case it's helpful to you
and thanks again